带有HIBERNATE的JPA插入速度非常慢

Yel*_*uck 1 sql database sql-server hibernate jpa

我试图通过使用JAP和HIBERNATE向SQL Server 2008 R2插入一些数据.一切都"有效",除非它非常慢.要插入20000行,大约需要45秒,而C#脚本大约需要不到1秒.

这个域名中的任何退伍军人都可以提供一些帮助吗?我会很感激.

更新:从下面的答案得到了一些很好的建议,但它仍然没有按预期工作.速度是一样的.

这是更新的persistence.xml:

<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="ClusterPersist"
    transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>cluster.data.persist.sqlserver.EventResult</class>
    <exclude-unlisted-classes>true</exclude-unlisted-classes>
    <properties>
        <property name="javax.persistence.jdbc.url"
            value="jdbc:sqlserver://MYSERVER:1433;databaseName=MYTABLE" />
        <property name="javax.persistence.jdbc.user" value="USER" />
        <property name="javax.persistence.jdbc.password" value="PASSWORD" />
        <property name="javax.persistence.jdbc.driver"
            value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
        <property name="hibernate.show_sql" value="flase" />
        <property name="hibernate.hbm2ddl.auto" value="update" />

        <property name="hibernate.connection.provider_class"
            value="org.hibernate.service.jdbc.connections.internal.C3P0ConnectionProvider" />

        <property name="hibernate.c3p0.max_size" value="100" />
        <property name="hibernate.c3p0.min_size" value="0" />
        <property name="hibernate.c3p0.acquire_increment" value="1" />
        <property name="hibernate.c3p0.idle_test_period" value="300" />
        <property name="hibernate.c3p0.max_statements" value="0" />
        <property name="hibernate.c3p0.timeout" value="100" />
        <property name="hibernate.jdbc.batch_size" value="50" />
        <property name="hibernate.cache.use_second_level_cache" value="false" />
    </properties>
</persistence-unit>
Run Code Online (Sandbox Code Playgroud)

这是更新的代码部分:

public static void writeToDB(String filePath) throws IOException {

    EntityManager entityManager = entityManagerFactory.createEntityManager();
    Session session = (Session) entityManager.getDelegate();
    Transaction tx = session.beginTransaction();
    int i = 0;

    URL filePathUrl = null;
    try {
        filePathUrl = new URL(filePath);
    } catch (MalformedURLException e) {
        filePathUrl = (new File(filePath)).toURI().toURL();
    }

    String line = null;
    BufferedReader stream = null;

    try {
        InputStream in = filePathUrl.openStream();
        stream = new BufferedReader(new InputStreamReader(in));


        // Read each line in the file
        MyRow myRow = new MyRow();
        while ((line = stream.readLine()) != null) {
            String[] splitted = line.split(",");
            int num1 = Integer.valueOf(splitted[1]);
            float num2= Float.valueOf(splitted[6]).intValue();

            myRow.setNum1(num1);
            myRow.setNum2(num2);

            session.save(myRow);

            if (i % 50 == 0) { 
                session.flush();
                session.clear();
            }

            i++;

        }
        tx.commit();

    } finally {
        if (stream != null)
            stream.close();
    }
    session.close();

}
Run Code Online (Sandbox Code Playgroud)

更新了,这是MyRow的来源:

@Entity
@Table(name="MYTABLE")
public class MyRow {    

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY) 
private Long id;

@Basic
@Column(name = "Num1")
private int Num1;

@Basic
@Column(name = "Num2")
private float Num2;

public Long getId() {
    return id;
}

public void setId(Long id) {
    this.id = id;
}

public float getNum1() {
    return Num1;
}

public void setNum1(float num1) {
    Num1 = num1;
}

public int getNum2() {
    return Num2;
}

public void setNum2(int num2) {
    Num2 = num2;
}
}
Run Code Online (Sandbox Code Playgroud)

Ell*_*sch 6

要启用JDBC批处理,您应该将属性hibernate.jdbc.batch_size初始化为10到50之间(仅限int)

hibernate.jdbc.batch_size=50
Run Code Online (Sandbox Code Playgroud)

如果它仍然没有预期的那么快,那么我将审查上面的文件,注意注意事项和4.1节.特别是那些说"如果你使用身份标识符生成器,​​Hibernate透明地禁用JDBC级别的插入批处理".


Vol*_*man 6

问题

如果您使用 Hibernate 作为 ORM,那么主要的性能影响之一是其“脏检查”的实现方式(因为如果没有字节码增强,这是所有基于 JDO 的 ORM 和其他一些标准中的标准,脏检查将始终是一种低效的黑客攻击)。

