Hibernate makes additional query with #NamedEntityGraph - spring

I have next database diagram:
As you can see, tables have not foreign keys between themselves.
My entities look like this:
#Entity
#NamedEntityGraph(name = "test", attributeNodes = [
NamedAttributeNode("room"),
NamedAttributeNode("department")
])
class Employee(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
var firstName: String? = null,
var entryId: String? = null,
) {
#OneToOne
#JoinColumn(name = "entryId", referencedColumnName = "empId", insertable = false, updatable = false)
#Fetch(FetchMode.JOIN)
var room: Room? = null
#OneToOne
#JoinColumn(name = "entryId", referencedColumnName = "empId", insertable = false, updatable = false)
#Fetch(FetchMode.JOIN)
var department: Department? = null
}
#Entity
class Room(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
var number: Int? = null,
var empId: String? = null,
) : Serializable
#Entity
class Department(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
var name: String? = null,
var empId: String? = null,
) : Serializable
I use #NamedEntityGraph to fetch all entities in one query. And repository looks as follows:
interface EmpRepository: JpaRepository<Employee, Long> {
#EntityGraph("test")
fun getEntityById(id: Long): Employee
}
As a result, when "Deparment" and "Room" are exist in database, Hibernate generates only one request to database, which is correct:
Hibernate:
select
employee0_.id as id1_1_0_,
department1_.id as id1_0_1_,
room2_.id as id1_2_2_,
employee0_.entry_id as entry_id2_1_0_,
employee0_.first_name as first_na3_1_0_,
department1_.emp_id as emp_id2_0_1_,
department1_.name as name3_0_1_,
room2_.emp_id as emp_id2_2_2_,
room2_.number as number3_2_2_
from
employee employee0_
left outer join
department department1_
on employee0_.entry_id=department1_.emp_id
left outer join
room room2_
on employee0_.entry_id=room2_.emp_id
where
employee0_.id=?
But, when "Department" or "Room" with such emp_id is not presented in database, Hibernate generates additional request to fetch this entity again:
Hibernate:
select
employee0_.id as id1_1_0_,
department1_.id as id1_0_1_,
room2_.id as id1_2_2_,
employee0_.entry_id as entry_id2_1_0_,
employee0_.first_name as first_na3_1_0_,
department1_.emp_id as emp_id2_0_1_,
department1_.name as name3_0_1_,
room2_.emp_id as emp_id2_2_2_,
room2_.number as number3_2_2_
from
employee employee0_
left outer join
department department1_
on employee0_.entry_id=department1_.emp_id
left outer join
room room2_
on employee0_.entry_id=room2_.emp_id
where
employee0_.id=?
Hibernate:
select
department0_.id as id1_0_0_,
department0_.emp_id as emp_id2_0_0_,
department0_.name as name3_0_0_
from
department department0_
where
department0_.emp_id=?
My question is how to prevent this Hibernate behaviour, because in my real application it's a normal when inner entity is not presented in database.

Related

Spring Boot JPA - Projection selecting all fields from table when has a collection

