Java Spring JpaRepositroy.save() 似乎不会在重复保存时抛出异常
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/40605834/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring JpaRepositroy.save() does not appear to throw exception on duplicate saves
提问by Verric
I'm currently playing around on Spring boot 1.4.2 in which I've pulled in Spring-boot-starter-web and Spring-boot-starter-jpa.
我目前正在玩 Spring boot 1.4.2,其中我已经拉入了 Spring-boot-starter-web 和 Spring-boot-starter-jpa。
My main issue is that when I save a new entity it works fine (all cool).
我的主要问题是,当我保存一个新实体时,它工作正常(都很酷)。
However if I save a new product entity with the same id (eg a duplicate entry), it does not throw an exception. I was expecting ConstrintViolationException or something similar.
但是,如果我保存具有相同 ID(例如重复条目)的新产品实体,它不会引发异常。我期待 ConstraintViolationException 或类似的东西。
Given the following set up:
鉴于以下设置:
Application.java
应用程序.java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
ProductRepository.java
产品库.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();
}
}
Note JpaConfig.java and Application.java are in the same package.
注意 JpaConfig.java 和 Application.java 在同一个包中。
ProductController.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());
}
}
}
and finally Product.java
最后 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;
}
The getter, setter and equalsHashcode.. are lombokannotations.
吸气,setter和equalsHashcode ..是龙目岛的注解。
Miscellaneous:
各种各样的:
Spring boot : 1.4.2
弹簧靴:1.4.2
Hibernate ORM: 5.2.2.FINAL
休眠 ORM:5.2.2.FINAL
This issue happens regardless if I annotate the controller with or without @Transactional
无论我是否对控制器进行注释,都会发生此问题 @Transactional
The underlying db shows the exception clearly
底层数据库清楚地显示了异常
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
I know that is better (more common) to break the data access stuff into its own service layer instead of dumping it in the controller
我知道将数据访问内容分解到其自己的服务层而不是将其转储到控制器中会更好(更常见)
The semantics of the controller aren't ReST
控制器的语义不是 ReST
Things I've tried:
我尝试过的事情:
Spring CrudRepository exceptions
I've tried implementing the answer from this question, unfortunately my code never ever hits the DataAccesException exception
我试过实现这个问题的答案,不幸的是我的代码从来没有遇到过 DataAccesException 异常
Does Spring JPA throw an error if save function is unsuccessful?
Again similar response to the question above.
再次对上面的问题做出类似的回答。
http://www.baeldung.com/spring-dataIntegrityviolationexception
http://www.baeldung.com/spring-dataIntegrityviolationexception
I tried adding the bean to my JPAconfig.java class that is:
我尝试将 bean 添加到我的 JPAconfig.java 类中:
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
But nothing seemed to happen.
但似乎什么也没发生。
Sorry for long post, ty in advance
抱歉,帖子太长了,请提前
采纳答案by shazin
I think you are aware of CrudRepository.save()
is used for both insert and update. If an Id is non existing then it will considered an insert if Id is existing it will be considered update. You may get an Exception if your send the Id as null.
我想你知道 CrudRepository.save()
用于插入和更新。如果 Id 不存在,那么它将被视为插入,如果 Id 存在,它将被视为更新。如果您将 Id 发送为 null,您可能会收到异常。
Since you don't have any other annotations apart from @Id
on your id
variable, The Unique Id generation must be handled by your code Or else you need to make use of @GeneratedValue
annotation.
既然你没有任何其他注释除了@Id
你的id
变量,唯一ID生成必须由您的代码处理要不然你需要使用的@GeneratedValue
注解。
回答by Verric
To build upon Shazins answer and to clarify. the CrudRepositroy.save()or JpaRespository.saveAndFlush()both delegate to the following method
以 Shazins 的回答为基础并进行澄清。所述CrudRepositroy.save()或JpaRespository.saveAndFlush()都代表以下方法
SimpleJpaRepository.java
SimpleJpaRepository.java
@Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
Hence if a user tries to createa new entity that so happens to have the same id as an existing entity Spring data will just update that entity.
因此,如果用户尝试创建一个新实体,而该实体恰好与现有实体具有相同的 id,则 Spring 数据只会更新该实体。
To achieve what I originally wanted the only thing I could find was to drop back down to JPA solely, that is
为了实现我最初想要的,我唯一能找到的就是完全回到 JPA,那就是
@Transactional
@PostMapping("/createProduct")
public Product createProduct(@RequestBody @Valid Product product) {
try {
entityManager.persist(product);
entityManager.flush();
}catch (RuntimeException ex) {
System.err.println(ex.getCause().getMessage());
}
return product;
}
Here if we try to persist and newentity with an id already existing in the database it will throw will throw the constraint violation exception as we originally wanted.
在这里,如果我们尝试持久化数据库中已经存在 id 的新实体,它将抛出我们最初想要的约束冲突异常。
回答by adarshr
My solution is a lot cleaner. Spring Data already provides a nice way for us to define how an entity is considered to be new. This can easily be done by implementing Persistable
on our entities, as documented in the reference.
我的解决方案要干净得多。Spring Data 已经为我们提供了一种很好的方式来定义如何将实体视为新实体。这可以通过Persistable
在我们的实体上实现来轻松完成,如参考中所述。
In my case, as is the OP's, the IDs come from an external source and cannot be auto generated. So the default logic used by Spring Data to consider an entity as new if the ID is null wouldn't have worked.
就我而言,与 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;
}
}
Here, I have provided a mechanism for the entity to express whether it considers itself new or not by means of another transient boolean property called update
. As the default value of update
will be false
, all entities of this type are considered new and will result in a DataIntegrityViolationException
being thrown when you attempt to call repository.save(entity)
with the same ID.
在这里,我为实体提供了一种机制,通过另一个名为update
. 由于update
will的默认值是false
,该类型的所有实体都被认为是新的,DataIntegrityViolationException
当您尝试repository.save(entity)
使用相同的 ID调用时,将导致抛出。
If you do wish to perform a merge, you can always set the update
property to true
before attempting a save. Of course, if your use case never requires you to update entities, you can always return true
from the isNew
method and get rid of the update
field.
如果您确实希望执行合并,您始终update
可以true
在尝试保存之前将该属性设置为。当然,如果您的用例从不要求您更新实体,您可以随时true
从isNew
方法返回并删除该update
字段。
The advantages of this approach over checking whether an entity with the same ID already exists in the database before saving are many:
这种方法比在保存之前检查数据库中是否已经存在具有相同 ID 的实体的优点有很多:
- Avoids an extra round trip to the database
- We cannot guarantee that by the time one thread has determined that this entity doesn't exist and is about to persist, another thread doesn't attempt to do the same and result in inconsistent data.
- Better performance as a result of 1 and having to avoid expensive locking mechanisms.
- Atomic
- Simple
- 避免额外的数据库往返
- 我们不能保证当一个线程确定该实体不存在并且即将持久化时,另一个线程不会尝试这样做并导致数据不一致。
- 由于 1 并且不得不避免昂贵的锁定机制,因此性能更好。
- 原子
- 简单的
EDIT: Don't forget to implement a method using JPA callbacks that sets the correct state of the update
boolean field just before persisting and just after loading from the database. If you forget to do this, calling deleteAll
on the JPA repository will have no effect as I painfully found out. This is because the Spring Data implementation of deleteAll
now checks if the entity is new before performing the delete. If your isNew
method returns true, the entity will never be considered for deletion.
编辑:不要忘记使用 JPA 回调实现一个方法,该方法update
在持久化之前和从数据库加载之后设置布尔字段的正确状态。如果您忘记这样做,调用deleteAll
JPA 存储库将没有任何效果,正如我痛苦地发现的那样。这是因为deleteAll
now的 Spring Data 实现在执行删除之前检查实体是否是新的。如果您的isNew
方法返回 true,则永远不会考虑删除该实体。