3

我已经为此苦苦挣扎了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 
}

使用一个新的/非托管的地址实例和一个托管的成员实例,我试图在一个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 应用程序重现问题的人都需要:

  • 马文
  • 吉特
  • JDK 6
  • MySQL

他们可以按照以下步骤重现问题:

  • git clone git@github.com:balteo/StaleObjectStateException.git
  • 在 mysql 中创建一个名为 sose 的数据库模式create database sose;
  • mvn test
  • 瞧:BOOM

谁能向我解释为什么在我的情况下会发生此异常以及如何更新地址实例而不会出现此异常?

4

1 回答 1

3

我在您的代码中看到两个问题:

1) 设置成员地址时,不要修改id,而是设置地址对象。updateMemberAddress应该是这样的

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

2)您在成员和地址之间具有一对一的关系。我不知道您的确切数据模型,但据我所知,成员有一个 ID,而地址没有明确定义的 ID。这不是必需的,因为地址具有成员的 ID,同时它是主键(1:1 关系)。

但是,当您更改地址(语句)的 id 时,address.setId(addressId);此时您将拥有两个具有相同主键的地址对象(以前附加到成员的旧地址和新地址)。Hibernate 无法处理具有相同主键的两个实例。

在将新地址实例附加到此成员之前,您必须删除旧地址实例。(更好的解决方案是添加版本号或单独的 id 来寻址并将关系成员:地址更改为 1:n。)

可能问题2)产生错误。

于 2013-04-11T13:14:36.197 回答