JPA/休眠批量(批处理)插入

2022-09-01 17:30:39

以下是我在阅读了有关jpa大容量插入的几个主题后创建的简单示例,我有2个持久对象User和Site。一个用户可以拥有多个站点,因此我们在这里有一对多的关系。假设我想创建用户并创建/链接多个站点到用户帐户。以下是代码的外观,考虑到我是否愿意对 Site 对象使用批量插入。

User user = new User("John Doe");

user.getSites().add(new Site("google.com", user));
user.getSites().add(new Site("yahoo.com", user));

EntityTransaction tx = entityManager.getTransaction();
tx.begin();
entityManager.persist(user);
tx.commit();

但是当我运行此代码时(我使用hibernate作为jpa实现提供程序),我看到以下sql输出:

Hibernate: insert into User (id, name) values (null, ?)
Hibernate: call identity()
Hibernate: insert into Site (id, url, user_id) values (null, ?, ?)
Hibernate: call identity()
Hibernate: insert into Site (id, url, user_id) values (null, ?, ?)
Hibernate: call identity()

所以,我的意思是“真正的”批量插入不起作用,或者我很困惑?

这是此示例项目的源代码,这是maven项目,因此您只需下载并运行mvn install即可检查输出。

更新:

在Ken Liu善意地建议之后,我已经禁用了站点对象ID自动生成:

    User user = new User("John Doe");
    user.getSites().add(new Site(1, "google.com", user));
    user.getSites().add(new Site(2, "yahoo.com", user));
    entityManager.setFlushMode(FlushModeType.COMMIT);
    EntityTransaction tx = entityManager.getTransaction();
    tx.begin();
    entityManager.persist(user);
    tx.commit();

现在我在调试输出中有以下行:

DEBUG: org.hibernate.jdbc.AbstractBatcher - 执行批大小:2

它的工作原理!


答案 1

如果使用数据库生成 id,则 Hibernate 必须执行查询以为每个实体生成主键。


答案 2

我发现绕过批量插入的休眠要有效得多。您必须取消 ORM(对象关系映射),但您仍然可以利用与当前会话和事务管理关联的连接。

虽然您暂时失去了ORM的便利性,但收益是显着的,特别是如果您具有本机生成的Id,因为休眠通常会为每个ID执行一个。SELECTINSERT

Session.doWork非常方便地促进这一点。

private MyParentObject saveMyParentObject(final MyParentObject parent, final List<MyChildObject> children)
{
    transaction = session.beginTransaction();
    try
    {
        session.save(parent); // NOTE: parent.parentId assigned and returned here

        session.doWork(new Work()
        {
            public void execute(Connection con) throws SQLException
            {
                // hand written insert SQL - can't use hibernate
                PreparedStatement st = con.prepareStatement("INSERT INTO my_child (parent_id, name, ...) values (?, ?, ...)");

                for (MyChildObject child : children)
                {
                    MyChildObject child = new MyChildObject();
                    child.setParentId(parent.getParentId()); // assign parent id for foreign key

                    // hibernate can't help, determine jdbc parameters manually
                    st.setLong(1, child.getParentId());
                    st.setString(2, child.getName());
                    ...
                    st.addBatch();
                }

                // NOTE: you may want to limit the size of the batch
                st.executeBatch();
            }
        });

        // if your parent has a OneToMany relationship with child(s), refresh will populate this 
        session.refresh(parent);
        transaction.commit();
        return parent;
    }
    catch(Throwable e)
    {
        transaction.rollback();
        throw new RuntimeException(e);
    }   
}

推荐