Performance 使用Spring3.1、Hibernate4.1、Dbunit等改进数据库测试的性能
我目前正在启动一个新项目,我已经有大约190个存储库测试。我注意到的一件事是,针对HSQLDB(2.2.8)的集成测试运行速度比我认为的慢得多,我也不完全清楚为什么会发生这种情况 我想我已经跟踪了瓶颈,直到每次测试前插入数据。对于大多数测试,设置数据库的时间范围为.15到.38秒。这是不能接受的。我会想象内存中的数据库会快得多:( 以下是数据库测试类,我的所有存储库测试都从该类扩展而来:Performance 使用Spring3.1、Hibernate4.1、Dbunit等改进数据库测试的性能,performance,spring,hibernate,unit-testing,dbunit,Performance,Spring,Hibernate,Unit Testing,Dbunit,我目前正在启动一个新项目,我已经有大约190个存储库测试。我注意到的一件事是,针对HSQLDB(2.2.8)的集成测试运行速度比我认为的慢得多,我也不完全清楚为什么会发生这种情况 我想我已经跟踪了瓶颈,直到每次测试前插入数据。对于大多数测试,设置数据库的时间范围为.15到.38秒。这是不能接受的。我会想象内存中的数据库会快得多:( 以下是数据库测试类,我的所有存储库测试都从该类扩展而来: @ContextConfiguration(locations = {"classpath:applicat
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
@TransactionConfiguration(defaultRollback=true)
@Transactional
public abstract class DatabaseTest {
public static final String TEST_RESOURCES = "src/test/resources/";
@Autowired
protected SessionFactory sessionFactory;
@Autowired
protected UserRepository userRepository;
@Autowired
protected DataSource dataSource;
protected IDatabaseTester databaseTester;
protected Map<String, Object> jdbcMap;
protected JdbcTemplate jdbcTemplate;
@PostConstruct
public void initialize() throws SQLException, IOException, DataSetException {
jdbcTemplate = new JdbcTemplate(dataSource);
setupHsqlDb();
databaseTester = new DataSourceDatabaseTester(dataSource);
databaseTester.setSetUpOperation(DatabaseOperation.CLEAN_INSERT);
databaseTester.setTearDownOperation(DatabaseOperation.NONE);
databaseTester.setDataSet(getDataSet());
}
@Before
public void insertDbUnitData() throws Exception {
long time = System.currentTimeMillis();
databaseTester.onSetup();
long elapsed = System.currentTimeMillis() - time;
System.out.println(getClass() + " Insert DB Unit Data took: " + elapsed);
}
@After
public void cleanDbUnitData() throws Exception {
databaseTester.onTearDown();
}
public IDataSet getDataSet() throws IOException, DataSetException {
Set<String> filenames = getDataSets().getFilenames();
IDataSet[] dataSets = new IDataSet[filenames.size()];
Iterator<String> iterator = filenames.iterator();
for(int i = 0; iterator.hasNext(); i++) {
dataSets[i] = new FlatXmlDataSet(
new FlatXmlProducer(
new InputSource(TEST_RESOURCES + iterator.next()), false, true
)
);
}
return new CompositeDataSet(dataSets);
}
public void setupHsqlDb() throws SQLException {
Connection sqlConnection = DataSourceUtils.getConnection(dataSource);
String databaseName = sqlConnection.getMetaData().getDatabaseProductName();
sqlConnection.close();
if("HSQL Database Engine".equals(databaseName)) {
jdbcTemplate.update("SET DATABASE REFERENTIAL INTEGRITY FALSE;");
// MD5
jdbcTemplate.update("DROP FUNCTION MD5 IF EXISTS;");
jdbcTemplate.update(
"CREATE FUNCTION MD5(VARCHAR(226)) " +
"RETURNS VARCHAR(226) " +
"LANGUAGE JAVA " +
"DETERMINISTIC " +
"NO SQL " +
"EXTERNAL NAME 'CLASSPATH:org.apache.commons.codec.digest.DigestUtils.md5Hex';"
);
} else {
jdbcTemplate.update("SET foreign_key_checks = 0;");
}
}
protected abstract DataSet getDataSets();
protected void flush() {
sessionFactory.getCurrentSession().flush();
}
protected void clear() {
sessionFactory.getCurrentSession().clear();
}
protected void setCurrentUser(User user) {
if(user != null) {
Authentication authentication = new UsernamePasswordAuthenticationToken(user,
user, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
protected void setNoCurrentUser() {
SecurityContextHolder.getContext().setAuthentication(null);
}
protected User setCurrentUser(long userId) {
User user = userRepository.find(userId);
if(user.getId() != userId) {
throw new IllegalArgumentException("There is no user with id: " + userId);
}
setCurrentUser(user);
return user;
}
protected User getCurrentUser() {
return (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}
这会导致它运行得更慢而不是更快吗?想法是,如果我只需要核心xml(语言、省份等),我只需要加载那些记录。我认为这会使测试套件更快,但速度仍然太慢
我可以通过创建一个专门为每个测试类设计的单独的xml数据集来节省一些时间。这会删除一些insert语句。但即使在一个xml数据集中有20条insert语句(因此,除了将数据集直接插入java代码之外,I/O损失最小),在初始化数据库数据的过程中,每个测试仍然需要0.1到0.15秒!我不相信将20条记录插入内存需要0.15秒
在我的另一个使用Spring 3.0和Hibernate 3.x的项目中,在每次测试之前插入所有内容需要30毫秒,但实际上每次测试都要插入100行或更多行。对于只有20行插入的测试,它们的运行就像没有延迟一样。这是我所期望的。我开始认为问题在于Spring的annot或者我在我的DatabaseTest
类中设置它们的方式。这基本上是现在唯一不同的地方
此外,我的存储库正在使用sessionFactory.getCurrentSession()而不是HibernateTemplate。这是我第一次从Spring开始使用基于注释的单元测试内容,因为Spring测试类已被弃用。这可能是它们速度变慢的原因吗
如果你还有什么需要知道的,请告诉我。我有点难堪
编辑:我输入了答案。问题是hsqldb 2.2.x。恢复到2.0.0可以解决问题。看起来很快,IMHO。我看到过速度慢得多的集成测试。也就是说,有多种方法可以让您的测试更快:
- 减少插入的数据量
- 如果数据集相同,并且上一个测试是只读测试,则避免插入与上一个测试相同的数据(通常情况下,尤其是在每个测试结束时回滚时)
我想使用DbUnit应该是可能的。如果您准备好使用另一个框架,您可以使用我自己的框架,它支持开箱即用。问题是Hsqldb 2.2.8。我恢复到2.0.0,性能立即提高了8-10倍或更好。它没有花费150-280毫秒,而是下降到了7-15毫秒(有时是20)毫秒 我的整个测试套件(490个测试)现在只需18秒,而不是80秒
我想大家应该注意一点:避免使用hsqldb 2.2.x。我认为他们添加了多线程支持,这导致了这种类型的用例的性能问题。不幸的是,我没有插入那么多记录,最大的情况下可能总共40条。这基本上意味着插入一条记录需要0.01秒,我发现这是难以置信的对于一台有4千兆内存和4个处理器的计算机来说,.15到.38乘以190个测试是浪费大量时间的。我有另一个使用Hibernate 3和Spring 3.0的项目,我可以在100秒内运行所有1800个测试。设置非常相似,只不过该项目使用Hibernate模板和Spring/Hibernate的旧版本。你有吗任何DbSetup?的基准测试都使用maven?作为旁白,您的框架不使用注释太糟糕了。因为读多于写,所以您应该这样做,只需为写案例指定它(而不是为读案例添加额外的代码).这是我考虑过的,但我选择了安全的路径:如果忘记将测试声明为只读,则不会发生任何不好的事情(除了不必要的DB设置)。另一方面,如果您忘记将测试标记为读写,则后续测试可能会因此遗漏而失败,这可能会导致调试噩梦。Ragarding注释,我不希望项目与任何特定的测试框架结合。给人们一个选项,以便他们可以选择自己的噩梦:-)也许我会。另一方面,DbSetupTracker包含大约15行开源Java代码,因此编写一个应用反向逻辑的自定义代码应该不会太难。顺便问一下:您确定安装所花费的时间来自插入,而不是从文件系统读取数据集吗?
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:applicationContext.properties"/>
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="${database.driver}"/>
<property name="jdbcUrl" value="${database.url}"/>
<property name="user" value="${database.username}"/>
<property name="password" value="${database.password}"/>
<property name="initialPoolSize" value="10"/>
<property name="minPoolSize" value="10"/>
<property name="maxPoolSize" value="50"/>
<property name="idleConnectionTestPeriod" value="100"/>
<property name="acquireIncrement" value="2"/>
<property name="maxStatements" value="0"/>
<property name="maxIdleTime" value="1800"/>
<property name="numHelperThreads" value="3"/>
<property name="acquireRetryAttempts" value="2"/>
<property name="acquireRetryDelay" value="1000"/>
<property name="checkoutTimeout" value="5000"/>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mappingResources">
<list>
<value>...</value>
</list>
</property>
<property name="namingStrategy">
<bean class="org.hibernate.cfg.ImprovedNamingStrategy"/>
</property>
<property name="hibernateProperties">
<props>
<prop key="javax.persistence.validation.mode">none</prop>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}
</prop>
<prop key="hibernate.generate_statistics">false</prop>
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.cache.use_second_level_cache">false</prop>
<prop key="hibernate.cache.provider_class">
</prop>
</props>
</property>
</bean>
<bean class="org.springframework.orm.hibernate4.HibernateExceptionTranslator"/>
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
public enum DataSet {
NONE(create()),
CORE(create("core.xml")),
USERS(combine(create("users.xml"), CORE)),
TAGS(combine(create("tags.xml"), USERS)),