【问题标题】:Spring Data JPA with ManyToOne save operation is throwing org.hibernate.PersistentObjectException具有 ManyToOne 保存操作的 Spring Data JPA 正在抛出 org.hibernate.PersistentObjectException
【发布时间】:2016-12-09 15:07:04
【问题描述】:

我正在尝试使用 Spring Data JPA,但在保存多对一关系时遇到问题。

@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$0(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看到完整的代码

【问题讨论】:

    标签: java spring-data spring-data-jpa many-to-one


    【解决方案1】:

    添加关系的另一端,使其成为双向的。 在Address 类中,

    @OneToMany(mappedBy = "homeAddress", fetch = FetchType.LAZY)
    private Set<Customer> customers
    

    编辑:上述建议不能解决问题。 你的问题让我很好奇,我在当地查了一下。 当您在他们的事务上运行这些操作时,实体管理器在第一次保存后分离保存的address1(每次存储库交互后关闭实体管理器/会话)。

    例如,如果您将所有操作包装在一个保持实体管理器打开的事务中(这通常在服务上完成,具体取决于您的回滚逻辑),那么您的方法将起作用 我用下面的代码进行了测试,它可以工作。

        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 启动器

    【讨论】:

    • 我遇到了同样的异常。此外,我想要一个可以保持单向性的解决方案。
    • 在我看来一切似乎都是正确的。你能提供运行一些测试的源代码吗?
    • @simas_ch 查看完整代码在 [this git location](github.com/suleimana/spring-data-jpa)
    • 问题在于 new Customer("Chloe", "O'Brian",address1)
    • 地址1的引用是在错误状态下保存jack之后。当有多对一时,你应该像阿卜杜拉建议的那样单独保存一对一
    【解决方案2】:

    首先使用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:我还没有测试过,但这应该可以工作。

    【讨论】:

    • 这个解决方案也很有效,但我不得不更改 cascade=CascadeType.MERGE 。
    【解决方案3】:

    所以这是对我实际工作的总结:

    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 是更理想的解决方案,因为我的操作最终会进入服务级别。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-09-04
      • 1970-01-01
      • 2022-01-15
      • 2021-07-27
      • 1970-01-01
      • 2021-12-16
      • 2020-08-24
      • 1970-01-01
      相关资源
      最近更新 更多