项目中有个流程最后提交审批的时候由于后台处理比较复杂,事务处理没有优化,导致数据库中对同一张表即update又select,产生了几个死锁,但是死锁过一会就自动消失了。报错如下:
2018-08-07 08:58:08.302 [http-nio-9999-exec-6] ERROR o.a.e.i.interceptor.CommandContext - Error while closing command context
org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL [ select distinct t.name,e.employeeId,c.date from AttendanceHolidayType t left join AttendanceCalendar c on t.attendanceHolidayTypeId=c.attendanceHolidayTypeId left join AttendanceEmpRank e on e.corporationId = c.corporationId where cast(e.employeeId as varchar(36)) = ? and convert(varchar(10),c.[Date] , 120) = ? ]; SQL state [S000169]; error code [1206]; Microsoft 分布式事务处理协调器(MS DTC)已取消此分布式事务。; nested exception is com.microsoft.sqlserver.jdbc.SQLServerException: Microsoft 分布式事务处理协调器(MS DTC)已取消此分布式事务。
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:84)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:649)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:684)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:716)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:726)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:776)
原本测试时候是没有问题的,后来上线测试,由于用户量多了一些,导致事物处理的时候产生了错误。所以假如你的也有类似的错误,你可以查看一下你的后台代码逻辑是否会产生事务锁。
下面是我的代码,由于公司保密原因,代码有删减,但大概意思还是有的。
for (ProcHrRankChangeDtDto procHrRankChangeDtDto : procHrRankChangeDtList) {
List rankChangeList = new ArrayList();
ProcHrRankChangeDtDto dt = new ProcHrRankChangeDtDto();
BeanUtils.copyProperties(procHrRankChangeDtDto, dt);
List dateList = DateUtil.findDateListByTwoDate(bTime, eTime);
for (int i=0;i
Date date = dateList.get(i);
String rankTypeId = dt.getRankType();
//查询语句
String employeeId = map.get(dt.getEmpJobNumber()).getEmployeeId();
if("true".equals(dt.getJumpHoliday())||"Y".equals(dt.getJumpHoliday())) {
List nameList = attendanceRankService.getNameByCalendarHolidayEmpRank(employeeId, date);
if(nameList.size()>0 && BpmConsts.HR_DEFAULTHOLIDAYTYPE_NAME_004.equals(nameList.get(0).getName())) {
rankTypeId = dt.getHolidayRankType();
holidayTypeId = BpmConsts.HR_DEFAULTHOLIDAYTYPE_004;
}
}
if("true".equals(dt.getJumpFestival())||"Y".equals(dt.getJumpFestival())) {
List nameList = attendanceRankService.getNameByCalendarHolidayEmpRank(employeeId, date);
if(nameList.size()>0 && BpmConsts.HR_DEFAULTHOLIDAYTYPE_NAME_003.equals(nameList.get(0).getName())) {
rankTypeId = dt.getFestivalRankType();
holidayTypeId = BpmConsts.HR_DEFAULTHOLIDAYTYPE_003;
}
}
Date lastModifiedDate = new Date();
String lastModifiedBy = map.get(AppUtil.getUserInfoContext().getEmployeeNo()).getEmployeeId();
//修改语句
attendanceEmpRankService.updateEmpRankByDao(rankTypeId, holidayTypeId,lastModifiedDate,lastModifiedBy, employeeId, date);
rankChange.setEmployeeId(employeeId);
//查询
String oldATRankId = null;
AttendanceEmpRankDto empRankDto = attendanceEmpRankService.findEmpRankByEmployeeAndDate(employeeId, date);
if(empRankDto!=null) {
oldATRankId = empRankDto.getAttendanceRankId();
}
rankChangeList.add(rankChange);
}
//新增
attendanceRankChangeService.saveAttendanceRankChangeList(rankChangeList);
}
我的代码是在foreach里面嵌套一个foreach语句,在里层的foreach有sql多表查询,又有update语句。这时候程序执行就比较慢了。由于表中数据量比较大,用map存放的方式也不是太理想,我把最里层的查询语句去掉,在第一层循环用map存放对象。
然后把循环里面的update全部用list保存对象,在循环结束后用JPA执行update集合。
修改后的代码
for (ProcHrRankChangeDtDto procHrRankChangeDtDto : procHrRankChangeDtList) {
ProcHrRankChangeDtDto dt = new ProcHrRankChangeDtDto();
BeanUtils.copyProperties(procHrRankChangeDtDto, dt);
List<Date> dateList = DateUtil.findDateListByTwoDate(bTime, eTime);
String employeeId0 = map.get(dt.getEmpJobNumber()).getEmployeeId();
Map mapp = new HashMap();
if("true".equals(dt.getJumpHoliday()) || "Y".equals(dt.getJumpHoliday()) || "true".equals(dt.getJumpFestival()) || "Y".equals(dt.getJumpFestival())) {
List nameList0 = attendanceRankService.getNameByCalendarHolidayEmpRank(employeeId0, null);
for (AttendanceRankDto attendanceRankDto : nameList0) {
mapp.put(DateUtil.getDate(attendanceRankDto.getDate()), attendanceRankDto);
}
}
for (int i=0;i0 && mapp.get(DateUtil.getDate(date))!=null) {
if("true".equals(dt.getJumpHoliday())||"Y".equals(dt.getJumpHoliday())) {
if(BpmConsts.HR_DEFAULTHOLIDAYTYPE_NAME_004.equals(mapp.get(DateUtil.getDate(date)).getName())) {
rankTypeId = dt.getHolidayRankType();
holidayTypeId = BpmConsts.HR_DEFAULTHOLIDAYTYPE_004;
}
}
if("true".equals(dt.getJumpFestival())||"Y".equals(dt.getJumpFestival())) {
if(BpmConsts.HR_DEFAULTHOLIDAYTYPE_NAME_003.equals(mapp.get(DateUtil.getDate(date)).getName())) {
rankTypeId = dt.getFestivalRankType();
holidayTypeId = BpmConsts.HR_DEFAULTHOLIDAYTYPE_003;
}
}
}
AttendanceEmpRank entity = attce.findOne(employeeId, date);
AttEmpRankDto empDto = new AttEmpRankDto();
empDto.setDate(date);
dtoList.add(empDto);
eList.add(entity);
rankChange.setOldATRankId(oldATRankId);
rankChangeList.add(rankChange);
}
}
for (AttendanceEmpRank entity : eList) {
for (AttendanceEmpRankDto dto : dtoList) {
if(dto.getEmployeeId().equals(entity.getEmployeeId()) && DateUtil.getDate(dto.getDate()).equals(DateUtil.getDate(entity.getDate()))) {
entity.setAttendanceRankId(dto.getAttendanceRankId());
break;
}
}
}
atte.updateList(eList);
ae.saveList(rankChangeList);
综合起来,我要表达的意思就是在代码里能减少查询语句就尽量减少,可以一下子全查出来存放到集合里面。对于同张表,尽量不要查询修改混合来操作。
使用jpa时,每当执行set()方法时,jpa就会执行update语句。
假如还不行,那就可能是别人说的什么服务器一类的问题。