我有一个使用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_NBR
和CUST_NBR
列(包括外表),这些列在聚集在一起时是唯一的。所以我的加入条件包括这两个。
外表的模型看起来完全相同(使用和嵌入的ID,如上面的主表),当然除了ACCT_NBR
和CUST_NBR
之外还有自己的一组列。
现在我只关心外表中除了上面提到的ID列之外的其他两列:TYPE_ID
和ACCT_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事务被关闭了。
答案 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
。这是更好的方式。