EntityManager tries to create existing entity - java

I'm creating an application that allows users to create hotel room reservations.
I have the entities room, user, reservation
These are my relationships set in entity class Reservation:
#Id
#GeneratedValue
private int id;
private Date reservationDate;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Room room;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private User user;
When I try to save a new Reservation object containing an existing User using EntityManager.persist(), I get a PSQLException saying user with "id=..." violates Unique-Constraint »user_pkey«, because that user already exists. How do I tell EntitiyManager to not try to create that User (and Room at a later point too) as it already exists?

You get that error because JPA cascade the persist operation down to user which is seen as a new entity.
To prevent this:
reservation.setUser(entityManager.getReference(User.class, user.getId()));
where getId must be replaced accordingly.
The above code assumes you are not interested in modifying the user. Otherwise, a merge operation is needed:
reservation.setUser(entityManager.merge(user));

Related

Ebean model mapping itself with #OneToMany

I'm currently creating a ebean model User which trying to map back to itself. Since the User and it's child uses the data structure, my idea is to reuse them without creating a new model / table.
#Entity
public class User extends Model {
#Id
private String id;
private String email;
#ManyToOne(cascade = CascadeType.PERSIST)
private User parent;
#ElementCollection(fetch = FetchType.LAZY)
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<User> childs = new ArrayList<>();
....
}
And the database schema looks like this:
CREATE TABLE user (
id CHAR(7),
email CHAR(255),
parent_id CHAR(255),
...
);
The mapping #ManyToOne from child to parent is working fine. But i get the error below when running the app with the #OneToMany mapping introduced:
javax.persistence.PersistenceException: Error on models.User.childs Can not find mappedBy property [user] in [models.User]
I suspect ebean was trying to look for user_id in user table, which in fact it was linked by parent_id as screen in the database structure.
Any idea if this even supported by ebean or is there anything i'm missing here?

JPA orphanRemoval not working when cascading MERGE

I have a three classes scenario. Classes User and UserDetail are associated by a #OneToOne relationship, while UserDetail and Document are associated by a #OneToMany relationship. I have used CascadeType.ALL and orphanRemoval=true on both of them, as described below.
As the classes specify, a User does not necessarilly have a UserDetail, but every UserDetail must have a User (therefore the optional flag). Every Document must be associated to a UserDetail. This is not an optional relationship!
public class User {
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, optional = true, orphanRemoval = true)
private UserDetail details;
}
public class UserDetail {
#OneToOne(optional = false)
private User user;
#OneToMany(mappedBy = "detail", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Document> documents;
}
public class Document {
#ManyToOne
private UserDetail detail;
}
Everything works fine on persisting, whith all data being saved, but when I try to set UserDetail to null whith a document list is populated, I get an integrity constraint violation exception at the Documents. The foreign key to UserDetail can't be set to null.
As far as I could understand, when setting details to null on User object and trying to merge it, the provider tries to delete the orphan UserDetail, but does not cascade the delete command correctly. If I try do DELETE the User, then the cascading operation works perfectly and no exception gets thrown.
Question:
Shouldn't merge operation trigger the deletion of everything, since I'm cascading and using orphanRemoval?
Obs.: Using (Hibernate 4.3.7) as provider

What is purpose of using cascade={CascadeType.TYPE_NAME}

I am going through spring project and in some model classes there is type
cascade={CascadeType.ALL}
written in parameters for eg: ,
#ManyToOne(fetch = FetchType.EAGER,cascade=CascadeType.ALL)
#JoinColumn(name="USER_ID", nullable=false)
private User user;
My question is in what purpose we should use this ?
Thanky you.
This attribute means that ALL (because CascadeType.ALL) operations associated with objects of the class (Outer class) will be executed for associated object of class User (Inner class).
For example:
#Entity
public class Group {
#ManyToOne(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
#JoinColumn(name="USER_ID", nullable=false)
private User user`
If you will try to remove Group from DB it will cause removing of associated user.
Enum CascadeType will help you to specify which kind of operations you want to perform with associated user.
If you want to specify cascading execution just for removing and persist you have to do something like that:
#ManyToOne(cascade = {CascadeType.REMOVE, CascadeType.PERSIST}, fetch = FetchType.EAGER)

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.

Why do I have to persist both objects if Address is nested in User?

I am trying to better familiarize myself with JPA so I created a very simple project. I have a User Class and an Address class. It appears that I have to persist both even though I am adding Address to my User class?
User:
import javax.persistence.*;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
#Entity
#Table(name = "usr") // #Table is optional, but "user" is a keyword in many SQL variants
#NamedQuery(name = "User.findByName", query = "select u from User u where u.name = :name")
public class User {
#Id // #Id indicates that this it a unique primary key
#GeneratedValue // #GeneratedValue indicates that value is automatically generated by the server
private Long id;
#Column(length = 32, unique = true)
// the optional #Column allows us makes sure that the name is limited to a suitable size and is unique
private String name;
// note that no setter for ID is provided, Hibernate will generate the ID for us
#OneToMany(fetch=FetchType.LAZY, mappedBy="user")
private List<Address> addresses;
Address:
#Entity
public class Address {
#Id // #Id indicates that this it a unique primary key
#GeneratedValue // #GeneratedValue indicates that value is automatically generated by the server
private Long id;
#Column(length=50)
private String address1;
#ManyToOne
#JoinColumn(name="user_id")
private User user;
EntityManager:
EntityManager entityManager = Persistence.createEntityManagerFactory("tutorialPU").createEntityManager();
entityManager.getTransaction().begin();
User user = new User();
user.setName("User");
List<Address> addresses = new ArrayList<Address>();
Address address = new Address();
address.setAddress1("Address1");
addresses.add(address);
user.setAddresses(addresses);
entityManager.persist(user);
entityManager.persist(address);
entityManager.getTransaction().commit();
entityManager.close();
Probably doing something wrong...just not sure what it is?
Any suggestions would be appreciated.
Thanks,
S
Try the cascade element for the annotation.
#OneToMany(fetch=FetchType.LAZY, mappedBy="user", cascade=CascadeType.PERSIST)
private List<Address> addresses;
The documentation says that by default no operation is cascaded. It also states that the cascade operation is optional, so it really depends on the implementation that you are using.
Also, while setting the relationship, make sure you set both sides of the relationship. Set addresses to the user and user to the addresses.
What you're talking about it's called Cascading. That means doing the same action to nested objects, such as an Address in your User. By default, there is no cascading at all if you don't specify any CascadeType.
You can define various cascade types at once
#OneToMany(fetch=FetchType.LAZY, mappedBy="user", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
private List<Address> addresses;
or just tell JPA to cascade every operation:
#OneToMany(fetch=FetchType.LAZY, mappedBy="user", cascade = CascadeType.ALL)
private List<Address> addresses;
Both ways will result in, for example, a persisted Address while persisting an User or the deletion of the associated Address when an User is removed.
But!... if you remove CascadeType.REMOVE from the first example, removing an User won't remove its associated Address (The removal operation won't be applied to nested objects).
You are using a oneToMany annotation. From my understanding you have to persist the parent class (the USer) when you want to add a child class (Address) to it.
By persisting the User class, you do let know JPA know which row to update.

Categories