"Not-null property references a transient value" 尝试使用 Spring Data JPA + Lombok 级联持久性时

"Not-null property references a transient value" when attempting to cascade persistence using Spring Data JPA + Lombok

我正在尝试从父实体级联子实体的持久性,但这种关系没有得到维护,我也不知道为什么。

我试过将级联放在 other/both 侧,以及使用 CascadeType.PERSIST 而不是 CascadeType.ALL(正如某些文章所建议的那样),但没有成功.

如有任何帮助,我们将不胜感激。

@DataJpaTest
public class TaskEntityRepositoryIntegrationTests {
    @Inject
    private TaskEntityRepository taskEntityRepository;

    @Test
    void testRelationshipPersistence() {
        TaskDao taskDao = TaskDao.builder()
                .title("Title")
                .build();

        TagDao tagDao = TagDao.builder()
                .tag("Tag")
                .task(taskDao)
                .build();

        taskDao = taskDao.toBuilder()
                .tag(tagDao)
                .build();

        TaskDao result = this.taskEntityRepository.save(taskDao);

        assertEquals(tagDao, result.getTags().iterator().next());
    }
}
2020-09-16 11:16:57.317  INFO 11406 --- [           main] o.s.t.c.transaction.TransactionContext   : Began transaction (1) for test context [DefaultTestContext@5038d0b5 testClass = TaskEntityRepositoryIntegrationTests, testInstance = TaskEntityRepositoryIntegrationTests@1169afe1, testMethod = testRelationshipPersistence@TaskEntityRepositoryIntegrationTests, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@32115b28 testClass = TaskEntityRepositoryIntegrationTests, locations = '{}', classes = '{class TasksApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer@2ad48653 key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@618425b5, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@f5acb9d, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@54bff557, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@8b05feb1, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@59505b48, org.springframework.boot.test.context.SpringBootTestArgs@1], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@46a0ef6f]; rollback [true]
Hibernate: insert into tasks (id, title) values (null, ?)
2020-09-16 11:16:57.868  WARN 11406 --- [           main] o.h.a.i.UnresolvedEntityInsertActions    : HHH000437: Attempting to save one or more entities that have a non-nullable association with an unsaved transient entity. The unsaved transient entity must be saved in an operation prior to saving these dependent entities.
    Unsaved transient entity: ([TaskDao#<null>])
    Dependent entities: ([[TagDao#<null>]])
    Non-nullable association(s): ([TagDao.task])
2020-09-16 11:16:57.883  INFO 11406 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@5038d0b5 testClass = TaskEntityRepositoryIntegrationTests, testInstance = TaskEntityRepositoryIntegrationTests@1169afe1, testMethod = testRelationshipPersistence@TaskEntityRepositoryIntegrationTests, testException = org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : TagDao.task -> TaskDao; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : TagDao.task -> TaskDao, mergedContextConfiguration = [MergedContextConfiguration@32115b28 testClass = TaskEntityRepositoryIntegrationTests, locations = '{}', classes = '{class TasksApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer@2ad48653 key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@618425b5, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@f5acb9d, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@54bff557, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@8b05feb1, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@59505b48, org.springframework.boot.test.context.SpringBootTestArgs@1], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]


org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : TagDao.task -> TaskDao; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : TagDao.task -> TaskDao
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name="tasks")
@Builder(toBuilder = true)
public class TaskDao implements Serializable {
    private static final long serialVersionUID = -1128682442577176872L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;

    @Column
    String title;

    @Singular
    @OneToMany(mappedBy= "task", cascade = CascadeType.ALL)
    Collection<TagDao> tags;
}
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name="tags")
@Builder(toBuilder = true)
public class TagDao implements Serializable {
    private static final long serialVersionUID = -1128682442577176872L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;

    @Column
    String tag;

    @ManyToOne(optional = false)
    TaskDao task;
}
@Repository
public interface TaskEntityRepository extends JpaRepository<TaskDao, Long> {
}

如果我删除这些行,则保存成功,但断言失败,因为父端的关系为空

taskDao = taskDao.toBuilder()
    .tag(tagDao)
    .build();
2020-09-16 12:09:46.134  INFO 12078 --- [           main] o.s.t.c.transaction.TransactionContext   : Began transaction (1) for test context [DefaultTestContext@18f8cd79 testClass = TaskEntityRepositoryIntegrationTests, testInstance = TaskEntityRepositoryIntegrationTests@33f676f6, testMethod = testRelationshipPersistence@TaskEntityRepositoryIntegrationTests, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@3e2055d6 testClass = TaskEntityRepositoryIntegrationTests, locations = '{}', classes = '{class TasksApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer@50029372 key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@5acf93bb, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@614ca7df, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@6392827e, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@8b05feb1, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@2dc54ad4, org.springframework.boot.test.context.SpringBootTestArgs@1], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@787a4519]; rollback [true]
Hibernate: insert into tasks (id, title) values (null, ?)
Hibernate: select taskdao0_.id as id1_3_, taskdao0_.title as title7_3_ from tasks taskdao0_
2020-09-16 12:09:46.828  INFO 12078 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@18f8cd79 testClass = TaskEntityRepositoryIntegrationTests, testInstance = TaskEntityRepositoryIntegrationTests@33f676f6, testMethod = testRelationshipPersistence@TaskEntityRepositoryIntegrationTests, testException = java.util.NoSuchElementException, mergedContextConfiguration = [MergedContextConfiguration@3e2055d6 testClass = TaskEntityRepositoryIntegrationTests, locations = '{}', classes = '{class TasksApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer@50029372 key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@5acf93bb, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@614ca7df, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@6392827e, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@8b05feb1, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@2dc54ad4, org.springframework.boot.test.context.SpringBootTestArgs@1], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]


java.util.NoSuchElementException
    at java.base/java.util.Collections$EmptyIterator.next(Collections.java:4277)
    at org.hibernate.collection.internal.AbstractPersistentCollection$IteratorProxy.next(AbstractPersistentCollection.java:883)
    at TaskEntityRepositoryIntegrationTests.testRelationshipPersistence(TaskEntityRepositoryIntegrationTests.java:98)

结果是 Lombok 构建器没有像我想的那样更新 Dao,而是替换它,然后导致 ORM 认为它是一个新对象