我的JPA / Hibernate应用程序中有几个映射对象。在网络上,我接收代表这些对象更新的数据包,或者实际上可能完全代表新对象。
我想写一个类似
的方法<T> T getOrCreate(Class<T> klass, Object primaryKey)
如果数据库中存在pk primaryKey,则返回所提供类的对象,否则会创建该类的新对象,并将其保留并返回。
我将对该对象做的下一件事是在事务中更新其所有字段。
在JPA中是否有惯用的方法,或者有更好的方法来解决我的问题?
答案 0 :(得分:19)
我想写一个像
这样的方法<T> T getOrCreate(Class<T> klass, Object primaryKey)
这并不容易。
一种天真的方法是做这样的事情(假设方法在事务中运行):
public <T> T findOrCreate(Class<T> entityClass, Object primaryKey) {
T entity = em.find(entityClass, primaryKey);
if ( entity != null ) {
return entity;
} else {
try {
entity = entityClass.newInstance();
/* use more reflection to set the pk (probably need a base entity) */
return entity;
} catch ( Exception e ) {
throw new RuntimeException(e);
}
}
}
但是在并发环境中,由于某些竞争条件,此代码可能会失败:
T1: BEGIN TX; T2: BEGIN TX; T1: SELECT w/ id = 123; //returns null T2: SELECT w/ id = 123; //returns null T1: INSERT w/ id = 123; T1: COMMIT; //row inserted T2: INSERT w/ name = 123; T2: COMMIT; //constraint violation
如果您运行多个JVM,同步将无济于事。并且没有获得表锁(这非常可怕),我真的不知道如何解决这个问题。
在这种情况下,我想知道系统地首先插入并处理可能的异常以执行后续选择(在新事务中)是否更好。
您应该添加一些有关上述约束的细节(多线程?分布式环境?)。
答案 1 :(得分:4)
使用纯JPA可以在具有嵌套实体管理器的多线程解决方案中乐观地解决这个问题(实际上我们只需要嵌套事务,但我认为纯粹的JPA不可能)。基本上,需要创建一个封装查找或创建操作的微事务。这种性能不是很好,不适合大批量生产,但对大多数情况来说应该足够了。
先决条件:
finder
factory
。代码:
public <T> T findOrCreate(Supplier<T> finder, Supplier<T> factory) {
EntityManager innerEntityManager = entityManagerFactory.createEntityManager();
innerEntityManager.getTransaction().begin();
try {
//Try the naive find-or-create in our inner entity manager
if(finder.get() == null) {
T newInstance = factory.get();
innerEntityManager.persist(newInstance);
}
innerEntityManager.getTransaction().commit();
} catch (PersistenceException ex) {
//This may be a unique constraint violation or it could be some
//other issue. We will attempt to determine which it is by trying
//to find the entity. Either way, our attempt failed and we
//roll back the tx.
innerEntityManager.getTransaction().rollback();
T entity = finder.get();
if(entity == null) {
//Must have been some other issue
throw ex;
} else {
//Either it was a unique constraint violation or we don't
//care because someone else has succeeded
return entity;
}
} catch (Throwable t) {
innerEntityManager.getTransaction().rollback();
throw t;
} finally {
innerEntityManager.close();
}
//If we didn't hit an exception then we successfully created it
//in the inner transaction. We now need to find the entity in
//our outer transaction.
return finder.get();
}
答案 2 :(得分:0)
在orElse
之后使用findByKeyword
函数怎么样?如果没有找到记录,您可以返回一个新实例。
SearchCount searchCount = searchCountRepository.findByKeyword(keyword)
.orElse(SearchCount.builder()
.keyword(keyword)
.count(0)
.build()) ;
答案 3 :(得分:-3)