首页 > 解决方案 > JPArepository 的 saveAll() 方法中的并发修改异常(Spring DATA JPA + Hibernate)

问题描述

我在保存具有 oneToOne/oneToMany/ManyToMany 类型关系的实体“员工”时遇到了这个问题。

我正在使用带有 Hibernate 和 postgres 作为数据库的 Spring Boot + Spring Data JPA。

请找到以下课程。

员工班——

@Entity
@Table(name = "Employee")
public class Employee {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "emp_id")
private int emp_id;

@Column(name = "emp_name")
private String emp_name;

@Column(name = "d_o_b")
private Date date_of_birth;

@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "community_employee_mapping", joinColumns = { @JoinColumn(name = "employee_id") }, inverseJoinColumns = { @JoinColumn(name = "community_id") })
private List<Community> listOfCommunities = new ArrayList<>();

@OneToOne(cascade = CascadeType.ALL)
private Department dept;

@OneToOne(cascade = CascadeType.ALL)
private Address address;

部门班——

@Entity
@Table(name = "department")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int dept_id;

@Column(name = "dept_name")
private String departmentName;

@Column(name = "dept_desc")
private String departmentDesc;

@OneToMany(mappedBy = "dept", cascade = CascadeType.ALL)
private List<Employee> lemp = new ArrayList<>();

社区班——

@Entity
@Table(name = "community")
public class Community {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "com_id")
private int community_id;

@Column(name = "name")
private String community_name;

@ManyToMany(mappedBy = "listOfCommunities", cascade = CascadeType.ALL)
private List<Employee> listOfEmployees = new ArrayList<>();

@Column(name = "description")
private String description;

地址类——

