Reputation: 3696
@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)
Upvotes: 4
Views: 18543
Reputation: 135762
In your case, saveOrUpdate()
will generate:
INSERT
if the accountsId
is null
.UPDATE
otherwise.That is why when you have line 20 (accountDetails.setAccountsId(...);
), it is UPDATE
ing.
The simplest way out is probably checking it yourself and calling save()
if the accountsId
does not yet exist or update()
otherwise.
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 equalsunsaved-value
(see below), it is considered unsaved and thensave()
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 aunsaved-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
.
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>
Upvotes: 8