【发布时间】:2020-08-23 10:41:05
【问题描述】:
我正在尝试实现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,
Long version,
Timestamp createdDate,
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
【问题讨论】:
-
(1) 为什么你觉得你需要一个带有
@GeneratedValue-annotated id 的Persistable的自定义定义?示例代码上方的标题为:“实体的基类具有手动分配的标识符”(2) 如果您使用@Transactional注释测试,它是否有效? (3)mapper.dtoToObject(...)这一行如何处理Question.answers?
标签: java spring-boot hibernate jpa spring-data-jpa