Spring Data Just Get the Same Entry Over and Over Again
We've covered a lot of topics on Hibernate already on this blog in a overnice sequential format. If you lot oasis't had the pleasure of going through all the tutorials, I'd suggest going to this page and having a flip through all the topics.
How to Avoid Duplicate Records from Hibernate Queries
This problem was the blight of my existence when I first started using Hibernate because I had no thought where the trouble was coming from.
If you're executing a query and finding that y'all have a bunch of duplicate records and haven't a clue why, and so you're in the correct identify.
Y'all run into the problem is typically acquired by having left joins (or optional joins) in your objects. When y'all have a base object, like say User and it joins to some other tabular array/object in an optional 1-to-Many or optional Many-to-Many format, so you may get duplicates.
Consider this scenario… A User objects joins to the LoginHistory object, which keeps rail of all the times a particular User has logged into the system. And let's say our user has logged in many times. You'll take a situation where yous have many records in the LoginHistory table.
And then what happens when you run a query that joins to the LoginHistory table? Well it will return every bit many rows equally at that place are entries for that User in the LoginHistory table.
Then because of this, Hibernate doesn't massage the data for you, information technology just returns exactly what it got from the database. The ball is in your courtroom to tell Hide what to practise with records it has retrieved.
There are ii solutions to this problem:
- Declare your joining object as a
Set - Make use of Distinct Root Entity Results Transformer
The Problem at a Glance
So here's an case of the problem in action. Below you'll encounter an outline of the optional one-to-many join between the User class and the LoginHistory class.
User.java
import coffee.util.List; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Table; @Entity @Table(name="users") public class User { private Long userId; individual String username; private Cord countersign; private List<LoginHistory> loginHistory; @Id @GeneratedValue(strategy=GenerationType.AUTO) @Column(proper name="user_id") public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public String getUsername() { render username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(Cord password) { this.password = password; } @OneToMany(cascade=CascadeType.ALL, mappedBy="user", fetch=FetchType.EAGER) public List<LoginHistory> getLoginHistory() { return loginHistory; } public void setLoginHistory(List<LoginHistory> loginHistory) { this.loginHistory = loginHistory; } } LoginHistory.java
package com.howtoprogramwithjava.case.persistence; import java.util.Engagement; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; @Entity @Table(proper name="login_history") public form LoginHistory { individual Long loginHistoryId; individual Date loggedIn; private User user; @Id @GeneratedValue(strategy=GenerationType.AUTO) @Column(name="login_history_id") public Long getLoginHistoryId() { return loginHistoryId; } public void setLoginHistoryId(Long loginHistoryId) { this.loginHistoryId = loginHistoryId; } @Cavalcade(name="logged_in") public Date getLoggedIn() { return loggedIn; } public void setLoggedIn(Engagement loggedIn) { this.loggedIn = loggedIn; } @ManyToOne(cascade=CascadeType.ALL, optional=true, fetch=FetchType.EAGER) @JoinColumn(name="user_id") public User getUser() { return user; } public void setUser(User user) { this.user = user; } } And so nosotros accept the entry in a DAO class that will query for Users by passing in a username.
public List<User> getUserByUsername (String username) { Session session = sessionFactory.getCurrentSession(); return session.createCriteria(User.grade).add(Restrictions.eq("username", username)).listing(); } So in the code direct to a higher place, nosotros are querying the database for Users that accept a username matching the String that'southward being passed in.
The problem with this is that in one case it runs this query, information technology volition return multiple rows if the User information technology finds has logged into the arrangement more than than once.
Again, it does this because it's an optional (left) join. If you were to have a await at the query built past Hibernate, it could await something like this (assuming the username being passed in is tpage):
select * from users left bring together login_history on login_history.user_id = users.user_id where users.username = 'tpage';
This query returns multiple results (three results to be verbal, in my database) as I've inserted three separate rows into the Login_history tabular array that point back to the tpage user.
Alright, so hopefully you lot fully empathise the problem, now let's talk about some solutions to this problem.
Declaring the Join as a Set
On the parent side of the human relationship, you'll be declaring a collection of objects that embodies your *-to-Many relationship. What you lot'll need to exercise here is to use a Set as the backing collection as opposed to something like a List (which is what we were using in the User form to a higher place).
Here's an case of how to gear up it up correctly:
import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Table; @Entity @Table(name="users") public class User { private Long userId; individual String username; private String password; private Set<LoginHistory> loginHistory; @Id @GeneratedValue(strategy=GenerationType.AUTO) @Column(name="user_id") public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public Cord getUsername() { render username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return countersign; } public void setPassword(Cord password) { this.password = password; } @OneToMany(cascade=CascadeType.ALL, mappedBy="user") public Set<LoginHistory> getLoginHistory() { return loginHistory; } public void setLoginHistory(Prepare<LoginHistory> loginHistory) { this.loginHistory = loginHistory; } } Once more, the primal is to employ a Set as the drove of child entities. And so in our example above we used Prepare<LoginHistory>
Distinct Root Entity Results Transformer
This sounds like pure gibberish, only thankfully it's not hard to implement. All y'all need to exercise is brand certain that yous prepare a ResultsTransformer when y'all're building your query.
Think that DAO query we talked almost near the beginning of this article? I'll show you that query again for the sake of completion:
public List<User> getUserByUsername (String username) { Session session = sessionFactory.getCurrentSession(); return session.createCriteria(User.class) .add(Restrictions.eq("username", username)) .list(); } This query is alright, just equally we've seen, it doesn't piece of work well when we have an choice join and we're dealing with a List of entities.
In the consequence that we Demand to utilize a Listing of entities, we can alter this query to use the ResultsTransformer. Permit's meet what the corrected lawmaking looks like:
public List<User> getUserByUsername (Cord username) { Session session = sessionFactory.getCurrentSession(); return session.createCriteria(User.grade) .add(Restrictions.eq("username", username)) .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) .list(); } You encounter the divergence? We but added one additional line to the query: setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) and voila! You lot will no longer have duplicates in your returned outcome prepare.
bolligerjusholl44.blogspot.com
Source: https://www.coderscampus.com/how-to-avoid-duplicate-records-in-hibernate/
0 Response to "Spring Data Just Get the Same Entry Over and Over Again"
Post a Comment