I have two Java Hibernate entities user and userPick:
#Entity
#Table(name="users")
public class User{
#Column(length=50)
private String name;
#OneToMany
private List<UserPick> userPicks = new ArrayList<UserPick>(0);
...
and
#Entity
#Table(name="usersPicks")
public class UserPick {
...
User user; // this line no exist in code
// I want get it when load all picks
// I am confused here or I can do it, because in user is
// List<userPick> and it sounds like circle
// user.getUserPicks().getUser().getUserPicks()....
...
When I load user everythings ok.
public User findByUserName(String name) {
Criteria criteria = session.createCriteria(User.class);
criteria.add(Restrictions.eq("name",name));
List<User> users = criteria.list();
if (users.size() > 0) {
return users.get(0);
} else {
return null;
}
}
But I looking way how I can get all usersPick's in one list and get picks users, something like:
public List<UserPick> getAllPicks(){
Criteria criteria = session.createCriteria(UserPick.class);
List<UserPick> picks = criteria.list();
return picks;
}
I want to print pick user name System.out.print(picks.get(0).getUser().getName())
It is ok to have an association to User in the UserPick. To associate User and UserPick by a foreign key fk_user in the UserPick, please, add mappedBy = "user" to User and make association to User lazy in the UserPick. To specify a foreign key column name you need #JoinnColumn(name = "fk_user").
#Entity
#Table(name="users")
public class User{
#Column(length=50)
private String name;
#OneToMany(mappedBy = "user")
private List<UserPick> userPicks = new ArrayList<UserPick>();
}
#Entity
#Table(name="usersPicks")
public class UserPick {
#ManyToOne(fetch = FetchType.LAZY)
#JoinnColumn(name = "fk_user")
private User user;
}
And you can fetch users by the way suggested by #StanislavL (except that you don't need to make an alias to user)
public List<UserPick> getAllPicks(){
Criteria criteria = session.createCriteria(UserPick.class)
.setFetchMode("user", FetchMode.JOIN);
return criteria.list();
}
Even if an association to User in UserPick is not lazy It is not a problem because of Hibernate uses a cache to set the same User to all user's UserPick.
criteria.setFetchMode("user", FetchMode.JOIN)
.createAlias("user", "user")
The FetchMode.JOIN should solve n+1 query problem
Related
I have the following entities:
#Entity
#Table(name = "user_data")
public class UserData {
...
#ManyToOne
private User user;
...
}
#Entity
#Table(name = "user_cars")
public class UserCar {
...
private Integer userId;
...
}
#Entity
#Table(name = "users")
public class User {
...
#OneToMany(mappedBy = "userId", cascade = CascadeType.ALL)
private List<UserCar> userCars;
...
}
As you can see, userCars are loaded lazily (and I am not going to change it). And now I use Specifications in order to fetch UserData:
public Page<UserData> getUserData(final SpecificationParameters parameters) {
return userDataRepository.findAll(createSpecification(parameters), parameters.pageable);
}
private Specification<UserData> createSpecification(final SpecificationParameters parameters) {
final var clazz = UserData.class;
return Specification.where(buildUser(parameters.getUserId()));
}
private Specification<UserData> buildUser(final Integer userId) {
return (root, criteriaQuery, criteriaBuilder) -> {
if (Objects.nonNull(userId)) {
final Join<UserData, User> joinParent = root.join("user");
return criteriaBuilder.equal(joinParent.get("id"), userId);
} else {
return criteriaBuilder.isTrue(criteriaBuilder.literal(true));
}
};
}
But I have no idea how to add there a fetch join clause in order to fetch user cars. I tried to add it in different place and I got either LazyInitializationException (so it didn't work) or some other exceptions...
Slightly different approach from the prior answer, but I think the idea jcc mentioned is on point, i.e. "Hibernate is complaining because it it unable to find the owner, user in this case, of the userCars relationship."
To that end, I'm wondering if the Object-Relational engine is getting confused because you have linked directly to a userId (a primitive) instead of a User (the entity). I'm not sure if it can assume that "userId" the primitive necessarily implies a connection to the User entity.
Can you try to re-arrange the mapping so that it's not using an integer UserId in the join table and instead using the object itself, and then see if it allows the entity manager to understand your query better?
So the mapping might look something like this:
#Entity
#Table(name = "user_cars")
public class UserCar {
...
#ManyToOne
#JoinColumn(name="user_id", nullable=false) // Assuming it's called user_id in this table
private User user;
...
}
#Entity
#Table(name = "users")
public class User {
...
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<UserCar> userCars;
...
}
It would also be more in line with
https://www.baeldung.com/hibernate-one-to-many
In addition of the suggestion #crizzis provided in the question comments, please, try to join fetch the user relationship as well; in the error you reported:
org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list
Hibernate is complaining because it it unable to find the owner, user in this case, of the userCars relationship.
It is strange in a certain way because the #ManyToOne relationship will fetch eagerly the user entity and it will be projected as well while obtaining userData but probably Hibernate is performing the query analysis prior to the actual fetch phase. It would be great if somebody could provide some additional insight about this point.
Having said that, please, consider to set the fetch strategy explicitly to FetchType.LAZY in your #ManyToOne relationship:
#Entity
#Table(name = "user_data")
public class UserData {
...
#ManyToOne(fetch= FetchType.LAZY)
private User user;
...
}
Your Specification code can look like the following:
public Page<UserData> getUserData(final SpecificationParameters parameters) {
return userDataRepository.findAll(createSpecification(parameters), parameters.pageable);
}
private Specification<UserData> createSpecification(final SpecificationParameters parameters) {
final var clazz = UserData.class;
return Specification.where(buildUser(parameters.getUserId()));
}
private Specification<UserData> buildUser(final Integer userId) {
return (root, criteriaQuery, criteriaBuilder) -> {
// Fetch user and associated userCars
final Join<UserData, User> joinParent = (Join<UserData, User>)root.fetch("user");
joinParent.fetch("userCars");
// Apply filter, when provided
if (Objects.nonNull(userId)) {
return criteriaBuilder.equal(joinParent.get("id"), userId);
} else {
return criteriaBuilder.isTrue(criteriaBuilder.literal(true));
}
};
}
I did not pay attention to the entity relations themself previously, but Atmas give you a good advice indeed, it will be the more performant way to handle the data in that relationship.
At least, it would be appropriate to define the relationship between User and UserCars using a #JoinColumn annotation instead of mapping through a non entity field in order to prevent errors or an incorrect behavior of your entities. Consider for instance:
#Entity
#Table(name = "users")
public class User {
...
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "user_id")
private List<UserCar> userCars;
...
}
This is my entity:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "surname")
private String surname;
#ManyToOne(fetch = FetchType.LAZY, cascade=CascadeType.MERGE)
#JoinColumn(name = "id_city")
private City city;
//...
}
In my Repository I have:
public interface UserRepository extends JpaRepository<User, Long>{
#Query("SELECT u FROM User u JOIN FETCH u.city")
public List<User> findAllUserForApi();
}
If there are any cities in table, findAllUserForApi() shows me full information about user:
[{"id":1,"name":"John","surname":"Pillman","city":{"id":1,"name":"New York"}]
If there are no cities, I want to get at least [{"id":1,"name":"John","surname":"Pillman","city":null]
But I've got nothing: []
Help me please.
Given that you are already using a custom query, the simplest solution would be a LEFT JOIN FETCH:
#Query("SELECT u FROM User u LEFT JOIN FETCH u.city")
This way all users will be loaded regardless of whether they have a city or not; and for those who have a city, it'll be available by user.getCity().
Why you write custom query here. You dont need.
Firstly you have to follow general convention:
#ManyToOne(fetch = FetchType.LAZY, cascade=CascadeType.MERGE)
#JoinColumn(name = "CITY_ID")
private City city;
...
And here JPA shows all information related with User.
public interface UserRepository extends JpaRepository<User, Long>{
public List<User> findAll();
}
It looks like you are trying to use Lazy Loading with a predefined Query, I don't think this is going to work.
See, the JOIN FETCH in your query state the following:
Get all the Users which has u.City
So if you don't have a u.City for a user, the return would be empty.
More info on Join and Fetch
What you really want is the following:
public User findUserByID(Long userId)
{
Session session = sessionFactory.getCurrentSession();
User user = (Users) session.createCriteria(User.class).add(Restrictions.idEq(userId)).uniqueResult();
// this will force SQL to execute the query that will join with the user's city and populate
// the appropriate information into the user object.
Hibernate.initialize(user.geCity());
return user;
}
If the u.City is NULL, it will return a NULL. while the User object contains data.
Or in your case Find all users :
public List<User> findUserByID(Long userId)
{
Session session = sessionFactory.getCurrentSession();
List<User> users = (List<User>) session.createCriteria(User.class);
// this will force SQL to execute the query that will join with the user's city and populate
// the appropriate information into the user object.
for (User user : users)
Hibernate.initialize(user.geCity());
return user;
}
Note:
I did not test the code, this is pseudo so you may want to change some of it.
source
I have three Java hibernate entities. And I want using hibernate criteria get all Users who has pick with specific id in their picks list.
Users entity:
#Entity
#Table(name="users")
public class User {
...
#ManyToMany
private List<UserPick> picks = new ArrayList<UserPick>(0);
...
UserPick entity:
#Entity
#Table(name="usersPicks")
public class UserPick {
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fk_user")
private User user;
#ManyToOne
private MatchPick pick;
...
I want get all of the users that have pick with specific pick.id, like:
// return all users if exist pick with pickId in picks list
public List<User> getUsersByPick(int pickId) {
Criteria criteria = session.createCriteria(User.class);
criteria... //
return criteria.list();
}
// Goal is get target but using Hibernate criteries
public List<User> getUsersByPick(int pickId) {
Criteria criteria = session.createCriteria(User.class);
List<User> users = criteria.list();
List<User> target = new List<>();
for(User u:users)
for(UserPick p:u.getPicks())
if(p.getId == pickId)target.add(u);
return target;
}
Work for me.
public List<User> getUsersByPick(int pickId) {
Criteria criteria = session.createCriteria(User.class)
.createCriteria("userPicks", "picks")
.createCriteria("pick", "pick")
.add( Restrictions.eq("pick.id",pickId) );
return criteria.list();
}
I have an simple association between 2 entities:
public class Car {
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
...
}
and
public class User {
#Id
#GeneratedValue
#Column(name = "user_id")
private long userId;
...
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<Car> cars;
...
}
Then I get some user id from client. For example, userId == 5;
To save car with user I need to do next:
User user = ... .findOne(userId);
Car car = new Car();
car.setUser(user);
... .save(car);
My question is: Can I persist car record without fetching user?
Similarly like I would do by using native SQL query: just insert userId like string(long) in Car table.
With 2nd lvl cache it will be faster but in my opinion I don't need to do extra movements. The main reason that I don't want to use native Query is because I have much more difficult associations in my project and I need to .save(car) multiple times. Also i don't want to manually control order of query executions.
If I use session.createSQLQuery("insert into .....values()") will the Hibernate's batch insert work fine?
Correct me if I'm wrong.
Thanks in advance!
UPDATE:
Actually the mapping is similar to:
There is #ManyToMany association between User and Car. But cross table is also an entity which is named, for example, Passanger. So the mapping is next:
public class User{
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", targetEntity = Passenger.class)
private Set<Passenger> passengers;
}
Cross entity
#IdClass(value = PassengerPK.class)
public class Passenger {
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "car_id")
private Car car;
... other fields ...
}
Car entity:
public class Car {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "car", targetEntity = Passenger.class, cascade = CascadeType.ALL)
private Set<Passenger> passengers;
}
And the code:
List<User> users = ... .findInUserIds(userIds); // find user records where userId is IN userIds - collection with user Ids
Car car = new Car(); //initialization of car's fields is omitted
if (users != null) {
car.setPassengers(new HashSet<>(users.size()));
users.forEach((user) -> car.getPassengers().add(new Passenger(user, car)));
}
... .save(car);
"Can I persist car record without fetching user?"
Yes, that's one of the good sides of Hibernate proxies:
User user = entityManager.getReference(User.class, userId); // session.load() for native Session API
Car car = new Car();
car.setUser(user);
The key point here is to use EntityManager.getReference:
Get an instance, whose state may be lazily fetched.
Hibernate will just create the proxy based on the provided id, without fetching the entity from the database.
"If I use session.createSQLQuery("insert into .....values()") will the Hibernate's batch insert work fine?"
No, it will not. Queries are executed immediately.
If someone is using Spring Data JPA: The same can be achieved in Spring Data JPA can be done using the method
JpaRepository.getReferenceById(ID id)
This replaced the former
getOne(ID)
Hibernate users can implement this method:
public <T extends Object> T getReferenceObject(Class<T> clazz, Serializable id) {
return getCurrentSession().get(clazz, id);
}
And call like:
MyEntity myEntity = getRefererenceObject(MyEntity.class, 1);
You can change id type to Integer or Long as per your entity model.
Or T can be inherited from your BaseEntity if you have one base class for all entities.
The following approach works for me:
User user = new User();
user.setId(userId);
car.setUser(user);
Here is my entity models.
#Entity
#Table(name = "Folder")
public class Folder implements Serializable{
private User user;
//unidirectional association
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "AssignedToUserID" ,nullable=true)
public User getUser() {
return user;
}
}
#Entity
#Table(name = "User")
public class User implements Serializable{
#Id
#Column(name = "UserID")
public Integer getUserId() {
return this.userId;
}
}
Basically i want retrieve all folders together whether the folder has assigned user or not.
And here is my HQL query:
*SELECT folder from Folder folder inner join fetch folder.user user*
SQL generated By hibnermate:
select
folder0_.FolderID ,
folder0_.FolderName ,
folder0_.AssignedToUserID ,
user_0.UserID ,
user_0.UserName
from
Folder folder0_
left outer join
User user_0
on folder0_.AssignedToUserID=user_0.UserID
I wanted to eagerly load all associated entities, I really wanted to avoid the other select statements because it hurts performance and i am retrieving around 500k of records.
I am expecting that hibernate will return null instance when it sees that AssignedToUserID is NULL. But unfortunately it throws EntityNotFoundException though.
Am i missing something here? Any suggestion is appreciated.
Try to use
left outer join fetch folder.user user