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

新手上路,请多包涵

我目前正在玩 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);
    }

ProductRepository.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 在同一个包中。

ProductController.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());
        }
    }
}

最后是 Product.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 和 equalsHashcode.. 是 lombok 注释。

各种各样的:

弹簧启动:1.4.2

休眠 ORM:5.2.2.FINAL

无论我使用或不使用 @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 异常

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

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

再次对上述问题做出类似的回答。

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

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

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

但似乎什么也没有发生。

抱歉发了很长的帖子,提前联系

原文由 Verric 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.1k
2 个回答

我想您知道 CrudRepository.save() 用于插入和更新。如果 Id 不存在,那么它将被视为插入,如果 Id 存在,它将被视为更新。如果您将 Id 发送为 null,则可能会出现异常。

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

原文由 shazin 发布,翻译遵循 CC BY-SA 3.0 许可协议

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

就我而言,与 OP 一样,ID 来自外部来源,无法自动生成。因此,如果 ID 为 null,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;
    }
}

在这里,我为实体提供了一种机制,通过另一个名为 update 的临时布尔属性来表达它是否认为自己是新的。由于 update 的默认值将是 false ,该类型的所有实体都被认为是新的,并且将导致 DataIntegrityViolationException 尝试抛出 repository.save(entity) 具有相同的 ID。

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

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

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

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

原文由 adarshr 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题