获取数据时,JPA / Hibernate非常慢

时间:2015-12-30 03:56:07

标签: java sql-server spring hibernate jpa

我有一个使用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);
    }
}

我也改用了香草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列之外的其他两列: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克劳斯中,我选择FOREIGN_TABLE.ACCT_BALANCE,如果上面的左连接没有匹配的行,那么当然会为空。

这个方法是从一个Spring批处理应用程序的Tasklet中调用的,我觉得一旦tasklet完成处理就会给出一个空指针异常,因此,readonly事务被关闭了。

1 个答案:

答案 0 :(得分:2)

在评论中,您指出您的@Joincolumn是或FetchType.EAGER。这非常具有攻击性和你的问题。

Eager表示Hibernate会JOIN FETCH查询所有列中的所有列,解析并加载所有记录,以便在实体的新实例中调度数据。您可能知道,如果您加入表A和B,结果将是一个巨大的A.*, B.* A x B条记录和A重复多次。这就是Eager正在发生的事情。这可能会非常迅速地升级,特别是对于许多Eager列。

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

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