首页 > 解决方案 > 附加新的 OneToMany 实体时,瞬态字段丢失

问题描述

我有两个通过 OneToMany 关系链接的实体:

@Entity
@Table(name="bookcase")
public class BookCase {

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

    @Transient
    @Getter @Setter private Long oldId;

    /*
    https://vladmihalcea.com/a-beginners-guide-to-jpa-and-hibernate-cascade-types/
    */
    @OneToMany(mappedBy = "bookCase", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<Bookshelf> bookShelves = new HashSet<>();

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public Set<Bookshelf> getBookShelves() { return bookShelves; }
    public void setBookShelves(Set<Bookshelf> bookShelves) { this.bookShelves = bookShelves; }
}



@Entity
@Table(name="bookshelf")
public class Bookshelf {
    private static final Logger log = LoggerFactory.getLogger(Bookshelf.class);
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Transient
    @Getter @Setter private Long oldId;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "bookcase_id")
    private BookCase bookCase;

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }


    public BookCase getBookCase() { return bookCase; }

    public void setBookCase(BookCase bookCase) {
        this.bookCase = bookCase;
        bookCase.getBookShelves().add(this);
    }

    @Transient
    @Setter private OldIdListener oldIdListener;

    /*
    When the id is saved, listening DTOs can update their ids
    */
    @PostPersist
    public void triggerOldId() {
        log.info("Postpersist triggered for {}", id);
        if (oldIdListener != null) {
            oldIdListener.updateId(oldId, id);
        }
    }
}

public interface OldIdListener {
    void updateId(long oldId, long newId);
}

以下测试失败:

@Test
public void testThatCascadingListenerIsTriggered() {

    var mock = mock(OldIdListener.class);
    var mock2 = mock(OldIdListener.class);
    var mock3 = mock(OldIdListener.class);

    var bookcase = new BookCase();

    var shelf1 = new Bookshelf();
    shelf1.setOldId(-5L);
    shelf1.setBookCase(bookcase);
    shelf1.setOldIdListener(mock);

    var shelf2 = new Bookshelf();
    shelf2.setOldId(-6L);
    shelf2.setBookCase(bookcase);
    shelf2.setOldIdListener(mock2);

    var saved = bookCaseRepository.save(bookcase);

    verify(mock).updateId(eq(-5L), anyLong());
    verify(mock2).updateId(eq(-6L), anyLong());


    var savedBookCase = bookCaseRepository.findById(saved.getId()).get();

    assertThat(savedBookCase.getBookShelves()).hasSize(2);


    var shelf3 = new Bookshelf();
    shelf3.setOldId(-10L);
    shelf3.setBookCase(savedBookCase);
    shelf3.setOldIdListener(mock3);

    savedBookCase.getBookShelves().add(shelf3);
    bookCaseRepository.save(savedBookCase);
    verify(mock3).updateId(eq(-10L), anyLong());
}

mock3 永远不会被调用。在调试代码时,我可以看到当在对象架子 3 上调用方法时,瞬态字段oldIdoldIdListener设置为空,而不是架子 1 和 2。@PostPersist

我认为这是因为我正在修改 Set 对象;但该对象被正确持久化,它只是丢失了所有瞬态字段。第一次持久化整个树时不会发生这种情况。

这是将新元素插入 OneToMany 集合的错误方法还是这里的错误在哪里?

我正在使用 Spring Boot 2.1。

谢谢!

标签: javaspringspring-bootjpaspring-data-jpa

解决方案


带有@Transient 注解的字段不会持久化到数据库中,所以如果要持久化,必须去掉@Transient。


推荐阅读