【发布时间】: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_NBR 和CUST_NBR 列(包括外部表),它们在聚集在一起时是唯一的。所以我的加入条件包括这两个。
外部表的模型看起来完全一样(带有和上面的主表一样的嵌入 ID),当然除了 ACCT_NBR 和 CUST_NBR 之外还有自己的一组列。
现在我只关心外部表中除了上面提到的 ID 列之外的另外 2 个列: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 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