Spring Data JPA: пакетная вставка для вложенных объектов
У меня есть тестовый случай, когда мне нужно сохранить 100000 экземпляров сущностей в базе данных. Код, который я сейчас использую, делает это, но до сохранения всех данных в базе данных требуется до 40 секунд. Данные считываются из файла JSON, размер которого составляет около 15 МБ.
Теперь я уже реализовал метод пакетной вставки в другом хранилище ранее для другого проекта. Тем не менее, в этом случае у меня было много сущностей высшего уровня для сохранения, только с несколькими вложенными сущностями.
В моем текущем случае у меня есть 5 Job
объекты, которые содержат список около ~30 JobDetail
юридические лица. Один JobDetail
содержит от 850 до 1100 JobEnvelope
юридические лица.
При записи в базу данных я фиксирую Список Job
сущности по умолчанию save(Iterable<Job> jobs)
интерфейсный метод. Все вложенные сущности имеют CascadeType PERSIST
, У каждой сущности есть своя таблица.
Обычный способ включить пакетную вставку состоит в реализации пользовательского метода, такого как saveBatch
который вспыхивает время от времени. Но моей проблемой в этом случае являются JobEnvelope
юридические лица. Я не сохраняю их с JobEnvelope
хранилище, вместо этого я позволю хранилище Job
сущность справиться с этим. Я использую MariaDB в качестве сервера базы данных.
Поэтому мой вопрос сводится к следующему: Как я могу сделать JobRepository
вставить вложенные объекты в пакетном режиме?
Это мои 3 предмета в вопросе:
работа
@Entity
public class Job {
@Id
@GeneratedValue
private int jobId;
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST, mappedBy = "job")
@JsonManagedReference
private Collection<JobDetail> jobDetails;
}
JobDetail
@Entity
public class JobDetail {
@Id
@GeneratedValue
private int jobDetailId;
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
@JoinColumn(name = "jobId")
@JsonBackReference
private Job job;
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST, mappedBy = "jobDetail")
@JsonManagedReference
private List<JobEnvelope> jobEnvelopes;
}
JobEnvelope
@Entity
public class JobEnvelope {
@Id
@GeneratedValue
private int jobEnvelopeId;
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
@JoinColumn(name = "jobDetailId")
private JobDetail jobDetail;
private double weight;
}
2 ответа
Убедитесь, что вы правильно настроили свойства, связанные с пакетами Hibernate:
<property name="hibernate.jdbc.batch_size">100</property>
<property name="hibernate.order_inserts">true</property>
<property name="hibernate.order_updates">true</property>
Дело в том, что последовательные операторы могут быть пакетированы, если они манипулируют одной и той же таблицей. Если оператор выполняет вставку в другую таблицу, предыдущий пакетный режим должен быть прерван и выполнен перед этим оператором. С hibernate.order_inserts
свойство, которое вы даете Hibernate для изменения порядка вставок перед построением пакетных операторов (hibernate.order_updates
имеет тот же эффект для операторов обновления).
jdbc.batch_size
максимальный размер пакета, который будет использовать Hibernate. Попробуйте проанализировать различные значения и выберите тот, который показывает наилучшую производительность в ваших случаях использования.
Обратите внимание, что пакетирование операторов вставки отключено, если IDENTITY
используется генератор идентификаторов.
Специфично для MySQL, вы должны указать rewriteBatchedStatements=true
как часть URL соединения. Чтобы убедиться, что пакетирование работает должным образом, добавьте profileSQL=true
для проверки SQL драйвер отправляет в базу данных. Подробнее здесь.
Если ваши сущности версионированы (для целей оптимистической блокировки), то для использования пакетных обновлений (не влияет на вставки) вам также необходимо включить:
<property name="hibernate.jdbc.batch_versioned_data">true</property>
С помощью этого свойства вы сообщаете Hibernate, что драйвер JDBC способен возвращать правильное количество затронутых строк при выполнении пакетного обновления (необходимого для проверки версии). Вы должны проверить, работает ли это правильно для вашей базы данных / драйвера jdbc. Например, он не работает в Oracle 11 и более ранних версиях Oracle.
Вам также может потребоваться очистить и очистить контекст постоянства после каждого пакета, чтобы освободить память, в противном случае все управляемые объекты остаются в контексте постоянства, пока он не будет закрыт.
Кроме того, этот блог может оказаться полезным, так как он хорошо объясняет детали механизма пакетной обработки Hibernate.
Чтобы завершить предыдущий ответ Драгана Бозановича. Hibernate иногда молча деактивирует порядок выполнения пакетов, если, например, он сталкивается с циклическими отношениями между сущностями при построении графа зависимостей между пакетами (см. метод InsertActionSorter.sort(..)). Для hibernate было бы интересно проследить такое поведение, когда это происходит.