如何解决Spring Data JPA在InvalidDataAccessApiUsageException中实现Persistable结果:传递了分离的实体以持久化
我正在尝试实现Persistable
以便将实体状态检测委托给接口,并最终在保存/更新实体的同时最大程度地减少数据库查询的数量。实施后,我在尝试更新现有实体时得到InvalidDataAccessApiUsageException: detached entity passed to persist
。
使用Spring Data Repository save
方法时,每个类都会发生这些异常。在没有Persistable
的情况下,代码可以正常工作,但有更多的查询。
总体而言,是摆脱使用Spring Data JPA save
方法时生成的许多查询。如果不执行,所有方法都可以正常工作。
这是基于Spring Data JPA docs的超类的Persistable
实现
有人对这里发生的事情有任何了解吗?
根据文档实现Persistable
。
@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;
}
}
实体类示例
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Question extends BaseEntity {
private String contents;
private String mainTech;
private String specificTech;
@Enumerated(EnumType.STRING)
private SkillLevel skillLevel;
@OneToMany(mappedBy = "question",cascade = CascadeType.ALL,fetch = FetchType.LAZY,orphanRemoval = true)
private List<Answer> answers = new ArrayList<>();
@Builder
public Question(UUID id,Timestamp lastModifiedDate,java.lang.String contents,String mainTech,java.lang.String specificTech,ArrayList<Answer> answers,SkillLevel skillLevel) {
super(id,version,createdDate,lastModifiedDate);
this.contents = contents;
this.mainTech = mainTech;
this.specificTech = specificTech;
this.answers = answers;
this.skillLevel = skillLevel;
}
public void addAnswer(Answer answer) {
this.getAnswers().add(answer);
answer.setQuestion(this);
}
public void removeAnswer(Answer answer) {
this.getAnswers().remove(answer);
answer.setQuestion(null);
}
@Override
public java.lang.String toString() {
return "Question{" +
"id='" + this.getId() + '\'' +
"contents='" + contents + '\'' +
",mainTech=" + mainTech +
",specificTech='" + specificTech + '\'' +
",skillLevel=" + skillLevel +
",answers=" + answers.size() +
'}';
}
}
服务等级
@Service
public class QuestionServiceImpl implements QuestionService {
private final QuestionRepository questionRepository;
private final QuestionMapper mapper;
private final EntityManagerFactory emf;
public QuestionServiceImpl(
QuestionRepository questionRepository,EntityManagerFactory emf,QuestionMapper mapper
) {
this.questionRepository = questionRepository;
this.mapper = mapper;
this.emf = emf;
}
@Override
public QuestionDto saveOrUpdate(QuestionDto questionDto) {
Question save = questionRepository.save(mapper.dtoToObject(questionDto,contextProvider()));
return mapper.objectToDto(save,contextProvider());
}
public QuestionDto findByUuId(UUID uuid) {
Question question = emf.createEntityManager().createQuery("select q from Question q " +
"left join fetch q.answers " +
"where q.id = :uuid",Question.class)
.setParameter("uuid",uuid)
.getSingleResult();
return mapper.objectToDto(question,contextProvider());
}
//More code ...
}
存储库
@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);
}
测试用例
@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
解决方法
以下代码在BaseEntiy中不可用。无论您是OneToMany还是ManyToOne,都应在两个实体中声明
@ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
@JoinColumn(name = "foreign_key")
private Question question;
,
为什么在示例代码上方的标题显示为:'具有手动分配的标识符的实体的基类Persistable和带有@GeneratedValue
的id的自定义/ strong>”?
以这种方式实现Persistable
意味着,无论何时通过DTO映射创建实体对象,Spring Data都会将其视为新对象。因此,JpaRepository.save()
将在内部调用EntityManager.persist()
。这意味着在测试中,在service.saveOrUpdate(...)
上对QuestionDto
的所有后续调用(在最初的调用之后)将尝试持久化现有对象(而不是将它们合并到上下文中),从而导致错误。>
如果要使自定义Persistable
实现有效,则需要使用单独的save
和update
方法,其中update
看起来像这样:
questionRepository.findById(questionDto.getId())
.ifPresent(dbQuestionVersion ->
mapper.mapDtoOntoObject(questionDto,dbQuestionVersion,contextProvider()))`
不过,我猜那不是您想要的。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。