我目前正在使用Spring boot 1.4.2,其中我引入了Spring-boot-starter-web和Spring-boot-starter-jpa。
我的主要问题是,当我保存一个新的实体时,它工作得很好(都很酷)。
然而,如果我用相同的id保存一个新的产品实体(例如一个重复的条目),它不会抛出异常。我期待的是ConstrintViolationException或类似的东西。
给定以下设置:
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();
}
}
注意: 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注解。
Miscellaneous:
Spring boot : 1.4.2
Hibernate ORM: 5.2.2
无论我使用或不使用@Transactional
注释控制器,都会发生此问题
基础db清楚地显示了异常。
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 exceptions
我已经尝试实现了这个问题的答案,不幸的是,我的代码从未遇到过DataAccesException异常
Does Spring JPA throw an error if save function is unsuccessful?
再次对上面的问题做出类似的回答。
http://www.baeldung.com/spring-dataIntegrityviolationexception
我尝试将bean添加到我的JPAconfig.java类中,即:
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
但似乎什么也没发生。
很抱歉发了这么长的帖子,太早了。
发布于 2016-11-15 19:38:34
我想你应该知道CrudRepository.save()
同时用于插入和更新。如果Id不存在,那么它将被认为是插入,如果Id存在,它将被认为是更新。如果您将Id作为null发送,则可能会出现异常。
由于您的id
变量上除了@Id
之外没有任何其他注释,因此惟一Id的生成必须由您的代码处理,否则您需要使用@GeneratedValue
注释。
发布于 2017-08-17 18:37:46
我的解决方案要干净得多。Spring Data已经为我们提供了一种很好的方式来定义一个实体是如何被认为是新的。这可以很容易地通过在我们的实体上实现Persistable
来实现,比如documented in the reference。
在我的例子中,就像OP一样,In来自外部来源,不能自动生成。因此,Spring Data使用的默认逻辑在ID为null时将实体视为新实体将不起作用。
@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
,因此此类型的所有实体都被视为新实体,并将导致在您尝试使用相同ID调用DataIntegrityViolationException
时抛出repository.save(entity)
。
如果您确实希望执行合并,则始终可以在尝试保存之前将update
属性设置为true
。当然,如果您的用例从不要求您更新实体,那么您始终可以从isNew
方法返回true
,并去掉update
字段。
与在保存之前检查数据库中是否已经存在具有相同ID的实体相比,这种方法有很多优点:
而获得更好的性能
编辑:不要忘记使用JPA回调实现一个方法,该回调在持久化之前和从数据库加载之后设置update
布尔值字段的正确状态。如果您忘记这样做,那么在JPA存储库上调用deleteAll
将不会有任何效果,正如我痛苦地发现的那样。这是因为deleteAll
的Spring Data实现现在在执行删除之前检查实体是否是新的。如果您的isNew
方法返回true,则永远不会考虑删除该实体。
发布于 2016-11-18 07:29:30
以Shazins的答案为基础并加以澄清。CrudRepositroy.save()或JpaRespository.saveAndFlush()都委托给以下方法
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);
}
}
因此,如果用户尝试创建,那么恰好与现有实体具有相同id的新实体Spring data将只更新该实体。
为了实现我最初想要的东西,我唯一能找到的就是回到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;
}
这里,如果我们尝试持久化和数据库中已经存在的id的新实体,它将抛出违反约束的异常,正如我们最初希望的那样。
https://stackoverflow.com/questions/40605834
复制相似问题