Spring Data JPA causes EntityExistsException with cascading save - java

I have a Challenge class, which has a many to one relationship with the User class. It is uni-directional, so it looks like this:
#Entity
#Table(name = "UserTable")
public class User {
#Id
private String userId;
}
#Entity
#Table(name = "ChallengeTable")
public class Challenge {
#Id
private String challengeId;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinColumn(name = "userId")
private User user;
}
I'm using Spring Data JPA, and when I use the save method from the CRUDRepository on a Challenge object, I want it to persist the attached user if that user doesn't already exist, and merge the user into the old user if it does already exist.
I'm using a findOne(String id) method in the UserRepository to get a user using a userId, and that's the user I'm setting in the Challenge.
It cascades just fine if the user doesn't already exist, but when I try to save it with a pre-existing user I get the exception:
javax.persistence.EntityExistsException: a different object with the same identifier value was already associated with the session: [com.mywebsite.model.User#zk9moo78sx685g6o9yphegdx6lpoll9x]
I'm not sure what I'm doing wrong here. Changing the CascadeType to ALL doesn't change anything. Trying to remove the CascadeType entirely and manually saving the User first doesn't work either. That gives me the error:
org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing: com.mywebsite.model.Challenge.user -> com.mywebsite.model.User
That appears to take place when the transaction exits (as I have my service layer class annotated with #Transactional).
If I take out the #Transactional annotation and manually persist the user it seems to all work fine. (I still want the cascading saves and transactions on the service level though.)
Taking out the #Transactional and trying to use cascading saves fails with a SQLIntegrityConstraintViolationException exception because it seems like the User becomes a detached entity and it tries to persist it anew, but that primary key already exists so it fails.
Can anyone help me understand what's going on here, and help me get cascading saves working with transactions in Spring Data JPA?

I tried using hibernate-specific cascading options, and everything else I could think of, but I couldn't get cascading saves to work as a Hibernate CascadeType.SAVE_UPDATE is supposed to. I believe it's a hard limitation of JPA with Spring Data.
Instead, I added a layer between the service and the interface repository. It saves the dependent entity (the user) then the challenge.

Related

Why is an entity being automatically saved without calling persist when using foreign generation strategy in bidirectional one-to-one mapping?

I have been practicing one-to-one mapping in hibernate and don't understand this particular case. I have to say, the program is working fine and as I intended, but apparently I can omit a perist() call and it still works smoothly. The fact that it's working is good, but I want to know exactly why the call is optional. Let me write some details:
This is the user class, which is supposed to be the owning side of the mapping:
#Data
#Entity
public class User {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
private Long id;
private String name;
#OneToOne
private Ticket ticket;
public User() {}
public User(String name) {
this.name=name;
}
}
And this is the ticket class that's supposed to be the dependent one:
#Data
#Entity
public class Ticket {
#Id
#GeneratedValue(generator="foreignGenerator")
#GenericGenerator(name="foreignGenerator", strategy="foreign",
parameters=#org.hibernate.annotations.Parameter(name="property", value="user"))
private Long id;
#OneToOne(optional = false, mappedBy="ticket")
#PrimaryKeyJoinColumn
private User user;
public Ticket() {
}
public Ticket(User user) {
this.user=user;
}
}
I am trying to test the "shared primary key" strategy in one-to-one mapping. As you can see I have set up the generator with foreign strategy, which is supposed to make Ticket's id the same as it's corresponding User's id.
#Bean
CommandLineRunner loadData() {
return args->{
EntityManager em=emf.createEntityManager();
em.getTransaction().begin();
User user=new User("Test User");
Ticket ticket=new Ticket(user);
//em.persist(user);
user.setTicket(ticket);
em.persist(ticket);
em.getTransaction().commit();
em.close();
//We don't have to call persist on user
};
}
}
This program runs perfectly. Uncommenting the line which calls persist on user makes no difference. I am assuming that persisting ticket, which has it's user property set, automatically saves the user as well. Therefore, the reason it makes no difference is that no matter if user is getting saved or not, it will get persisted when we call ticket.
I want to know if my assumption is correct and any additional links to articles/documentation would be greatly appreaciated. Especially I am wondering about this part that I said above-"I am assuming that persisting ticket, which has it's user property set, automatically saves the user as well." I couldn't find anything that would confirm or deny this. I know that the "shared primary key" approach in one-to-one mapping is the only use case of "foreign" generation strategy, so there are not a lot of posts about it, and whatever posts are there are getting overshadowed by "foreign key" during the search.
Any help regarding this or any other issue that might be wrong with the code provided above would be appreciated. Thanks for taking your time to read this
The JPA specification states this behavior is wrong:
Looking at the 3.0 release:
section "3.2.2. Persisting an Entity Instance" implies user is unmanaged after your persist (you can check with the em.contains method).
Section "3.2.4. Synchronization to the Database" covers the flush/commit which states:
• If X is a managed entity, it is synchronized to the database.
..
◦ For any entity Y referenced by a relationship from X, where the relationship to Y has not been annotated with the cascade element value cascade=PERSIST or cascade=ALL:
▪ If Y is new or removed, an IllegalStateException will be thrown by the flush operation (and the transaction marked for rollback) or the transaction commit will fail.
User is new, so this should be resulting in an exception. That it works might be a glitch in how Hibernate is handling the #PrimaryKeyJoinColumn annotation (speculation on my part) and custom "foreignGenerator".
This is not a pattern I'd suggest you rely on, and should instead call persist to avoid inconsistencies with the behavior on other mapping setups.

what is the reason that Hibernate does not allow to save the object which references an unsaved transient instance?

I'm newbie in Hibernate and I'm trying to learn about JPA and Hibernate.
I want to know that what is the reason that Hibernate does not allow to save the object which references an unsaved transient instance? I want to know WHY this is a problem?
I asked someone and some of them answer me like this:
How could we possibly map the customer to the address, if there is no
adress record in the DB yet?
and
you are assigning particular Address to Customer. But Address does
not have any ID
but honestly I can't understand them.
(I know that an exception will be thrown and the solution is Cascade but I want to the reason of the problem inside the database)
now, let's assume we have all of these code:
(I use Bidirectional One-To-One relationship for my example)
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
#OneToOne(mappedBy = "customer")
private Address address;
}
#Entity
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String street;
private String zipCode;
#OneToOne
private Customer customer;
}
public static void main(String[] args) {
EntityManager entityManager = emf.createEntityManager();
entityManager.getTransaction().begin(); // Begin Transaction
Customer c1 = new Customer("Mi", "S");
Address addrss1 = new Address("5412 S 5th", "212524");
c1.setAddress(addrss1);
addrss1.setCustomer(c1);
entityManager.persist(c1);
entityManager.getTransaction().commit(); // Commit
entityManager.close();
}
and let's assume that the exception is not thrown and java and hibernate have allowed us to run our code and this is our customer table.
id firstName lastName
---------------------------------
1 Mi S
and this is our address table:
id street zipCode customer_id
---------------------------------------------
- - - -
now, what is the problem? everything in these Bidirectional One-To-One relationship seems right.
then what is the problem?
PS:
if it is possible, please explain and show me code.
I can understand better with code. thank you.
I want to see for example if we are allowed to save the object which references an unsaved transient instance, what problems will we face in our code and in our tables (for example do we have any problem when we want to retrieve a customer and etc)
Because your adress entity have the primay key of customer as a foreign key ,(since mappedby is in Customer entity) ,and the customer referenced by the adress has no id ,which tells hibernate that that entity was never persisted in the database (which literally means transient) ,and hibernate needs a persisted/managed entity to make sure it exists in the database so that the adress object can be associated with an existing customer.
Customer is new, and it is clear from the persist call you want to insert it, but it isn't clear what you want to happen to any of customer's references. To make it clear, you define what you want the JPA provider (Hibernate) to do in the mappings under any/all circumstances - this is what the cascade operations refer to. In this case, JPA will look at the customer.address OneToOne mapping and find nothing defined; Address is NOT managed in this EntityManager context, so it doesn't know what to do to handle this relationship, so it signals you've made a mistake by throwing an error.
If it let it through, your Customer instance references something that does not exist, and its state does not match what is in the database. What you pass into persist should be what you would get back on reads, so it should reflect the state that is in the database.
The issue isn't directly with your persist call, as the spec does allow providers to ignore references to detached/new instances that don't have cascade settings - what happens is just undefined. Where you go wrong in this situation is on flush/commit, which is when the persistence unit is synchronized to the database (section 3.2.4 of JPA 3.0), which requires providers to go through managed entities and then determine any changes. Adding a new address pre persist will result in the same issue as if you did it post persist, and requires providers to throw an IllegalStateException if it discovers new or removed entities and rollback the transaction.
Why this is a problem: JPA is very big on entity Identity, as this enables caching of these entities in multiple levels of caches, and this entity might go into those caches as it is. It has to know what to do with references to entities that do not exist, and the spec decided to require an exception. Even to your app this is and should be a problem, as the EntityManager context is a unit of work, and the state within that unit of work is based on something that is wrong. Your Customer doesn't really have an address when this is said and done, yet your application business logic thinks it assigned one, with state that just isn't going to be there afterward.
You already know the solutions:
correct the customer to have a valid, managed address by calling persist on it directly in this same EntityManager context.
set the cascade options on the mapping to cascade persist to address for you
don't set addresses on a new customer in the same operation.

