Je joue actuellement sur Spring boot 1.4.2 dans lequel j'ai intégré Spring-boot-starter-web et Spring-boot-starter-jpa.
Mon principal problème est que lorsque je sauvegarde une nouvelle entité, cela fonctionne bien (tout est cool).
Cependant, si j'enregistre une nouvelle entité produit avec le même identifiant (par exemple une entrée en double), elle ne déclenche pas d'exception. Je m'attendais à ConstrintViolationException ou quelque chose de similaire.
Étant donné la configuration suivante:
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();
}
}
Remarque JpaConfig.Java et Application.Java sont dans le même 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());
}
}
}
et enfin 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;
}
Les getter, setter et equalsHashcode .. sont lombok annotations.
Divers:
Chaussure de printemps: 1.4.2
ORM en veille prolongée: 5.2.2.FINAL
Ce problème se produit indépendamment de l'annotation du contrôleur avec ou sans @Transactional
La base de données sous-jacente montre clairement l'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
Je sais que c'est mieux (plus courant) de casser les données d'accès aux données dans sa propre couche de service au lieu de les jeter dans le contrôleur
La sémantique du contrôleur n'est pas ReST
Ce que j'ai essayé:
Spring CrudRepository exceptions
J'ai essayé d'implémenter la réponse de cette question, malheureusement mon code ne frappe jamais l'exception DataAccesException
Spring JPA génère-t-il une erreur si la fonction de sauvegarde échoue?
Encore une réponse similaire à la question ci-dessus.
http://www.baeldung.com/spring-dataIntegrityviolationexception
J'ai essayé d'ajouter le bean à ma classe JPAconfig.Java qui est:
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
Mais rien ne semblait arriver.
Désolé pour le long post, ty à l'avance
Je pense que vous connaissez CrudRepository.save()
est utilisé à la fois pour l'insertion et la mise à jour. Si un identifiant est inexistant, il sera considéré comme un insert. S'il existe, il sera considéré comme une mise à jour. Vous pouvez obtenir une exception si vous envoyez l'identifiant comme nul.
Comme vous n'avez aucune autre annotation que @Id
sur votre variable id
, la génération de l'identifiant unique doit être gérée par votre code. Sinon, vous devez utiliser @GeneratedValue
annotation.
Ma solution est beaucoup plus propre. Spring Data nous fournit déjà un bon moyen de définir comment une entité est considérée comme nouvelle. Cela peut facilement être fait en implémentant Persistable
sur nos entités, comme documenté dans la référence .
Dans mon cas, tout comme les PO, les identifiants proviennent d'une source externe et ne peuvent pas être générés automatiquement. Ainsi, la logique par défaut utilisée par Spring Data pour considérer une entité comme nouvelle si l'ID est nul n'aurait pas fonctionné.
@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;
}
}
Ici, j'ai fourni un mécanisme permettant à l'entité d'exprimer si elle se considère comme nouvelle ou non au moyen d'une autre propriété booléenne transitoire appelée update
. Comme la valeur par défaut de update
sera false
, toutes les entités de ce type sont considérées comme nouvelles et entraîneront la levée d'un DataIntegrityViolationException
lorsque vous tenterez d'appeler repository.save(entity)
avec le même ID.
Si vous souhaitez effectuer une fusion, vous pouvez toujours définir la propriété update
sur true
avant de tenter une sauvegarde. Bien sûr, si votre cas d'utilisation ne vous oblige jamais à mettre à jour des entités, vous pouvez toujours renvoyer true
à partir de la méthode isNew
et vous débarrasser du champ update
.
Les avantages de cette approche par rapport à la vérification si une entité avec le même ID existe déjà dans la base de données avant l'enregistrement sont nombreux:
Pour s'appuyer sur la réponse de Shazins et clarifier. le CrudRepositroy.save () ou JpaRespository.saveAndFlush () les deux délégués à la méthode suivante
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);
}
}
Par conséquent, si un utilisateur essaie de créer une nouvelle entité qui se trouve avoir le même identifiant qu'une entité existante, les données Spring mettront simplement à jour cette entité.
Pour atteindre ce que je voulais à l'origine, la seule chose que j'ai pu trouver était de retomber uniquement sur JPA, c'est-à-dire
@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;
}
Ici, si nous essayons de persister et que l'entité new avec un id déjà existant dans la base de données, elle lèvera l'exception de violation de contrainte comme nous le voulions à l'origine.