刷新时,需要对会话中的每个对象执行脏检查以查看它是否“脏”,即自数据库加载以来,其属性之一已更改。对于所有“脏”(已更改)对象,Hibernate 必须生成 SQL 更新以更新表示脏对象的记录。

Hibernate 脏检查在除少量对象之外的任何对象上都非常缓慢,因为它需要在内存中的对象与首次从数据库加载对象时拍摄的快照之间执行“逐字段”比较。对象越多,比如说,一个 HTTP 请求加载以显示一个页面,那么在调用 commit 时需要的脏检查就越多。

Hibernate 脏检查机制的技术细节

您可以在此处阅读有关 Hibernate 的脏检查机制的更多信息,该机制实现为“逐字段”比较:

Hibernate 如何检测实体对象的脏状态?

问题在其他 ORM 中是如何解决的

其他一些 ORM 使用的一种更有效的机制是使用自动生成的“脏标志”属性而不是“逐字段”比较,但这传统上仅在使用和提升字节的 ORM(通常是基于 JDO 的 ORM)中可用代码增强或字节码“编织”,有时被称为例如http://datanucleus.org

在字节码增强期间,通过 DataNucleus 或任何其他支持此功能的 ORM,每个实体类都被增强为:

  • 添加隐式脏标志属性
  • 将代码添加到类中的每个 setter 方法中,以在调用时自动设置脏标志

然后在刷新期间,只需要检查脏标志,而不是逐个字段进行比较 - 正如您想象的那样,这要快几个数量级。

“逐个字段”脏检查的其他负面后果

Hibernate 脏检查的另一个低效率是需要在内存中保存每个加载对象的快照,以避免在脏检查期间重新加载和检查数据库。

每个对象快照都是其所有字段的集合。

除了刷新时 Hibernate 脏检查机制的性能影响之外,此机制还会给您的应用带来额外的内存消耗和 CPU 使用量,这些额外的内存消耗和 CPU 使用量与实例化和初始化从数据库加载的每个单个对象的这些快照相关联 - 这可以根据您的应用程序运行到数千或数百万。

Hibernate 引入了字节码增强来解决这个问题,但我已经在许多 ORM 持久项目(Hibernate 和非 Hibernate)上工作过,我还没有看到使用该功能的 Hibernate 持久项目,可能是由于多种原因:

  • Hibernate 传统上将其“不需要字节码增强”作为人们评估 ORM 技术时的一项功能
  • Hibernate 的字节码增强实现的历史可靠性问题,它可能不如从一开始就使用和促进字节码增强的 ORM 成熟
  • 一些人仍然害怕使用字节码增强,因为反对“字节码增强”的立场以及某些群体在 ORM 早期灌输人们对使用字节码增强的恐惧

如今,字节码增强用于许多不同的事情——不仅仅是持久性。它几乎成为主流。


小智 5

老话题,但今天遇到了这个寻找别的东西。我不得不在这个常见问题上发帖,不幸的是,这个问题并没有得到很好的理解和记录。很长一段时间,Hibernate 的文档只有上面发布的简短说明。从第 5 版开始,有一个更好但仍然很薄的解释:https : //docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#identifiers-generators-identity

非常大的集合插入慢的问题只是Id生成策略选择不当:

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY) 
Run Code Online (Sandbox Code Playgroud)

使用标识策略时,需要理解的是数据库服务器在物理插入上创建行的标识。Hibernate 需要知道分配的 Id 以使对象在会话中处于持久状态。数据库生成的 Id 仅在插入的响应中是已知的。Hibernate 别无选择,只能执行 20000 次单独插入以检索生成的 Id。据我所知,它不适用于批处理,不适用于 Sybase,不适用于 MSSQL。这就是为什么,无论您多么努力地尝试并正确配置了所有批处理属性,Hibernate 都会执行单独的插入。

我知道并多次应用的唯一解决方案是选择客户端 Id 生成策略,而不是流行的数据库端 Identity 策略。我经常使用:

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@GenericGenerator(strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator")
Run Code Online (Sandbox Code Playgroud)

有更多的配置可以让它工作,但这就是它的本质。当使用客户端 Id 生成时,Hibernate 将在访问数据库之前设置所有 20000 个对象的 Id。并且在前面的答案中看到适当的批处理属性,Hibernate 将按预期批量插入。

不幸的是,身份生成器如此方便和流行,它出现在所有示例中,但没有明确说明使用此策略的后果。我阅读了许多所谓的“高级”Hibernate 书籍,但迄今为止从未见过解释 Identity 对大型数据集的基础插入性能的影响的书籍。