1.描述
完整的错误日志:
WARN o.h.e.j.s.SqlExceptionHelper:145: SQL Error: 1062, SQLState: 23000
2022-03-01_15:28:49 [http-nio-8080-exec-1] ERROR o.h.e.j.s.SqlExceptionHelper:147: Duplicate entry '75302-16969' for key 'customer_menu_id'
2022-03-01_15:28:49 [http-nio-8080-exec-1] ERROR c.d.a.c.f.RestResponseEntityExceptionHandler:81: could not execute statement; SQL [n/a]; constraint [customer_menu_id]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [customer_menu_id]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
Duplicate entry '75302-16969' for key 'customer_menu_id'
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
其实抛出这种日志异常的问题就是,表中有重复的数据,无法添加新的数据。
2.出现场景
出现这种场景的原因是,我做了一个更新操作,是先把之前的数据删除,然后在添加新的数据进去,由于代码中使用了事务注解
导致,在提交的时候,数据并没有删除完整,然后添加的时候就出现了数据重复的错误。
请看实例代码:
//由于方法需要删除,所有加了事务注解
@Transactional(rollbackFor = Exception.class)
public TAuthRole updateRole(TAuthRole tAuthRole) {
//查询需要删除的数据,然后全部删除掉
this.tAuthRole2PermissionRepository.delete(this.tAuthRole2PermissionRepository.findByRoleId(
tAuthRole.getRoleId()));
String type = getRoleType(policy.getStatements());
tAuthRoleRepository.updateType(tAuthRole.getRoleId(), type);
//更新添加新的数据进去
TAuthRole authRole = tAuthRoleRepository.save(tAuthRole);
return authRole;
}
代码逻辑很简单,先删除然后在添加数据进去,但是这里问题就是,事务在这一个方法中,先删除的数据没有提交,所以导致有外键关联的数据出现重复报错。
3.源码分析解决
由于事务的原因,我们在删除的时候,事务并没有提交,而是在这个方法执行完后在去提交,这就导致,添加的时候,数据还存在,所以我们能不能在,delete这个方法的时候,删除就立即提交呢,这样子,后面添加的时候,就不会出现这个重复的问题了。延着这个思路,我们来看看有没有方法能解决,或者我们看看jpa有没有方法帮我们解决。
delete接口方法:
void delete(ID var1);
void delete(T var1);
void delete(Iterable<? extends T> var1);
实现:由于这里是批量删除,我们直接看批量删除的实现代码
代码在 SimpleJpaRepository 类中:
@Transactional
public void delete(Iterable<? extends T> entities) {
Assert.notNull(entities, "The given Iterable of entities not be null!");
Iterator var2 = entities.iterator();
while(var2.hasNext()) {
T entity = var2.next();
this.delete(entity);
}
}
可以看到,实现很简单,就是一个迭代删除,没有任何的处理
deleteInBatch:方法实现:
@Transactional
public void deleteInBatch(Iterable<T> entities) {
Assert.notNull(entities, "The given Iterable of entities not be null!");
if (entities.iterator().hasNext()) {
QueryUtils.applyAndBind(QueryUtils.getQueryString("delete from %s x", this.entityInformation.getEntityName()), entities, this.em).executeUpdate();
}
}
可以看出来这个方法最大的区别就在于executeUpdate 删除就提交,这个就很满足我们上面的那个思路了。
解决方法:
把delete 方法改为 deleteInBatch 即可解决。