@Entity
@Table(name = "address")
public class Address {

@Id
@GeneratedValue(strategy = GenerationType.TABLE)
private int add_id;

@Column(name = "state")
private String state;

@Column(name = "city")
private String city;

@Column(name = "zip_code")
private String zip_code;

@Column(name = "country")
private String country;

@OneToOne(mappedBy = "address", cascade = CascadeType.ALL)
private Employee emp;

下面是我的spring boot @test 方法——

@Autowired
private EmployeeRepo erepo;

@Autowired
private CommunityRepo crepo;

@Autowired
private DeptRepo drepo;

@Autowired
private AddressRepo arepo;

@Test
public void contextLoads() {

    int max = 1 * 1000 * 100;

    Date date = new GregorianCalendar(1992, 06, 18).getTime();

    long start = System.currentTimeMillis();

    for (int index = 0; index < 50000; index++) {
        Employee emp0 = new Employee();
        Employee emp1 = new Employee();
        Employee emp2 = new Employee();

        emp0.setEmp_name(RandomStringUtils.randomAlphabetic(10) + " " + RandomStringUtils.randomAlphabetic(5));
        emp0.setDate_of_birth(DateUtils.addDays(date, MathUtil.nextInt(0, 5000)));

        emp1.setEmp_name(RandomStringUtils.randomAlphabetic(10) + " " + RandomStringUtils.randomAlphabetic(5));
        emp1.setDate_of_birth(DateUtils.addDays(date, MathUtil.nextInt(0, 5000)));

        emp2.setEmp_name(RandomStringUtils.randomAlphabetic(10) + " " + RandomStringUtils.randomAlphabetic(5));
        emp2.setDate_of_birth(DateUtils.addDays(date, MathUtil.nextInt(0, 5000)));

        Community comm0 = new Community();
        comm0.setCommunity_name(RandomStringUtils.randomAlphabetic(10) + " " + RandomStringUtils.randomAlphabetic(6));
        comm0.setDescription(RandomStringUtils.randomAlphabetic(20));

        Community comm1 = new Community();
        comm1.setCommunity_name(RandomStringUtils.randomAlphabetic(10) + " " + RandomStringUtils.randomAlphabetic(6));
        comm1.setDescription(RandomStringUtils.randomAlphabetic(20));

        List<Employee> lemp = new ArrayList<>();
        lemp.add(emp0);
        lemp.add(emp1);
        comm0.setListOfEmployees(lemp);

        List<Community> lcomm = new ArrayList<>();
        lcomm.add(comm0);

        emp0.setListOfCommunities(lcomm);
        emp1.setListOfCommunities(lcomm);

        List<Employee> lemp2 = new ArrayList<>();
        lemp2.add(emp2);
        comm1.setListOfEmployees(lemp2);

        List<Community> lcomm2 = new ArrayList<>();
        lcomm2.add(comm1);

        emp2.setListOfCommunities(lcomm2);

        Department dept0 = new Department();
        dept0.setDepartmentDesc(RandomStringUtils.randomAlphabetic(25));
        dept0.setDepartmentName(RandomStringUtils.randomAlphabetic(5));

        Department dept1 = new Department();
        dept1.setDepartmentDesc(RandomStringUtils.randomAlphabetic(25));
        dept1.setDepartmentName(RandomStringUtils.randomAlphabetic(5));

        List<Employee> lempp = new ArrayList<>();
        lempp.add(emp0);
        lempp.add(emp1);
        dept0.setLemp(lemp);

        emp0.setDept(dept0);
        emp1.setDept(dept1);

        List<Employee> lempp2 = new ArrayList<>();
        lempp2.add(emp2);
        dept1.setLemp(lempp2);

        emp2.setDept(dept1);

        Address add0 = new Address(RandomStringUtils.randomAlphabetic(7), RandomStringUtils.randomAlphabetic(5), String.valueOf(MathUtil.nextInt(999999, 9999999)),
                RandomStringUtils.randomAlphabetic(5), emp0);
        Address add1 = new Address(RandomStringUtils.randomAlphabetic(7), RandomStringUtils.randomAlphabetic(5), String.valueOf(MathUtil.nextInt(999999, 9999999)),
                RandomStringUtils.randomAlphabetic(5), emp1);
        Address add2 = new Address(RandomStringUtils.randomAlphabetic(7), RandomStringUtils.randomAlphabetic(5), String.valueOf(MathUtil.nextInt(999999, 9999999)),
                RandomStringUtils.randomAlphabetic(5), emp2);

        emp0.setAddress(add0);
        emp1.setAddress(add1);
        emp2.setAddress(add2);

        // erepo.save(emp0);
        // erepo.save(emp1);
        // erepo.save(emp2);

        List<Employee> saveEmp = new ArrayList<>();
        saveEmp.add(emp0);
        saveEmp.add(emp1);
        saveEmp.add(emp2);

        erepo.saveAll(saveEmp);

    }

    long end = System.currentTimeMillis();

    long elapsedTime = end - start;
    System.out.println("elapsed : " + (elapsedTime) + " mili sec.........");

    System.out.println("done writing to db ======================================================================");

}

我在每个关系中都使用 CascadeType.ALL,因此只保留父实体,即员工。

当我在下面做时,整个代码工作得很好。这种方法的问题是它很慢,因为我们在后面打开三个会话并关闭,而我们有可以在单个会话中完成的 saveAll(iterable args) 方法。

// erepo.save(emp0);
// erepo.save(emp1);
// erepo.save(emp2);

不写

erepo.saveAll(saveEmp);

但是,当我从 erepo 编写 saveAll 时,它会创建一个包含所有项目的 arrayList,我们将其作为可迭代参数传递。下面是 Spring 框架的 SimpleJPARepository 类的代码片段。

@Transactional
public <S extends T> List<S> saveAll(Iterable<S> entities) {

    Assert.notNull(entities, "The given Iterable of entities not be null!");

    List<S> result = new ArrayList<S>();

    for (S entity : entities) {
        result.add(save(entity));
    }

    return result;
}

我想在这里确认两件事......

1)因为我没有将任何 ID 附加到任何实体并让它在运行时使用 @GeneratedValue 生成。在持久化实体时,hibernate 正在尝试将 id 与实体附加,从而更改其状态并导致 ConcurrentModificationException。

2)我可以通过使用 CopyOnWriteArrayList 使其工作,但这将再次消耗堆中的空间,因为它会克隆整个对象图,因此性能肯定会偏离。

有什么方法可以让我坚持使用 saveAll() 而不会对性能造成任何影响。

如果我在任何地方错了,请纠正我。

标签: hibernatespring-bootjpaspring-data-jpa

解决方案


推荐阅读