【问题标题】:JPA / Hibernate is very slow when fetching dataJPA / Hibernate在获取数据时非常慢
【发布时间】:2016-04-04 00:35:01
【问题描述】:

我有一个使用 JPA/Hibernate 从 SQL Server 2008 R2 获取数据的 DAO。在我的特定用例中,我正在执行一个简单的 SELECT 查询,该查询返回大约 100000 条记录,但需要超过 35 分钟才能完成。

我通过手动加载 sql server 驱动程序创建了一个基本的 JDBC 连接,并且相同的查询在 15 秒内返回了 100k 条记录。所以我的配置出了点问题。

这是我的 springDataContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

    <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="myDataSource"/>
        <property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
        <property name="persistenceXmlLocation" value="classpath:persistence.xml"/>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.SQLServer2008Dialect</prop>
                <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
                <prop key="hibernate.format_sql">true</prop>
                <prop key="hibernate.hbm2ddl.auto">none</prop>
                <prop key="hibernate.use_sql_comments">false</prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql:false}</prop>
                <prop key="jadira.usertype.autoRegisterUserTypes">true</prop>
                <prop key="jadira.usertype.javaZone">America/Chicago</prop>
                <prop key="jadira.usertype.databaseZone">America/Chicago</prop>
                <prop key="jadira.usertype.useJdbc42Apis">false</prop>
            </props>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

    <tx:annotation-driven/>

    <jpa:repositories base-package="com.mycompany.foo"/>

    <context:component-scan base-package="com.mycompany.foo.impl" />
</beans>

bean myDataSource 由使用 DAO 的任何应用程序提供,其定义如下:

<bean id="myDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
    <property name="url" value="jdbc:sqlserver://mysqlhost.mycompany.com:1433;database=MYDB"/>
    <property name="username" value="username"/>
    <property name="password" value="chucknorris"/>
</bean>

我有一个复杂的查询,我在其中设置fetchSize

package com.mycompany.foo.impl;

import com.mycompany.foo.RecurringLoanPaymentAccountsDao;
import org.hibernate.Query;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.joda.time.DateTime;

import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Named
public class FooDaoImpl implements FooDao {
    @PersistenceContext
    private EntityManager entityManager;

    public ScrollableResults getData(int chunkSize, DateTime tomorrow, DateTime anotherDate) {
        Session session = entityManager.unwrap(Session.class);

        Query query = session.createQuery(
            "from MyAccountTable as account " +
            // bunch of left joins (I am using @JoinColumns in my models)
            "where account.some_date >= :tomorrow " +
            "and account.some_other_date < :anotherDate"
            // <snip snip>
        );

        query.setParameter("tomorrow", tomorrow)
             .setParameter("anotherDate", anotherDate)
             .setFetchSize(chunkSize);

        return query.scroll(ScrollMode.FORWARD_ONLY);
    }
}

我也切换到 vanilla JPA 并使用了 jpaQuery.getResultList(),但这同样慢。

如果需要,我可以提供其他相关代码。关于我在这里做错了什么的任何线索?

更新 1:添加架构详细信息

很遗憾,我在一家银行工作,所以我无法分享确切的代码。但让我试着代表相关的位。

这是我要查询的主表:

@Entity
@Table(name = "MY_TABLE")
public class MyTable {
    @EmbeddedId
    private Id id = new Id();

    @Column(name = "SOME_COL")
    private String someColumn;

    // other columns

    @OneToMany(fetch = FetchType.LAZY)
    @JoinColumns({
        @JoinColumn(name = "ACCT_NBR", referencedColumnName = "ACCT_NBR", insertable = false, updatable = false),
        @JoinColumn(name = "CUST_NBR", referencedColumnName = "CUST_NBR", insertable = false, updatable = false)
    })
    private List<ForeignTable> foreignTable;

    // getter and setter for properties

    public static class Id implements Serializable {
        @Column(name = "ACCT_NBR")
        private short accountNumber;

        @Column(name = "CUST_NBR")
        private short customerNumber;
    }
}

基本上,每个表都有ACCT_NBRCUST_NBR 列(包括外部表),它们在聚集在一起时是唯一的。所以我的加入条件包括这两个。