I trying to get two fields and a #ElementCollection from entity using projection with interface, but the JPA are selecting all fields from my entity and when i remove the method that get the list of my #ElementCollection the JPA select the only two fields.
My entity class:
#Entity(name = "users")
data class User(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
#Column(nullable = false)
var name: String? = null,
#Column(unique = true, nullable = false)
var cpf: String? = null,
#Column(name = "phone_number", nullable = false)
var phoneNumber: String? = null,
#Column(unique = true, nullable = false)
var email: String? = null,
#Column(name = "password_hash")
var passwordHash: String? = null,
#Column(name = "password_hash_recovery")
var passwordHashRecovery: String? = null,
#Column(name = "password_hash_recovery_date")
var passwordHashRecoveryDate: String? = null,
#Column(name = "self_employed", nullable = false)
var selfEmployed: Boolean? = null,
#JoinColumn(name = "user_photo", referencedColumnName = "id")
#OneToOne(fetch = FetchType.LAZY)
var userPhoto: File? = null,
#JoinColumn(name = "id_location", referencedColumnName = "id")
#OneToOne(fetch = FetchType.LAZY)
var location: Location? = null,
#Column(name = "rating_star", nullable = false)
#Enumerated(EnumType.STRING)
var ratingStar: RatingStar = RatingStar.ONE,
#JoinColumn(name = "id_area", referencedColumnName = "id")
#OneToOne(fetch = FetchType.LAZY)
var area: Area? = null,
#OneToMany(mappedBy = "user", cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
var workTimes: List<WorkTime> = arrayListOf(),
#OneToMany(mappedBy = "contractor", cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
var contractorOrders: List<Order>? = arrayListOf(),
#OneToMany(mappedBy = "selfEmployed", cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
var selfEmployedOrders: List<Order>? = arrayListOf(),
) {
#ElementCollection(fetch = FetchType.EAGER)
#Enumerated(EnumType.STRING)
#CollectionTable(
name = "profiles_authorties",
joinColumns = [JoinColumn(name = "user_id", referencedColumnName = "id")],
)
#Column(name = "authority")
private val _authorities: MutableSet<ProfileAuthorities> = HashSet()
init {
_authorities.add(ProfileAuthorities.CLIENT)
}
fun setAsAdmin() =
_authorities.add(ProfileAuthorities.ADMIN)
fun getAuthorities(): Set<ProfileAuthorities> = _authorities
}
My interface for projection:
interface LoginUserProjection {
fun getId(): Long
fun getPasswordHash(): String
fun getAuthorities(): Set<ProfileAuthorities>
}
The result query is:
Hibernate: select user0_.id as id1_12_, user0_.id_area as id_area11_12_, user0_.cpf as cpf2_12_, user0_.email as email3_12_, user0_.id_location as id_loca12_12_, user0_.name as name4_12_, user0_.password_hash as password5_12_, user0_.password_hash_recovery as password6_12_, user0_.password_hash_recovery_date as password7_12_, user0_.phone_number as phone_nu8_12_, user0_.rating_star as rating_s9_12_, user0_.self_employed as self_em10_12_, user0_.user_photo as user_ph13_12_ from users user0_ where user0_.id=?
Hibernate: select authoriti0_.user_id as user_id1_8_0_, authoriti0_.authority as authorit2_8_0_ from profiles_authorties authoriti0_ where authoriti0_.user_id=?
when i remove fun getAuthorities(): Set<ProfileAuthorities> from LoginUserProjection the result is:
Hibernate: select user0_.id as col_0_0_, user0_.password_hash as col_1_0_ from users user0_ where user0_.id=?
My repository method:
#Repository
interface UserRepository : JpaRepository<User, Long> {
fun <T> getUserProjectionById(id: Long, projection: Class<T>): T?
}

Spring boot lazy collection not loaded properly

