我目前在使用Spring-boot-starter-web和Spring-boot-starter-jpa的Spring boot 1.4.2中玩游戏。
我的主要问题是,当我保存一个新实体时,它工作正常(非常酷)。
但是,如果我保存具有相同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注释。
杂
Spring boot:1.4.2
Hibernate ORM:5.2.2.FINAL
无论是否使用@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
我尝试过的事情:
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();
}
但似乎没有任何事情发生。
很抱歉很长一段时间,ty提前
答案 0 :(得分:12)
我的解决方案更清洁了。 Spring Data已经为我们定义一个实体如何被认为是新的提供了一种很好的方式。这可以通过在我们的实体上实施Persistable
轻松完成,如documented in the reference。
在我的情况下,与OP一样,ID来自外部源,无法自动生成。因此,如果ID为null,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;
}
}
在这里,我为实体提供了一种机制,通过另一个名为update
的瞬态布尔属性来表达它是否认为自己是新的。由于update
的默认值为false
,因此当您尝试使用DataIntegrityViolationException
调用repository.save(entity)
时,此类型的所有实体都会被视为新的实体,并且会导致update
被抛出相同的ID。
如果您确实希望执行合并,则可以在尝试保存之前始终将true
属性设置为true
。当然,如果您的用例从不要求您更新实体,则始终可以从isNew
方法返回update
并删除But i am getting below error:
Please use CMSClassUnloadingEnabled in place of CMSPermGenSweepingEnabled in the future
Could not load Logmanager "org.jboss.logmanager.LogManager"
java.lang.ClassNotFoundException: org.jboss.logmanager.LogManager
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
at java.util.logging.LogManager$1.run(LogManager.java:191)
at java.security.AccessController.doPrivileged(Native Method)
at java.util.logging.LogManager.<clinit>(LogManager.java:181)
at java.util.logging.Logger.demandLogger(Logger.java:339)
at java.util.logging.Logger.getLogger(Logger.java:393)
at com.sun.jmx.remote.util.ClassLogger.<init>(ClassLogger.java:55)
at sun.management.jmxremote.ConnectorBootstrap.<clinit>(ConnectorBootstrap.java:830)
at sun.management.Agent.startAgent(Agent.java:260)
at sun.management.Agent.startAgent(Agent.java:456)
WARNING: Failed to load the specified log manager class org.jboss.logmanager.LogManager
Aug 17, 2017 5:46:51 AM org.jboss.msc.service.ServiceContainerImpl <clinit>
INFO: JBoss MSC version 1.0.4.GA-redhat-1
Aug 17, 2017 5:46:51 AM org.jboss.as.server.ApplicationServerService start
INFO: JBAS015899: JBoss EAP 6.1.0.GA (AS 7.2.0.Final-redhat-8) starting
Aug 17, 2017 5:46:52 AM org.jboss.as.controller.AbstractOperationContext executeStep
ERROR: JBAS014612: Operation ("parallel-extension-add") failed - address: ([])
java.lang.RuntimeException: JBAS014670: Failed initializing module org.jboss.as.logging
at org.jboss.as.controller.extension.ParallelExtensionAddHandler$1.execute(ParallelExtensionAddHandler.java:99)
at org.jboss.as.controller.AbstractOperationContext.executeStep(AbstractOperationContext.java:440)
at org.jboss.as.controller.AbstractOperationContext.doCompleteStep(AbstractOperationContext.java:322)
at org.jboss.as.controller.AbstractOperationContext.completeStepInternal(AbstractOperationContext.java:229)
at org.jboss.as.controller.AbstractOperationContext.executeOperation(AbstractOperationContext.java:224)
at org.jboss.as.controller.ModelControllerImpl.boot(ModelControllerImpl.java:232)
at org.jboss.as.controller.AbstractControllerService.boot(AbstractControllerService.java:225)
at org.jboss.as.server.ServerService.boot(ServerService.java:333)
at org.jboss.as.server.ServerService.boot(ServerService.java:308)
at org.jboss.as.controller.AbstractControllerService$1.run(AbstractControllerService.java:188)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.util.concurrent.ExecutionException: java.lang.IllegalStateException: JBAS011592: The logging subsystem requires the log manager to be org.jboss.logmanager.LogManager. The subsystem has not be initialized and cannot be used. To use JBoss Log Manager you must add the system property "java.util.logging.manager" and set it to "org.jboss.logmanager.LogManager"
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:188)
at org.jboss.as.controller.extension.ParallelExtensionAddHandler$1.execute(ParallelExtensionAddHandler.java:91)
... 10 more
Caused by: java.lang.IllegalStateException: JBAS011592: The logging subsystem requires the log manager to be org.jboss.logmanager.LogManager. The subsystem has not be initialized and cannot be used. To use JBoss Log Manager you must add the system property "java.util.logging.manager" and set it to "org.jboss.logmanager.LogManager"
at org.jboss.as.logging.LoggingExtension.initialize(LoggingExtension.java:103)
at org.jboss.as.controller.extension.ExtensionAddHandler.initializeExtension(ExtensionAddHandler.java:97)
at org.jboss.as.controller.extension.ParallelExtensionAddHandler$ExtensionInitializeTask.call(ParallelExtensionAddHandler.java:127)
at org.jboss.as.controller.extension.ParallelExtensionAddHandler$ExtensionInitializeTask.call(ParallelExtensionAddHandler.java:113)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
at org.jboss.threads.JBossThread.run(JBossThread.java:122)
Aug 17, 2017 5:46:52 AM org.jboss.as.server.ServerService boot
FATAL: JBAS015957: Server boot has failed in an unrecoverable manner; exiting. See previous messages for details.
Thanks & Regards,
Rishabh Jain
字段。
这种方法的优点是在保存之前检查数据库中是否已存在具有相同ID的实体的优点很多:
答案 1 :(得分:5)
我想您知道CrudRepository.save()
用于插入和更新。如果Id不存在,那么如果Id存在则它将被视为插入,它将被视为更新。如果您将Id发送为空,则可能会收到异常。
由于除了@Id
变量上的id
之外,您没有任何其他注释,因此必须由您的代码处理唯一ID生成,否则您需要使用{{ 1}}注释。
答案 2 :(得分:0)
以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数据将只更新该实体。
为了实现我原本想要的东西,我唯一能找到的就是单独退回到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,它将抛出将抛出我们最初想要的约束违例异常。
答案 3 :(得分:0)
请注意,这里有3种情况:
首先,如果没有选择(例如OP),即如果您“手动”设置自己的ID,则Spring Data JPA假定您要检查是否存在重复项(因此 SELECT ),因此如果没有现有记录或,它将执行“(i)SELECT +(ii)INSERT” “(i)选择+(ii)更新” (如果已经存在)。简而言之,有2条SQL!
第二(更干净,更好)是使用ID生成器,例如:
@Id
@GeneratedValue(generator = "my-uuid")
@GenericGenerator(name = "my-uuid", strategy = "uuid2")
private UUID id;
在这种情况下,只有总是条 1 INSERT 语句。
第三,已经实现了@adarshr的出色回答,但也更痛苦的是实施Persistable
(而不是Serializable
)并实施{ {1}}方法。另外, 1 INSERT 语句。
欢呼
答案 4 :(得分:0)
根据 Spring Data documentation Spring 持久化一个实体如果不存在或合并,这意味着更新,现有的:
<块引用>保存实体可以通过 CrudRepository.save(…)-Method 来执行。它将使用底层 JPA EntityManager 持久化或合并给定实体。如果实体尚未持久化,Spring Data JPA 将通过调用 entityManager.persist(…)-Method 保存实体,否则将调用 entityManager.merge(…)-Method。