Spring JpaRepositroy.save() 似乎没有在重复保存时抛出异常

2022-09-01 07:43:28

我目前正在玩Spring boot 1.4.2,其中我已经拉入了Spring-boot-starter-web和Spring-boot-starter-jpa。

我的主要问题是,当我保存一个新实体时,它工作正常(都很酷)。

但是,如果我保存具有相同ID的新产品实体(例如重复的条目),它不会引发异常。我期待ConstrintViolationException或类似的东西。

给定以下设置:

应用.java

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

产品存储库.java

@Repository
public interface ProductRepository extends JpaRepository<Product, String> {}

JpaConfig.java

@Configuration
@EnableJpaRepositories(basePackages = "com.verric.jpa.repository" )
@EntityScan(basePackageClasses ="com.verric.jpa")
@EnableTransactionManagement
public class JpaConfig {

    @Bean
    JpaTransactionManager transactionManager() {
        return new JpaTransactionManager();
    }
}

注意:JpaConfig.java 和 Application.java位于同一包中。

产品控制器.java

@RestController
@RequestMapping(path = "/product")
public class ProductController {

    @Autowired
    ProductRepository productRepository;

    @PostMapping("createProduct")
    public void handle(@RequestBody @Valid CreateProductRequest request) {
        Product product = new Product(request.getId(), request.getName(), request.getPrice(), request.isTaxable());
        try {
            productRepository.save(product);
        } catch (DataAccessException ex) {
            System.out.println(ex.getCause().getMessage());
        }
    }
}

最后是产品.java

@Entity(name = "product")
@Getter
@Setter
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
public class Product {

    protected Product() { /* jpa constructor*/ }

    @Id
    private String id;

    @Column
    private String name;

    @Column
    private Long price;

    @Column
    private Boolean taxable;
}

getter, setter and equalsHashcode..是龙目岛注释。

杂项:

弹簧靴 : 1.4.2

休眠 ORM: 5.2.2.最终版

无论我是否使用或不带注释控制器,都会发生此问题@Transactional

基础数据库清楚地显示异常

2016-11-15 18:03:49 AEDT [40794-1] verric@stuff ERROR:  duplicate key value violates unique constraint "product_pkey"
2016-11-15 18:03:49 AEDT [40794-2] verric@stuff DETAIL:  Key (id)=(test001) already exists

我知道将数据访问内容分解到自己的服务层中更好(更常见),而不是将其转储到控制器中

控制器的语义不是 ReST

我尝试过的事情:

Spring CrudRepository exceptions

我尝试过实现这个问题的答案,不幸的是,我的代码从未遇到过DataAccesException异常。

如果保存功能不成功,Spring JPA是否会抛出错误?

对上述问题也同样作出类似答复。

http://www.baeldung.com/spring-dataIntegrityviolationexception

我尝试将bean添加到我的JPAconfig.java类,即:

   @Bean
   public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
      return new PersistenceExceptionTranslationPostProcessor();
   }

但似乎什么也没发生。

很抱歉发了很久的帖子,提前


答案 1

我的解决方案更干净。Spring Data已经为我们提供了一个很好的方法来定义一个实体如何被认为是新的。这可以通过在我们的实体上实现 Persistable 来轻松完成,如参考中所述

在我的情况下,与OP一样,ID来自外部源,无法自动生成。因此,如果ID为空,Spring Data用于将实体视为新实体的默认逻辑将不起作用。

@Entity
public class MyEntity implements Persistable<UUID> {

    @Id
    private UUID id;

    @Transient
    private boolean update;

    @Override
    public UUID getId() {
        return this.id;
    }

    public void setId(UUID id) {
        this.id = id;
    }

    public boolean isUpdate() {
        return this.update;
    }

    public void setUpdate(boolean update) {
        this.update = update;
    }

    @Override
    public boolean isNew() {
        return !this.update;
    }

    @PrePersist
    @PostLoad
    void markUpdated() {
        this.update = true;
    }
}

在这里,我为实体提供了一种机制,通过另一个名为 的瞬态布尔属性来表达它是否认为自己是新的。由于 的默认值 will 为 ,此类型的所有实体都被视为新实体,当您尝试使用相同的 ID 进行调用时,将导致抛出。updateupdatefalseDataIntegrityViolationExceptionrepository.save(entity)

如果确实希望执行合并,则始终可以在尝试保存之前将属性设置为。当然,如果您的用例从不要求您更新实体,则始终可以从该方法返回并删除该字段。updatetruetrueisNewupdate

与在保存之前检查数据库中是否已存在具有相同 ID 的实体相比,此方法的优点很多:

  1. 避免了对数据库的额外往返
  2. 我们无法保证,当一个线程确定此实体不存在并且即将持续存在时,另一个线程不会尝试执行相同的操作并导致数据不一致。
  3. 由于 1 并且必须避免昂贵的锁定机制,因此性能更好。
  4. 原子
  5. 简单

编辑:不要忘记使用JPA回调实现一个方法,该方法在持久化之前和从数据库加载之后设置布尔字段的正确状态。如果您忘记这样做,调用JPA存储库将没有任何效果,正如我痛苦地发现的那样。这是因为 now 的 Spring Data 实现在执行删除之前会检查实体是否为新实体。如果您的方法返回 true,则永远不会考虑删除该实体。updatedeleteAlldeleteAllisNew


答案 2

我想您知道用于插入和更新。如果 Id 不存在,则如果 Id 存在,则将被视为插入,它将被视为更新。如果将 Id 作为空值发送,则可能会收到异常。CrudRepository.save()

由于除了变量之外没有其他注释,因此唯一 ID 生成必须由代码处理,否则您需要使用注释。@Idid@GeneratedValue


推荐