I have 3 entities:
User, Shelter and ShelterUsers
ShelterUsers is a join table for many to many relationship between Shelter and User with additional column. (design based on this suggestion)
I also have a #OneToMany relationship from User to Role which also uses a join table but without an additional field so there is no entity for that table
When I get the User from the database Roles are also attached with a join if I set the fetch to EAGER and they're loaded with additional query if I set fetch to LAZY (even without calling the user.getRoles() explicitly, which I also don't understand).
Problem is that ShelterUsers are not attached to user, no matter if I set it to EAGER or LAZY. After inspecting the sql that hibernate generates it seems like it generates the wrong sql:
First query:
SELECT user0_.id AS id1_7_0_,
user0_.created_at AS created_2_7_0_,
user0_.email AS email3_7_0_,
user0_.first_name AS first_na4_7_0_,
user0_.last_name AS last_nam5_7_0_,
user0_.password AS password6_7_0_,
user0_.updated_at AS updated_7_7_0_,
user0_.username AS username8_7_0_,
roles1_.user_id AS user_id1_8_1_,
role2_.id AS role_id2_8_1_,
role2_.id AS id1_4_2_,
role2_.name AS name2_4_2_
FROM users user0_
LEFT OUTER JOIN users_roles roles1_ ON user0_.id=roles1_.user_id
LEFT OUTER JOIN ROLES role2_ ON roles1_.role_id=role2_.id
WHERE user0_.id=?
Second query:
SELECT shelters0_.shelter_id AS shelter_3_6_0_,
shelters0_.id AS id1_6_0_,
shelters0_.id AS id1_6_1_,
shelters0_.shelter_id AS shelter_3_6_1_,
shelters0_.user_id AS user_id4_6_1_,
shelters0_.user_role AS user_rol2_6_1_,
user1_.id AS id1_7_2_,
user1_.created_at AS created_2_7_2_,
user1_.email AS email3_7_2_,
user1_.first_name AS first_na4_7_2_,
user1_.last_name AS last_nam5_7_2_,
user1_.password AS password6_7_2_,
user1_.updated_at AS updated_7_7_2_,
user1_.username AS username8_7_2_
FROM shelter_users shelters0_
LEFT OUTER JOIN users user1_ ON shelters0_.user_id=user1_.id
WHERE shelters0_.shelter_id=?
Where clause should be WHERE user1_.id = ?
Here is all relevant code:
User:
#Entity
#Data
#Table(name = "users")
public class User {
...
#OneToMany(fetch = FetchType.EAGER)
#JoinTable(name = "users_roles",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id")
)
private Collection<Role> roles = new ArrayList<>();
#OneToMany(mappedBy = "shelter")
private Collection<ShelterUsers> shelters = new ArrayList<>();
...
}
Shelter:
#Entity
...
#Table(name = "shelters")
public class Shelter {
...
#OneToMany(mappedBy = "user")
private List<ShelterUsers> users = new ArrayList<>();
}
ShelterUsers:
#Entity
public class ShelterUsers {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
#ManyToOne
#JoinColumn(name = "shelter_id", referencedColumnName = "id")
private Shelter shelter;
#Enumerated(EnumType.STRING)
#Column(name = "user_role")
private ShelterUserRole userRole;
}
Recap:
Roles get loaded with user no matter the fetch type.
ShelterUsers are not loaded
JSON example:
{
...
"roles": [
{
"id": 1,
"name": "ROLE_ADMIN"
}
],
"shelters": [],
...
}
What I would ideally want to achieve is this:
{
"roles": [
{
"id": 1,
"name": "ROLE_ADMIN"
}
],
"shelters": [
{
//Shelter entity (not ShelterUser)
},
]
}

Select a unique names from Users that have more than 3 Articles. Spring Data, H2

