我遇到了Spring控制器方法的问题。它实际上对同一个实体进行了两次更新,导致 StaleObjectStateException 。
问题在于,当我检索成员实例时,我认为它会以某种方式导致广告实例的更新(请参阅//UPDATE ONE
)(实际上并不需要)以及更新广告实例时(请参阅{ {1}}),然后抛出 StaleObjectStateException 。
我的问题是如何防止在我的情况下发生此异常(请记住我使用Spring Data JPA)?
这是//UPDATE TWO
实体类:
Member
并在@Entity
public class Member {
...
@OneToMany(fetch = FetchType.LAZY, mappedBy = "member")
private List<Advertisement> advertisements;
...
实体类中:
Advertisement
这是控制器方法:
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
private Member member;
这是stacktrace:
@RequestMapping(value = "/family/advertisement/edit", method = RequestMethod.POST, produces = "text/html")
public String editFamilyAdvertisement(@ModelAttribute @Validated(value = Validation.AdvertisementCreation.class) FamilyAdvertisementInfo familyAdvertisementInfo, BindingResult bindingResult, Model model) {
Member member = memberService.retrieveCurrentMember();//UPDATE ONE
if (!advertisementService.advertisementBelongsToMember(familyAdvertisementInfo.getFamilyAdvertisement(), member)) {
throw new IllegalStateException("advertisement does not belong to member");
}
if (bindingResult.hasErrors()) {
populateModel(model, familyAdvertisementInfo);
return "family/advertisement/edit";
}
familyAdvertisementInfo.getFamilyAdvertisement().setMember(member);
advertisementService.editFamilyAdvertisement(familyAdvertisementInfo.getFamilyAdvertisement());//UPDATE TWO
return "redirect:/family/advertisement/edit/" + familyAdvertisementInfo.getFamilyAdvertisement().getId();
}
编辑1 :
SQL日志:
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.bignibou.domain.FamilyAdvertisement#1]
org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:303)
org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:76)
org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:903)
org.hibernate.internal.SessionImpl.merge(SessionImpl.java:887)
org.hibernate.internal.SessionImpl.merge(SessionImpl.java:891)
org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:879)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:601)
org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:366)
com.sun.proxy.$Proxy45.merge(Unknown Source)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:601)
org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:241)
com.sun.proxy.$Proxy44.merge(Unknown Source)
org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:345)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:601)
org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:334)
org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:319)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:155)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
org.springframework.data.jpa.repository.support.LockModeRepositoryPostProcessor$LockModePopulatingMethodIntercceptor.invoke(LockModeRepositoryPostProcessor.java:91)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:91)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
com.sun.proxy.$Proxy52.save(Unknown Source)
com.bignibou.service.AdvertisementServiceImpl_Roo_Service.ajc$interMethod$com_bignibou_service_AdvertisementServiceImpl_Roo_Service$com_bignibou_service_AdvertisementServiceImpl$updateFamilyAdvertisement(AdvertisementServiceImpl_Roo_Service.aj:58)
com.bignibou.service.AdvertisementServiceImpl.updateFamilyAdvertisement(AdvertisementServiceImpl.java:1)
com.bignibou.service.AdvertisementServiceImpl_Roo_Service.ajc$interMethodDispatch1$com_bignibou_service_AdvertisementServiceImpl_Roo_Service$com_bignibou_service_AdvertisementServiceImpl$updateFamilyAdvertisement(AdvertisementServiceImpl_Roo_Service.aj)
com.bignibou.service.AdvertisementServiceImpl.editFamilyAdvertisement(AdvertisementServiceImpl.java:27)
com.bignibou.controller.AdvertisementController.editFamilyAdvertisement(AdvertisementController.java:85)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
广告:
2013-04-06 11:23:24,339 [http-bio-8080-exec-9] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Returning handler method [public java.lang.String com.bignibou.controller.AdvertisementController.editFamilyAdvertisement(com.bignibou.controller.helpers.FamilyAdvertisementInfo,org.springframework.validation.BindingResult,org.springframework.ui.Model)]
Hibernate:
/* load com.bignibou.domain.GeolocationPostcode */ select
geolocatio0_.id as id8_0_,
geolocatio0_.postcode as postcode8_0_,
geolocatio0_.version as version8_0_
from
geolocation_postcode geolocatio0_
where
geolocatio0_.id=?
Hibernate:
/* load com.bignibou.domain.Member */ select
member0_.id as id6_1_,
member0_.activated as activated6_1_,
member0_.address as address6_1_,
member0_.email as email6_1_,
member0_.last_connection_date as last4_6_1_,
member0_.password as password6_1_,
member0_.registration_date as registra6_6_1_,
member0_.role as role6_1_,
member0_.token as token6_1_,
member0_.version as version6_1_,
address1_.id as id3_0_,
address1_.formatted_address as formatted2_3_0_,
address1_.latitude as latitude3_0_,
address1_.longitude as longitude3_0_,
address1_.version as version3_0_
from
member member0_
left outer join
address address1_
on member0_.address=address1_.id
where
member0_.id=?
Hibernate:
/* load one-to-many com.bignibou.domain.Member.advertisements */ select
advertisem0_.member as member6_1_,
advertisem0_.id as id0_1_,
advertisem0_.id as id0_0_,
advertisem0_.active as active0_0_,
advertisem0_.creation_date as creation3_0_0_,
advertisem0_.description as descript4_0_0_,
advertisem0_.expiration_date as expiration5_0_0_,
advertisem0_.member as member0_0_,
advertisem0_.validated as validated0_0_,
advertisem0_.version as version0_0_,
advertisem0_.childminder_status as childmin1_10_0_,
advertisem0_.clazz_ as clazz_0_
from
( select
id,
active,
creation_date,
description,
expiration_date,
validated,
version,
member,
null as childminder_status,
1 as clazz_
from
family_advertisement
union
select
id,
active,
creation_date,
description,
expiration_date,
validated,
version,
member,
childminder_status,
2 as clazz_
from
childminder_advertisement
) advertisem0_
where
advertisem0_.member=?
FamilyAdvertisement:
@RooJavaBean
@RooToString
@RooEquals
@RooJpaEntity(inheritanceType = "TABLE_PER_CLASS")
@Entity
@DynamicUpdate
public abstract class Advertisement {
@ElementCollection
private Set<ChildcareType> childcareTypes;
@ManyToMany
private List<DayToTimeSlot> dayToTimeSlots;
@NotNull(groups = { Validation.AdvertisementCreation.class })
@ManyToMany
private Set<GeolocationPostcode> postcodes;
@NotNull(groups = { Default.class })
@Size(min = 6, max = 300, groups = { Default.class, Validation.AdvertisementCreation.class })
@Column(length = 300)
private String description;
@Temporal(TemporalType.TIMESTAMP)
@DateTimeFormat(pattern = "dd/MM/yyyy HH:mm:ss")
private Date creationDate;
@Temporal(TemporalType.TIMESTAMP)
@DateTimeFormat(pattern = "dd/MM/yyyy HH:mm:ss")
private Date expirationDate;
@NotNull(groups = { Default.class })
private boolean active;
@NotNull(groups = { Default.class })
private boolean validated;
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
private Member member;
public abstract boolean isChildcareTypesValid();
@AssertTrue(groups = { Validation.AdvertisementCreation.class })
public boolean isPostcodesValid() {
return (postcodes != null && !postcodes.isEmpty());
}
}
编辑2 :我必须提到我使用Spring Roo + JPA默认提供的实体版本。我很确定必须考虑到这一点。< / p>
编辑3 :我已经设置了一个示例应用程序,用于演示github上的问题here。任何希望帮助我的人都可以按如下方式克隆应用:@RooJavaBean
@RooToString
@RooEquals
@RooJpaEntity
@Entity
@DynamicUpdate
public class FamilyAdvertisement extends Advertisement {
@NotNull(groups = Validation.AdvertisementCreation.class)
@ElementCollection
private Set<Need> needs;
@ElementCollection
private Set<ChildminderStatus> childminderStatuses;
@AssertTrue
@Override
public boolean isChildcareTypesValid() {
return true;
}
@AssertTrue
public boolean isNeedsValid() {
if (needs.isEmpty()) {
return false;
}
if (needs.contains(Need.CHILDMINDER_TO_FAMILY)) {
return false;
}
return true;
}
}
。
答案 0 :(得分:9)
我有类似的问题。这是我在运行mvn test
时遇到的错误:
org.hibernate.StaleObjectStateException:行被另一个事务更新或删除(或未保存的值映射不正确)
这是因为,我错误地将实体的version
字段初始化为NULL
:
mysql> select * from lap;
+----+-------------+---------------------+------+------------------+---------+---------+
| id | code | cumulative_distance | name | partial_distance | version | race_id |
+----+-------------+---------------------+------+------------------+---------+---------+
| 1 | AF0CUL00011 | 1000 | LAP1 | 1000 | NULL | 1 |
| 2 | AQ000000012 | 2000 | LAP2 | 1000 | NULL | 2 |
| 3 | AR000000013 | 3000 | LAP3 | 1000 | NULL | 3 |
| 4 | AR0000N0114 | 4000 | LAP4 | 1000 | NULL | 4 |
+----+-------------+---------------------+------+------------------+---------+---------+
4 rows in set (0.00 sec)
有一次,我用0初始化version
字段,一切正常:
mysql> select * from lap;
+----+-------------+---------------------+------+------------------+---------+---------+
| id | code | cumulative_distance | name | partial_distance | version | race_id |
+----+-------------+---------------------+------+------------------+---------+---------+
| 1 | AF0CUL00011 | 1000 | LAP1 | 1000 | 0 | 1 |
| 2 | AQ000000012 | 2000 | LAP2 | 1000 | 0 | 2 |
| 3 | AR000000013 | 3000 | LAP3 | 1000 | 0 | 3 |
| 4 | AR0000N0114 | 4000 | LAP4 | 1000 | 0 | 4 |
+----+-------------+---------------------+------+------------------+---------+---------+
4 rows in set (0.00 sec)
我希望它有所帮助, 奥古斯丁
答案 1 :(得分:3)
“org.hibernate.StaleObjectStateException:行被另一个事务更新或删除(或未保存的值映射不正确):[com.bignibou.domain.FamilyAdvertisement#1]”提出了两个原因 - 一些意外更新(鉴于您正在选择数据,这会很奇怪)或JPA映射中的错误。
请添加SQL查询日志记录并发布由JPA生成的SQL查询。还请发布FamilyAdvertisment课程。查询日志记录取决于JPA提供程序(请参阅此提示:How to view the SQL queries issued by JPA?)