How to resolve hibernate n+1 problem OneToMany and ManyToOne - java

I have problem with N+1 Hibernate.
I have followings entities :
public class Coupon {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private Long id;
#Builder.Default
#OneToMany(fetch = FetchType.LAZY,
targetEntity = CouponType.class,
cascade = CascadeType.ALL,
mappedBy = "coupon")
private List<CouponType> couponTypeList = new ArrayList<>();
public class CouponType {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
private Match match;
#ManyToOne
private Coupon coupon;
public class Match {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private Long id;
#Builder.Default
#OneToMany(fetch = FetchType.LAZY,
targetEntity = CouponType.class,
mappedBy = "match")
private List<CouponType> couponTypeList = new ArrayList<>();
And I want to avoid N+1 Problem in hibernate. How can I wrtie properly query in JPQL ?
Solution :
#Query("select c from Coupon c join fetch c.couponTypeList t join fetch t.match where c.id = ?1")
Optional<Coupon> getCoupon(Long couponId)

Try to use the following query:
#Query("select c from Coupon c join fetch c.couponTypeList where c.id = ?1")
Optional<Coupon> getCoupon(Long couponId);

Related

How can I query all entities that have an associated entity in Hibernate Query Language?

I'm struggling to understand how can I query all products that have specified category association.
I might need something like this
select p.* from product p
inner join product_category pc on p.product_id = pc.product_id
inner join category c on pc.category_id = c.category_id
where c.name = :categoryName;
but in HQL.
Assuming that you use the following mapping:
#Entity
#Table(name = "product")
class Product
{
#Id
#Column(name = "product_id")
private Long id;
#ManyToMany
#JoinTable(name = "product_category",
joinColumns = #JoinColumn(name = "category_id"),
inverseJoinColumns = #JoinColumn(name = "product_id")
)
private List<Category> categories;
// getters/ setters
}
#Entity
#Table(name = "category")
class Category
{
#Id
#Column(name = "category_id")
private Long id;
#Column(name = "name")
private String name;
// getters/ setters
}
you can use the following HQL:
select p from Product
join fetch p.categories c
where c.name = :categoryName

JPA multi fetch with #OrderColumn return multiple null value

