我在用着:
- 春天 3.1
- 春季数据 JPA 1.1
- 休眠 4.1
我有个问题。我有一个标准的 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 />
第一轮 - 问题
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从不输出选择(我的 hack 修复中的例外)。Sooo....我哪里错了?我确信 Hibernate 确实按照文档工作,只是我做了一些愚蠢的事情。