Reputation: 24659
I have been struggling with the StaleObjectStateException
for more than a week now and decided to post here a simple app that reproduces the problem.
I understand the org.hibernate.StaleObjectStateException
is an optimistic locking exception. Furthermore, optimistic locking relies on the use of a version field in each of the entity classes.
Now let me explain how I reproduced the above exception: the sample app has an Member
entity class which is as follows:
@RooJavaBean
@RooToString
@RooJpaEntity
public class Member {
@OneToOne(cascade=CascadeType.ALL)
private Address address;
//id, version fields as well as mutator/accessors are located in a separate Roo ITD/aspect
}
Here is the Address
entity class:
@RooJavaBean
@RooToString
@RooJpaEntity
public class Address {
private String formattedAddress;
private double lng;
private double lat;
//id, version fields as well as mutator/accessors are located in a separate Roo ITD/aspect
}
Using a fresh/unmanaged Address instance and a managed Member instance, I am trying to update the member's address in a ServiceImpl
class as follows:
@Override
public void updateMemberAddress(Member member, Address address) {
long addressId = member.getAddress().getId();
address.setId(addressId);
updateAddress(address);
}
Here is the test class:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/META-INF/spring/applicationContext*.xml")
@TransactionConfiguration(defaultRollback = false)
public class AddressIntegrationTest {
@Autowired
private Service service;
@Before
@Transactional
public void testInsertOneMember() {
Member member = new Member();
Address address = new Address();
address.setFormattedAddress("Eiffel Tower, Paris");
address.setLat(48.005);
address.setLng(3.288);
member.setAddress(address);
service.saveMember(member);
}
@Test
@Transactional
public void testUpdateAddress() {
Member member = service.findAllMembers().get(0);
Address address = new Address();
address.setFormattedAddress("Empire State Building, New York");
address.setLat(200.033);
address.setLng(36.665);
service.updateMemberAddress(member, address);
}
}
Unfortunately, I get the dreaded StaleObjectStateException as follows:
org.springframework.orm.jpa.JpaOptimisticLockingFailureException: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [org.sose.domain.Address#1];
Anyone wishing to reproduce the problem using the sample github app needs:
They can reproduce the problem by following the steps below:
git clone [email protected]:balteo/StaleObjectStateException.git
create database sose;
mvn test
BOOM
!Can anyone please explain to me why this exception is occurring in my case and how to update the address instance without getting this exception?
Upvotes: 3
Views: 10634
Reputation: 5293
I see two problems in your code:
1) When setting the address of a member, you should not modify the id but set the address object. updateMemberAddress
should be like this
@Override
public void updateMemberAddress(Member member, Address address) {
member.setAddress(address);
updateMember(member);
}
2) You have a one-to-one relation between member and address. I don't know your exact data model, but as far as I can see member has an id and address does not have an explicitly defined id. This is not necessary, because address has the id of member which at the same moment it the primary key (1:1 relation).
But when you change the id of address (statement address.setId(addressId);
) then you have at that moment two address objects (the old address which formerly was attached to the member and the new address) with the same primary key. Hibernate can't handle two instances with the same primary key.
You have to delete the old address instance before you attach the new one to this member. (A better solution would be to add a version number or a separate id to address and change the relation member:address to 1:n.)
Probably problem 2) produces the error.
Upvotes: 4