我有一个计数器实体如下:对于每个前缀(当前年和月),我维护一个计数器,我需要增加。使用JPA在数据库中更新计数器,避免手动同步
@Entity
@Table(name = "T_COUNTER")
@SequenceGenerator(allocationSize = 1, name = "S_COUNTER", sequenceName = "S_COUNTER")
public class CodeCounter implements Serializable {
private static final long serialVersionUID = 6431190800245592165L;
@Id
@GeneratedValue(strategy = SEQUENCE, generator = "S_COUNTER")
@Column(name = "ID")
private Long id;
@Column(name = "PREFIX", nullable = false,unique = true)
private String prefix;
@Column(name = "COUNTER", nullable = false)
private Integer counter;
这是我最简单的JPA存储库,使用Spring数据:
public interface CodeCounterRepository extends JpaRepository<CodeCounter, Long> {
@Transactional
CodeCounter findByPrefix(String prefix);
}
现在,每当我的服务被调用时,我需要得到正确的计数器感谢前缀(如果创建它还不存在,这是本月的第一次),增加并保存。这是我迄今已实现了它:
//entry point in the service
public String generateUniqueRequestCode() {
System.out.println("Generating new Request Code for Consolidated Request");
Integer counter = getUniqueCodeAndUpdateCounter(calendarProvider);
String requestCode = format("%s_%04d_%02d_%06d", REQUEST_CODE_PREFIX, calendarProvider.getCurrentYear(), calendarProvider.getCurrentMonth(),
counter);
System.out.println("New Request Code for Consolidated Request:: " + requestCode);
return requestCode;
}
@Transactional
private synchronized Integer getUniqueCodeAndUpdateCounter(CalendarProvider calendarProvider) {
System.out.println("entering..");
String prefix = format("%04d_%02d", calendarProvider.getCurrentYear(), calendarProvider.getCurrentMonth());
CodeCounter codeCounter = codeCounterRepository.findByPrefix(prefix);
if (codeCounter != null) {
codeCounter.setCounter(codeCounter.getCounter() + 1);
} else {
codeCounter = new CodeCounter();
codeCounter.setPrefix(prefix);
codeCounter.setCounter(1);
}
CodeCounter counter=codeCounterRepository.save(codeCounter);
int result=counter.getCounter();
System.out.println("..exiting");
return result;
}
我添加了一个多线程的单元测试(使用的Tempus福吉特库)与H2 DB,那就说明它的工作时2个线程尝试生成的唯一代码同时,但我不太满意我的代码:我想摆脱方法,并仅依靠正确的事务配置。
如果我删除关键字,那么这两个线程执行在同一时间的方法和因为它们产生相同的前缀,这是不应该的(唯一索引或主键冲突),失败。这里的日志:
Generating new Request Code for Consolidated Request
Generating new Request Code for Consolidated Request
entering..
entering..
Hibernate: select codecounte0_.id as id1_4_, codecounte0_.counter as counter2_4_, codecounte0_.prefix as prefix3_4_ from t_counter codecounte0_ where codecounte0_.prefix=?
Hibernate: select codecounte0_.id as id1_4_, codecounte0_.counter as counter2_4_, codecounte0_.prefix as prefix3_4_ from t_counter codecounte0_ where codecounte0_.prefix=?
Hibernate: call next value for S_COUNTER Hibernate: call next value for S_COUNTER
Hibernate: insert into t_counter (counter, prefix, id) values (?, ?, ?)
Hibernate: insert into t_counter (counter, prefix, id) values (?, ?, ?)
..exiting
New Request Code for Consolidated Request:: CR_2016_09_000001
17:02:39.853 WARN SqlExceptionHelper - SQL Error: 23505, SQLState: 23505
17:02:39.854 ERROR SqlExceptionHelper - Unique index or primary key violation...
任何想法如何实现这个没有在代码中同步我自己?
我不确定我在@PrePersist中可以做什么来确保第二个线程“等待”操作被刷新并可见,以便它看到记录存在,并且不会尝试创建第二个相同的前缀。 –