Spring JpaRepositroy.save () does not seem to throw a duplication exception

I am currently playing Spring boot 1.4.2, which I pulled into Spring-boot-starter-web and Spring-boot-starter-jpa.

My main problem is that when I save a new object, it works fine (everything's cool).

However, if I save a new product object with the same identifier (for example, a duplicate element), it does not throw an exception. I was expecting a ConstrintViolationException or something similar.

Given the following setting:

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> {} 

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.

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

and finally 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 and equalsHashcode .. are lombok annotations.

Miscellanea:

Spring boot: 1.4.2

Hibernate ORM: 5.2.2.FINAL

This issue occurs regardless of whether I comment on the controller with or without @Transactional

Base db explicitly throws exception

 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 it’s better (more often) to break the data access material into your own service level instead of dumping it into the controller

Controller semantics are not REST

What I tried:

Spring CrudRepository Exceptions

I tried to execute the answer from this question, unfortunately my code never falls into DataAccesException

Does Spring JPA give an error if the save function is unsuccessful?

Again a similar answer to the question above.

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

I tried adding a bean to my JPAconfig.java class, which:

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

But nothing happened.

Sorry for the long post, enter in advance

+7
java spring-data spring-data-jpa
source share
3 answers

I think you know that CrudRepository.save() used for both insert and update. If the identifier does not exist, then it will consider the insert; if the identifier exists, it will be considered an update. You can get an exception if you send Id as null.

Since you have no other annotations besides @Id in your id variable, the generation of a unique identifier should be handled by your code. Or you need to use the @GeneratedValue annotation.

+3
source share

My solution is much cleaner. Spring Data already provides us with a good way to determine how an object is considered new. This can be easily done by implementing Persistable for our objects, as described in the link .

In my case, like the OP, identifiers come from an external source and cannot be generated automatically. Therefore, the default logic used by Spring Data to treat the object as new, if the identifier is null, will not work.

 @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; } } 

Here I provided a mechanism for an entity to express whether it considers itself new or not using another transient logical property called update . Since the default value of update is false , all objects of this type are considered new and lead to the fact that when you try to call repository.save(entity) with the same identifier, a DataIntegrityViolationException with the same identifier will be DataIntegrityViolationException .

If you want to perform a merge, you can always set the update property to true before trying to save. Of course, if your use-case never requires updating objects, you can always return true from the isNew method and get rid of the update field.

The advantages of this approach when checking whether an entity with the same identifier already exists in the database before saving exists are many:

  • Prevents an extra trip to the database
  • We cannot guarantee that by the time one thread determines that this object does not exist and is going to continue, the other thread is not trying to do the same and leads to inconsistent data.
  • Better performance in result 1 and the need to avoid costly locking mechanisms.
  • Atomic
  • Plain
+4
source share

Rely on Shazintsov to answer and clarify. CrudRepositroy.save () or JpaRespository.saveAndFlush () both delegate the following method

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); } } 

Therefore, if the user tries to create , a new object that has the same identifier as the existing Spring object will simply update that object.

In order to achieve what I originally wanted, the only thing I could find was to simply abandon the JPA, i.e.

 @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 save a new object with an existing one in the database, it will throw an exception for violation of restrictions, as we wanted.

0
source share

All Articles