[英]Creating unique and merging existing row in database with JPA
I have 2 tables: User
and Loan
. 我有2个表:
User
和Loan
。 User
have 3 fields: id
(PK), first_name
and last_name
. User
具有3个字段: id
(PK), first_name
和last_name
。 Loan
table have field user_id
that is foreign key to User
table: Loan
表的字段user_id
是User
表的外键:
By persisting a new Loan
I need to create new User
if his first_name
and last_name
are unique, otherwise put his id
in uder_id
. 如果他的
first_name
和last_name
是唯一的,则通过保留新的Loan
我需要创建一个新的User
,否则将其id
放在uder_id
。
The source code of my Loan class: 我的Loan类的源代码:
public class Loan {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Long sum;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "user_id")
private User user;
... methods ...
I'm using this method to persist new User
: 我正在使用这种方法来保留新
User
:
@PersistenceContext
private EntityManager em;
public void save(User user) {
if (user.getId() == null) {
em.persist(user);
} else {
em.merge(user);
}
}
And when I try to save a new Loan
it always persists a new User
with the same first_name
and last_name
but different id
. 当我尝试保存新的
Loan
它总是以first_name
和last_name
相同但id
不同的方式持久保存一个新User
。
loan.setSum(sum);
loan.setUser(new User(firstName, lastName));
loanService.save(loan);
To use user's first_name
and last_name
as a PK is not a solution, I need an id
. 要使用用户的
first_name
和last_name
作为PK不是解决方案,我需要一个id
。
UPDATE_1 UPDATE_1
I tried to find User
by his name: 我试图通过
User
名查找User
:
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);
return query.getSingleResult();
}
But when I enter new user I got an exception: 但是当我输入新用户时,我得到了一个例外:
javax.faces.FacesException: #{loanBean.requestLoan()}: javax.persistence.NoResultException: getSingleResult() did not retrieve any entities.
And when I enter existing user, it adds a new one with the same firstName
and lastName
but new id
. 当我输入现有用户时,它将添加一个具有相同
firstName
和lastName
但具有新id
新用户。 When I repeat this operation I got another exception: 当我重复此操作时,我得到另一个异常:
javax.servlet.ServletException: javax.persistence.NonUniqueResultException: More than one result was returned from Query.getSingleResult()
UPDATE_2 Thanks a lot Pietro Boido for your really helpful suggestions.I created unique index on first_name
and last_name
fields in DB and refactor the save()
method. UPDATE_2非常感谢Pietro Boido的宝贵建议。我在DB的
first_name
和last_name
字段上创建了唯一索引,并重构了save()
方法。 But now when I enter existing user's data I got new 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
You should first execute a query to find the user by name and create a new one only if it was not found: 您应该首先执行查询以按名称查找用户,并仅在未找到用户时创建一个新用户:
User user = userService.find(firstName, lastName);
if (user == null) {
user = loanService.createUser(new User(firstName, lastName));
}
loan.setSum(sum);
loan.setUser(user);
loanService.save(loan);
Since there might be no user with the given name, use getResultList when you query for the user, because getSingleResult expects to always find a result. 由于可能没有给定名称的用户,因此在查询用户时请使用getResultList,因为getSingleResult希望始终找到结果。
List<User> users = query.getResultList();
if (!users.isEmpty()) {
return users.iterator().next();
} else {
return null;
}
The code assumes that the database has a unique index on first_name, last_name. 该代码假定数据库在first_name,last_name上具有唯一索引。
You should not cascade operations on the ManyToOne relationship. 您不应该对ManyToOne关系进行级联操作。 Think about this: if you delete a loan, should the user be deleted too?
考虑一下:如果您删除贷款,是否也应该删除用户?
Cascading operations should be used when the related entity is part of the relating entity and their life cycles are managed together. 当关联实体是关联实体的一部分并且它们的生命周期被一起管理时,应该使用级联操作。
@Entity
@Table(name="loans")
public class Loan implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "total")
private Long sum;
@ManyToOne()
@JoinColumn(name = "user_id")
private User user;
//...
}
Here is a possible working example: 这是一个可能的工作示例:
@Stateless
public class LoanService implements LoanServiceRemote {
@PersistenceContext
private EntityManager em;
@Override
public User createUser(User user) {
em.persist(user);
return user;
}
@Override
public Loan createLoan(Loan loan) {
em.persist(loan);
System.out.println("loan persisted: id=" + loan.getId());
return loan;
}
@Override
public Loan saveLoan(Loan loan) {
em.merge(loan);
return loan;
}
@Override
public Long incrementLoan(Integer loanId, long amount) {
Loan loan = em.find(Loan.class, loanId);
if (loan != null) {
long sum = loan.getSum() + amount;
/*
* The entity is bound to the entity manager,
* because it was returned by the find method.
* We can simply set its properties and
* the entity manager will update the datasource
* after the method returns and the transaction commits.
* No need to call persist or merge.
*/
loan.setSum(sum);
return sum;
}
return null;
}
@Override
public boolean deleteLoan(Integer loanId) {
Loan loan = em.find(Loan.class, loanId);
if (loan != null) {
em.remove(loan);
return true;
}
return false;
}
@Override
public Loan findLoan(Integer loanId) {
return em.find(Loan.class, loanId);
}
@Override
public List<Loan> requestLoans(LoanRequest loanRequest) {
User user;
TypedQuery<User> query = em.createQuery("select user from User user where user.firstName = :firstName and user.lastName = :lastName", User.class);
query.setParameter("firstName", loanRequest.getFirstName());
query.setParameter("lastName", loanRequest.getLastName());
List<User> users = query.getResultList();
if (users.isEmpty()) {
user = new User();
user.setFirstName(loanRequest.getFirstName());
user.setLastName(loanRequest.getLastName());
//new entities must be persisted
em.persist(user);
} else {
user = users.get(0);
}
List<Loan> loans = new ArrayList<>();
Long[] totals = loanRequest.getTotals();
for (int i = 0; i < totals.length; i++) {
Loan loan = new Loan();
loan.setSum(totals[i]);
loan.setUser(user);
em.persist(loan);
loans.add(loan);
}
return loans;
}
}
A unit test: 单元测试:
@Test
public void testLoan() {
User user = loanService.createUser(newUser());
Loan loan1 = new Loan();
loan1.setSum(10L);
loan1.setUser(user);
Loan loan2 = loanService.createLoan(loan1);
assertNotNull(loan2);
Integer loanId = loan2.getId();
assertNotNull(loanId);
assertEquals(loan1.getSum(), loan2.getSum());
assertEquals(loan1.getUser(), user);
User user2 = loanService.createUser(newUser());
loan2.setUser(user2);
loan2.setSum(20L);
Loan loan3 = loanService.saveLoan(loan2);
assertLoanEquals(loan2, loan3);
Long total = loanService.incrementLoan(loanId, 10L);
assertNotNull(total);
assertEquals((Long)(loan3.getSum() + 10L), total);
loan3.setSum(total);
Loan loan4 = loanService.findLoan(loanId);
assertLoanEquals(loan3, loan4);
boolean result = loanService.deleteLoan(loanId);
assertTrue(result);
Loan loan5 = loanService.findLoan(loanId);
assertNull(loan5);
Long[] totals = new Long[]{1L,2L,3L};
LoanRequest loanRequest = new LoanRequest();
loanRequest.setFirstName("Amerigo");
loanRequest.setLastName("Vespucci");
loanRequest.setTotals(totals);
List<Loan> loans = loanService.requestLoans(loanRequest);
assertNotNull(loans);
assertEquals(3, loans.size());
for (int i = 0; i < 3; i++) {
assertEquals(totals[i], loans.get(i).getSum());
loanService.deleteLoan(loans.get(i).getId());
}
}
void assertLoanEquals(Loan loan1, Loan loan2) {
assertNotNull(loan1);
assertNotNull(loan2);
assertEquals(loan1.getSum(), loan2.getSum());
assertUserEquals(loan1.getUser(), loan2.getUser());
assertEquals(loan1.getId(), loan2.getId());
}
void assertUserEquals(User user, User user2) {
assertNotNull(user);
assertNotNull(user2);
assertEquals(user.getId(), user2.getId());
assertEquals(user.getFirstName(), user2.getFirstName());
assertEquals(user.getLastName(), user2.getLastName());
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.