我有一个包含多对一关系的jpa持久化对象模型:一个Account有多个transaction。一个事务有一个帐户。

下面是一段代码:

@Entity
public class Transaction {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
    private Account fromAccount;
....

@Entity
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
    private Set<Transaction> transactions;

我能够创建Account对象,向其添加事务,并正确地持久化Account对象。但是,当我创建一个事务,使用现有的已经持久化的帐户,并持久化的事务,我得到一个异常:

导致:org.hibernate.PersistentObjectException:传递给persist: com.paulsanwald.Account的分离实体 org.hibernate.event.internal.DefaultPersistEventListener.onPersist (DefaultPersistEventListener.java: 141)

因此,我能够持久化一个包含事务的Account,但不能持久化一个具有Account的Transaction。我认为这是因为帐户可能没有附加,但这段代码仍然给了我相同的异常:

if (account.getId()!=null) {
    account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
 // the below fails with a "detached entity" message. why?
entityManager.persist(transaction);

如何正确地保存与已经持久化的帐户对象相关联的事务?


当前回答

这里的问题是缺乏控制。

当我们使用CrudRepository/JPARepository保存方法时,我们失去了事务控制。

为了解决这个问题,我们有了事务管理

我更喜欢@Transactional机制

进口

import javax.transaction.Transactional;

完整源代码:

package com.oracle.dto;

import lombok.*;

import javax.persistence.*;
import java.util.Date;
import java.util.List;

@Entity
@Data
@ToString(exclude = {"employee"})
@EqualsAndHashCode(exclude = {"employee"})
public class Project {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO,generator = "ps")
    @SequenceGenerator(name = "ps",sequenceName = "project_seq",initialValue = 1000,allocationSize = 1)
    @Setter(AccessLevel.NONE)
    @Column(name = "project_id",updatable = false,nullable = false)
    private Integer pId;
    @Column(name="project_name",nullable = false,updatable = true)
    private String projectName;
    @Column(name="team_size",nullable = true,updatable = true)
    private Integer teamSize;
    @Column(name="start_date")
    private Date startDate;
    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name="projectemp_join_table",
        joinColumns = {@JoinColumn(name = "project_id")},
        inverseJoinColumns = {@JoinColumn(name="emp_id")}
    )
    private List<Employee> employees;
}
package com.oracle.dto;

import lombok.*;

import javax.persistence.*;
import java.util.List;

@Entity
@Data
@EqualsAndHashCode(exclude = {"projects"})
@ToString(exclude = {"projects"})
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO,generator = "es")
    @SequenceGenerator(name = "es",sequenceName = "emp_seq",allocationSize = 1,initialValue = 2000)
    @Setter(AccessLevel.NONE)
    @Column(name = "emp_id",nullable = false,updatable = false)
    private Integer eId;
    @Column(name="fist_name")
    private String firstName;
    @Column(name="last_name")
    private String lastName;
    @ManyToMany(mappedBy = "employees")
    private List<Project> projects;
}


package com.oracle.repo;

import com.oracle.dto.Employee;
import org.springframework.data.jpa.repository.JpaRepository;

public interface EmployeeRepo extends JpaRepository<Employee,Integer> {
}

package com.oracle.repo;

import com.oracle.dto.Project;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ProjectRepo extends JpaRepository<Project,Integer> {
}

package com.oracle.services;

import com.oracle.dto.Employee;
import com.oracle.dto.Project;
import com.oracle.repo.ProjectRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.transaction.Transactional;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

