【问题标题】:Duplicate check via Unique constraint in DB not working通过数据库中的唯一约束进行重复检查不起作用
【发布时间】:2021-02-22 14:39:20
【问题描述】:

我有一个 Spring Boot 服务,它应该在事务中将两种类型的多个实体持久化到 Oracle DB。第一个实体类型的表很大(每天 3 个 Mio。条目,分区,...),我有一个问题,我需要对重复项做出反应。我使用一些字段来创建哈希,并且我在该字段的数据库中具有唯一约束。我认为按实体保存AndFlush 并对ConstraintViolationException 做出反应是一个聪明的主意。根据保存第一个实体列表的结果,我需要创建第二个实体并保存它,但它会回滚所有内容。 我现在的问题是,如果这种方法通常是错误的,还是好的并且有一些小问题?如果它通常是错误的,那么我应该如何做这个重复检查(预先选择不是一个选项)?

这里有一些伪代码可以让你更好地理解

@Entity
public class Foo{

    public String uniqueHash;
    
    // couple of other properties that will be used to calculate the hash
}

@Entity
public class Bar{

    private List goodIds;
    private List badIds;
    
    public Bar(List goodIds, List badIds){
        this.goodIds = goodIds;
        this.badIds = badIds;
    }
}

@Repository
@Transactional(noRollbackFor = PersistenceException.class)
public interface FooRepository extends JpaRepository<Foo, String> {
  Foo saveAndFlush(Foo f) throws PersistenceException;
}

@Repository
@Transactional(noRollbackFor = PersistenceException.class)
public interface BarRepository extends JpaRepository<Bar, String> {
  Bar saveAndFlush(Bar b) throws PersistenceException;
}

SomeService

@Transactional(noRollbackFor = PersistenceException.class)
public void doSomething(List<Foo> foos){

    List<String> goodIds = new ArrayList();
    List<String> badIds = new ArrayList();
        
    for (Foo foo : foos) {
      try {     
        fooRepository.saveAndFlush(foo);
        goodIds.add(foo.getId());
      } catch (PersistenceException e) {
        if (e.getCause() instanceof ConstraintViolationException) {
          badIds.add(foo.getId);
        } else {
          throw e;
        }
      }
    }
    barRepository.saveAndFlush(new Bar(goodIds, badIds));
}

【问题讨论】:

  • 您可能想查看错误表,看看是否满足您的需求。错误表的一个问题是所有列值都转换为 VARCHAR2(4000)
  • 为什么不在插入之前检查该实体是否存在?
  • @ggr 所说的,特别是考虑到唯一索引查找应该非常快
  • 我有几个服务实例,并且担心会出现不利条件,即多个服务会收到相同的消息,并以具有重复的结尾。此外,我不想将数据库交互加倍。问题是我的方法是否完全不可行,因为我读过一些线程已经表明,无论你做什么,事务中的异常都会回滚(尽管我认为我的场景是一个非常可控的场景)
  • @ggr 我知道几个服务接收到相同的消息,这不在我的手中(我的意思是客户端不在我们的控制范围内)。我主要关心的是在罕见的情况下将数据库交互加倍。我可能每 10K 个实例都有一个重复的 Foo,所以我最终决定更改数据处理方式,而是像您或多或少地提议的那样重试事务。我添加了代码作为答案以作为参考。

标签: java oracle spring-boot hibernate spring-data-jpa


【解决方案1】:

最后,我找到了实现预期行为的方法,甚至更好的是,我能够摆脱这些“noRollBackFor”属性。我只重构了流程并尝试将所有内容保存在事务中,如果失败,则在调用方法上捕获异常,输入被“清理”并再次调用事务方法(递归)。这些重复是罕见的情况(每 10k Foo 实例发生一次),因此从性能角度来看,有这些后续事务很好。这里又是修改后的伪代码

@Entity
public class Foo{

    public String uniqueHash;
    
    // couple of other properties that will be used to calculate the hash
}

@Entity
public class Bar{

    private List goodIds;
    private List badIds;
    
    public Bar(List goodIds, List badIds){
        this.goodIds = goodIds;
        this.badIds = badIds;
    }
    
    public List getGoodIds(){
      return goodIds;
    }
    
    public List getBadIds(){
      return badIds;
    }
}

@Repository
public interface FooRepository extends JpaRepository<Foo, String> {
}

@Repository
public interface BarRepository extends JpaRepository<Bar, String> {
}

public class FooException extends RuntimeException {

  private final Foo foo;

  public FooException(String message, Foo foo) {
    super(message);
    this.foo = foo;
  }
  
  public getFoo(){
  return foo;
  }
}

SomeService

public void doSomething(List<Foo> foos, Bar bar){
        try{
        doSomethingTransactional(foos,bar);
        }
        catch (FooException e) {           
          bar.getBadIds().add(e.getFoo().getId());
          foos.remove(foo);
          doSomething(foos, bar);
        }
}


@Transactional
public void doSomethingTransactional(List<Foo> foos, Bar bar){
        
    for (Foo foo : foos) {
      try {     
        fooRepository.saveAndFlush(foo);
        bar.getGoodIds.add(foo.getId());
      } catch(DataAccessException e) {
        if (e.getCause() instanceof ConstraintViolationException 
        && ((ConstraintViolationException) e.getCause()).getConstraintName().contains("Some DB Message")) {
          throw new FooException("Foo already exists", foo);
        } else {
          throw e;
        }
      }
    }
    barRepository.saveAndFlush(bar);
}

【讨论】:

    【解决方案2】:

    您也许可以使用自定义 @SQLInsert 来为此目的使用 Oracle 的 MERGE 语句。另见https://stackoverflow.com/a/64764412/412446

    【讨论】:

      猜你喜欢
      • 2015-08-18
      • 2017-04-29
      • 1970-01-01
      • 1970-01-01
      • 2017-03-26
      • 1970-01-01
      • 2011-10-21
      • 2013-11-11
      • 2018-04-16
      相关资源
      最近更新 更多