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

时间:2013-11-14 16:31:41

标签: sql sql-server database 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>

这是更新的代码部分:

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();

}

更新,以下是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;
}
}

4 个答案:

答案 0 :(得分:6)

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

hibernate.jdbc.batch_size=50

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

答案 1 :(得分:4)

问题

如果你使用Hibernate作为你的ORM,其中一个主要的性能命中是它的&#34;脏检查&#34;机构。

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

Hibernate脏检查在除了少量对象之外的任何东西上都很慢,因为它需要按字段执行&#34;字段&#34;内存中的对象与首次从数据库加载对象时拍摄的快照之间的比较。

Hibernate的脏检查机制的技术细节

您可以阅读更多关于Hibernate的脏检查机制,该字段是按字段&#34;实现的&#34;字段。比较一下:

How does Hibernate detect dirty state of an entity object?

如何在其他ORM中解决问题

一些其他ORM使用的更有效的机制是使用自动生成的&#34;脏标志&#34;属性而不是&#34;字段&#34;比较但传统上只有在ORM中可用,它们使用和促进字节码增强或字节码编织&#39;因为它有时被称为例如。http://datanucleus.org和其他人

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

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

然后在刷新期间,只需要检查脏标志,而不是通过字段比较执行字段比较 - 正如您可以想象的那样,它的速度要快几个数量级。

&#34;字段&#34;的其他负面后果脏检查

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

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

除了在刷新时Hibernate脏检查机制的性能损失之外,这种机制还会增加你的应用程序的额外内存消耗和与实例化和初始化从数据库加载的每个对象的这些快照相关的CPU使用率。 - 根据您的应用程序,可能会达到数千或数百万。

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

  • Hibernate传统上一直在宣传它不需要字节码增强&#34;作为人们评估ORM技术的一项功能
  • Hibernate的字节码增强实现的历史可靠性问题,可能不如从一开始就使用和提升字节码增强的ORM那样成熟
  • 由于推广了反字节码增强功能,有些人仍然害怕使用字节码增强功能。关于在ORM早期使用字节码增强的某些团体向人们灌输的立场和恐惧

这些天字节码增强用于许多不同的事情 - 而不仅仅是持久性。它几乎已成为主流。

答案 2 :(得分:1)

休眠“默认模式”很慢。

它的优点是对象关系映射和一些缓存(但显然它对批量插入不是很有用)。

使用批处理代替http://docs.jboss.org/hibernate/core/4.0/devguide/en-US/html/ch04.html

答案 3 :(得分:1)

旧话题,但今天却遇到了其他问题。我不得不发表关于这个常见问题的消息,不幸的是,这个问题还没有得到很好的理解和记录。太长时间了,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) 

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

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

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@GenericGenerator(strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator")

还有更多的配置可以使它正常工作,但这就是本质。当使用客户端ID生成时,Hibernate将在访问数据库之前设置所有20000对象的ID。而且如先前的答案所示,具有适当的批处理属性,Hibernate将按预期批量进行插入。

不幸的是,身份生成器如此方便和流行,它在所有示例中无处不在,没有清楚说明使用此策略的结果。我读过许多所谓的“高级” Hibernate书籍,但到目前为止,还从未见过一篇解释Identity对大数据集基础插入性能的影响的书。