4
@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();
}

Output:

this always runs update even there is no data in db.

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

If I comment account id in LINE no-20, it always runs insert.

Hibernate: insert into table test... ....

dto class:

@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;

}

Query: Oracle db:

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

My requirement is if there is no data in db then insert otherwise update.

EDIT

I have created a new field in my dto as:

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

and created a column in db as version number(100). Whenever I run the application, it always run one updte statement first, then it throws StaleObjectStateException as:

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)
TechFind
  • 3,696
  • 18
  • 47
  • 62

1 Answers1

7

TL;DR

In your case, saveOrUpdate() will generate:

  • an INSERT if the accountsId is null.
  • an UPDATE otherwise.

That is why when you have line 20 (accountDetails.setAccountsId(...);), it is UPDATEing.

The simplest way out is probably checking it yourself and calling save() if the accountsId does not yet exist or update() otherwise.

Details:

See Automatic State Detection - italics are written by me:

saveOrUpdate() does the following:

  • if the object is already persistent in this session, do nothing;
  • if another object associated with the session has the same identifier, throw an exception;
  • if the object has no identifier property, save() it;
    • No identifier property means no @Id or XML equivalent.
  • if the object's identifier has the value assigned to a newly instantiated object, save() it;
    • Notice that newly instantiated object is defined by the unsaved-value attribute.
    • In other words, this bullet says: if the @Id property value equals unsaved-value (see below), it is considered unsaved and then save() it.
    • This is your scenario. Notice that you are using unsaved-value=null because it is the default.
  • if the object is versioned by a <version> or <timestamp>, and the version property value is the same value assigned to a newly instantiated object, save() it;
    • This is the same as the item before, but with the <version> and <timestamp> properties instead of the @Id property.
    • Notice also both <version> and <timestamp> also have a unsaved-value=null attribute to define what is an unsaved or newly instantiated object.
  • otherwise update() the object

More details about the unsaved-value attribute (the quote below is from the unsaved-value for the @Id/identifier property):

unsaved-value (optional - defaults to a "sensible" value): an identifier property value that indicates an instance is newly instantiated (unsaved), distinguishing it from detached instances that were saved or loaded in a previous session.

For Long identifiers, the "sensible" default is most likely null.

Workarounds:

If just wanted to assign the identifier, we could use <generator class="assigned"/> and it'd be OK. But you wan't both of worlds: assign sometimes and generate the others.

1 - As suggested above, one option is to check if the object already exists yourself and calling save() or update() accordingly.

2 - Another option is to implement an Interceptor with the isTransient() method overridden. In that method you'd have to, again, check if the entity already exists yourself. Advantage: you only have to implement it once (on the Interceptor). Disadvantage: well, it is an Interceptor and you'd have to wire it and everything else it requires.

3 - Lastly, the docs hint there is a possibility (we'd have to confirm) that you could do what you want by both using a generator and setting the attribute unsaved-value="undefined". But you'd have to resort to XML mapping, as you can't set the unsaved-value through annotations:

The unsaved-value attribute is almost never needed in Hibernate and indeed has no corresponding element in annotations.

Setting unsaved-value via XML would be something like:

<id name="accountsId" column="accounts_id" type="long" unsaved-value="undefined">
    <generator class="auto" />
</id> 
acdcjunior
  • 132,397
  • 37
  • 331
  • 304
  • that means saveorupdate in hibernate is not like if there is a data in db then update otherwise save it. For my tasks, I need to first do createQuery for select and find if there is any data exists, if it is then update by session.update otherwise session.insert(). In that case merge query in oracle will do better I feel. – TechFind Sep 20 '14 at 20:19
  • `saveOrUpdate()` actually is like what you said: it will first decide if the data exists (sometimes it will look for the data in the DB), if it exists it will update, otherwise insert. The thing is you have to control how will hibernate decide if the data exists. Currently, **you** (via the defaults) are telling Hibernate: "If the ID is **not** `null`, then it already exists". But, obviously that is not what you want. – acdcjunior Sep 20 '14 at 20:24
  • problem is, when id is not null for the first time, it tries to run update query where there is no data in db and nothing updated or inserted in db. And not throwing any exception – TechFind Sep 20 '14 at 20:34
  • Yeah, it does that because the current config you are using makes it think like: "if the ID is not `null`, then the data already exists and I must `update` instead of `insert`". To achieve what you want, either you check yourself and use `save()` sometimes, or you change the way it decides/thinks, by creating a `Interceptor.isUnsaved()`. – acdcjunior Sep 20 '14 at 20:35
  • ok, is using VERSION is better or Interceptor.isUnsaved() using will solve my problem? Interceptor.isUnsaved() I dont find this method isUnsaved in Interceptor api of hibernate. do you know any sample link? – TechFind Sep 20 '14 at 20:40
  • There is a sample here: http://www.mkyong.com/hibernate/hibernate-interceptor-example-audit-log/ You'd have to implement, instead of `onSave()` or others, the `isTransient()`. But I got a feeling doing the query yourself is simpler, you know... – acdcjunior Sep 20 '14 at 20:59
  • updated my question above, I have tried with version, first it runs update statement( but data is empty in db) then throws stale state exception. – TechFind Sep 22 '14 at 19:02
  • I don't think using `version` or `timestamp` would help in your case. If the `version`/`timestamp` is `null` it'll `INSERT`, if not, it will either `UPDATE` or throw an exception (when the `version`/`timestamp` do not match the one on the DB). In other words, it is still up to you (again, if you set the `version`/`timestamp`, it will `INSERT`, and `UPDATE` otherwise), and not up to hibernate to decide if it inserts or updates. – acdcjunior Sep 22 '14 at 21:08
  • 1
    Sometimes when using composite keys, hibernate will query the database to decide if it will `INSERT` or `UPDATE`. It is almost what you wanted, but it still is not, as you want auto generation as well (and [generation won't work with composite keys](http://stackoverflow.com/a/10102852/1850609) - not without a hack, at least). I honestly think all you can do is one of those three options I gave in the answer. Most code I've seen uses option 1, as it is the most straightforward. Option 3 may do exactly what you want, but you'd have to use XML binding and we don't know for sure if it'd work. – acdcjunior Sep 22 '14 at 21:16