I have 2 tables: User
and Loan
. User have 3 fields: id
(PK), first_name
and last_name
. Loan
table have field user_id
that is foreign key to User
table:
The logic of my application: when I create new Loan
object and set there a corresponding user
it should create a new one for unique user or set a id
of a user
into user_id
for existing user.
For that purpose my database have a unique index on first_name
, last_name
.
The Loan
class uses User
with @ManyToOne
relationship:
public class Loan {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name = "user_id")
private User user;
... methods ...
When I add new user, everything is fine, it persists to db with new PK. But when I try to add an existing one I got and exception:
javax.servlet.ServletException: org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.0.v20150309-bf26070): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: The statement was aborted because it would have caused a duplicate key value in a unique or primary key constraint or unique index identified by 'FIRST_LAST_NAME' defined on 'USER'.
Error Code: 20000
Again when I put @ManyToOne(cascade = CascadeType.MERGE)
I'm able only to enter existing users, as only I pass a new one that is not persisted in DB I got an exception:
javax.servlet.ServletException: org.springframework.dao.InvalidDataAccessApiUsageException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: User{id=null, firstName='a', lastName='a'}.;
@ManyToOne(cascade = CascadeType.ALL)
didn't work as well.
UPDATE
the code for insertion new Loan
is:
user = userService.findByName(firstName, lastName);
if (user == null) {
user = new User(firstName, lastName);
}
loan.setUser(user);
loanService.save(loan);
findByName()
and save()
methods are:
private EntityManager em;
public User findByName(String firstName, String lastName) {
TypedQuery<User> query = em.createQuery(
"SELECT u FROM User u WHERE u.firstName = :firstName " +
"AND u.lastName = :lastName", User.class)
.setParameter("firstName", firstName).setParameter("lastName", lastName);
List<User> users = query.getResultList();
if (!users.isEmpty()) {
return users.iterator().next();
} else {
return null;
}
}
public void save(User user) {
if (user.getId() == null) {
em.persist(user);
} else {
em.merge(user);
}
}
If you call Persist on Loan a) with cascade.Persist set on the user relationship and the referenced user exists, set you will get an exception if the User instance was not read in from the same EntityManager instance/context - You are reading it through the userService and then saving the loan in the loanService, so the context would have to be container managed and within the same transaction to work.
b) If cascade.PERSIST (or cascade.ALL) is not set and the reference user is new, you will get the "new object was found" exception.
If you cannot perform the read of the user in the same EntityManager you are going to save the loan in, switching to using merge may help, as JPA should then check referenced entities and merge as appropriate. You will then need to have the cascade.MERGE option set on the relationships for the merge to be applied to the User instance.
Note that using merge is different from Persist in that what you passed in does not become managed by the context. That means after the transaction commits, the entities passed in will still not have any primary key values set. You may want to pass back the entity returned from Merge if you are going to continue to use the entities for any other operations.
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments