使用瞬态对象指定用于保存 ManyToOne 关系的 ID

Using transient objects to specify ids for saving ManyToOne relations

提问人:Cloud 提问时间:7/11/2023 更新时间:7/11/2023 访问量:125

问:

我想通过将大量Spring JPA实体批量导入数据库来保存它们。 某些实体与其他实体具有 ManyToOne 关系。 由于存在大量数据,我不希望跟踪内存中所有相关实体,以便将其设置为其他实体的 ManyToOne 关系。我只有关系的 id,而不是持久化的实体。

无论如何,我都遇到过几次以下建议作为设置关系的解决方案:

{
@ManyToOne
@JoinColumn(name = "author_id")
private Author author;
...
    public void setAuthorId(Long authorId) {
    Author author = new Author();
    author.setId(authorId);
    this.author = author;
}

因此,使用瞬态对象作为占位符,将一个实体与另一个实体相关联。(假设相关对象被保存为传递给 saveAll() 调用的其他对象之一)

我在官方 Spring 文档中根本没有看到这种方法的参考。

这是否被认为是仅基于 id 保存关系的受支持方式,还是您说这只是一个肮脏的黑客?

spring-data-jpa 多对一 瞬态

评论

3赞 Davide D'Alto 7/11/2023
正确的方法是使用 getReference: baeldung.com/jpa-entity-manager-get-reference
0赞 Cloud 7/11/2023
我们确实尝试了 getReference 方法,但它导致了“无法找到实体”错误和保存行为的慢得多,因为如果我们将保存实体和引用它们的实体合并在同一个 saveAll() 中,getReference 调用会导致在保存实体时执行提交和 SELECTS(导致合并)
0赞 Davide D'Alto 7/11/2023
本文对此进行了解释,但如果你在引用的实体上调用一个 setter/getter,Hibernate 将首先加载它。如果不对引用的实体执行任何操作,则不会运行选择。
0赞 Cloud 7/11/2023
getReference 返回的任何引用都没有调用 setter 或 getter,但 save 方法中的合并仍然会导致问题,这就是我们停止该尝试的原因。

答:

1赞 v.ladynev 7/11/2023 #1

使用瞬态对象作为占位符

我的团队在生产中使用这种方法,经常使用 Hibernate(没有 Spring Data)。没有任何问题。

缺点

Spring-data 使用内部存储库方法。Hibernate 将生成额外的(不必要的)数据库查询,以更新瞬态对象状态。merge()save()SELECT

您可以将自定义存储库与方法一起使用,以避免额外的查询。描述如下:persist()/update()SELECT

最好的Spring Data JpaRepository

使用 getReferenceById() (自 2.7 起) / getReference() 方法

我们经常使用这种方法。如果获取引用并使用相同的持久性上下文(范围)保存实体,则不会导致任何其他数据库查询。@Transactional

缺点

我们需要一个存储库来使用方法。例如,不能在实体的瞬态实用方法中使用此方法,如 .getReferenceById()setAuthorId()

使用无状态会话

Hibernate 的 StatelessSession – 它是什么以及如何使用它

在 JdbcTemplate 中使用自定义存储库

要插入大量记录,最好将低级批处理与 SQL 一起使用。我们在生产中经常使用这种方法。如果您使用 MySQL,请不要忘记添加到连接 URL。rewriteBatchedStatements=true

@Repository
class NumbersRepositoryImpl implements NumbersRepositoryCustom {

    private static final int BATCH_SIZE = 1000;

    @PersistenceContext
    private EntityManager entityManager;

    private final JdbcTemplate template;

    @Autowired
    public NumbersRepositoryImpl(JdbcTemplate template) {
        this.template = template;
    }

    @Override
    public void batchSave(List<Number> numbers) {
        final String sql = "INSERT INTO NUMBERS " +
                "(number) " +
                "VALUES (?)";

        template.execute(sql, (PreparedStatementCallback<Void>) ps -> {
            int recordsCount = 0;

            for (Number number : numbers) {
                ps.setString(1, number.getValue());
                ps.addBatch();

                recordsCount++;
                if (recordsCount % BATCH_SIZE == 0) {
                    ps.executeBatch();
                }
            }

            ps.executeBatch();
            return null;
        });
    }

}