hibernate - 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() 而不会对性能造成任何影响。
如果我在任何地方错了,请纠正我。
解决方案
推荐阅读
- node.js - Getting error testing readstream response with Jest
- javascript - Binary to it's original format
- sql - Change column datatype from NChar to Date
- delphi-7 - Delphi 7 - Checking a Letter with TMemo.Lines
- mongodb - Meteor don't close Mongo connections?
- dialogflow-es - 我们可以在对话流中为特定用户使用相同的 session_id 吗?
- visual-studio - Intellisense not working for CMake CUDA project in Visual Studio 2019
- javascript - updating client time with website time that uses lightstreamer
- asp.net-mvc - ASP .NET MVC - getting null querystring values in controller
- css - 为什么导入的图标库的图标在VUE中不显示?