2016-09-22 54 views
0

我有一个计数器实体如下:对于每个前缀(当前年和月),我维护一个计数器,我需要增加。使用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...

任何想法如何实现这个没有在代码中同步我自己?

回答

0

是写在你的实体的方法被注释与@PrePersist@PreUpdate东西,你正在寻找?

+0

我不确定我在@PrePersist中可以做什么来确保第二个线程“等待”操作被刷新并可见,以便它看到记录存在,并且不会尝试创建第二个相同的前缀。 –