【问题标题】:Spring Data JPA implementing Persistable results in InvalidDataAccessApiUsageException: detached entity passed to persistSpring Data JPA 实现 Persistable 导致 InvalidDataAccessApiUsageException:分离的实体传递给持久化
【发布时间】: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


【解决方案1】:

当示例代码上方的标题显示为:“具有手动分配标识符的实体的基类”时,为什么您觉得需要自定义定义 Persistable@GeneratedValue-annotated id ?

以这种方式实现Persistable 意味着每当您通过从DTO 映射创建实体对象时,Spring Data 都会将其视为新对象。因此,JpaRepository.save() 将在内部调用EntityManager.persist()。这意味着在您的测试中(在初始调用之后)对 QuestionDto 的所有后续调用都将尝试保留现有对象(而不是将它们合并到上下文中),因此会出现错误。

如果您希望自定义 Persistable 实现工作,您将需要单独的 saveupdate 方法,其中 update 看起来像:

questionRepository.findById(questionDto.getId())
.ifPresent(dbQuestionVersion ->
    mapper.mapDtoOntoObject(questionDto, dbQuestionVersion, contextProvider()))`

不过,我猜这不是你想要的。

【讨论】:

  • 你好@crizzis!主要动机是通过将实体状态检测委托给 Persistable 方法和字段,在使用 Spring Data JPA 提供的save() 时摆脱多余的 SELECT。在我的例子中,所有的 id 都被生成并且映射器正在将实体转换为 DTO 1:1 - 我正在使用映射结构 - 这意味着从保存的 DTO 获取的所有属性在保存之前都被放入另一个实体对象中,但我必须检查如何mapper 会处理“isNew”,因此当从 DTO 映射的实体默认获取 isNew 时,可能会出现这种情况。
  • 不确定我是否关注,which SELECT 由 Hibernate 完成,你觉得是多余的吗? Hibernate 需要发出SELECT 作为merge 的一部分,这只是the way it works。此外,Hibernate 对Persistable 一无所知,因为它是一个 Spring Data 接口。修改Persistable 不会影响merge 的工作方式
  • 如果你真的坚持要去掉SELECTthis article suggestssession.update()不发一个。不过,您需要自定义 CrudRepository.save 方法或添加您自己的方法。而且,对于新实体,默认的isNew() 实现应该导致persist() 而不是merge(),因此已经避免了任何SELECTs
  • 谢谢@crizzis!来自弗拉德的非常好的概念。至于这一刻,Persistable 正在通过使用public boolean isNew() { return null == this.getId(); } 使其变得更简单。 SQL 日志现在看起来几乎一样,所以我想这是期望的行为,我认为不应该这么玩。
  • 嗯,是的,该实现与默认实现相同,因此日志看起来相同也就不足为奇了
【解决方案2】:

以下代码在 BaseEntiy 中不可用。当你是 OneToMany 或 ManyToOne 时,你应该在这两个实体中声明

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "foreign_key")
private Question   question;

【讨论】:

  • 在两个实体中声明什么
猜你喜欢
  • 1970-01-01
  • 2020-11-02
  • 1970-01-01
  • 2013-06-26
  • 2017-10-21
  • 1970-01-01
  • 2019-12-10
  • 2015-12-26
  • 2018-01-05
相关资源
最近更新 更多