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 было бы интересно проследить такое поведение, когда это происходит.

Другие вопросы по тегам