@Component
public class DBServices {
    @Autowired
    private ProjectRepo repo;
    @Transactional
    public void performActivity(){

        Project p1 = new Project();
        p1.setProjectName("Bank 2");
        p1.setTeamSize(20);
        p1.setStartDate(new Date(2020, 12, 22));

        Project p2 = new Project();
        p2.setProjectName("Bank 1");
        p2.setTeamSize(21);
        p2.setStartDate(new Date(2020, 12, 22));

        Project p3 = new Project();
        p3.setProjectName("Customs");
        p3.setTeamSize(11);
        p3.setStartDate(new Date(2010, 11, 20));

        Employee e1 = new Employee();
        e1.setFirstName("Pratik");
        e1.setLastName("Gaurav");

        Employee e2 = new Employee();
        e2.setFirstName("Ankita");
        e2.setLastName("Noopur");

        Employee e3 = new Employee();
        e3.setFirstName("Rudra");
        e3.setLastName("Narayan");

        List<Employee> empList1 = new LinkedList<Employee>();
        empList1.add(e2);
        empList1.add(e3);

        List<Employee> empList2 = new LinkedList<Employee>();
        empList2.add(e1);
        empList2.add(e2);

        List<Project> pl1=new LinkedList<Project>();
        pl1.add(p1);
        pl1.add(p2);

        List<Project> pl2=new LinkedList<Project>();
        pl2.add(p2);pl2.add(p3);

        p1.setEmployees(empList1);
        p2.setEmployees(empList2);

        e1.setProjects(pl1);
        e2.setProjects(pl2);

        repo.save(p1);
        repo.save(p2);
        repo.save(p3);

    }
}

其他回答

这是一个老问题,但最近又遇到了同样的问题。在这里分享我的经验。

实体

@Data
@Entity
@Table(name = "COURSE")
public class Course  {

    @Id
    @GeneratedValue
    private Long id;
}

保存实体(JUnit)

Course course = new Course(10L, "testcourse", "DummyCourse");
testEntityManager.persist(course);

Fix

Course course = new Course(null, "testcourse", "DummyCourse");
testEntityManager.persist(course);

结论:如果实体类的主键(id)有@GeneratedValue,那么确保您没有传递主键(id)的值

如果没有任何帮助,并且您仍然得到这个异常,请检查equals()方法—并且不要在其中包含子集合。特别是当你有嵌入式集合的深层结构时(例如A包含B, B包含c,等等)。

以Account ->为例:

  public class Account {

    private Long id;
    private String accountName;
    private Set<Transaction> transactions;

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (!(obj instanceof Account))
        return false;
      Account other = (Account) obj;
      return Objects.equals(this.id, other.id)
          && Objects.equals(this.accountName, other.accountName)
          && Objects.equals(this.transactions, other.transactions); // <--- REMOVE THIS!
    }
  }

在上面的例子中,从equals()检查中删除事务。这是因为hibernate将暗示您不尝试更新旧对象,而是在更改子集合上的元素时传递一个新对象来持久化。 当然,这种解决方案并不适用于所有应用程序,您应该仔细设计想要包含在equals和hashCode方法中的内容。

在我的情况下,我正在提交事务时,持久化方法被使用。 在更改persist to save方法时,该问题得到了解决。

也许这是OpenJPA的bug,当回滚时它重置了@Version字段,但pcVersionInit保持true。我有一个声明@Version字段的AbstraceEntity。我可以通过重置pcVersionInit字段来解决它。但这不是一个好主意。我认为它不工作时,级联坚持实体。

    private static Field PC_VERSION_INIT = null;
    static {
        try {
            PC_VERSION_INIT = AbstractEntity.class.getDeclaredField("pcVersionInit");
            PC_VERSION_INIT.setAccessible(true);
        } catch (NoSuchFieldException | SecurityException e) {
        }
    }

    public T call(final EntityManager em) {
                if (PC_VERSION_INIT != null && isDetached(entity)) {
                    try {
                        PC_VERSION_INIT.set(entity, false);
                    } catch (IllegalArgumentException | IllegalAccessException e) {
                    }
                }
                em.persist(entity);
                return entity;
            }

            /**
             * @param entity
             * @param detached
             * @return
             */
            private boolean isDetached(final Object entity) {
                if (entity instanceof PersistenceCapable) {
                    PersistenceCapable pc = (PersistenceCapable) entity;
                    if (pc.pcIsDetached() == Boolean.TRUE) {
                        return true;
                    }
                }
                return false;
            }

我基于Spring Data jpa的回答是:我只是在外部方法中添加了一个@Transactional注释。

为什么它有效

由于没有活动的Hibernate Session上下文,子实体立即被分离。提供一个Spring (Data JPA)事务可以确保存在Hibernate会话。

参考:

https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/