hibernate many to one mapping, should I insert this way? - java

Let's say we have two tables, user and domain. User may have one Domain, one Domain may be for many users. So we will do unidirectional many to one:
#Entity
#Table(name = "DOMAIN")
public class Domain implements Serializable
{
#Id
#Column(name = "DOMAIN_ID")
private Integer domainId;
#Column(name = "NAME")
private String domainName;
}
#Entity
#Table(name = "\"USER\"")
public class User implements Serializable
{
#Id
#GeneratedValue(generator = "USER_SEQ")
#GenericGenerator(name = "USER_SEQ", strategy = "sequence", parameters = #Parameter(name = "sequence", value = "SEQ_USER_ID"))
#Column(name = "USER_ID", nullable = false)
private Long userId;
#Column(name = "FIRST_NAME")
private String firstName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "DOMAIN_ID")
#ForeignKey(name = "DOMAIN_ID")
private Domain domain;
}
Domain table is something unchangable, dictionary. While User is editable table.
I'm creating a basic form where user selects domain in which new user will be created. Lets say that my controller received those data:
Integer domainId = 1;
String firstName = "aaa";
So I'm creating new user:
User newUser = new User();
newUser.setFirstName( firstName );
Now comes my question, should I do this way?
Domain domain = somthingThatWillFetchObjectFromDb.getDomain( domainId );
newUser.setDomain( domain );
//save user
THis will generate additional select, to fetch domain. Of course I can use Integer domainId instead of POJO, but that's not ORM. So once again the question, is this the way it should be done?

Yes, that's what you should do. If you don't want to actually load the domain information from the database, use the strangely named Session.load() method instead of the Session.get() method. If the domain is not already loaded into the session, Session.load() will simply return you an unitialized entity proxy for the domain (just like if you had loaded some entity with a lazy association to the domain), without hitting the database.
That said, if domain is unchangeable, why do you set #Cascade(CascadeType.ALL) on the domain field? This means that every time you're merging or updating a user, the domain will also be merged or updated. And even worse: if you delete a user, the domain will also be deleted (which of course will lead to an exception if other users are referencing the same domain).

Yes. To save a child Entity like User, you need to set the parent Entity i.e. Domain Entity in it.
One otherway is to define bidirectional mapping(OneToMany) in parent Entity ie. Domain, load the Domain, add one or more User object in Domain and save Domain Entity only e.g.
Domain Entity:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "domain")
List<User> users = null;
public List<User> getUsers(){
return this.users;
}
public void setUsers(List<User> user){
this.users = users;
}
public void addUser(User user){
user.setDomain(this);//set the parent entity
if(this.users==null){
this.users = new ArrayList<User>();
}
this.user.add(user);
}
Then to save users:
User user1 = new User();
......
User user2 = new User();
......
Domain domain = loadDomain();//<- use actual method to load the domain
//< add all the users to be saved at once
domain.addUser(user1);
domain.addUser(user2);
//save parent entity i.e. domain
saveDomain(domain);//use actual method to save the entity

Related

Using JPA Entity Graph in DAO

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

Foreign key in hibernate, why create objects, how to update a record in referenced entity

