【问题标题】:Hibernate savOrUpdate either insert or update onlyHibernate savOrUpdate 仅插入或更新
【发布时间】:2014-09-20 19:35:06
【问题描述】:
@Autowired
private SessionFactory sessionFactory;
public String getAccountsDetails(List<Account> accountList) {
    Session session = sessionFactory.openSession();
    for (Account account : accountList) {
        int i = 0;
        AccountDetails accountDetails = new AccountDetails();
        accountDetails.setAccountsId(Long.parseLong(account.getId()));//LINE no -20
        accountDetails.setName(account.getName());
        accountDetails.setSubAccount(account.getAccountSubType());
        session.saveOrUpdate(accountDetails);
        if (++i % 20 == 0) {
            session.flush();  
            session.clear();  
        } 
    }
        session.getTransaction().commit();
        session.close();
}

输出:

即使 db 中没有数据,它也会始终运行更新。

   Hibernate: update name=?, subaccount=? where accounts_id=?
    ....

如果我在 LINE no-20 中评论帐户 id,它总是运行插入。

休眠:插入表测试... ....

dto 类:

@Entity
@Table(name="test")
public class AccountDetails{
    @Id
    @GeneratedValue
    @Column(name = "accounts_id")
    private Long accountsId;

    @Column(name = "name")
    private String name;

    @Column(name = "subaccount")
    private String subAccount;

}

查询: 甲骨文数据库:

 create table test (accounts_id NUMBER(10) NOT NULL PRIMARY KEY, 
    name VARCHAR(100),
    subaccount VARCHAR(100) 
    )

我的要求是如果数据库中没有数据则插入,否则更新。

编辑

我在我的 dto 中创建了一个新字段:

@Version
@Column(name = "version")
private long version;

并在 db 中创建了一个列作为版本号 (100)。每当我运行应用程序时,它总是首先运行一个 updte 语句,然后它抛出 StaleObjectStateException 为:

Hibernate: update test set accountsubtype=?, accounttype=?, acctnum=?, active=?, currentbalance=?, currentbalancewithsubaccounts=?, description=?, fullyqualifiedname=?, name=?, subaccount=?, version=? where accounts_id=? and version=?
ERROR 2014-09-22 11:57:25,832 [[qbprojects].connector.http.mule.default.receiver.04] org.hibernate.event.def.AbstractFlushingEventListener: Could not synchronize database state with session
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.trinet.mulesoft.quickbooks.dto.AccountDetails#63]
    at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1932)
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2576)

【问题讨论】:

标签: hibernate


【解决方案1】:

TL;DR

在您的情况下,saveOrUpdate() 将生成:

  • 如果accountsIdnull,则为INSERT
  • UPDATE 否则。

这就是为什么当你有第 20 行 (accountDetails.setAccountsId(...);) 时,它是 UPDATEing。

最简单的方法可能是自己检查,如果accountsId 尚不存在,则调用save(),否则调用update()

详情:

Automatic State Detection - 斜体是我写的:

saveOrUpdate() 执行以下操作:

  • 如果对象已在此会话中持久存在,则不执行任何操作;
  • 如果与会话关联的另一个对象具有相同的标识符,则抛出异常;
  • 如果对象没有标识符属性,save()它;
    • 没有标识符属性意味着没有 @Id 或 XML 等效项。
  • 如果对象的标识符具有分配给新实例化对象的值,save()它;
    • 注意新实例化的对象unsaved-value 属性定义。
    • 换句话说,这个项目符号表示:如果@Id 属性值等于unsaved-value(见下文),则认为它未保存,然后save() 它。
    • 这是你的场景。请注意,您使用的是unsaved-value=null,因为它是默认设置。
  • 如果对象由&lt;version&gt;&lt;timestamp&gt;进行版本控制,并且版本属性值与分配给新实例化对象的值相同,则save()它;
    • 这与之前的项目相同,但使用&lt;version&gt;&lt;timestamp&gt; 属性而不是@Id 属性。
    • 还要注意&lt;version&gt;&lt;timestamp&gt; 也有一个unsaved-value=null 属性来定义什么是未保存或新实例化的对象
  • 否则update()对象

有关unsaved-value attribute 的更多详细信息(以下引用来自unsaved-value@Id/identifier 属性):

unsaved-value(可选 - 默认为“合理”值):一个 指示实例是新的标识符属性值 实例化(未保存),将其与分离的实例区分开来 在上一个会话中保存或加载。

对于Long 标识符,“明智的”默认值很可能是null

解决方法:

如果只是想分配标识符,我们可以使用&lt;generator class="assigned"/&gt; 就可以了。但你不想两个世界:有时分配并生成其他世界。

1 - 如上所述,一种选择是自己检查对象是否已经存在并相应地调用save()update()

2 - 另一个选项是 implement an Interceptor 覆盖 isTransient() method。在这种方法中,您必须再次检查实体是否已经存在。优点:您只需实现一次(在Interceptor 上)。缺点:嗯,它是一个Interceptor,你必须连接它以及它需要的所有其他东西。

3 - 最后,docs hint 有可能(我们必须确认)您可以通过使用生成器和设置属性 unsaved-value="undefined" 来做您想做的事情。但是你必须求助于 XML 映射,就像你 can't set the unsaved-value through annotations:

unsaved-value 属性在 Hibernate 和 注解中确实没有对应的元素。

通过 XML 设置 unsaved-value 类似于:

<id name="accountsId" column="accounts_id" type="long" unsaved-value="undefined">
    <generator class="auto" />
</id> 

【讨论】:

  • 这意味着休眠中的 saveorupdate 不像 db 中有数据然后更新否则保存它。对于我的任务,我需要首先为 select 执行 createQuery 并查找是否存在任何数据,如果是则由 session.update 更新,否则为 session.insert()。在那种情况下,我觉得 oracle 中的合并查询会做得更好。
  • saveOrUpdate()其实就像你说的那样:它会先判断数据是否存在(有时会在DB中查找数据),如果存在则更新,否则插入。问题是你必须控制休眠将如何决定数据是否存在。目前,you(通过默认值)告诉 Hibernate:“如果 ID 是 not null,那么它已经存在”。但是,显然这不是你想要的。
  • 问题是,当 id 第一次不为 null 时,它会尝试在 db 中没有数据且没有更新或插入 db 的情况下运行更新查询。并且不抛出任何异常
  • 是的,它这样做是因为您使用的当前配置使它认为:“如果 ID 不是 null,那么数据已经存在,我必须 update 而不是 @987654379 @"。为了实现你想要的,要么检查自己并有时使用save(),要么通过创建Interceptor.isUnsaved()来改变它的决定/思考方式。
  • 有时在使用复合键时,hibernate会查询数据库来决定是INSERT还是UPDATE。这几乎是你想要的,但它仍然不是,因为你也想要自动生成(和generation won't work with composite keys - 至少不是没有黑客)。老实说,我认为您所能做的就是我在答案中给出的三个选项之一。我见过的大多数代码都使用选项 1,因为它是最直接的。选项 3 可能完全符合您的要求,但您必须使用 XML 绑定,我们不确定它是否有效。
猜你喜欢
  • 1970-01-01
  • 2011-11-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-05
  • 2012-02-16
  • 2016-05-02
相关资源
最近更新 更多