I use: Spring Boot, Spring Data, and H2 (in-memory) DataBase.
DB TABLES:
CREATE TABLE user
(
id INT AUTO_INCREMENT,
name VARCHAR(250) NOT NULL,
age INT NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE article
(
id INT AUTO_INCREMENT,
text TEXT NOT NULL,
color ENUM ('red', 'green', 'blue', 'yellow', 'pink') NOT NULL,
user_id INT,
PRIMARY KEY (id),
FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE ON UPDATE CASCADE
);
In ArticleRepository exploited such query:
SELECT u.name, a.user_id, count() FROM user AS u INNER JOIN article AS a ON u.id = a.user_id GROUP BY a.user_id HAVING COUNT() > 3;
#Repository
public interface ArticleRepository extends JpaRepository<Article, Integer> {
#Query(value = "SELECT u.name, a.user_id, count(*) FROM user AS u INNER JOIN article AS a ON u.id = a.user_id GROUP BY a.user_id HAVING COUNT(*) > 3", nativeQuery = true)
List<Article> findUserNamesByArticlesCountMoreThan();
}
After request I receive the error:
In Postman:
"error": "Internal Server Error", "message": "could not execute query;
SQL [SELECT u.name, a.user_id, count(text) FROM user AS u INNER JOIN
article AS a ON u.id = a.user_id GROUP BY a.user_id HAVING COUNT(*) >
3]; nested exception is org.hibernate.exception.SQLGrammarException:
could not execute query",
In IntelliJ Idea:
02:54:24.681 [http-nio-9090-exec-1] WARN SqlExceptionHelper - SQL
Error: 42122, SQLState: 42S22 02:54:24.681 [http-nio-9090-exec-1]
ERROR SqlExceptionHelper - Column "id" not found [42122-197]
02:54:24.703 [http-nio-9090-exec-1] ERROR [dispatcherServlet] -
Servlet.service() for servlet [dispatcherServlet] in context with path
[] threw exception [Request processing failed; nested exception is
org.springframework.dao.InvalidDataAccessResourceUsageException: could
not execute query; SQL [SELECT u.name, a.user_id, count(text) FROM
user AS u INNER JOIN article AS a ON u.id = a.user_id GROUP BY
a.user_id HAVING COUNT(*) > 3]; nested exception is
org.hibernate.exception.SQLGrammarException: could not execute query]
with root cause org.h2.jdbc.JdbcSQLException: Column "id" not found
[42122-197] at
org.h2.message.DbException.getJdbcSQLException(DbException.java:357)
at org.h2.message.DbException.get(DbException.java:179) at
org.h2.message.DbException.get(DbException.java:155) at
org.h2.jdbc.JdbcResultSet.getColumnIndex(JdbcResultSet.java:3148) at
org.h2.jdbc.JdbcResultSet.get(JdbcResultSet.java:3247) at
org.h2.jdbc.JdbcResultSet.getInt(JdbcResultSet.java:346)..............
Help me to resolve this problem, and find a mistake.
Entities:
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "article")
public class Article {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#Column(name = "text", nullable = false)
private String text;
#Column(name = "color", nullable = false, unique = true)
#Enumerated(EnumType.STRING)
private Color color;
#JsonIgnore
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL )
#JoinColumn(name = "user_id", referencedColumnName="id")
private User user;
}
.
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#ToString(exclude = "articles")
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "age", nullable = false)
private Integer age;
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<Article> articles;
}
Service:
#Service
public class ArticleService {
private ArticleRepository articleRepository;
#Autowired
public ArticleService(ArticleRepository articleRepository) {
this.articleRepository = articleRepository;
}
public List<Article> findAllUserNamesByArticlesMoreThan() {
return articleRepository.findUserNamesByArticlesCountMoreThan(); }}
ArticleDtoService:
#Service
public class ArticleDtoService {
private ArticleService articleService;
#Autowired
public ArticleDtoService(ArticleService articleService) {
this.articleService = articleService;
}
public ResponseEntity<List<ArticleDto>> getAllUserNamesByArticlesMoreThan() {
List<Article> articles = articleService.findAllUserNamesByArticlesMoreThan();
Link link = linkTo(methodOn(ArticleController.class).getAllUserNamesListByArticlesMoreThan()).withSelfRel();
return new ResponseEntity<>(createArticleDtoList(articles, link), HttpStatus.OK); }}
ArticleController:
#RestController
public class ArticleController {
private final ArticleDtoService articleDtoService;
#Autowired
public ArticleController(ArticleDtoService articleDtoService) {
this.articleDtoService = articleDtoService;
}
#GetMapping(value = "/api/articles/count")
public ResponseEntity<List<ArticleDto>> getAllUserNamesListByArticlesMoreThan() {
return articleDtoService.getAllUserNamesByArticlesMoreThan(); }}
ArticleDto:
public class ArticleDto extends ResourceSupport {
public Article article;
public ArticleDto(Article article, Link selfLink) {
this.article = article;
add(selfLink); }}

Inner join in spring boot data jpa