I'm new to Hibernate environment. I have a basic question and I'm still trying to understand why people are picking Hibernate over SQL.
I have two tables, lets say one is user, one is a book.
A user can has many books, but a book only has a one owner.
If I used SQL, I try to write at
Table User,
int uid
PRIMARY KEY (uid)
Table Book,
int bid
int oid //ownerid
PRIMARY KEY (bid)
FOREIGN KEY (oid) REFERENCES User(uid)
I couldn't do this in Hibernate. I've tried the following:
Generated a user table without any relation, only #Id annotation for uid.
#Entity
#Table(name="USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "uid")
private Long uid;
public Long getUid()
{ return uid;}
}
Generated a book table, but I cannot understand this point. Everything on the internet says that I need to use #ManyToOne and #JoinColumns. If I use them I need to have an object:
#Entity
#Table(name = "BOOK")
public class Book{
#ManyToOne
#JoinColumn(name="uid",
referencedColumnName = "What should I write here?")
#Column(name ="oid") //owner id
private User user;
}
1- Why would I need to create a user object for my uid in book table? I just want to store a userid in my books table with Foreign Key constraint (If the user does not exist, then the book cannot be exist either)
2- Also, if I want to reach the userid of the owner, should I use this:
public Long getOwnerId()
{
return user.getUid();
}
3- If I want to change the owner of the book, what should I do? Since there is a whole user member in my book object, how can I update only the ownerid in my book table?
Why would I need to create a user object for my uid in book table?
You don't need to change anything to your database table. Hibernate will read the oid from the book table, and will populate the Book.user field with the object of type User identified by this oid.
That way, when you display the information about a book in your application for example, and you want the name of the owner to be displayed, all you need to do is
display(book.getUser().getName());
Instead of having to get the ID of the user, then execute a second database query to get the user.
if I want to reach the userid of the owner, should I use this:
yes. Or you don't ad any method, because any caller is able to do book.getUser().getUid() by itself.
If I want to change the owner of the book, what should I do?
Assuming you have the uid of the new owner, you would get the user identified by this ID, and set in on the book:
User newOwner = entityManager.find(User.class, newOwnerId);
// or User newOwner = entityManager.getReference(User.class, newOwnerId);
// depending on what you prefer
book.setUser(newOwner);
Regarding your mapping of the ManyToOne, it's wrong. You need to read the Hibernate manual, and the javadoc, instead of trying random things. It should simply be
#ManyToOne
#JoinColumn(name = "oid")
private User user;
In a ManyToOne Entity you just need to specify the name of yourforeign key inside the #JoinColumn annotation, like this:
#Entity
#Table(name = "BOOK")
public class Book{
//private Long bid;
#ManyToOne
#JoinColumn(name="oid")
private User user;
}
The referencedColumnName paramether is most used for ManyToMany relationships.
Hibernate is a object relational mapper to represent the table based data structure of a relational database in a usual object oriented way, nothing more or less.
You dont have to use annotations like #ManyToOne, #OneToMany, etc. You can just do this:
#Entity
#Table(name="USER")
public class User {
#Id
#Column(name = "uid")
private Long uid;
// setters and getters
}
#Entity
#Table(name = "BOOK")
public class Book {
#Id
#Column(name = "bid")
private Long bid;
#Column(name = "oid")
private Long oid;
// setters and getters
}
EntityManager em;
Long uid = em.createQuery("SELECT u.uid FROM User u WHERE ....", Long.class).getSingleResult();
book.setUid(uid);
Most developers dont want to handle with that much withs IDs and database access etc. They just want to access their business model in a natural object oriented way and there hibernate or JPA comes in:
EntityManager em;
User user1 = em.find(User.class, 1L);
Book book1 = new Book();
book1.setUser(user1);
You dont have to create a User instance for every single Book instance. Just reuse the existing User instance.
In your Example i would do the following:
#Entity
#Table(name="USER")
public class User {
// ...
#OneToMany(mappedBy = "user")
private Collection<Book> books = new ArrayList<>();
public void add(Book book) {
book.setUser(this);
books.add(book);
}
public void remove(Book book) {
book.setUser(null);
books.remove(book);
}
}
#Entity
#Table(name = "BOOK")
public class Book {
// ...
#ManyToOne
#JoinColumn(name="oid", referencedColumnName = "uid")
private User user;
}
EntityManager em;
User user1 = em.find(User.class, 1L);
Book book1 = new Book();
Book book2 = new Book();
user1.addBook(book1);
user1.addBook(book2);
book1.getUser(); // user1
book2.getUser(); // user1
Yes, if u want to handle with bare IDs
Set another User via setter. The corresponding uid in the book table will be updated.
book1.setUser(user2);
will result in the statement
update BOOK set oid = 2 where bid = 1;

JPA. How to return null instead of LazyInitializationException