Have 3 entities
Group
#Entity
#Table(name = "`group`")
public class Group implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_group")
private Long id;
#OneToMany(mappedBy = "group",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#OrderColumn(name = "id_student")
private List<Student> students = new ArrayList<>();
#ManyToOne
#JoinColumn(name = "id_faculty")
private Faculty faculty;
.....getters/setters
}
Student
#Entity
#Table(name = "student")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Long.class)
public class Student implements Serializable {
#Id
#Column(name = "id_student")
private Long id;
......
#OneToMany(mappedBy = "student",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private List<Rating> ratings = new ArrayList<>();
#ManyToOne
#JoinColumn(name = "id_okr")
private OKR okr;
#ManyToOne
#JoinColumn(name = "id_group")
private Group group;
.....getters/setters
}
Rating
#Entity
#Table(name = "rating")
public class Rating implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_rating")
private Long id;
#Temporal(TemporalType.DATE)
#Column
private Date date;
#ManyToOne
#JoinColumn(name = "id_student")
private Student student;
#ManyToOne
#JoinColumn(name = "id_paragraph")
private Paragraph paragraph;
.....getters/setters
}
JPA Query
#Query(value = "SELECT g FROM Group g INNER JOIN FETCH g.students s LEFT JOIN FETCH s.ratings r WHERE g.id = :id AND s.group.id = g.id AND (r.student.id = s.id AND r.date BETWEEN :startMonth and :endMonth OR r IS NULL) GROUP BY s.id")
Group findGroupByStudentGroupId(#Param("id") Long id ,#Param("startMonth") Date startMonth, #Param("endMonth") Date endMonth );
I have the student with id 8000 and after extracting query the result list contains 8001 elements which contain 8 students that I have and 7993 null values. If I remove annotation #OrderColumn I have MultiBagException(cannot simultaneously fetch). If add #OrderColumn to rating #OneToMany association in the entity Student I have null values in Rating collection and in Student collection.
For me, the logic of #OrderColumn which return null values as biggest id in collection seems very strange. Is any way how to solve it?
Hibernate version 5.1.0 Final
Spring Data JPA 1.8.2
As you are trying to fetch students with particular group and ratings, you can have your query fetch from student entity and have join on Ratings entity. You don't have to explicitly add join for group entity as it is already in many to one mapping with student.
You can Change your query as follows:
#Query(value = "SELECT s FROM Student s LEFT JOIN s.ratings r WHERE s.group.id = :id AND (r.date BETWEEN :startMonth and :endMonth OR r IS NULL) GROUP BY s.id")
List<Student> findGroupByStudentGroupId(#Param("id") Long id ,#Param("startMonth") Date startMonth, #Param("endMonth") Date endMonth );

Get primary key instead the whole object in OneToMany relationship

I have the following classes:
#Entity
#Table(name = "elements")
#Inheritance(strategy=InheritanceType.JOINED)
#XmlRootElement
public abstract class Elements implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "idelement")
private Integer idElement;
#Basic(optional = false)
#Column(name = "code")
private String code;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "idElement")
#Fetch(value = FetchMode.SUBSELECT)
private Collection<Alarms> alarmsCollection;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "idElement")
#Fetch(value = FetchMode.SUBSELECT)
private Collection<ElementsHistorical> elementsHistoricalCollection;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST, mappedBy = "elementsCollection")
#Fetch(value = FetchMode.SUBSELECT)
private Collection<ElementsGroups> elementsGroupsCollection;
//Constructors, getters and setters
}
#Entity
#Table(name = "alarms")
#XmlRootElement(name = "Alarms")
public class Alarms implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "idalarm")
private Integer idAlarm;
#JoinColumn(name = "idelement", referencedColumnName = "idelement")
#ManyToOne(optional = false)
private Elements idElement;
//Constructors, getters and setters
}
I created a jersey webservice and a DAO class with CRUD operations to treat Alarms data. With this implementation, when I call GET method, I get the whole Elements object inside the Alarms one. This is the DAO method called:
public List<Alarms> getAlarms(){
Session session = SessionUtil.getSession();
Query query = session.createQuery("from Alarms");
List<Alarms> alarms = query.list();
session.close();
return alarms;
}
I don't want this fetch = FetchType.EAGER fetch type, as I just need the PK of Elements, but after some research this is the only way I found to make my service work.
I tried this and this approach, but I've not been able to make it work.
You can use left outer join fetch in you query.
Query query = session.createQuery("from Alarms alarm left outer join fetch alarm.idElement element");
Now you can keep fetch = FetchType.LAZY (which is default).
Try to add #XmlTransient annotation to fields in Elements class that you want to ignore.
like this :
#Entity
#Table(name = "elements")
#Inheritance(strategy=InheritanceType.JOINED)
#XmlRootElement
public abstract class Elements implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "idelement")
private Integer idElement;
#Basic(optional = false)
#Column(name = "code")
#XmlTransient
private String code;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "idElement")
#Fetch(value = FetchMode.SUBSELECT)
#XmlTransient
private Collection<Alarms> alarmsCollection;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "idElement")
#Fetch(value = FetchMode.SUBSELECT)
#XmlTransient
private Collection<ElementsHistorical> elementsHistoricalCollection;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST, mappedBy = "elementsCollection")
#Fetch(value = FetchMode.SUBSELECT)
#XmlTransient
private Collection<ElementsGroups> elementsGroupsCollection;
//Constructors, getters and setters
}
You can use multiselect with Tuple, for select only specific columns.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<Alarms> root = cq.from(Alarms.class);
CollectionJoin<Object, Object> joinCollection = root.joinCollection("alarmsCollection");
cq.multiselect(joinCollection.get("idElement"));
List<Tuple> tupleResult = entityManager.createQuery(cq).getResultList();
for (Tuple t : tupleResult) {
Long idElement = (Long) t.get(0);
}

