使用JPA更新DB中的计数器,避免手动同步

时间:2016-09-22 11:47:00

标签: java jpa spring-data

我有一个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 - 唯一索引或主键违规...

如果没有在代码中同步自己,如何实现这一点?

1 个答案:

答案 0 :(得分:0)

是否在您的实体中编写了一个用 @PrePersist @PreUpdate 注释的方法?