笔者最近一个在搞的一个单机秒杀系统里边有个异步写库的功能:@Scheduled定时任务从内存队列中拿到记录列表,写入数据库,然后清空队列。这样就对写入数据库的性能有一定的要求,所以想起来之前jdbcTemplate是有batchInsert功能的。而springboot jpa也有类似的功能。
实体类和Repository
/**
* 测试网点表
* */
@Entity
@Table(name="t_test_outlet")
@Data
public class TOutlet {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "outlet_id")
private Long outletId;
@Column(name = "outlet_name", columnDefinition="varchar(50) COMMENT '网点名称' ")
private String outletName;
@Column(name = "address", columnDefinition="varchar(150) COMMENT '网点地址' ")
private String address;
}
public interface TOutletRepository extends CrudRepository<TOutlet, Long>, JpaSpecificationExecutor<TOutlet>{
List<TOutlet> findAll();
}
三种insert方式的对比(插入1000条记录):
@Slf4j
@Service
public class TestDBInsertService {
@Autowired
private TOutletRepository tOutletRepository;
@PersistenceContext
private EntityManager entityManager;
//循环save
public void write10KToDB() {
long startTime = System.nanoTime();
for(int i=1; i<=1000; i++) {
TOutlet t = new TOutlet();
t.setOutletName("网点" + i);
t.setAddress("地址" + i);
tOutletRepository.save(t);
}
long estimatedTime = System.nanoTime() - startTime;
log.info("循环save耗时{} ms" , estimatedTime/1000000);
}
//saveAll
public void write10KToDB_batch() {
long startTime = System.nanoTime();
List<TOutlet> list = new ArrayList<>();
for(int i=1; i<=1000; i++) {
TOutlet t = new TOutlet();
t.setOutletName("网点batch" + i);
t.setAddress("地址batch" + i);
list.add(t);
}
tOutletRepository.saveAll(list);
long estimatedTime = System.nanoTime() - startTime;
log.info("saveAll耗时{} ms" , estimatedTime/1000000);
}
@Transactional(rollbackFor = Exception.class)
public void batchInsert() {
long startTime = System.nanoTime();
for(int i=1; i<=1000; i++) {
TOutlet t = new TOutlet();
t.setOutletName("网点entityManager" + i);
t.setAddress("地址entityManager" + i);
entityManager.persist(t);
}
entityManager.flush();
entityManager.clear();
long estimatedTime = System.nanoTime() - startTime;
log.info("entityManager.persist耗时{} ms" , estimatedTime/1000000);
}
}
输出结果:
[2021-10-05 15:04:03] [ INFO ] [http-nio-8080-exec-4] - 循环save耗时49285 ms
[2021-10-05 15:04:15] [ INFO ] [http-nio-8080-exec-4] - saveAll耗时12377 ms
[2021-10-05 15:04:27] [ INFO ] [http-nio-8080-exec-4] - entityManager.persist耗时12226 ms
经过多次验证,耗时跟上面都比较接近。
从上面结果来看,循环Repository.save(entity)性能最差,saveAll(entities)和直接用entityManager循环执行persist(t)方法性能相当,耗时都只有循环save的25%左右。
entityManager循环执行persist(t)相比saveAll()少了每次去select再决定insert还是update这样一个查询和判断,虽然从上面结果来看,这个提升不是很明显,可能是因为我测试的表本身数据量不大、与数据库之间的带宽1Mbit/s、数据库服务器配置很低只有1核2G内存等原因。