`
yhailj
  • 浏览: 35249 次
  • 性别: Icon_minigender_1
  • 来自: 浮云
文章分类
社区版块
存档分类
最新评论

Hibernate 插入海量数据时的性能 与 Jdbc 的比较

阅读更多
系统环境:




MySQL 数据库环境:
mysql> select version();
--------------
select version()
--------------

+----------------------+
| version()            |
+----------------------+
| 5.1.41-community-log |
+----------------------+
1 row in set (0.00 sec)


eclipse 运行时参数:
--launcher.XXMaxPermSize
256M
-showsplash
org.eclipse.platform
--launcher.XXMaxPermSize
256m
-vm
C:/Program Files/Java/jdk1.6.0_18/bin/javaw
-vmargs
-Dosgi.requiredJavaVersion=1.5
-Xms40m
-Xmx512m
 


使用 Spring 2.5、Hibernate3.3

Entity:

package com.model;

import java.io.Serializable;
import java.sql.Timestamp;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * @author <a href="liu.anxin13@gmail.com">Tony</a>
 */
@Entity
@Table(name = "T_USERINFO")
@org.hibernate.annotations.Entity(selectBeforeUpdate = true, dynamicInsert = true, dynamicUpdate = true)
public class UserInfo implements Serializable {

	private static final long serialVersionUID = -4855456169220894250L;

	@Id
	@Column(name = "ID", length = 32)
	private String id = java.util.UUID.randomUUID().toString().replaceAll("-", "");

	@Column(name = "CREATE_TIME", updatable = false)
	private Timestamp createTime = new Timestamp(System.currentTimeMillis());

	@Column(name = "UPDATE_TIME", insertable = false)
	private Timestamp updateTime = new Timestamp(System.currentTimeMillis());
	// setter/getter...
}


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

	<context:component-scan base-package="com.dao,com.service" />

	<context:property-placeholder location="classpath:jdbc.properties" />

	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
		destroy-method="close">
		<property name="driverClass" value="${jdbc.driver}" />
		<property name="jdbcUrl" value="${jdbc.url}" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
		
		<property name="maxPoolSize" value="50" />
		<property name="minPoolSize" value="2" />
		<property name="initialPoolSize" value="5" />
		<property name="acquireIncrement" value="5" />
		<property name="maxIdleTime" value="1800" />
		<property name="idleConnectionTestPeriod" value="1800" />
		<property name="maxStatements" value="1000"/>
		<property name="breakAfterAcquireFailure" value="true" />
		<property name="testConnectionOnCheckin" value="true" />
		<property name="testConnectionOnCheckout" value="false" />
	</bean>
	
	<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
		<property name="dataSource" ref="dataSource"/>
		<property name="configLocation" value="classpath:hibernate.cfg.xml" />
	</bean>
	
	<bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>
	
	<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>
	
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<tx:method name="*" isolation="READ_COMMITTED" rollback-for="Throwable" />
		</tx:attributes>
	</tx:advice>
	
	<aop:config>
		<aop:pointcut id="services" expression="execution(* com.service.*.*.*.*(..))" />
		<aop:advisor advice-ref="txAdvice" pointcut-ref="services" />
	</aop:config>

</beans>


hibernate.cfg.xml:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
          "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

	<session-factory>

		<property name="hibernate.dialect">
			org.hibernate.dialect.MySQLDialect
		</property>
		
		<!--
			默认 15, 这是我个人不能完全理解的参数, 不明白这个参数的实际意义
		-->
		<!-- <property name="hibernate.jdbc.batch_size">50</property> -->
		
		<!-- 排序插入和更新, 避免出现 锁si -->
		<property name="hibernate.order_inserts">true</property>
		<property name="hibernate.order_updates">true</property>

		<property name="hibernate.hbm2ddl.auto">update</property>

		<property name="hibernate.show_sql">false</property>
		<property name="hibernate.format_sql">false</property>
		
		<property name="hibernate.current_session_context_class">
			org.hibernate.context.JTASessionContext
		</property>

		<mapping class="com.model.UserInfo" />

	</session-factory>

</hibernate-configuration>


log4j.properties:
log4j.rootLogger=INFO, CONS
log4j.appender.CONS=org.apache.log4j.ConsoleAppender
log4j.appender.CONS.layout=org.apache.log4j.PatternLayout
log4j.appender.CONS.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss sss} [%p] %r %l [%t] %x - %m%n

# log4j.logger.org.springframework=WARN
# log4j.logger.org.hibernate=WARN
# log4j.logger.com.mchange=WARN


HibernateDAO.java:
package com.dao;

import java.io.Serializable;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.HibernateTemplate;

@Repository
public class HibernateDAO {

	private static final Logger log = Logger.getLogger(HibernateDAO.class);

	@Autowired
	private HibernateTemplate template;

	public Serializable save(Object entity) {
		try {
			return template.save(entity);
		} catch (Exception e) {
			log.info("save exception : " + e.getMessage());
			// 异常的目的只是为了记录日志
			throw new RuntimeException(e);
		}
	}

}


CommonServiceImpl.java:
package com.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.dao.HibernateDAO;
import com.model.UserInfo;

@Service("commonService")
public class CommonService {

	private static final Logger log = Logger.getLogger(CommonService.class);

	@Autowired
	private HibernateDAO dao;

	public void testOcean(long num) {
		for (int i = 0; i < num; i++) {
			// 在循环中 new 大量对象. 此为 outOfMemory 的根源
			// 将 user 申明在 循环外面, 循环体内部才将其指向具体的对象, 这样可以提高一点效率
			UserInfo user = new UserInfo();
			dao.save(user);
			
			user = null;
			// 实际意义不大, 就算手动运行, gc 也是异步的, 没有办法控制
			if (i % 100000 == 0)
				System.gc();
			/*
			if (i % 30 == 0) {
				dao.flush();
				dao.clear();
			}
			*/
		}
	}
	
}


Test.java:
package com;

import org.apache.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.service.CommonService;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/applicationContext.xml" })
public class TestHibernate {

	@Autowired
	@Qualifier("commonService")
	private CommonService service;
	
	private static final Logger log = Logger.getLogger(TestHibernate.class);
	
	@Test
	public void testHibernate() {
		// 海量数据的条数. 分别以 1W, 10W, 50W, 100W 为数值
		// 虽然都算不上海量, 但做为测试也应该算够了吧
		long num = 10000;
		
		log.info("开始");
		long begin = System.currentTimeMillis();
		service.testOcean(num);

		long end = System.currentTimeMillis();
		log.info("耗费时间: " + num + " 条. " + (end - begin) / 1000.00000000 + " 秒");
		System.out.println("赶紧查看内存");
		try {
			Thread.sleep(10000);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
}



结果:

SQL 生成语句如下:

Hibernate: 
    insert 
    into
        T_USERINFO
        (CREATE_TIME,  ID) 
    values
        (?, ?)

 
不使用批量

1W: 耗费时间: 3.65 秒   内存占用见下图



10W: 耗费时间: 33.844 秒


50W : outOfMemory


30条为一个周期, 也就是将 if (i % 30 == 0) 这段注释解开

1W: 耗费时间: 3.797 秒. 查看内存, 这个时间就明显的感觉到运行时的压力已经有一些转换到数据库上面来了(运行时看得更清楚一些)



MySQL 的进程没能抓到图, 而且其是在运行时耗的资源. 也并不是很大, 比上图要小

10W: 耗费时间: 34.922 秒



50W: 耗费时间: 182.531 秒


100W outOfMemory


50条为一个周期, 即将上面的 30 改为 50

1W: 耗费时间: 3.625 秒



10W: 耗费时间: 32.031 秒


50W: 耗费时间: 161.391 秒



100W 同样 outOfMemory .


一路看下来, 我并不觉得 Hibernate 有多慢. 4秒以内处理 1W 条数据 . 至于内存溢出, 还是因为在 循环里面 new 了太多的对象, 尽管处理完我就将其引用指向 null 并显式调用 gc , 但 JVM 内部运行 gc 是异步的...

当然 性能上使用 50批量 的策略会好很多, 可以将压力让 服务器 和 数据库 同时去承担 .


保存下来再去测试 jdbc 批量看看...

使用 jdbc 测试:

在 applicationContext.xml 中添加:

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
	<property name="dataSource" ref="dataSource" />
</bean>


JdbcDAO.java:
package com.dao;

import java.sql.Connection;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class JdbcDAO {
	
	private static final Logger log = Logger.getLogger(JdbcDAO.class);
	
	@Autowired
	private JdbcTemplate template;
	
	public Connection getConn() {
		try {
			return template.getDataSource().getConnection();
		} catch (Exception e) {
			log.info("获取连接时异常: " + e.getMessage());
			throw new RuntimeException(e);
		}
	}

}


JdbcService.java:
package com.service;

import java.sql.Connection;
import java.sql.PreparedStatement;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.dao.JdbcDAO;
import com.model.UserInfo;

@Service("jdbcService")
public class JdbcService {
	
	private static final Logger log = Logger.getLogger(JdbcService.class);
	
	@Autowired
	private JdbcDAO dao;

	public void testOcean(long num) {
		Connection conn = dao.getConn();
		try {
			conn.setAutoCommit(false);
			String sql = "insert into T_USERINFO(CREATE_TIME, ID) values(?, ?)";
			PreparedStatement pstm = conn.prepareStatement(sql);
			for (int i = 0; i < num; i++) {
				// 要保证公平, 也在循环中 new 对象
				UserInfo user = new UserInfo();
				pstm.setTimestamp(1, user.getCreateTime());
				pstm.setString(2, user.getId());
				pstm.execute();
				
				user = null;
				if (i % 10000 == 0)
					System.gc();
				// 批处理
				/*
				if (i % 30 == 0) {
					pstm.executeBatch();
					conn.commit();
					pstm.clearBatch();
				}
				*/

			}
			// 将循环里面的批处理解开后, 就要将此处的commit注释, 并将下面注释的语句解开
			conn.commit();
			// pstm.executeBatch();
		} catch (Exception e) {
			log.info("异常: " + e.getMessage());
		} finally {
			try {
				conn.close();
			} catch (Exception e) {
				conn = null;
			}
		}
	}

}


TestJdbc.java: (这里很郁闷, 我用的 Junit 4.6 版本, 在上面的测试中正常. 在这里异常, 换成 4.4 就好了)
package com;

import org.apache.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.service.JdbcService;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/applicationContext.xml" })
public class TestJdbc {
	
	private static final Logger log = Logger.getLogger(TestJdbc.class);
	
	@Autowired
	@Qualifier("jdbcService")
	private JdbcService service;

	@Test
	public void testJdbc() {
		long num = 500000;
		
		log.info("开始");
		long begin = System.currentTimeMillis();
		service.testOcean(num);
		long end = System.currentTimeMillis();
		log.info("耗费时间: " + num + " 条. " + (end - begin) / 1000.00000000 + " 秒");
		
		System.out.println("赶紧查看内存");
		try {
			Thread.sleep(10000);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

 

结果如下:

不使用批量:

1W: 耗费时间: 3.359 秒(这个效率确实比 Hibernate 要高)



10W: 耗费时间: 212.812 秒 (我不确定是不是我 jdbc的代码写得不够好, 反正这个效率让我很是郁闷)


50W 的时候我已经没有耐心再等下去了.  11 分钟过去了还没看到结果


使用30条一次批量

说实话, 这个时候的性能已经让我到了一种发狂的地步.
接近 3 秒 运行 100 条 . 1W 条数据花了 288.266 秒的时间
是的, 你没有看错, 就是 3 秒运行 100 条, 到后面的 50 我也就没有再继续测下去.

也可能是我 jdbc 代码写得不对吧. 毕竟 hibernate 底层也是用的 jdbc, 其再快也是不可能快过 Native SQL, 我这个贴的目的也只是想了解下差距的多少, 只是这个效率...

如果哪位有过 jdbc 方面的操作, 给个提醒...
谢谢.


  • 大小: 6.9 KB
  • 大小: 3.4 KB
  • 大小: 7.1 KB
  • 大小: 7.2 KB
  • 大小: 6.8 KB
  • 大小: 7 KB
  • 大小: 3.4 KB
  • 大小: 3.6 KB
  • 大小: 7.1 KB
  • 大小: 3.3 KB
  • 大小: 8 KB
2
0
分享到:
评论
4 楼 bushkarl 2010-08-23  
3 楼 yhailj 2010-08-22  
peterwei 写道
我的机器配置是1.8G的老机子,差不多800M内存。你想想都比你的快那么多,肯定是你哪写得不对了。
1.if (i % 10000 == 0)  
                    System.gc(); 这个没什么用吧

2.没看见有ps.addBatch();我的有的,而用ps不用批处理时用的是executeUpdate();

3.就算我在里面new东西,50w也只是多了10秒不到。

你可以仔细看一下我的代码。



1. 我在测试 Hibernate 的时候写过
// 实际意义不大, 就算手动运行, gc 也是异步的, 没有办法控制
if (i % 10000 == 0)   
    System.gc(); 

以上代码也只是想让 gc 整理 循环体内部因为申明太多指向跟 new 太多对象造成的内存溢出

2. 的确, 直接执行 pstm.execute(); 是直接执行, 不是批量了! 如果要批量的话, 应该像下面这样:
for (int i = 0; i < num; i++) {
	// 要保证公平, 也在循环中 new 对象
	UserInfo user = new UserInfo();
	pstm.setTimestamp(1, user.getCreateTime());
	pstm.setString(2, user.getId());

	pstm.addBatch();

	user = null;
	if (i % 10000 == 0)
		System.gc();

	// 批处理
	if (i % 30 == 0) {
		pstm.executeBatch();
		conn.commit();
		pstm.clearBatch();
	}

}
pstm.executeBatch();
conn.commit();


哪天我再测一下, 这还是去年在公司测的. 一直没动过
2 楼 peterwei 2010-08-22  
我的机器配置是1.8G的老机子,差不多800M内存。你想想都比你的快那么多,肯定是你哪写得不对了。
1.if (i % 10000 == 0)  
                    System.gc(); 这个没什么用吧

2.没看见有ps.addBatch();我的有的,而用ps不用批处理时用的是executeUpdate();

3.就算我在里面new东西,50w也只是多了10秒不到。

你可以仔细看一下我的代码。
1 楼 peterwei 2010-08-22  
你的用法有问题吧,这个要靠你多试试多种情况。我的机子比你差多了。还没你说的情况。我的测试如下:
================================
清空表
通过PrepareStatement插入数据:
插入数据量:500000
<运行时间: 96.266 秒>
运行时间:96266 毫秒
96.266
================================
清空表
用批处理插入数据:
批量更新成功 500000 条记录!
<运行时间: 96.515 秒>
运行时间:96515 毫秒
96.515
================================
你可以看看我的blog,我也写了个测试jdbc性能的。

相关推荐

    JDBC与Hibernate的比较

    ### JDBC与Hibernate的比较 在软件开发领域,尤其是在基于Java的应用程序中,数据库访问技术的选择对项目的成功至关重要。本文将详细探讨两种常见的Java数据库访问方式:JDBC(Java Database Connectivity)与...

    JDBC与Hibernate区别

    此外,Hibernate支持缓存机制,可以通过二级缓存提高性能,尤其是在查询小部分数据时,Iterator方式的效率较高。 对于数据状态,JDBC操作的数据通常是瞬时的,需要开发者手动同步数据库和内存中的数据。而Hibernate...

    hibernate入门数据简单插入

    标题"hibernate入门数据简单插入"指的是使用Hibernate框架进行数据库的基本操作,特别是数据的插入。Hibernate是一个开源的对象关系映射(ORM)框架,它允许Java开发者在应用程序中处理数据库对象,而无需直接编写...

    Hibernate插入数据

    本篇将详细探讨“Hibernate插入数据”的相关知识点,结合学习心得,深入理解并掌握其核心原理与实践技巧。 首先,Hibernate通过对象关系映射(ORM)技术,将数据库表与Java类关联起来,使得数据库操作可以通过对象...

    kingbaseV8 hibernate jdbc 驱动

    在使用Hibernate时,需要配置正确的JDBC驱动,以便Hibernate能与KingbaseV8数据库建立连接。KingbaseV8的JDBC驱动通常包括对应的jar文件,例如:kingbase-jdbc.jar,这个文件应被添加到项目的类路径中。 在提供的...

    hibernate 链接access数据的jdbc驱动包

    当涉及到与Microsoft Access数据库的交互时,Hibernate同样能够提供支持,但需要相应的JDBC驱动。在这个场景中,"hibernate 链接access数据的jdbc驱动包"就是用来建立Hibernate与Access数据库连接的关键组件。 首先...

    jdbc和hibernate的区别

    3. **数据状态**:JDBC操作的数据是瞬时的,数据库与内存中的数据可能不一致,而Hibernate提供了一种对象-关系映射机制,使得数据具有持久性,能够保持数据库与对象的一致性。 **性能比较:** 1. **创建操作**:...

    hibernate+junit+mysql-jdbc开发核心jar包三合一

    总之,“hibernate+junit+mysql-jdbc开发核心jar包三合一”是Java Web开发中的关键组件,它们共同构成了数据持久化、单元测试和数据库连接的基础。掌握这些工具的使用,对于提升开发效率和保证软件质量具有重要意义...

    本人理解hibernate 与 JDBC 的最本质区别

    标题中的“本人理解hibernate 与 JDBC 的最本质区别”揭示了我们即将探讨的核心主题:Hibernate 和 JDBC 在处理数据库操作时的不同之处。 Hibernate 是一个对象关系映射(ORM)框架,而 JDBC(Java Database ...

    Hibernate中大量数据的更新

    在这些场景中,如果使用传统的 INSERT 语句逐条插入数据,会导致性能下降和内存溢出问题。因此,使用批量更新机制可以大大提高性能和降低内存占用。 Hibernate 的批量更新机制 Hibernate 提供了两种批量更新机制:...

    hibernate所需包:hibernate3,依赖包,JDBC

    Hibernate通过JDBC与数据库进行通信,执行SQL语句。在Hibernate中,JDBC驱动程序是必要的,以便应用程序能够连接到特定的数据库。例如,"mysql-connector-java-3.1.13-bin.jar"就是MySQL的JDBC驱动,用于连接到MySQL...

    JDBC+Hibernate将Blob数据写入Oracle

    ### JDBC与Oracle BLOB的交互 #### 1. 理解Oracle BLOB特性 在Oracle中,BLOB用于存储大量的二进制数据,如图像文件。与传统的文本或数字字段不同,BLOB字段有自己的游标(Cursor),这使得直接写入数据变得复杂。...

    Hibernate与JDBC对于数据库CRUD操作性能示例

    Hibernate与JDBC对于数据库的性能操作对比事例,配置,更多信息资源

    Hibernate and JDBC

    - **性能问题**:对于某些复杂查询,Hibernate可能无法达到最佳性能,尤其是在处理大量数据时。 - **学习曲线**:虽然Hibernate简化了许多操作,但对于新手来说,理解其核心概念和配置仍需一定时间。 - **灵活性限制...

    优化Hibernate性能的几点建议

    当处理大量的数据时,使用`Iterator`而不是`List`来遍历结果集可以节省大量的内存资源。这是因为`Iterator`在每次调用`next()`方法时只会加载当前的记录,而不会一次性加载所有的结果到内存中。这种方式特别适用于...

    Jdbc 和hibernate

    4. **Hibernate与JDBC比较**: - Hibernate抽象了数据库层,使代码更简洁,降低了维护成本。 - Hibernate提高了开发效率,减少了SQL编写工作。 - Hibernate的查询优化可能不如直接手写的SQL,但在大部分场景下...

    Hibernate插入数据-Eclipse

    最近自学java中的框架-struts写了一些小例子,这都是很经典的程序,如果大家瞧得起要下载去看看,顺便给俺找找不足的地方。我的qq 821865130 email qingtian_hechen@163.com 希望大家能多多给我帮助...在此谢谢各位!!

    struts+hibernate+jdbc双表查询

    虽然Hibernate可以简化数据操作,但在某些复杂查询或性能优化时,开发者可能直接使用JDBC编写SQL语句以获取更高的灵活性和效率。在双表查询中,JDBC可以用于编写自定义的JOIN查询,以满足特定的需求。 在实际项目中...

    Hibernate下数据批量处理解决方案

    在上述例子中,当尝试向数据库插入100,000条数据时,由于默认情况下Hibernate会将所有持久化对象保留在一级缓存中,随着数据量的增加,内存消耗也会迅速增长,最终导致内存溢出。因此,我们需要对一级缓存进行管理。...

Global site tag (gtag.js) - Google Analytics