Reputation: 24659
I have a Member
entity which has a @OneToOne
relationship to an Address
entity as follows:
In the Member
entity:
@OneToOne(cascade=CascadeType.ALL)
private Address address;
The Address
entity:
@RooJpaEntity
public class Address {
private String formattedAddress;
private double latitude;
private double longitude;
}
The issue I have is that each time I update the member's address as follows:
public void modifyAddress(Member member, Address address){
member.setAddress(address);
memberRepository.save(member);
}
... instead of updating the row in the address table it inserts a new row in the address table and updates the FK in the member table as in the sql given below:
Generated SQL:
Hibernate:
/* insert com.bignibou.domain.Address
*/ insert
into
address
(formatted_address, latitude, longitude, version)
values
(?, ?, ?, ?)
Hibernate:
/* update
com.bignibou.domain.Member */ update
member
set
address=?,
version=?
where
id=?
and version=?
I am not sure how to fix the issue (I don't want to inline the Adress data in the member table for now). I would like for my app to update the address table instead of inserting a new row in that table.
Can anyone please advise?
edit 1:
I have modified my entities as follows:
Here is the full Address
entity:
@RooJpaEntity
public class Address {
private String formattedAddress;
private double latitude;
private double longitude;
@OneToOne(cascade = CascadeType.ALL, mappedBy = "address")
private Member member;
}
Here is the relevant property in Member
entity:
@OneToOne
private Address address;
My method:
@Override
public void modifyAddress(Member member, Address address){
address.setMember(member);
updateAddress(address);
}
The behavior of my app is unchanged...
edit 2: If I modify the app as below I get a StaleObjectStateException.
@Override
public void modifyAddress(Member member, Address address){
long addressId = member.getAddress().getId();
address.setId(addressId);
address.setMember(member);
updateAddress(address);
}
Address:
@RooJpaEntity
public class Address {
private String formattedAddress;
private double latitude;
private double longitude;
@OneToOne
private Member member;
}
In Member:
@OneToOne
private Address address;
StaleObjectStateException:
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.bignibou.domain.Address#5]
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.$Proxy49.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.$Proxy48.merge(Unknown Source)
org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:353)
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:333)
org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:318)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96)
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260)
org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
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:92)
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.$Proxy65.save(Unknown Source)
com.bignibou.service.PreferencesServiceImpl_Roo_Service.ajc$interMethod$com_bignibou_service_PreferencesServiceImpl_Roo_Service$com_bignibou_service_PreferencesServiceImpl$updateAddress(PreferencesServiceImpl_Roo_Service.aj:53)
com.bignibou.service.PreferencesServiceImpl.updateAddress(PreferencesServiceImpl.java:1)
com.bignibou.service.PreferencesServiceImpl_Roo_Service.ajc$interMethodDispatch1$com_bignibou_service_PreferencesServiceImpl_Roo_Service$com_bignibou_service_PreferencesServiceImpl$updateAddress(PreferencesServiceImpl_Roo_Service.aj)
com.bignibou.service.PreferencesServiceImpl.modifyAddress_aroundBody12(PreferencesServiceImpl.java:100)
com.bignibou.service.PreferencesServiceImpl$AjcClosure13.run(PreferencesServiceImpl.java:1)
org.springframework.transaction.aspectj.AbstractTransactionAspect.ajc$around$org_springframework_transaction_aspectj_AbstractTransactionAspect$1$2a73e96cproceed(AbstractTransactionAspect.aj:59)
org.springframework.transaction.aspectj.AbstractTransactionAspect$AbstractTransactionAspect$1.proceedWithInvocation(AbstractTransactionAspect.aj:65)
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260)
org.springframework.transaction.aspectj.AbstractTransactionAspect.ajc$around$org_springframework_transaction_aspectj_AbstractTransactionAspect$1$2a73e96c(AbstractTransactionAspect.aj:63)
com.bignibou.service.PreferencesServiceImpl.modifyAddress(PreferencesServiceImpl.java:93)
com.bignibou.controller.PreferenceController.modifyAddress(PreferenceController.java:168)
edit 3:
Address's hashcode:
public int hashCode() {
return new HashCodeBuilder().append(formattedAddress).append(getId()).append(latitude).append(longitude).toHashCode();
}
Address's equals (from ITD):
public boolean Address.equals(Object obj) {
if (!(obj instanceof Address)) {
return false;
}
if (this == obj) {
return true;
}
Address rhs = (Address) obj;
return new EqualsBuilder().append(formattedAddress, rhs.formattedAddress).append(id, rhs.id).append(latitude, rhs.latitude).append(longitude, rhs.longitude).append(member, rhs.member).isEquals();
}
ITD for Address entity:
privileged aspect Address_Roo_Jpa_Entity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long Address.id;
@Version
@Column(name = "version")
private Integer Address.version;
public Long Address.getId() {
return this.id;
}
public void Address.setId(Long id) {
this.id = id;
}
public Integer Address.getVersion() {
return this.version;
}
public void Address.setVersion(Integer version) {
this.version = version;
}
}
By the way, ITDs are just aspects.
edit 4: I have set up a sample app for you here: https://github.com/balteo/sample-app-gab
Upvotes: 1
Views: 5888
Reputation: 8323
(wrong original diagnostic i delete, i let the link anyway cause they are still instructive)
See : http://www.objectdb.com/api/java/jpa/OneToOne and JPA Hibernate One-to-One relationship and http://www.techavalanche.com/2012/05/10/one-to-one-unidirectional-mapping-using-foreign-key/
Edit :
Ofc, the flush call here are needed only if operations take place in same transaction.
All of this run fine with your sample app.
Please note an interesting thing, OneToOne relationship DOES NOT ensure unicity by itself between address and member. (you can have 2 member refering to same address). You have to explicit unique constraint using @JoinColumn(name = "address", unique=true)
Upvotes: 2