2013-04-10 3 views
3

Я уже более недели борется с StaleObjectStateException и решил опубликовать здесь простое приложение, которое воспроизводит проблему.Понимание оптимистической блокировки StaleObjectStateException

Я понимаю, что org.hibernate.StaleObjectStateException является исключение оптимистической блокировки. Кроме того, оптимизационная блокировка основана на использовании поля версии в каждом из классов сущностей.

Теперь позвольте мне объяснить, как я воспроизвел выше исключение: пример приложение имеет класс Member сущности, которая заключается в следующем:

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

Вот класс Address объекта:

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

Использование a префикс/неуправляемый экземпляр адреса и управляемый экземпляр пользователя, я пытаюсь обновить адрес участника в ServiceImpl класс следующим образом:

@Override 
public void updateMemberAddress(Member member, Address address) { 
    long addressId = member.getAddress().getId(); 
    address.setId(addressId); 
    updateAddress(address); 
} 

Вот тестовый класс:

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

К сожалению, я получаю страшился StaleObjectStateException следующим образом:

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

Любой желающий воспроизвести проблему с помощью для примера github требуется приложение:

  • Maven
  • Гит
  • JDK 6
  • MySQL

Они могут воспроизвести проблему, выполнив следующие действия:

  • git clone [email protected]:balteo/StaleObjectStateException.git
  • В MySQL создать схему базы данных под названием sose create database sose;
  • mvn test
  • и voila: BOOM!

Может кто-нибудь объяснить мне, почему это исключение происходит в моем случае и как обновить экземпляр адреса без получения этого исключения?

ответ

3

Я вижу две проблемы в вашем коде:

1) При установке адреса члена, вы не должны изменять идентификатор, но задающие адрес объекта. updateMemberAddress должно быть, как это

@Override 
public void updateMemberAddress(Member member, Address address) { 
    member.setAddress(address); 
    updateMember(member); 
} 

2) У вас есть отношение один-к-одному между членом и адрес.Я не знаю вашу точную модель данных, но, насколько я вижу, у члена есть идентификатор, а адрес не имеет явно определенного идентификатора. Это необязательно, потому что адрес имеет идентификатор члена, который в то же время является первичным ключом (отношение 1: 1).

Но когда вы меняете идентификатор адреса (оператор address.setId(addressId);), у вас есть в тот момент два адресных объекта (старый адрес, который ранее был присоединен к члену и новый адрес) с тем же самым первичным ключом. Hibernate не может обрабатывать два экземпляра с одним и тем же основным ключом.

Вы должны удалить экземпляр старого адреса, прежде чем присоединять его к этому члену. (Лучшим решением было бы добавить номер версии или отдельный идентификатор для адресации и изменить член отношения: адрес 1: n.)

Вероятно, проблема 2) вызывает ошибку.

+0

Hi Johanna! Ummm ... Address также имеет определенное поле id. Он фактически расположен в отдельном файле aspectj (как и в случае с членом). Что произойдет, если я не установил идентификатор адреса, это то, что в итоге я получаю другой экземпляр Address в таблице адресов ... – balteo

+0

И если вы задали идентификатор адреса, то у вас есть только один экземпляр в базе данных, потому что второй экземпляр не написан из-за исключения ;-). Даже с отдельным идентификатором адреса в настройке адреса идентификатор в том виде, в котором вы делаете это, - это нонсенс. Затем у вас также есть два экземпляра с одним и тем же основным ключом. В вашем заявлении «Address address = new Address();» вы явно создаете второй экземпляр адреса, и для этого нужен уникальный идентификатор. Если вам не нужен второй экземпляр адреса, вы должны это сделать: «Адрес address = member.getAddress(); address.setXXX (...); service.updateMember (member); ' – Johanna

+0

Используя это:' Адрес address = member.getAddress(); 'Мне тогда нужно управлять/копировать поля по одному, что может быть утомительным, если есть много полей. Я хотел этого избежать. – balteo