Java Spring jparepository.save()似乎不会在重复保存时引发异常

Java Spring jparepository.save()似乎不会在重复保存时引发异常,java,spring-data,spring-data-jpa,Java,Spring Data,Spring Data Jpa,我目前正在玩SpringBoot1.4.2,其中我引入了SpringBootStarterWeb和SpringBootStarterJPA 我的主要问题是,当我保存一个新实体时,它可以正常工作(很酷) 但是,如果我用相同的id保存一个新的产品实体(例如重复条目),它不会引发异常。我期待着ConstructionViolationException或类似的东西 鉴于以下设置: Application.java @SpringBootApplication public class Applicat

我目前正在玩SpringBoot1.4.2,其中我引入了SpringBootStarterWeb和SpringBootStarterJPA

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

但是,如果我用相同的id保存一个新的产品实体(例如重复条目),它不会引发异常。我期待着ConstructionViolationException或类似的东西

鉴于以下设置:

Application.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> {}
@Transactional
public <S extends T> S save(S entity) {

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}
注意: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。。是注释

杂项:

弹簧靴:1.4.2

Hibernate ORM:5.2.2.4最终版本

无论我在控制器上添加或不添加
@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

我尝试过的事情:

我尝试过实现这个问题的答案,不幸的是,我的代码从来没有遇到过DataAccesseException异常

对上述问题的回答同样相似

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

   @Bean
   public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
      return new PersistenceExceptionTranslationPostProcessor();
   }
但似乎什么也没发生


很抱歉发了这么长的帖子,请提前修改我想您已经知道了
crudepository.save()
用于插入和更新。如果Id不存在,则它将被视为插入。如果Id存在,则它将被视为更新。如果您发送的Id为null,则可能会出现异常


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

以Shazin的回答为基础并澄清。crudepository.save()或jparepository.saveAndFlush()都委托给以下方法

SimpleJpaRepository.java

@Repository
public interface ProductRepository extends JpaRepository<Product, String> {}
@Transactional
public <S extends T> S save(S entity) {

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

在这里,如果我们尝试持久化,并且新的实体的id已经存在于数据库中,它将抛出约束冲突异常,正如我们最初所希望的那样。

我的解决方案要干净得多。Spring数据已经为我们提供了一种很好的方法来定义实体是如何被视为新的。这可以通过在我们的实体上实现来轻松实现,如

在我的例子中,与OP一样,ID来自外部源,不能自动生成。因此,如果ID为null,Spring数据用于将实体视为新的默认逻辑将不起作用。

@实体
公共类MyEntity实现持久化{
@身份证
私有UUID;
@短暂的
私有布尔更新;
@凌驾
公共UUID getId(){
返回此.id;
}
公共无效设置id(UUID id){
this.id=id;
}
公共布尔值更新(){
返回此文件。更新;
}
公共无效设置日期(布尔更新){
this.update=更新;
}
@凌驾
public boolean isNew(){
return!this.update;
}
@预科生
@后装
void markUpdated(){
this.update=true;
}
}
这里,我为实体提供了一种机制,通过另一个名为
update
的临时布尔属性来表示它是否认为自己是新的。由于
update
的默认值将为
false
,因此此类型的所有实体都被视为新实体,并将导致在尝试调用具有相同ID的
存储库时引发
DataIntegrityViolationException

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

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

  • 避免到数据库的额外往返
  • 我们不能保证,当一个线程确定该实体不存在并且即将持久化时,另一个线程不会尝试执行相同的操作并导致数据不一致
  • 由于1和必须避免昂贵的锁定机制,因此性能更好
  • 原子的
  • 简单的

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

    请注意,这里有3种情况:

    首先,如果没有选择(如OP),即如果您“手动”设置自己的id,Spring Data JPA假设您希望检查是否存在重复项(因此选择
    ),因此它将执行“(i)选择+(ii)插入“,如果没有现有记录或”(i)选择+(ii)更新”如果已有记录。简而言之,2个SQL

    第二个更干净更好的方法是使用ID生成器,例如:

     @Id
     @GeneratedValue(generator = "my-uuid")
     @GenericGenerator(name = "my-uuid", strategy = "uuid2")
     private UUID id;
    
    在这种情况下,总是只有<