Hibernate级联持久性:保存子对象,但未分配ID(仅在一种情况下)

时间:2018-06-27 04:22:20

标签: java hibernate spring-boot spring-data-jpa

我正在与Hibernate一起启动一个小型Spring Boot项目,并创建了一个父实体GameDefinition,该父实体将管理其子项的持久性:

@Entity
@Table(name = "game")
public class GameDefinition {

    @Id
    @Column(name = "game_id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    // other fields

    @OneToMany(mappedBy = "gameDefinition", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    private Collection<GamePointDefinition> pointDefinitions = new ArrayList<>();

    // getters and setters

    public void addGamePointDefinition(GamePointDefinition entity) {
        this.pointDefinitions.add(entity);
        entity.setGameDefinition(this);
    }
}

及其子实体GamePointDefinition

@Entity
@Table(name = "game_pointdef")
public class GamePointDefinition {

    @Id
    @Column(name = "game_pointdef_id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "game_id", nullable = false)
    private GameDefinition gameDefinition;

    // other fields

    // getters and setters

    public void setGameDefinition(GameDefinition gameDefinition) {
        this.gameDefinition = gameDefinition;
    }
}

我使用Spring Data Repository对象创建了一个存储库组件:

public interface GameDefinitionRepository extends Repository<GameDefinition, Long> {
    Optional<GameDefinition> findById(Long id);
    GameDefinition save(GameDefinition entity);
}

我使用SpringRunner设置了一些单元测试,并将H2用作数据库(Hibernate生成模式)

@RunWith(SpringRunner.class)
@SpringBootTest(classes = { Application.class, TestConfiguration.class })
public class GameDefinitionRepositoryTest {

    @Autowired
    private GameDefinitionRepository repository;

    @Autowired // util bean defined in TestConfiguration
    private IntegrationUtil integrationUtil;

    @Before
    public void init() {
        integrationUtil.truncateDatabase();
    }

    // other passing tests

    @Test
    public void testCascadeAddGamePointDefinitions() {
        GameDefinition saved = new GameDefinition();
        saved.setName(...);  // set other fields

        GamePointDefinition point1 = new GamePointDefinition();
        point1.setName(...);  // set other fields
        GamePointDefinition point2 = new GamePointDefinition();
        point2.setName(...);  // set other fields
        saved.addGamePointDefinition(point1);
        saved.addGamePointDefinition(point2);

        saved = repository.save(saved);

        GameDefinition fetched = repository.findById(saved.getId()).get();
        assertEquals(2, fetched.getPointDefinitions().size());
        assertThat(fetched.getPointDefinitions(), hasItem(hasProperty("id", is(point1.getId()))));
        assertThat(fetched.getPointDefinitions(), hasItem(hasProperty("id", is(point2.getId()))));

        GamePointDefinition point3 = new GamePointDefinition();  // (**)
        point3.setName(...);  // set other fields
        saved.addGamePointDefinition(point3);

        saved = repository.save(saved);

        fetched = repository.findById(saved.getId()).get();
        assertEquals(3, fetched.getPointDefinitions().size());
        assertThat(fetched.getPointDefinitions(), hasItem(hasProperty("id", is(point1.getId()))));
        assertThat(fetched.getPointDefinitions(), hasItem(hasProperty("id", is(point2.getId()))));
        assertThat(fetched.getPointDefinitions(), hasItem(hasProperty("id", is(point3.getId()))));  // (*)
    }
}

运行测试类时,我的testCascadeAddGamePointDefinitions失败,并出现以下错误:

java.lang.AssertionError: 
Expected: a collection containing hasProperty("id", is null)
     but: property 'id' was <5L>, property 'id' was <6L>, property 'id' was <7L>
    at ....

在由// (*)标记的行中引发了错误。这意味着point3对象已保存,并且在数据库中具有ID,但是对象中的id仍然为空。奇怪的是point1point2对象确实得到了DB {em>设置的id 。 (在调试器中确认)。

此外,使用获取的父级版本而不是持久化的第一个对象(即,位于saved = fetched;之前的行中的// (**))会在同一位置返回相同的错误。

调试时,我发现point1point2插入了原始的ArrayList,并且在save之后,ArrayList变成了PersistentBag包含原始的point1point2实例。但是在第二次保存中,PersistentBag的内容成为3个新对象,(在内存中)与我在测试中实例化的对象不同。

为什么每种情况下的行为都不一样?是否可以保存ID并将其分配给point3对象?

PD:测试application.properties

# Database configuration
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=sa

# Hibernate configuration
spring.jpa.hibernate.ddl-auto=create

0 个答案:

没有答案