Spring Data JPA解决批量修改与唯一索引冲突的问题

介绍

最近发现数据库凭证表中有脏数据(数量不多,直接手动修复了),于是想到了加唯一索引,加上之后发现有个批量修改的地方不能用了,报索引冲突问题,只能先撤回想解决办法

索引规约

强制 业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。

解决办法

发现JPA中的save方法不会发送sql更新语句,要等事务提交或者有新的查询语句需要用到之前修改的数据时才会更新到数据库,于是查阅资料和源码后找到了flush方法。详细解决见代码

表结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*凭证表*/
drop table if exists voucher;
create table voucher(
id bigint NOT NULL AUTO_INCREMENT,
account_book_id bigint,
accessory_no int,
audit_date date,
audit_person varchar(32),
cash_flow_flag int,
category int,
dftotal decimal(14,2),
isassigned int,
jftotal decimal(14,2),
operator int,
period varchar(64),
remark varchar(255),
state int,
voucher_date date NOT NULL,
voucher_no varchar(64),
written_date TIMESTAMP default CURRENT_TIMESTAMP,
written_person varchar(32),
primary key (id)
);
create unique index UK_voucher on voucher(account_book_id, period, voucher_no);

原来的逻辑

1
2
3
4
5
6
7
8
9
10
11
String voucherNo = voucherDto.voucherNo;
// 之后的每张凭证号加一,如果下一张凭证号不重复了就跳出
for (Voucher v : vouchers) {
if (v.voucherNo.equals(voucherNo)) {
voucherNo = getNextNo(voucherNo, VOUCHER_NO_LENGTH);
v.voucherNo = voucherNo;
voucherDao.save(v);
} else {
break;
}
}

修改后的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
String voucherNo = voucherDto.voucherNo;
List<Voucher> needToSaveVouchers = new ArrayList<>();
// 之后的每张凭证号加一,如果下一张凭证号不重复了就跳出
for (Voucher v : vouchers) {
if (v.voucherNo.equals(voucherNo)) {
voucherNo = getNextNo(voucherNo, VOUCHER_NO_LENGTH);
needToSaveVouchers.add(0, v);
} else {
break;
}
}
// 从后往前改,防止唯一索引重复
for (Voucher v : needToSaveVouchers) {
v.voucherNo = getNextNo(v.voucherNo, VOUCHER_NO_LENGTH);
voucherDao.saveAndFlush(v);
}

注意:

  • flush,saveAndFlush会刷新当前session中所有持久化对象的状态到数据库,所以在调用时要考虑别的持久化对象的状态
  • 保存顺序不是按照list的顺序,所以要手动控制
  • flush,saveAndFlush需要dao继承JpaRepository才能使用
文章目录
  1. 1. 介绍
    1. 1.1. 索引规约
    2. 1.2. 解决办法
    3. 1.3. 表结构
    4. 1.4. 原来的逻辑
    5. 1.5. 修改后的逻辑