我有一个Counter实体,如下所示:对于每个前缀(当前年份和月份),我维护一个我需要递增的计数器。
@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;
}
我添加了一个带有H2 DB的多线程单元测试(使用tempus fugit库),当两个线程同时尝试生成一个唯一的代码时,它显示它正在工作,但我对我的代码不太满意: 我想摆脱synchronized
方法并完全依赖正确的交易配置。
如果我删除了synchronized
关键字,那么两个线程同时执行该方法并且它会失败,因为它们生成相同的前缀,这不应该发生(唯一索引或主键冲突)。这是日志:
为合并请求生成新的请求代码
为合并请求生成新的请求代码
进入..
进入..
Hibernate:选择codecounte0_.id作为id1_4_,codecounte0_.counter作为counter2_4_,codecounte0_.prefix作为前缀3_4_来自t_counter codecounte0_,其中codecounte0_.prefix =?
Hibernate:选择codecounte0_.id作为id1_4_,codecounte0_.counter作为counter2_4_,codecounte0_.prefix作为前缀3_4_来自t_counter codecounte0_,其中codecounte0_.prefix =?
Hibernate:为S_COUNTER Hibernate调用下一个值:为S_COUNTER调用下一个值
Hibernate:插入t_counter(counter,prefix,id)值(?,?,?)
Hibernate:插入t_counter(counter,prefix,id)值(?,?,?)
..退出
合并请求的新请求代码:: CR_2016_09_000001
17:02:39.853 WARN SqlExceptionHelper - SQL错误:23505,SQLState:23505
17:02:39.854错误SqlExceptionHelper - 唯一索引或主键违规...
如果没有在代码中同步自己,如何实现这一点?
答案 0 :(得分:0)
是否在您的实体中编写了一个用 @PrePersist 或 @PreUpdate 注释的方法?