web-dev-qa-db-ja.com

JPA / Hibernateバルク(バッチ)インサート

これは、jpaバルク挿入に関するいくつかのトピックを読んだ後に作成した簡単な例です。2つの永続オブジェクトUserとSiteがあります。 1人のユーザーが多くのサイトを持つことができるため、ここでは1対多の関係があります。ユーザーを作成し、いくつかのサイトを作成/ユーザーアカウントにリンクしたいとします。 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();

しかし、このコードを実行すると(jpa実装プロバイダーとしてhibernateを使用しています)、次の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が親切に助言した後、Siteオブジェクト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();

今、私はデバッグ出力に次の行があります:

デバッグ:org.hibernate.jdbc.AbstractBatcher-実行中のバッチサイズ:2

できます!

25
abovesun

データベースを使用してIDを生成している場合、Hibernateはクエリを実行して各エンティティの主キーを生成する必要があります。

20
Ken Liu

一括挿入の場合は休止状態をバイパスする方がはるかに効率的です。 ORM(オブジェクトリレーショナルマッピング)を廃止する必要がありますが、現在のセッションに関連付けられている接続とトランザクション管理を引き続き利用できます。

ORMの利便性が一時的に失われますが、特にhibernateがSELECTごとに1つのINSERTを実行するため、ネイティブにIDを生成した場合は特に、大きなメリットがあります。

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);
    }   
}
6
pstanton

私は、バッチ挿入の落とし穴について説明し、Hibernateを使用したバッチ挿入を開始するために必要なすべての適切な構成を持つ小さなプロジェクトへのポインターを持つ短いブログを書きました。詳細は http://sensiblerationalization.blogspot.com/2011/03/quick-tip-on-hibernate-batch-operation.html を参照してください

5
prabhat jha