SpringData/Hibernate #ManyToOne cascading automatically when it should not

Given two entities like so
#Entity
public class Thing {
#ManyToOne
private ThingType thingType;
...
}
#Entity
public class ThingType {
private String name;
...
}
From everything I have read, the default cascade should be nothing so if I get a reference to a Thing thing, and change the name field of its ThingType, then using a JpaRepository<Thing, Long> call thingRepo.save(thing), I would expect the change to ThingTypes name not to be persisted.
However this is not the case and the change is persisted. I am not sure why this is happening? What I am missing here?
Relevant versions:
org.springframework.boot:spring-boot:jar:1.5.7.RELEASE
org.hibernate:hibernate-core:jar:5.0.12.Final
org.springframework.data:spring-data-jpa:jar:1.11.7.RELEASE
Well, I would have expected the same, but it seems that Hibernate has its own default behaviour. In the Hibernate forum someone asked almost the same question. The answer refers to the Hibernate "dirty check" feature, which will detect and perstst/merge the change. You might change that behaviour by using Hibernate's Cascade annotation.
Well cascading is something else, let me ask you something. Do following things:
Thing thing = session.get(Thing .class, someId);
thing.getThingType().setTitle("new title");
and nothing more, again you see hibernate updates thingType.
It is called dirty checking, as long as an entity is attached to an active hibernate session, and its persistence state changes hibernate automatically updates its associated row in database. Event without calling a save or update.
So what is cascade?
Consider following case:
Thing myThing = new Thing();
ThingType myThingType = new ThingType();
myThing.setThingType(myThingType);
session.save(myThing);
if the association cascade type is not set, then you will get an exception, because you are referencing a transient thingType object. But if you set the cascade type to persist, then hibernate first saves the thingType and then saves the thing, and everything goes fine.
So remember, if you fetch an object, then update its properties in the same session, there is no need to call a update or saveOrUpdate method on hibernate session (or jpa entityManager) because it is already in attached state, and its state is traced by hibernate.