外部表的模型看起来完全一样(带有和上面的主表一样的嵌入 ID),当然除了 ACCT_NBRCUST_NBR 之外还有自己的一组列。

现在我只关心外部表中除了上面提到的 ID 列之外的另外 2 个列:TYPE_IDACCT_BALANCE

TYPE_ID 是我希望在LEFT JOIN 条件中使用的内容,因此最终的 SQL 如下所示:

LEFT JOIN FOREIGN_TABLE FRN
ON MAIN.ACCT_NBR = FRN.ACCT_NBR
AND MAIN.CUST_NBR = FRN.CUST_NBR
AND FRN.TYPE_ID = <some_static_id>

当这个特定的TYPE_ID 不匹配时,我希望 LEFT JOIN 产生 NULL 数据。

在我的SELECT claus 中,我选择FOREIGN_TABLE.ACCT_BALANCE,如果上面的左连接没有匹配的行,它当然会为空。

这个方法是从一个spring批处理应用程序的Tasklet调用的,我有一种感觉,一旦tasklet完成处理,它就会给出一个空指针异常,因此,只读事务被关闭了。

【问题讨论】:

  • 你能确定你的语句是只读的吗? Hibernate 在其会话中处理大量对象时非常糟糕。另外,检查你的 JDBC 连接是否启用了游标,默认情况下 JDBC 会复制内存中的所有内容,而不是使用数据库的游标
  • 您也应该考虑将 JTDS 作为替代驱动程序。微软提供的有一些严重的bug,而且比JTDS慢很多。
  • 我在调用此 DAO 方法的方法上有一个 @Transactional(readOnly = true) 注释。如何将此语句标记为只读?我是否需要通过删除设置器将实体标记为只读?
  • @Transactional(readOnly = true) 是正确的。 Hibernate 不应该对会话对象进行脏检查。这将大大加快查询速度。
  • 好吧,不幸的是,我已经在里面了。而且它仍然非常缓慢。让我看看JTDS。作为一名 .net 开发人员,这一切对我来说都非常令人生畏:)

标签: java sql-server spring hibernate jpa


【解决方案1】:

在 cmets 中,您指出您的 @Joincolumn 是或 FetchType.EAGER。这是非常激进的问题。

Eager 表示 Hibernate 将 JOIN FETCH 您查询中的所有列,解析并加载所有记录以在您的实体的新实例中调度数据。您可能知道,如果您连接表 A 和 B,结果将是一个巨大的 A.*, B.*,其中 A x B 记录和 A 重复了很多次。这就是Eager 的情况。这可能会迅速升级,尤其是在有许多 Eager 列的情况下。

切换到Lazy 告诉 Hibernate 不加载数据。它只是准备一个Proxy 对象,该对象仅在需要时(如果您的事务仍处于打开状态)调用单独的SELECT

您始终可以使用 JOIN FETCH table 在 HQL 中手动强制使用 FETCH。这是最好的方法。

【讨论】:

  • 现在我有一个不同的问题,当我尝试访问加入的实体时,我得到了 NullPointerException。而且我也不能使用 JOIN FETCH,因为我有一个“with”子句(因为我必须将外部表与其中一列连接起来 = 一些静态值)。有什么想法吗?
  • 这真的取决于你的架构。我没有看到实际代码就瞎了。访问惰性 getter,如果事务打开,则应调用 SELECT。 NullPointerException 不太可能是直接原因。您可以在您的问题中发布更多代码作为附录吗?
  • 我添加了一些细节。很抱歉,我无法发布确切的代码,因为我的公司有针对它的规定。我已尽力解释架构,但如果我遗漏了什么,请告诉我。
  • 并不是真正的平方,您发现了查询缓慢的问题。您只是还没有正确的解决方案。
  • 我已经完全消除了连接。非常感谢您在这个问题上的帮助!有一个美好的新的一年:)
猜你喜欢
  • 1970-01-01
  • 2013-11-27
  • 1970-01-01
  • 2012-07-10
  • 2011-11-08
  • 1970-01-01
  • 2023-04-08
  • 2020-04-11
  • 1970-01-01
相关资源
最近更新 更多