Java Spring Data JPA在InvalidDataAccessApiUsageException中实现持久化结果:传递给持久化的分离实体
我试图实现Java Spring Data JPA在InvalidDataAccessApiUsageException中实现持久化结果:传递给持久化的分离实体,java,spring-boot,hibernate,jpa,spring-data-jpa,Java,Spring Boot,Hibernate,Jpa,Spring Data Jpa,我试图实现Persistable,将实体状态检测委托给接口,并最终在保存/更新实体时最小化数据库查询的数量。实现后,我获取了InvalidDataAccessApiUsageException:在尝试更新现有实体时传递给persist的分离实体 使用Spring数据存储库save方法时,每个类都会出现这些异常。代码运行良好,没有持久性,但有很多查询 总的来说,这种情况是为了消除使用Spring-Data-JPA-save方法时生成的许多查询。如果没有实现,所有方法都可以正常工作 这是基于 有人知
Persistable
,将实体状态检测委托给接口,并最终在保存/更新实体时最小化数据库查询的数量。实现后,我获取了InvalidDataAccessApiUsageException:在尝试更新现有实体时传递给persist的分离实体
使用Spring数据存储库save
方法时,每个类都会出现这些异常。代码运行良好,没有持久性
,但有很多查询
总的来说,这种情况是为了消除使用Spring-Data-JPA-save
方法时生成的许多查询。如果没有实现,所有方法都可以正常工作
这是基于
有人知道这里发生了什么吗
根据文档执行持久化
@MappedSuperclass
public class BaseEntity implements Persistable<UUID> {
public BaseEntity () {
}
public BaseEntity(UUID id, Long version, Timestamp createdDate, Timestamp lastModifiedDate) {
this.id = id;
this.version = version;
this.createdDate = createdDate;
this.lastModifiedDate = lastModifiedDate;
}
@Id
@GeneratedValue(generator = "UUID")
@GenericGenerator(
name = "UUID",
strategy = "org.hibernate.id.UUIDGenerator")
@Column(updatable = false, nullable = false )
private UUID id;
@Version
private Long version;
@CreationTimestamp
@Column(updatable = false, nullable = false)
private Timestamp createdDate;
@UpdateTimestamp
private Timestamp lastModifiedDate;
public int hashCode () {
return Objects.hash(this.id);
}
public boolean equals (Object that) {
return this == that || that instanceof BaseEntity && Objects.equals(this.id, ((BaseEntity) that).id);
}
public UUID getId() {
return id;
}
@Transient
private boolean isNew = true;
@Override
public boolean isNew() {
return isNew;
}
@PrePersist
@PostLoad
void markNotNew() {
this.isNew = false;
}
public void setId(UUID id) {
this.id = id;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
public Timestamp getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Timestamp createdDate) {
this.createdDate = createdDate;
}
public Timestamp getLastModifiedDate() {
return lastModifiedDate;
}
public void setLastModifiedDate(Timestamp lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
}
存储库
@Repository
public interface QuestionRepository extends JpaRepository<Question, UUID> {
@Override
List<Question> findAll();
@Override
<S extends Question> S save(S s);
@Override
Optional<Question> findById(UUID uuid);
Question findByContentsEquals(String contents);
List<Question> findAllByMainTechAndSpecificTech(String mainTech, java.lang.String specificTech);
List<Question> findAllByMainTech(String mainTech);
List<Question> findAllByMainTechAndSkillLevel(String mainTech, SkillLevel skillLevel);
List<Question> findAllByMainTechAndSkillLevelAndSpecificTech(String mainTech, SkillLevel skillLevel, String specificTech);
}
以下代码在BaseEntiy中不可用。当你是一个或多个实体时,你应该在这两个实体中声明
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "foreign_key")
private Question question;
当示例代码上方的标题为:“具有手动分配标识符的实体的基类”时,为什么您觉得需要一个带有
@GeneratedValue
-注释id的持久性的自定义定义
通过这种方式实现持久化
,意味着无论何时通过从DTO映射创建实体对象,Spring数据都会将其视为新对象。因此,JpaRepository.save()
将在内部调用EntityManager.persist()
。这意味着对服务的所有后续调用。对测试中的问题进行saveOrUpdate(…)
将尝试保留现有对象(而不是将它们合并到上下文中),因此会出现错误
如果希望自定义的持久化
实现工作,则需要单独的保存
和更新
方法,其中更新
看起来像:
questionRepository.findById(questionDto.getId())
.ifPresent(dbQuestionVersion ->
mapper.mapDtoOntoObject(questionDto, dbQuestionVersion, contextProvider()))`
不过,我猜这不是您想要的。声明这两个实体中的内容?(1)为什么您觉得需要一个带有@GeneratedValue
-注释id的持久性的自定义定义?示例代码上方的标题为:“具有手动分配标识符的实体的基类”(2)如果使用@Transactional
对测试进行注释,是否有效?(3) mapper.dtoToObject(…)
行如何处理问题.答案
?您好@crizzis!主要动机是在使用Spring Data JPA提供的save()
时,通过将实体状态检测委托给可持久化的方法和字段来消除多余的选择。在我的例子中,所有id都会生成,映射器会将实体转换为DTO 1:1-我使用的是map struct-这意味着保存DTO的所有属性在保存之前都会放入另一个实体对象中,但我必须检查映射器如何处理“isNew”,因此,当从DTO映射到的实体在默认情况下获得时可能就是这种情况。我不确定是否遵循,您认为Hibernate完成的选择是超级功能?Hibernate需要发出一个SELECT
,作为merge
的一部分,这就是。另外,Hibernate对持久化(Persistable)一无所知,因为它是一个Spring数据接口。修补Persistable
不会影响merge
在任何方面的工作方式如果您确实坚持取消SELECT
,则session.update()
不会发出一个。您需要自定义crudepository.save
方法或添加自己的方法。而且,对于新实体,默认的isNew()
实现应该导致persist()
而不是merge()
,因此已经避免了任何SELECT
s whatsoeverThank You@crizzis!Vlad的概念非常好。至于现在,Persistable
的工作原理是使用public boolean isNew(){return null==this.getId();}
使它变得更简单。SQL日志现在看起来差不多了,所以我想这是我们想要的行为,我认为不应该用这么多。嗯,是的,这个实现与默认实现相同,所以日志看起来一样也就不足为奇了
@RunWith(SpringRunner.class)
@SpringBootTest
class QuestionServiceImplTest {
@Autowired
QuestionRepository repository;
@Autowired
QuestionServiceImpl service;
@Autowired
QuestionMapper mapper;
QuestionDto question1;
QuestionDto question2;
@BeforeEach
void setUp() {
question1 = QuestionDto.builder()
.answers(new ArrayList<>())
.contents("testQuestion1")
.skillLevel(SkillLevel.ENTRY)
.specificTech("Core")
.mainTech("Java")
.build();
question2 = QuestionDto.builder()
.answers(new ArrayList<>())
.contents("testQuestion1")
.skillLevel(SkillLevel.ENTRY)
.specificTech("Core")
.mainTech("Java")
.build();
assertAll(
() -> assertThat(question1.getContents()).isEqualTo(question2.getContents())
);
}
@Test
public void callingSaveOrUpdateMultipleTimesShouldAlwaysReturnSingleEntity() {
// Given
QuestionDto savedQuestion1 = service.saveOrUpdate(question1);
System.out.println("first save 1" + savedQuestion1);
QuestionDto savedQuestion2 = service.saveOrUpdate(question2);
System.out.println("first save 2" + savedQuestion2);
QuestionDto savedQuestion1a = service.saveOrUpdate(savedQuestion1);
QuestionDto savedQuestion1b = service.saveOrUpdate(savedQuestion1a);
QuestionDto savedQuestion1c = service.saveOrUpdate(savedQuestion1b);
System.out.println("last save 1");
QuestionDto savedQuestion2a = service.saveOrUpdate(savedQuestion2);
QuestionDto savedQuestion2b = service.saveOrUpdate(savedQuestion2a);
service.saveOrUpdate(savedQuestion2b);
System.out.println("last save 2");
// When
QuestionDto searchResult = service.findByUuId(savedQuestion1.getId());
List<QuestionDto> all = service.findAll();
// Then
assertAll(
() -> assertThat(searchResult).isNotNull(),
() -> assertThat(searchResult.getId()).isEqualTo(savedQuestion1.getId()),
() -> assertThat(all.size()).isEqualTo(2),
() -> assertThat(all.get(0).getId()).isEqualTo(savedQuestion1.getId()),
() -> assertThat(all.get(1).getId()).isEqualTo(savedQuestion2.getId())
);
}
}
org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.github.pawelbialas.testgeneratorapp.entity.question.model.Question; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.github.pawelbialas.testgeneratorapp.entity.question.model.Question
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:319)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:255)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:178)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy156.save(Unknown Source)
at com.github.pawelbialas.testgeneratorapp.entity.question.service.QuestionServiceImpl.saveOrUpdate(QuestionServiceImpl.java:38)
at com.github.pawelbialas.testgeneratorapp.entity.question.service.QuestionServiceImplTest.callingSaveOrUpdateMultipleTimesShouldAlwaysReturnSingleEntity(QuestionServiceImplTest.java:70)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:675)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:125)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:132)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:124)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:74)
at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:104)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:62)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:43)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:35)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:202)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:198)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229)
at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197)
at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.github.pawelbialas.testgeneratorapp.entity.question.model.Question
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:127)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:62)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:108)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:702)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:688)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:314)
at com.sun.proxy.$Proxy139.persist(Unknown Source)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:554)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:371)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:204)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:657)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:621)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:605)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:366)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:99)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
... 72 more
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "foreign_key")
private Question question;
questionRepository.findById(questionDto.getId())
.ifPresent(dbQuestionVersion ->
mapper.mapDtoOntoObject(questionDto, dbQuestionVersion, contextProvider()))`