I am using spring boot data jpa 1.4 and I'm fairly new to it.
My table definition is here. Its fairly simple, there are 2 tables (Groups and Users).
The group table contains group_id(primary key), group_name, group_active(values=Y/N).
The group table can ideally have only one row which is has group_active to 'Y', the rest should have 'N'
The user table contains user_id(primary key), user_name, group_id(foreign key from group).
Following are my entity classes
Group:
#Entity
#Table(schema = "HR", name = "GROUPS")
public class Group {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "GROUP_ID")
private Long id;
#Column(name = "GROUP_NAME")
private String name;
#Column(name = "GROUP_ACTIVE")
private String active;
User:
#Entity
#Table(schema = "HR", name = "USERS")
public class User {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "USER_ID")
private Long id;
#Column(name = "USER_NAME")
private String name;
#Column(name = "GROUP_ID")
private Long groupId;
#ManyToMany
#JoinTable(
schema = "HR",
name = "GROUPS",
joinColumns = {#JoinColumn(table = "GROUPS", name = "GROUP_ID", insertable = false, updatable = false)},
inverseJoinColumns = {#JoinColumn(table = "USERS", name = "GROUP_ID", insertable = false, updatable = false)}
)
#WhereJoinTable(clause = "GROUP_ACTIVE='Y'")
private List<Group> group;
Repository class:
public interface UserRepository extends CrudRepository<User, Long>{
List<User> findByName (String name);
}
Query: This is the query I want to execute, which is a simple inner join.
SELECT U.*
FROM HR.USER U, HR.GROUP G
WHERE U.GROUP_ID=G.GROUP_ID
AND G.GROUP_ACTIVE='Y'
AND U.USER_NAME=?
What would be the correct way to write the #JoinTable or #JoinColumn such that I always get back one user that belongs to the active group with the name ?
I have done some tests based on your set-up and the solution would need to use filters (assuming there is only one Group with Group_Activity = 'Y'):
Group Entity
#Entity
#Table(schema = "HR", name = "GROUPS")
public class Group {
#OneToMany(mappedBy = "group")
#Filter(name = "activityFilter")
private Set<User> users;
User Entity
#Entity
#Table(schema = "HR", name = "USERS")
#FilterDef(name="activityFilter"
, defaultCondition="group_id =
(select g.id from groups g where g.GROUP_ACTIVE='Y')")
public class User {
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
When making a query
session.enableFilter("activityFilter");
session.createQuery("select u from Group g inner join g.users u where u.user_name = :userName");
Additionally if there are many groups with activity = 'Y' then try this:
#FilterDef(name="activityFilter"
, defaultCondition="group_id in
(select g.id from group g where g.GROUP_ACTIVE='Y')")

Oracle - JPA Alter Constraint

I'm having a problem with a constraint in oracle/jpa mapping.
I have these 4 tables
SucursalSolicitanteFornecimento(
Sucursal_ID (NUMBER) PK
Sucursal_Name(VARCHAR)
Sucursal_UF
);
ParamSolicitante(
paramID (NUMBER)
paramName(Varchar)
);
ConfiguracaoParametroSolicitante(
paramConfigID (NUMBER) PK,
paramID FK (from ParamSolicitante),
requestID (From SucursalSolicitanteFornecimento)
);
ConfiguracaoParametroSolicitanteSucursal(
ID (NUMBER) PK,
paramConfigID FK from ConfiguracaoParametroSolicitante,
SucursalSolicitanteFornecimento_ID
SucursalSolicitanteFornecimento_UF - Both From SucursalSolicitanteFornecimento
);
My problem is when I try to insert a new value in ConfiguracaoParametroSolicitanteSucursal I can't have 2 different ParamConfigID with the same SucursalSolicitanteFornecimento_UF and same SucursalSolicitanteFornecimento_ID because of the FK.
But now that's exactly what I need, more than one parameter for that SucursalSolicitante on the same state.
My Java class is mapped this way:
public class ConfiguracaoParametroSolicitanteSucursal{
#Id
#GeneratedValue()
private Integer codigoConfigParamSolctteSuc;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumns( { #JoinColumn(name = "SUCURSAL_ID", referencedColumnName = "SUCURSAL_ID", nullable = false),
#JoinColumn(name = "SUCURSAL_UF", referencedColumnName = "SUCURSAL_UF", nullable = false) })
private SucursalSolicitanteFornecimento sucursalParametro;
#JoinColumn(name = "paramConfigID", referencedColumnName = "paramConfigID", nullable = false)
#ManyToOne(fetch = FetchType.LAZY)
private ConfiguracaoParametroSolicitante configuracaoParametroSolicitante;
}
What can I do to achieve what I need? I was wondering if I have to change #OneToOne column to #ManyToOne and Delete the Constraint on my Database.

Resources