SQL Query Too Complex To Express In JPA Criteria API?

I have an SQL query that gets me exactly the data I need. The problem is that we are trying to express all queries in JPA Criteria API to maintain portability, and I can't figure out how to map this particular query.
The problem is that the JPA Criteria API Subquery class lacks the multiselect() method that CriteriaQuery class has. As you can see in the SQL query, I have computed fields in the sub-query which don't exist in the entity. Thus, I have no way to retrieve these fields.
I would be quite appreciative if anyone knows a solution or could offer guidance, or even if someone could validate that what I am trying to achieve in JPA Criteria API is not possible.
The SQL:
SELECT w.NAME AS 'wave_name',
Count(*) AS 'num_lines',
Sum(qty_ordered) AS 'num_units',
Count(DISTINCT unit_of_work_id) AS 'num_units_of_work',
Sum(completed_units) AS 'completed_units',
( Sum(completed_units) + Sum(qty_scratched) ) / Sum(qty_ordered) AS 'perc_completed_units'
FROM (SELECT t.id,
t.wave_id,
t.quantity_requested AS 'qty_ordered',
t.quantity_scratched AS 'qty_scratched',
t.unit_of_work_id AS 'unit_of_work_id',
Ifnull(m.quantity, 0) AS 'qty_picked',
CASE
WHEN Ifnull(m.quantity, 0) > quantity_requested THEN
quantity_requested
ELSE Ifnull(m.quantity, 0)
END AS 'completed_units'
FROM task t
LEFT OUTER JOIN (SELECT move.task_id,
Sum(quantity) AS 'quantity'
FROM move
GROUP BY task_id) m
ON m.task_id = t.id) s
JOIN wave w
ON w.id = s.wave_id
GROUP BY w.name;
The entities:
#Entity
#Table(name = "task")
public class Task {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#ManyToOne (cascade = CascadeType.ALL)
#JoinColumn (name = "wave_id", nullable = false)
private Wave wave;
#ManyToOne (cascade = CascadeType.ALL)
#JoinColumn (name = "unit_of_work_id", nullable = false)
private UnitOfWork unitOfWork;
#OneToMany (cascade = CascadeType.ALL, mappedBy = "task")
private Set<Move> moves = new HashSet<Move>();
#Column (name = "quantity_requested")
private Long quantityRequested;
#Column (name = "quantity_scratched")
private Long quantityScratched;
}
#Entity
#Table(name = "wave")
public class Wave {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "wave", cascade = CascadeType.ALL)
private Set<Task> tasks = new HashSet<Task>();
}
#Entity
#Table(name = "unit_of_work")
public class UnitOfWork {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
#Column (name = "id")
private Long id;
#OneToMany(mappedBy = "unitOfWork", cascade = CascadeType.ALL)
private Set<Task> tasks = new HashSet<Task>();
}
#Entity
#Table(name = "move")
public class Move {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#ManyToOne (cascade = CascadeType.ALL)
#JoinColumn (name = "task_id", nullable = false)
private Task task;
#Column (name = "quantity")
private Long quantity;
}
I would say use named parameters or native query approach for this.
For example:
Named parameters:
public interface Repo extends JpaRepository<AEntity, String> {
#Query("select a from AEntity a where a.BEntity.name = :name")
public aMethod( #Param("name") String name)
}
OR
Native query approach:
public interface Repo extends JpaRepository<AEntity, String> {
#Query(value = "select * from Tablename t where t.name = :name", nativeQuery=true)
public aMethod(#Param("name") String name)
}
Check this link if you are using spring jpa
http://docs.spring.io/spring-data/data-jpa/docs/1.4.x/reference/htmlsingle/#jpa.named-parameters

Hibernate Criteria Query for many to many Enum

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;

Categories

Resources