我正在使用:
我有问题。我有一个标准的Spring Data JPA DAO:
public interface DnarDao extends JpaRepository<Dnar, Long> {
// insert Spring Data magic here!
}
以下是Dnar
代码:
@Entity
@Table(name="req_dnar", schema=SCHEMA)
@Inheritance(strategy=JOINED)
@DiscriminatorColumn(name="form_type", discriminatorType=STRING, length=64)
public abstract class Dnar {
@Id
@GeneratedValue(strategy=SEQUENCE, generator="dnar_gen_seq")
@SequenceGenerator(name="dnar_gen_seq", sequenceName="req_dnar_seq")
@Column(name=C_DNAR_ID, nullable=false)
private Long dnarId;
@Column(name="form_type", nullable=false, length=64)
@Enumerated(EnumType.STRING)
private FormType formType;
// remainder omitted
}
实现类的一个例子:
@Entity
@Table(name="req_dnar_general_lv")
@DiscriminatorValue("GENERAL_LV")
public class GeneralLvDnar extends Dnar {
//remainder omitted
}
另外,这是我对Hibernate的Spring配置:
<bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource" destroy-method="close">
<property name="uRL" value="${JDBC_URL}"/>
<property name="user" value="${USER_ID}"/>
<property name="password" value="${PASSWORD}"/>
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan">
<list><value>com/mycomp/domain</value></list>
</property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="ORACLE" />
<property name="generateDdl" value="true" />
<property name="showSql" value="true" />
</bean>
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
<prop key="hibernate.id.new_generator_mappings">true</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<jpa:repositories base-package="com.mycomp.dao" />
<tx:annotation-driven />
第1轮 - 问题
DAO的客户端是DnarManagerImpl
类:
@Transactional
@Override
public Dnar save(final Dnar dnar) {
Dnar managedDnar = dnarDao.save(dnar);
return managedDnar;
}
以上代码有效,但始终会在数据库中创建新的dnar
行。我调试了Spring Data JPA代码,它似乎工作正常。例如,如果我将同一对象保存3次,则Hibernate调用为:
getSession().persist(entity);
getSession().merge(entity);
getSession().merge(entity);
Hibernate为上述输出的SQL是:
第2轮 - 黑客修复
通过一些实验,我发现我可以使用dnarDao.findOne(id);
告诉当前会话该实体是否存在。这解决了我的问题:
@Transactional
@Override
public Dnar save(final Dnar dnar) {
// Hack to get the Dnar in the current persistence context
if (dnar.getDnarId() != null) {
dnarDao.findOne(dnar.getDnarId());
}
Dnar managedDnar = dnarDao.save(dnar);
return managedDnar;
}
Hibernate会话调用再次是:
getSession().persist(entity);
getSession().merge(entity);
getSession().merge(entity);
Hibernate输出的SQL现在是正确的:
问题
根据Hibernate docs,这就是merge()
的工作原理:
显然,在我的例子中,第2项没有发生。 Hibernate不会尝试从数据库加载此实体。 Hibernate 从不输出一个select(我的hack修复中的异常)。 Sooo ....我哪里错了?我确信Hibernate按照文档工作,只是因为我做了一些愚蠢的事情。
答案 0 :(得分:0)
我认为问题是因为你保存而不是saveAndFlush。 save不保证将对象插入到数据库中,因此Hibernate会进行3次插入。
修改强>
好的,所以从JPA Repository中保存方法:它不被称为创建或更新,因为它根据主键的存在更新或创建 - 查看源代码。 保存也应该保存一个Object,无论它处于什么状态,这就是为什么它被称为保存,因为它不关心它是合并还是持久,它会保存对象NO它所处的状态很重要。
您正在处理的另一件事是对象“相等”,它在JPA中是在主键级实现的。如果你想要存储的对象内部有一个匹配的ID,那么你传入的ID =&gt;合并,else =&gt;持续。
因此,你的逻辑(几乎)完美无缺。在第一种情况下,save执行3次插入,因为对象的ID(主键)尚不存在。
在第二种情况下,通过在持久化上下文中引入ID,您只需执行此操作!第一个插入,然后其余是更新。
我无法完全重现你的测试用例,因为我没有时间:(但是,对我来说,你看到的东西是完全有效的。
我希望一些有经验的JPA开发人员也会对这个问题发表评论。
答案 1 :(得分:0)
查看此代码:
@Transactional
@Override
public Dnar save(final Dnar dnar) {
Dnar managedDnar = dnarDao.save(dnar);
return managedDnar;
}
如果您想保存新创建的对象,我认为您实际上正在做正确的事情。但是,如果您只是修改现有的,则应使用update。如果你从会话中获得了那个dnar实例,你知道它存在(至少在会话范围内),所以你可以在它上面使用update。如果您自己创建了对象并希望将其保存在数据库中,则应使用save。
有一个重要的方法调用为你处理,称为'saveOrUpdate',它将尝试通过调用之前不在db中的save来保持实体(基于缺少标识符)或如果您的实体有id,则会调用update。假设你的dao中有saveOrupdate,那么你的代码将改为this。在SO中,这是一个很好的答案,用于描述行为:
Hibernate saveOrUpdate behavior
@Transactional
@Override
public Dnar save(final Dnar dnar) {
Dnar managedDnar = dnarDao.saveOrUpdate(dnar);
return managedDnar;
}