I have two tables with 'one to many' relationship. I use Jpa + Spring JpaRepository. Sometimes I have to get object from Database with internal object. Sometimes I dont't have to. Repositories always return object with internal objects.
I try to get 'Owner' from Database and I always get Set books; It's OK. But when I read fields of this internal Book , I get LazyInitializationException. How to get null instead of Exception?
#Entity
#Table(name = "owners")
#NamedEntityGraph(name = "Owner.books",
attributeNodes = #NamedAttributeNode("books"))
public class Owner implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "owner_id", nullable = false, unique = true)
private Long id;
#Column(name = "owner_name", nullable = false)
private String name;
#OneToMany(fetch = FetchType.LAZY,mappedBy = "owner")
private Set<Book> books= new HashSet<>(0);
public Worker() {
}
}
#Entity
#Table(name = "books")
#NamedEntityGraph(name = "Book.owner",
attributeNodes = #NamedAttributeNode("owner"))
public class Book implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "book_id", unique = true, nullable = false)
private Long id;
#Column(name = "book_name", nullable = false, unique = true)
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "owner_id")
private Owner owner;
public Task() {
}
}
public interface BookRepository extends JpaRepository<Book,Long>{
#Query("select t from Book t")
#EntityGraph(value = "Book.owner", type = EntityGraph.EntityGraphType.LOAD)
List<Book> findAllWithOwner();
#Query("select t from Book t where t.id = :aLong")
#EntityGraph(value = "Book.owner", type = EntityGraph.EntityGraphType.LOAD)
Book findOneWithOwner(Long aLong);
}
You are getting LazyInitializationException because you are accessing the content of the books Set outside the context of a transaction, most likely because it's already closed. Example:
You get an Owner from the database with your DAO or Spring Data repository, in a method in your Service class:
public Owner getOwner(Integer id) {
Owner owner = ownerRepository.findOne(id);
// You try to access the Set here
return owner;
}
At this point you have an Owner object, with a books Set which is empty, and will only be populated when someone wants to access its contents. The books Set can only be populated if there is an open transaction. Unfortunately, the findOne method has opened and already closed the transaction, so there's no open transaction and you will get the infamous LazyInitializationException when you do something like owner.getBooks().size().
You have a couple of options:
Use #Transactional
As OndrejM said you need to wrap the code in a way that it all executes in the same transaction. And the easiest way to do it is using Spring's #Transactional annotation:
#Transactional
public Owner getOwner(Integer id) {
Owner owner = ownerRepository.findOne(id);
// You can access owner.getBooks() content here because the transaction is still open
return owner;
}
Use fetch = FetchType.EAGER
You have fetch = FecthType.LAZY in you #Column definition and that's why the Set is being loaded lazily (this is also the fetch type that JPA uses by default if none is specified). If you want the Set to be fully populated automatically right after you get the Owner object from the database you should define it like this:
#OneToMany(fetch = FetchType.EAGER, mappedBy = "owner")
private Set<Book> books= new HashSet<Book>();
If the Book entity is not very heavy and every Owner does not have a huge amount of books it's not a crime to bring all the books from that owner from the database. But you should also be aware that if you retrieve a list of Owner you are retrieving all the books from all those owners too, and that the Book entity might be loading other objects it depends on as well.
The purpose of LazyInitializationException is to to raise an error when the loaded entity has lost connection to the database but not yet loaded data which is now requested. By default, all collections inside an entity are loaded lazily, i.e. at the point when requested, usually by calling an operation on them (e.g. size() or isEmpty()).
You should wrap the code that calls the repository and then works with the entity in a single transaction, so that the entity does not loose connection to DB until the transaction is finished. If you do not do that, the repository will create a transaction on its own to load the data, and close the transaction right after. Returned entity is then without transaction and it is not possible to tell, if ots collections have some elements or not. Instead, LazyInitializationException is thrown.

Hibernate persist entity without fetching association object. just by id

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);

Make Hibernate ignore fields on certain HTTP requests

I have a class User defined as
#Entity
#Table(name = "users")
#JsonIdentityInfo(generator = ObjectIdGenerators.UUIDGenerator.class, property = "jsonUUID")
public class User implements Serializable, UserDetails
{
private static final long serialVersionUID = -7035881497059422985L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
protected String firstname;
protected String lastname;
protected String username;
protected ProfessionalCategory professional;
protected String companyName;
#Email
protected String email;
protected String password;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable
(
name = "role_user",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
protected Set<Role> roles;
}
When I perform a GET request on /users/{id} I want Hibernate to fetch the value from all the fields in user and return the "full" object. But when I perform a GET request on /users/ I want to return a list of users containing only firstname and lastname. I do not just want Jackson to ignore certain fields during serialization, I also want Hibernate not to fetch data it does not need (because fetching all the roles for each user can be very costly since I use a join table).
I know I could write my own SQL queries, but then I would loose the benefits of using Hibernate. So is there an elegant way of solving this problem?
The most elegant solution is to use Hibernate criteria and specify two different methods inside your DAO. One method will fetch a single user based on their ID, the other will fetch a list of all users with only first name and last name populated by using a ProjectionList.
public List<User> getAllUsers() {
Criteria query = sessionFactory.getCurrentSession().createCriteria(User.class);
query.setProjection(Projections.projectionList()
.add(Projections.property("firstName"), "firstName")
.add(Projections.property("lastName"), "lastName"))
.setResultTransformer(Transformers.aliasToBean(User.class));
return query.list();
}
The above code causes Hibernate to only fetch the firstName and lastName fields from the database, and then map the results back to your User class using the ResultTransformer. This method is less than ideal, because it is confusing that all the fields aren't populated.
The ideal solution would be to lazily load your collection of roles, so that Hibernate only loads it on request. For more information on how you can set this up, refer to my Q&A here.

Categories