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
Related
I have defined the JPA Entity Graph on my Entity class, and it looks like follows.
UserTable.java
#Entity
#Table(name = "USER")
#NamedEntityGraph(
name = "user-entity-graph-with-photos",
attributeNodes = {
#NamedAttributeNode(value = "photos"),
})
public class UserTable {
#Id
#Column(name = "USER_ID", nullable = false)
private Long userId;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<PhotoTable> photos = new HashSet<>();
The requirement is that sometimes I want to fetch the users along with the photos, and in some other cases I only want the users to be loaded from the database, but not the photos.
Now, I have created a DAO class for the User - UserDAO.java. In there, I have two methods, each for one case.
public Optional<UserTable> findByEmail(String email) {
final TypedQuery<UserTable> query = entityManager.createQuery(
"SELECT e FROM UserTable e WHERE e.email = :email", UserTable.class);
return Optional.ofNullable(query.setParameter("email", email).getSingleResult());
}
public Optional<UserTable> findByEmailWithPhotos(String email) {
final TypedQuery<UserTable> query = entityManager.createQuery(
"SELECT e FROM UserTable e WHERE e.email = :email", UserTable.class);
return Optional.ofNullable(query
.setParameter("email", email)
.setHint("javax.persistence.loadgraph", entityManager.getEntityGraph("user-entity-graph-with-photos"))
.getSingleResult());
}
I am a bit worried about the API in the DAO layer, since it now contains 2 methods like findByEmail and findByEmailWithPhotos which also loads the photos eagerly. Is this the correct approach? Should we really use one DAO method for each defined entity graph? Would some kind of a builder pattern be more effective here? Any advice is appreciated.
UPDATE
To explain further what I feel is bad about this design is the following. Let's suppose we have 3 entity graphs on the user
user-graph-with-photos
user-graph-with-messages
user-graph-with-followers
Then in the DAO would need to have the following methods:
findUsers
findUsersWithPhotos
findUsersWithMessages
findUsersWithFollowers
findUsersWithPhotosAndMessages
findUsersWithPhotosAndFollowers
findUsersWithMessagesAndFollowers
findUsersWithPhotosAndMessagesAndFollowers
I am using Spring Boot and I have those entities:
#Entity
#Table(name = "usuario")
#NamedQuery(name = "Usuario.findAll", query="select u from Usuario u where
u.estactiv=true order by u.user")
public class Usuario implements Serializable{
...
#Column(name="usu_estactiv",nullable=false)
private boolean estactiv=true;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
#JsonManagedReference
private List<Roles> roles = new ArrayList<>();
...}
#Entity
#Table(name = "roles")
#NamedQuery(name = "Roles.findAll", query="select r from Roles r where
r.estactiv=true order by r.descripcion")
public class Roles implements Serializable{
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(columnDefinition="integer", name="usu_id", nullable=false)
#JsonBackReference
private Usuario user;
#Column(name="rol_estactiv",nullable=false)
private boolean estactiv=true;
...}
And in my repositories:
#RepositoryRestResource(collectionResourceRel = "usuario", path = "usuario")
public interface UserRepository extends PagingAndSortingRepository<Usuario,
Integer> {
#Override
#Query
public Page<Usuario> findAll(Pageable pageable);
Usuario findByUserAndEstactivTrue(#Param("user") String user);
}
#RepositoryRestResource(collectionResourceRel = "roles", path = "roles")
public interface RolesRepository extends
PagingAndSortingRepository<Roles,Integer>{
#Override
#Query
public Page<Roles> findAll(Pageable pageable);
#Override
#Query
public Iterable<Roles> findAll();
}
When I go to the url:
http://localhost:8080/api/usuario/ I get the users with estactiv=true:
but when I go to http://localhost:8080/api/usuario/1/roles, I got this:
And I just want to get the roles with estactiv=true for each user ,for example:
The user admin (with id=1) has two roles: user(false) and admin(true) in my roles table, but when I go to /api/usuario/1/roles I just want to get admin (true)
I hope my question is clear, if not please let me know in order to fix it. Thanks in advance for your help.
You need to add a join with the table usario in your roles query in order to filter on the estactiv property of the usario table. The query should look like this:
select r
from Roles r
inner join Usario u on r.usu_id=u.id
where u.estactiv=true
order by r.descripcion
Try to filter the list further, make use of Query string and pass parameter as the query string in your URL like this:
http://localhost:8080/api/usuario/1/roles?estactiv=true.
Now, you can able to filter the output result on the basis of this query string try to filter your data probably by writing a new query/code behind.
I was reading the documentation and I find a solution:
First in my RolesRepository, I declare this method:
public Collection<Roles> findByEstactivTrueAndUserId(#Param("user") Integer
user);
Then in my UserController:
#GetMapping("/api/usuario/{userId}/roles")
public Collection<Roles> getRoles(#PathVariable Integer userId) {
return rolRepo.findByEstactivTrueAndUserId(userId);
}
And when I go to http://localhost:8080/api/usuario/1/roles , I get only the roles with estactiv=true for usuario with id=1.
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
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