JPA/Hibernate объемная (пакетная) вставка
Вот простой пример, который я создал после прочтения нескольких тем о массовых вставках jpa, у меня есть 2 постоянных объекта: Пользователь и Сайт. У одного пользователя может быть много сайтов, поэтому у нас есть отношения один ко многим. Предположим, я хочу создать пользователя и создать / связать несколько сайтов с учетной записью пользователя. Вот как выглядит код, учитывая мое желание использовать массовую вставку для объектов сайта.
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, чтобы проверить вывод.
ОБНОВЛЕНО:
После любезного совета Кена Лю я отключил автогенерацию идентификатора объекта сайта:
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
Оно работает!
3 ответа
Если вы используете базу данных для генерации идентификаторов, то Hibernate должен выполнить запрос для генерации первичного ключа для каждого объекта.
Я обнаружил, что гораздо эффективнее обходить спящий режим для массовых вставок. Вы должны отказаться от ORM (реляционное сопоставление объектов), но вы все равно можете использовать соединение, связанное с текущим сеансом и управлением транзакциями.
В то время как вы временно теряете удобство своего ORM, отдача значительна, особенно если у вас есть собственные сгенерированные идентификаторы, поскольку hibernate обычно выполняет один SELECT
для каждого INSERT
,
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);
}
}
Я написал небольшой блог, в котором рассказывается о полученных пакетных вставках, а также имеется указатель на небольшой проект, в котором есть все необходимые конфигурации для начала пакетной вставки с помощью Hibernate. Подробности смотрите по адресу http://sensiblerationalization.blogspot.com/2011/03/quick-tip-on-hibernate-batch-operation.html