I am facing the n+1 query issue when I use #JoinColumnsOrFormulas or #JoinColumns with #NamedEntityGraph(includeAllAttributes = true)
I tried to also left join the entity in Specification and it also cause n+1 query issue.
Entity:
#Data
#Entity
#Table(name = "book")
#NamedEntityGraph(name = "all", includeAllAttributes = true)
public class BookEntity implements JpaEntity {
#ManyToOne(targetEntity = CustomerEntity.class)
#JoinColumnsOrFormulas({
#JoinColumnOrFormula(column = #JoinColumn(name = "customer", referencedColumnName = "number", insertable = false, updatable = false)),
#JoinColumnOrFormula(column = #JoinColumn(name = "sub_customer", referencedColumnName = "sub_number", insertable = false, updatable = false)),
#JoinColumnOrFormula(formula = #JoinFormula(value = "'2019'", referencedColumnName = "year"))
})
private CustomerEntity customerEntity;
}
#Entity
#Data
#Table(name = "customer")
public class CustomerEntity implements JpaEntity {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "number", columnDefinition = "INTEGER")
private Integer number;
#Column(name = "sub_number", columnDefinition = "INTEGER")
private Integer subNumber;
#Column(name = "year")
private String year;
}
Repository:
#Override
#EntityGraph(value = "all")
Page<BookEntity> findAll(Specification<BookEntity> spec, Pageable pageable);
I see that the query is correct. I see the join for the first query, so it looks like I have the full entity and no need to make n+1 query. But after correct query I see additional selects for the CustomerEntity.
Can someone help me to avoid n+1 issue with this case?
Related
I am trying to sort my API result by a nested entity field.
My Entities look like that:
#Entity
#Table(name = "book_item")
public class BookItem {
#Id
#Column(name = "id", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#ManyToOne
#NotNull
private Book book;
...
}
#Entity
#Table(name = "book")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Book {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#Column(name = "title", nullable = false)
private String title;
private String subtitle;
#ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "rel_book_author", joinColumns = #JoinColumn(name = "book_id"), inverseJoinColumns = #JoinColumn(name = "author_id"))
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#JsonIgnoreProperties(value = { "books" }, allowSetters = true)
private Set<Author> authors = new HashSet<>();
#Column(name = "isbn", nullable = false)
private String isbn;
#Column(name = "pages", nullable = false)
private Integer pages;
...
}
Now I want to query all BookItems sorted by the Book titles.
My repository lloks like that:
public interface BookItemRepository extends JpaRepository<BookItem, UUID> {
#Query(
"select distinct b from BookItem b left join b.book.authors authors where (upper(authors.name) like upper(concat('%', ?2, '%')) or authors is NULL ) and upper(b.book.title) like upper(concat('%', ?1, '%'))"
)
Page<BookItem> findAllByTitleAndAuthor(String title, String name, Pageable pageable);
}
I've implemented an Angular component which does the following request:
GET http://localhost:9000/api/book/items?size=5&sort=title,asc
I get a 500 error code:
org.hibernate.QueryException: could not resolve property: title of: org.pickwicksoft.libraary.domain.BookItem [select distinct b from org.pickwicksoft.libraary.domain.BookItem b left join b.book.authors authors where (upper(authors.name) like upper(concat('%', ?2, '%')) or authors is NULL ) and upper(b.book.title) like upper(concat('%', ?1, '%')) order by b.title asc, b.id asc]; nested exception is java.lang.IllegalArgumentException
I also tried the following and it also does not work:
GET http://localhost:9000/api/book/items?size=5&sort=book_title,asc
How can I solve this problem?
I have two java entity classes :
#Table(name = "user")
public class UserEntity
{
#Id
#Column(name = "id", unique = true, nullable = false)
private Long id;
#OneToOne(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
#JoinColumn(name = "opportunity_id")
private OpportunityEntity opportunity;
}
and
#Table(name = "opportunity")
public class OpportunityEntity
{
#Id
#Column(name = "id", unique = true, nullable = false)
private Long id;
#OneToMany
#JoinColumn(name = "opportunity_id")
private List<UserEntity> users;
#OneToOne
#JoinColumn(name = "mainuser_id")
private UserEntity mainUser;
}
When i search for a list of Users [find users], i've got a "stackoverflow" when mapping User.opportunity.
the bug was clear that the opportunity.mainUser refer to User which itself refer to the same opportunity.
Is there another way to design my models ?
For example create a boolean isMain in User Model ?
Try to specify relationship to UserEntity by adding mappedBy to annotatation
#Table(name = "opportunity")
public class OpportunityEntity
{
#Id
#Column(name = "id", unique = true, nullable = false)
private Long id;
#OneToMany
#JoinColumn(name = "opportunity_id")
private List<UserEntity> users;
#OneToOne(mappedBy="opportunity")
#JoinColumn(name = "mainuser_id")
private UserEntity mainUser;
}
I have a problem with DiscriminatorColumn and DiscriminatorValue.
My expectation is :
-TypeOne, TypeTwo, TypeThree are options related to the User.
-A user can have TypeOne only 1 row.
-A user can have TypeTwo or TypeThree multiple rows.
-I used Pageable for search criteria and sorting by typeOneEntity.optionCode
User table.
|id|user_id|
|-|-|
|1114|a#a.com|
UserOption table.
|id|user_id|option_code|option_type_name|
|-|-|-|-|
|1|1114|CODE_1|TYPE_1|
|2|1114|CODE_2|TYPE_2|
|3|1114|CODE_3|TYPE_2|
|4|1114|CODE_4|TYPE_3|
Ps. User.id = UserOption.user_id
I have to create three entities map to this data.
#Entity
#Table(name = "user")
public class User{
#Column()
private Long id;
#Column()
private String userId;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id", referencedColumnName = "user_id", insertable = false, updatable = false)
private TypeOne typeOneEntity;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "id", referencedColumnName = "user_id", insertable = false, updatable = false)
private Set<TypeTwo> typeTwoEntities;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "id", referencedColumnName = "user_id", insertable = false, updatable = false)
private Set<TypeThree> typeThreeEntities;
}
#Table(name = "user_option")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "option_type_name")
public abstract class UserOption{
#Column
private Long id;
#Column(name = "user_id", nullable = false)
private Long userId;
#Column
private String optionCode;
}
#Entity
#DiscriminatorValue(value = "TYPE_1")
public class TypeOne extends UserOption {
}
#Entity
#DiscriminatorValue(value = "TYPE_2")
public class TypeTwo extends UserOption {
}
#Entity
#DiscriminatorValue(value = "TYPE_3")
public class TypeThree extends UserOption {
}
After I build and try to find data, I found two issues.
1.On typeOneEntity(#OneToOne) is null.
2.On #OneToMany : I got this error ORA-01722: invalid number (error message from Hibernate)
This Entity model that I create it correct or not?
Or it need to change entity model.
I am a newbie to the Spring boot (but worked in Laravel). I am facing a problem of cyclic redundancy in #ManyToMany relation. Let's go through the scenario -
What response I ma getting (fetching user's list which has many to many relationships with roles) -
Following is the ER-diagram of associated tables to manage many to many relationship between users and roles table.
User entity class has following code -
#Entity
#Where(clause = "deleted_at IS NULL")
#SQLDelete(sql = "UPDATE users SET deleted_at = CURRENT_TIMESTAMP WHERE id = ?", check = ResultCheckStyle.COUNT)
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "users")
#JsonIgnoreProperties(
value = {"createdAt", "updatedAt", "deletedAt"}
)
public class User {
#Id
#Column(name = "id", updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "name", nullable = false)
#NotBlank(message = "Name field can not be empty")
private String name;
.....
.....
.....
#ManyToMany(targetEntity = Role.class, fetch = FetchType.EAGER)
#JoinTable(name = "user_roles",joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
}
And Role entity is as follows -
#Entity
#Table(name = "roles")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#SQLDelete(sql = "UPDATE roles SET deleted_at = CURRENT_TIMESTAMP WHERE id = ?", check = ResultCheckStyle.COUNT)
#Where(clause = "deleted_at IS NULL")
#JsonIgnoreProperties(
value = {"createdAt", "updatedAt", "deletedAt"}
)
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private long id;
#Column(name = "title")
#NotBlank(message = "Title field must be not null")
private String title;
......
......
......
#OneToMany(targetEntity = User.class, fetch = FetchType.EAGER)
#JoinTable(name = "user_roles",joinColumns = #JoinColumn(name = "role_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
private List<User> users;
}
How to solve this problem? What I am doing wrong here?
Since you are fetching the list directly. You will have to mention the annotation #JsonIgnore everywhere you have mapping specified. By everywhere I don't mean literally everywhere. Just use the annotation and see how it works.
Edit -> Just do it in roles table where you have mapped it to the user table. It will then skip the user mapping while fetching the data.
#JsonIgnore
private List<User> users;
You could annotate users within Role with #JsonBackReference.
Easiest would probably be to annotate the List<T>'s with a #JsonIgnoreProperties annotation to break the cyclic dependencies.
#JsonIgnoreProperties("users")
private List<Role> roles;
#JsonIgnoreProperties("roles")
private List<User> users;
I have a class Comment:
#Entity
#Table(name = Constants.COMMENTS_TABLE)
#Audited
public class Comment {
#Column(name = "comment", nullable = false)
private String comment;
#ElementCollection(targetClass = CommentTopic.class)
#Enumerated(EnumType.STRING)
#Fetch(value = FetchMode.JOIN)
#CollectionTable(name = Constants.COMMENTS_TOPIC_JOIN_TABLE, joinColumns = #JoinColumn(name = "comment_id"))
#Column(name = "topic")
private Set<CommentTopic> commentTopics;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "comment_id", nullable = false)
private Long commentId;
}
Persisting the comment class works but the following criteria query:
Criteria criteria = session.createCriteria(Comment.class)
.add(Restrictions.eq("commentTopics", topic));
List<Comment> entries = criteria.list();
throws org.hibernate.exception.DataException: No value specified for parameter 1.
This is the query built:
select this_.comment_id as comment1_0_0_, this_.comment as comment0_0_, commenttop2_.comment_id as comment1_0_2_, commenttop2_.topic as topic2_ from comments this_ left outer join comments_topic commenttop2_ on this_.comment_id=commenttop2_.comment_id where this_.comment_id=?
Am I using incorrect annotations?
Is the criteria query not being constructed properly?
I placed the CommentTopic enum in a CommentTopicWrapper class.
I updated the annotation for the commentTopicsSet to:
#OneToMany(targetEntity = CommentTopicWrapper.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = Constants.COMMENTS_TOPIC_JOIN_TABLE, joinColumns = #JoinColumn(name = "comment_id"), inverseJoinColumns = #JoinColumn(name = "topic"))
private Set<CommentTopicWrapper> commentTopics;