首页 > 解决方案 > 使用大量数据提高休眠插入性能的最佳方法

问题描述

我正在处理一个使用 Hibernate 来处理对象关系阻抗不匹配的 Web 应用程序。我需要尽快在 MySQL 数据库中插入近 100 万个元组。我需要插入的数据包含一个从数据库自动生成的字段,所以我设置我的 POJO 类如下:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Datum {

@Id
@GeneratedValue(strategy = GenerationType.TABLE)
@Basic(optional = false)
private Integer idDatum;

private Long timestamp;
private Float temperature;
private Float pressure;
private Float humidity;
private Float rain;
private Float windModule;
private String windDirection;

@ManyToOne
@JoinColumn(name="idStation")
private Station station;

public Datum() {}

public Datum(Long timestamp, Float temperature, Float pressure, Float humidity, Float rain, Float windModule, String windDirection) {
    this.timestamp = timestamp;
    this.temperature = temperature;
    this.pressure = pressure;
    this.humidity = humidity;
    this.rain = rain;
    this.windModule = windModule;
    this.windDirection = windDirection;
}

// getter and setter not reported for brevity

}

上面的类是abstract因为它专精于多个类,这里​​举一个专精的例子:

@Entity
@Table(name = "DatumCountry")
public class DatumCountry extends Datum {

private Float dewPoint;

public DatumCountry() {}

public DatumCountry(Long timestamp, Float temperature, Float pressure, Float humidity, Float rain, Float windModule, String windDirection, Float dewPoint) {
    super(timestamp,temperature,pressure,humidity,rain,windModule,windDirection);
    this.dewPoint = dewPoint;
}

// getter and setter not reported for brevity

}

我正在使用批处理在数据库中插入元组,所以插入的代码是:

Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tr = session.beginTransaction();
int i = 0;
Integer batchSize = Integer.parseInt(new Configuration().configure().getProperty("hibernate.jdbc.batch_size"));
for (Object datum : data) {
     i++;
     session.persist(datum);
     if (i % batchSize == 0) {
         session.flush();
         session.clear();
     }
}
try {
    tr.commit();
} catch (RollbackException e) {
    System.err.println(e.getMessage());
    tr.rollback();
} finally {
    session.close();
}

hibernate.jdbc.batch_size设置为 50。

执行上面的代码,在服务器日志中我得到了以下 50 次:

Hibernate: select tbl.next_val from hibernate_sequences tbl where tbl.sequence_name=? for update
Hibernate: update hibernate_sequences set next_val=?  where next_val=? and sequence_name=?

然后,我得到了 50 次:

Hibernate: insert into DatumCountry (humidity, pressure, rain, idStation, temperature, timestamp, windDirection, windModule, dewPoint, idDatum) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

它一直这样做,直到所有元组都被插入。

显然,一切正常,但问题是它太慢了(插入 100 万个元组需要半个小时)。

我的猜测是,由于 id 是由数据库自动生成的,因此选择和更新是为了保持休眠和数据库本身的同步,但是执行这两个查询会导致性能损失很大。

在这一点上,我的问题是:有没有什么方法可以通过 Hibernate 插入大量数据而不执行这两个查询(从而提高性能)?

编辑:

该问题与运行我的应用程序的机器无关。我发现这篇文章解释了我的问题,显然,如何解决它。

修改该文章中所示的代码,引发了一个带有 root cause 的异常org.hibernate.MappingException: Cannot use identity column key generation with <union-subclass> mapping for: Model.DatumCountry

好像id的自动生成和代码的继承结构有些问题……

标签: javamysqlhibernatetomcat

解决方案


几天后,我设法为我的问题找到了最佳解决方案。我将在这里发布我是如何解决问题的,以避免将来给其他人带来痛苦的时间。

auto-increment如果主要目标是获得高性能,那么从数据库(在我的情况下是 of )和 Hibernate中自动生成的 id 似乎MySQL并不能真正相处。

出于这个原因,我决定重新考虑数据库结构并将DatumCountry表的主键从 Hibernate 仅对数据库执行查询即可知道的内容更改为 Hibernate 无需访问数据库即可知道的内容。在我的情况下timestampidStation足以唯一标识一个元组。

完成此优化后,我的应用程序的性能提高了很多,从半个多小时(实际上几乎是一个小时)到100 万个元组的大约 63 秒。这个结果也得益于批量插入。

无论如何,感谢任何试图帮助我的人。


推荐阅读