2

我正在尝试使用 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

4 回答 4

7

问题

如果您使用 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 使用负担,这些消耗与实例化和初始化从数据库加载的每个对象的这些快照相关联 - 这可以根据您的应用程序运行到数千或数百万。

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

  • 当人们评估 ORM 技术时,Hibernate 传统上将其“无需字节码增强”作为一个特性来宣传
  • Hibernate 的字节码增强实现的历史可靠性问题可能不如从一开始就使用和促进字节码增强的 ORM 成熟
  • 由于提倡反“字节码增强”的立场,以及某些团体在 ORM 早期向人们灌输对使用字节码增强的恐惧,一些人仍然害怕使用字节码增强

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

于 2017-02-13T19:55:42.880 回答
6

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

hibernate.jdbc.batch_size=50

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

于 2013-11-14T16:41:18.077 回答
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) 

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

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

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

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

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

于 2018-07-31T14:38:33.107 回答
2

休眠“默认模式”很慢。

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

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

于 2013-11-14T16:36:17.070 回答