Spring 带有 ManyToOne 保存操作的数据 JPA 正在抛出 org.hibernate.PersistentObjectException
Spring Data JPA with ManyToOne save operation is throwing org.hibernate.PersistentObjectException
我正在试验 Spring Data JPA,但在保存 ManyToOne 关系时遇到问题。
@Entity
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
@ManyToOne(cascade=CascadeType.ALL)
private Address homeAddress;
protected Customer() {}
// getters and setters ...
}
许多客户可以有相同的地址:
@Entity
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String postalCode;
private String city;
private String line1;
private String line2;
private String country;
public Address() {
// TODO Auto-generated constructor stub
}
// getters and setter
}
客户资料库
import org.springframework.data.jpa.repository.JpaRepository;
public interface CustomerRepository extends JpaRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
List<Customer> findByHomeAddress(Address address);
}
申请
@SpringBootApplication
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
@Bean
public CommandLineRunner demo(CustomerRepository repository) {
return (args) -> {
Address address1 = new Address("07093", "Brooklyn", "129 67th st", null, "USA");
Address address2 = new Address("03333", "Qeeens", "333 67th st", null, "USA");
// save a couple of customers
Customer jack = new Customer("Jack", "Bauer");
jack.setHomeAddress(address1);
repository.save(jack);
repository.save(new Customer("Chloe", "O'Brian",address1));
}
}
所以我试图用相同的地址保存两个客户,我得到以下异常:
Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: hello.Address; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: hello.Address
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:299) ~[spring-orm-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244) ~[spring-orm-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:491) ~[spring-orm-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59) ~[spring-tx-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213) ~[spring-tx-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147) ~[spring-tx-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133) ~[spring-data-jpa-1.10.5.RELEASE.jar:na]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) ~[spring-aop-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at com.sun.proxy.$Proxy60.save(Unknown Source) ~[na:na]
at hello.Application.lambda[=16=](Application.java:35) [classes/:na]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:800) [spring-boot-1.4.2.RELEASE.jar:1.4.2.RELEASE]
... 6 common frames omitted
我该如何解决这个问题?你可以在 this git location
看到完整的代码
添加关系的另一端,使其成为双向的。
在 Address
class,
@OneToMany(mappedBy = "homeAddress", fetch = FetchType.LAZY)
private Set<Customer> customers
编辑:以上建议没有解决问题。
你的问题让我很好奇,我在本地查了一下。
当您 运行 这些操作各自针对其事务时,实体管理器会在第一次保存后分离已保存的 address1
(实体 manager/session 在每次存储库交互后关闭)。
例如,如果您将所有操作包装在一个保持实体管理器打开的事务中(这通常在服务上完成,具体取决于您的回滚逻辑),您的方法将起作用
我用下面的代码进行了测试,它有效。
package hello;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CustomerService {
@Autowired
private CustomerRepository customerRepository;
@Transactional
public void store(){
Address address1 = new Address("07093", "Brooklyn", "129 67th st", null, "USA");
Address address2 = new Address("03333", "Qeeens", "333 67th st", null, "USA");
// save a couple of customers
Customer jack = new Customer("Jack", "Bauer");
jack.setHomeAddress(address1);
customerRepository.save(jack);
customerRepository.save(new Customer("Chloe", "O'Brian", address1));
}
}
此外,我不得不从 pom.xml
中删除 naryana 启动器
先使用 addressRepository
保留您的地址,然后再参考它。
试试这个
Address address1 = new Address("07093", "Brooklyn", "129 67th st", null, "USA");
Address address2 = new Address("03333", "Qeeens", "333 67th st", null, "USA");
address1 = addressRepository.save(address1);
address2 = addressRepository.save(address2);
// save a couple of customers
Customer jack = new Customer("Jack", "Bauer");
jack.setHomeAddress(address1);
repository.save(jack);
repository.save(new Customer("Chloe", "O'Brian",address1));
PS :我还没有测试过,但应该可以。
所以这是对我有用的总结:
1- 按照@Abdullah Wasi 的建议使用 CustomerRepository 和 AddressRepository。只要我有 cascade=CascadeType.MERGE 作为级联类型,这个就可以工作:
@ManyToOne(cascade=CascadeType.MERGE)
private Address homeAddress;
2- 使用 CustomerRepository 并根据 @isah 的回答使用 @Transactional 从服务执行操作。只要我将 cascade=CascadeType.ALL 作为级联类型,该选项也可以使用:
@ManyToOne(cascade=CascadeType.MERGE)
private Address homeAddress;
服务
package hello;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CustomerService {
@Autowired
private CustomerRepository customerRepository;
@Transactional
public void store(){
Address address1 = new Address("07093", "Brooklyn", "129 67th st", null, "USA");
Address address2 = new Address("03333", "Qeeens", "333 67th st", null, "USA");
// save a couple of customers
Customer jack = new Customer("Jack", "Bauer");
jack.setHomeAddress(address1);
customerRepository.save(jack);
customerRepository.save(new Customer("Chloe", "O'Brian", address1));
}
}
我认为@isah 是更优的解决方案,因为我的操作最终将投入使用 class。
我正在试验 Spring Data JPA,但在保存 ManyToOne 关系时遇到问题。
@Entity
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
@ManyToOne(cascade=CascadeType.ALL)
private Address homeAddress;
protected Customer() {}
// getters and setters ...
}
许多客户可以有相同的地址:
@Entity
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String postalCode;
private String city;
private String line1;
private String line2;
private String country;
public Address() {
// TODO Auto-generated constructor stub
}
// getters and setter
}
客户资料库
import org.springframework.data.jpa.repository.JpaRepository;
public interface CustomerRepository extends JpaRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
List<Customer> findByHomeAddress(Address address);
}
申请
@SpringBootApplication
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
@Bean
public CommandLineRunner demo(CustomerRepository repository) {
return (args) -> {
Address address1 = new Address("07093", "Brooklyn", "129 67th st", null, "USA");
Address address2 = new Address("03333", "Qeeens", "333 67th st", null, "USA");
// save a couple of customers
Customer jack = new Customer("Jack", "Bauer");
jack.setHomeAddress(address1);
repository.save(jack);
repository.save(new Customer("Chloe", "O'Brian",address1));
}
}
所以我试图用相同的地址保存两个客户,我得到以下异常:
Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: hello.Address; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: hello.Address
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:299) ~[spring-orm-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244) ~[spring-orm-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:491) ~[spring-orm-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59) ~[spring-tx-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213) ~[spring-tx-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147) ~[spring-tx-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133) ~[spring-data-jpa-1.10.5.RELEASE.jar:na]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) ~[spring-aop-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.4.RELEASE.jar:4.3.4.RELEASE]
at com.sun.proxy.$Proxy60.save(Unknown Source) ~[na:na]
at hello.Application.lambda[=16=](Application.java:35) [classes/:na]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:800) [spring-boot-1.4.2.RELEASE.jar:1.4.2.RELEASE]
... 6 common frames omitted
我该如何解决这个问题?你可以在 this git location
看到完整的代码添加关系的另一端,使其成为双向的。
在 Address
class,
@OneToMany(mappedBy = "homeAddress", fetch = FetchType.LAZY)
private Set<Customer> customers
编辑:以上建议没有解决问题。
你的问题让我很好奇,我在本地查了一下。
当您 运行 这些操作各自针对其事务时,实体管理器会在第一次保存后分离已保存的 address1
(实体 manager/session 在每次存储库交互后关闭)。
例如,如果您将所有操作包装在一个保持实体管理器打开的事务中(这通常在服务上完成,具体取决于您的回滚逻辑),您的方法将起作用 我用下面的代码进行了测试,它有效。
package hello;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CustomerService {
@Autowired
private CustomerRepository customerRepository;
@Transactional
public void store(){
Address address1 = new Address("07093", "Brooklyn", "129 67th st", null, "USA");
Address address2 = new Address("03333", "Qeeens", "333 67th st", null, "USA");
// save a couple of customers
Customer jack = new Customer("Jack", "Bauer");
jack.setHomeAddress(address1);
customerRepository.save(jack);
customerRepository.save(new Customer("Chloe", "O'Brian", address1));
}
}
此外,我不得不从 pom.xml
中删除 naryana 启动器先使用 addressRepository
保留您的地址,然后再参考它。
试试这个
Address address1 = new Address("07093", "Brooklyn", "129 67th st", null, "USA");
Address address2 = new Address("03333", "Qeeens", "333 67th st", null, "USA");
address1 = addressRepository.save(address1);
address2 = addressRepository.save(address2);
// save a couple of customers
Customer jack = new Customer("Jack", "Bauer");
jack.setHomeAddress(address1);
repository.save(jack);
repository.save(new Customer("Chloe", "O'Brian",address1));
PS :我还没有测试过,但应该可以。
所以这是对我有用的总结:
1- 按照@Abdullah Wasi 的建议使用 CustomerRepository 和 AddressRepository。只要我有 cascade=CascadeType.MERGE 作为级联类型,这个就可以工作:
@ManyToOne(cascade=CascadeType.MERGE)
private Address homeAddress;
2- 使用 CustomerRepository 并根据 @isah 的回答使用 @Transactional 从服务执行操作。只要我将 cascade=CascadeType.ALL 作为级联类型,该选项也可以使用:
@ManyToOne(cascade=CascadeType.MERGE)
private Address homeAddress;
服务
package hello;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CustomerService {
@Autowired
private CustomerRepository customerRepository;
@Transactional
public void store(){
Address address1 = new Address("07093", "Brooklyn", "129 67th st", null, "USA");
Address address2 = new Address("03333", "Qeeens", "333 67th st", null, "USA");
// save a couple of customers
Customer jack = new Customer("Jack", "Bauer");
jack.setHomeAddress(address1);
customerRepository.save(jack);
customerRepository.save(new Customer("Chloe", "O'Brian", address1));
}
}
我认为@isah 是更优的解决方案,因为我的操作最终将投入使用 class。