Detach entity within entity from persistence context in Hibernate

Hibernate persists modified entities at the of transactional methods, I can avoid by using session#evict(entity).
If I detach it from the persistence context, the entities whithin it will also be detached?
For instance, I have this classes:
#Entity
public class User extends BaseEntity{
#Column(name = "email")
private String email;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private List<Address> addresses;
// getters and setters
}
#Entity
public class Address extends BaseEntity{
#Column(name = "email")
private String email;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ID")
private User user;
// getters and setters
}
If I detach a user object, but change the address object in it, will the address be persisted at the end of transaction? Like this:
User user = userDAO.getById(id);
session.evict(user);
Address address = user.getAddresses().get(0);
address.setNumber(number);
addressDAO.saveOrUpdate(address); //will this work?
Entities that are updated or deleted using a EntityManager.createQuery() are not loaded into the Persistence Context, this only happens for select queries, and when you use find()or merge().
After you do an update or delete query your persistence context may actually be out-of-sync with the database, because the query doesn't update the entities which has already been loaded into the persistence context (you need to call refresh() to see the changes).
If you load a number of user (into the persistence context), and later doUpdate User set status='active' where id IN (:ids), then you have not modified any of the users in the persistence context, you have only modified the database. To modify a user, you must modify the actually managed Entity by calling `aUser.setStatus('active'), when the transaction commits, JPA will check all managed entities against a copy created when it was loaded, and if anything has changed it will do an Update.
If you are loading 5000 objects into the Persistence it may take some time for JPA to run though the entity graph, and detect the changes when the transaction commits. If you didn't modify anything, and would like to speed up the change-detection, there are two ways to do this. Load your entities using a read-only query, this tells JPA that it does not need to keep a copy of the loaded entity. The other option is to call EntityManager.clear() to throw away all managed entities. However, if you are interested in performance, the best solution is probably to avoid loading the entities into the persistence context. As I understand you problem, you need to do a Update User set ... where id IN (:ids)and for that you only need the user's id so you don't need to load the user, you just need the ids, and therefore you can do List<Long> ids = em.createQuery("select u.id from User u where ...", Long.class).getResultList();
Hope this clarifies things for you :)
EDIT: this is written from a JPA perspective, but for hibernate EntityManager just forwards directly to SessionImpl, so the behavior is exactly as described, except for find() being called get()in native Hibernate.
Since JPA 2.0
given an EntityManager you can call detach with the entity you want to be detached as parameter
void detach(Object entity)
more here
if you use injection then you can inject an EntityManger in the service where you want to detach the required entity.

One DAO per entity - how to handle references?

I am writing an application that has typical two entities: User and UserGroup. The latter may contain one or more instances of the former. I have following (more/less) mapping for that:
User:
public class User {
#Id
#GeneratedValue
private long id;
#ManyToOne(cascade = {CascadeType.MERGE})
#JoinColumn(name="GROUP_ID")
private UserGroup group;
public UserGroup getGroup() {
return group;
}
public void setGroup(UserGroup group) {
this.group = group;
}
}
User group:
public class UserGroup {
#Id
#GeneratedValue
private long id;
#OneToMany(mappedBy="group", cascade = {CascadeType.REMOVE}, targetEntity = User.class)
private Set<User> users;
public void setUsers(Set<User> users) {
this.users = users;
}
}
Now I have a separate DAO class for each of these entities (UserDao and UserGroupDao). All my DAOs have EntityManager injected using #PersistenceContext annotation, like this:
#Transactional
public class SomeDao<T> {
private Class<T> persistentClass;
#PersistenceContext
private EntityManager em;
public T findById(long id) {
return em.find(persistentClass, id);
}
public void save(T entity) {
em.persist(entity);
}
}
With this layout I want to create a new user and assign it to existing user group. I do it like this:
UserGroup ug = userGroupDao.findById(1);
User u = new User();
u.setName("john");
u.setGroup(ug);
userDao.save(u);
Unfortunately I get following exception:
object references an unsaved transient instance - save the transient
instance before flushing: x.y.z.model.User.group ->
x.y.z.model.UserGroup
I investigated it and I think it happens becasue each DAO instance has different entityManager assigned (I checked that - the references in each DAO to entity manager are different) and for user entityManager does not manager the passed UserGroup instance.
I've tried to merge the user group assigned to user into UserDAO's entity manager. There are two problems with that:
It still doesn't work - the entity manager wants to overwrite the existing UserGroup and it gets exception (obviously)
even if it worked I would end up writing merge code for each related entity
Described case works when both find and persist are made using the same entity manager. This points to a question(s):
Is my design broken? I think it is pretty similar to recommended in this answer. Should there be single EntityManager for all DAOs (the web claims otherwise)?
Or should the group assignment be done inside the DAO? in this case I would end up writing a lot of code in the DAOs
Should I get rid of DAOs? If yes, how to handle data access nicely?
any other solution?
I am using Spring as container and Hibernate as JPA implementation.
Different instances of EntityManager are normal in Spring. It creates proxies that dynamically use the entity manager that is currently in a transaction if one exists. Otherwise, a new one will be created.
The problem is that your transactions are too short. Retrieving your user group executes in a transaction (because the findById method is implicitly #Transactional ). But then the transaction commits and the group is detached. When you save the new user, it will create a new transaction which fails because the user references a detached entity.
The way to solve this (and to do such things in general) is to create a method that does the whole operation in a single transaction. Just create that method in a service class (any Spring-managed component will work) and annotate it with #Transactional as well.
I don't know Spring, but the JPA issue is that you are persisting a User that has a reference to a UserGroup, but JPA thinks the UserGroup is transient.
transient is one of the life-cycle states a JPA entity can be in. It means it's just created with the new operator, but has not been persisted yet (does not have a persistent identity yet).
Since you obtain your UserGroup instance via a DAO, it seems like something is wrong there. Your instance should not be transient, but detached. Can you print the Id of the UserGroup instance just after your received it from the DAO? And perhaps also show the findById implementation?
You don't have cascade persist on the group relation, so this normally should just work if the entity was indeed detached. Without a new entity, JPA simply has no way to set the FK correctly, since it would need the Id of the UserGroup instance here but that (seemingly) doesn't exist.
A merge should also not "overwrite" your detached entity. What is the exception that you're getting here?
I only partially agree with the answers being given by the others here about having to put everything in one transaction. Yes, this indeed may be more convenient as the UserGroup instance will still be 'attached', but it should not be -necessary-. JPA is perfectly capable of persisting new entities with references to either other new entities or existing (detached) entities that were obtained in another transaction. See e.g. JPA cascade persist and references to detached entities throws PersistentObjectException. Why?
I am not sure how but I've managed to solve this. The user group I was trying to assign the user to had NULL version field in database (the field annotated with #Version). I figured out it was an issue when I was testing GWT RequestFactory that was using this table. When I set the field to 1 everything started to work (no changes in transaction handling were needed).
If the NULL version field really caused the problem then this would be one of the most misleading exception messages I have ever got.

Categories