`
kylinsoong
  • 浏览: 235848 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Hibernate,JDBC性能探讨

 
阅读更多

      今天原打算研究一下Hibernate 1+N问题,却发现了一个Really Amazing的效率问题,问题如下,希望大牛可以解释一下:

1、简单描述一下测试中表结构(我用Mysql数据库),表结构如下图



 表名为Corporation,三个字段id、name、type,对应Mysql中表结构截图:



 

2、如下代码,简单调运JDBC测试插入10000条数据花费时间

public class DB {
	public static Connection getConn() {
		Connection conn = null;
		try {
			Class.forName("com.mysql.jdbc.Driver");
			conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/hibernate?user=root&password=root");	
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return conn;
	}
	public static Statement getStmt(Connection conn) {
		Statement stmt = null;
		try {
			if(conn != null) {
				stmt = conn.createStatement();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return stmt;
	}
	public static void main(String[] args) throws SQLException {
		Statement stmt = getStmt(getConn());
		Date start = new Date();
		for(int i = 0 ; i < 10000 ; i ++) {
			String sql = "insert into corporation(name,type) value('Tibco','IT')";
			stmt.executeUpdate(sql);
		}
		System.out.println("Total spent " + (new Date().getTime() - start.getTime()));
	}
}

 

运行时间如下

Total spent 249531

 这个时间我觉得是比较合理的执行一条SQL花费25毫秒左右,之前有关效率方面的测试和这个结果差不多

 

3. 下面给出Hibernate中测试时间的代码,先给出Hibernate配置代码

3.1 Corporation对应的Annotation配置类

@Entity
public class Corporation {
	private int id;
	private String name;
	private String type;
	@Id
	@GeneratedValue
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getType() {
		return type;
	}
	public void setType(String type) {
		this.type = type;
	}
}

 3.2 hibernate.cfg.xml配置信息

<hibernate-configuration>
    <session-factory>
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost/hibernate</property>
        <property name="connection.username">root</property>
        <property name="connection.password">root</property>
		<property name="connection.pool_size">1</property>
		<property name="current_session_context_class">thread</property>
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
        <property name="show_sql">true</property>
        <property name="format_sql">true</property>
        <property name="hbm2ddl.auto">update</property>
        <mapping class="com.kylin.test.Corporation"/>
    </session-factory>
</hibernate-configuration>

 3.3给出测试代码

package com.kylin.test;

import java.util.Date;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class AllTest {
	private static SessionFactory sessionFactory;
	@BeforeClass
	public static void beforeClass(){
		sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
	} 
	@AfterClass
	public static void afterClass() {
		sessionFactory.close();
	}
	@Test
	public void testExportTable() {
		new SchemaExport(new AnnotationConfiguration().configure()).create(true, true);
	}
	@Test
	public void saveCorporation() {
		Date start = new Date();
		Session session = sessionFactory.getCurrentSession();
		session.beginTransaction();
		for(int i = 0 ; i < 10000 ; i ++) {
			Corporation c = new Corporation();
			c.setName("Tibco");
			c.setType("IT");
			session.save(c);
		}
		session.getTransaction().commit();
		System.out.println("Total spent " + (new Date().getTime() - start.getTime()));
	}
}

 

如上saveCorporation()方法就是Hibernate想数据库中插入10000条数据的部分,这个结果确实令我吃惊,如下

Total spent 8781

我测试了多次这个时间都是8700毫秒左右,所以这个时间应该准确。 

249531 / 8781 = 28.41,28倍,的确是28倍

4、分析一下

      之前比较过Cassandra(分布式数据库)与Mysql的存储效率差别时发现关系数据库存储的SQL操作效率相当低,而Hibernate解决的是可以面向对象的操作关系数据库,所以我个人一直以为,Hibernate操作数据库的效率一定更低,但结果却恰恰相反,而且与我的之前的理解相差很大,所以我可以肯定的是Hibernate在数据操作效率上也有一定的技巧,大家谁知道可以说说。

 

PS。。本来还想再分析分析,但是周末了,不想再多呆在公司……

  • 大小: 2.1 KB
  • 大小: 5.2 KB
分享到:
评论
39 楼 feilian09 2014-03-20  
查询 select hibernate jdbc 那个效率快
38 楼 yhailj 2010-08-21  
为这个问题自己也一直挺郁闷的, 出现这种问题只能怪自己 jdbc 没有写好!

有 jdbc 方面经验的帮忙看下我这个 blog:

http://yhailj.iteye.com/admin/blogs/619890

37 楼 wenshao 2010-08-21  
完全不可理喻的对比,单个插入和批量插入的对比。

就如同,堵车的时候,我走路比汽车快。

这个案例有意义么?

要不是无聊,要不就是无耻
36 楼 JE帐号 2010-08-21  
pujia12345 写道
现在鼠辈横行啊!
殊不知: Hibernate也是封装的JDBC。就像Windows和DOS操作系统的关系。
现在只知SSH不知Java\Servlet的太多了


... ...
可能你说的是Win3.x 吧...
35 楼 freish 2010-08-21  
hibernate底层还是jdbc

你这jdbc代码写的太没效率了
34 楼 carlkkx 2010-08-21  
就像Windows和DOS操作系统的关系。
_______________________
晕倒,Windows和Dos是这种关系?
33 楼 beeke 2010-08-21  
你们真是闲得蛋疼啊(我也是)
hibernate内部也是执行JDBC
就好像汽车对车轮说:我跑得比你快
真无聊啊...
32 楼 pujia12345 2010-08-21  
现在鼠辈横行啊!
殊不知: Hibernate也是封装的JDBC。就像Windows和DOS操作系统的关系。
现在只知SSH不知Java\Servlet的太多了
31 楼 peterwei 2010-08-21  
今天闲得蛋疼,新自试了一遍。哈哈
    不知你在什么配置情况下操作。因为结果是4分多钟。对于此结论很是惊奇,因为以前做过数据同步的东西,于是马上亲自验证,数据库和测试参数和你的都一样。

    先说结果:我的测试最慢的只用了2.6秒。Statement最慢,PrepareStaement快了5秒钟,Batch和PrepareStatement并没有实质性的提高,只是一小点(这个倒是让我奇怪)。从一万到十万数据量都做过测试,但变化不大。我一直认为Batch会提高不少数量级的,可是结果让我失望,也不知哪写得不对,大家可以评点一下代码。

直接pringln 10000的一些对比数据:

清空表
普通的Statement插入数据:
插入数据量:10000
<运行时间: 2.656 秒>
运行时间:2656 毫秒
2.656
================================
清空表
通过PrepareStatement插入数据:
插入数据量:10000
<运行时间: 2.156 秒>
运行时间:2156 毫秒
2.156
================================
清空表
用批处理插入数据:
批量更新成功 10000 条记录!
<运行时间: 2.078 秒>
运行时间:2078 毫秒
2.078
================================

代码如下:

Java代码
package test;   
  
import java.sql.Connection;   
import java.sql.PreparedStatement;   
import java.sql.SQLException;   
import java.sql.Statement;   
  
import com.fastbo.util.Clock;   
import com.fastbo.util.ConnectionFactory;   
import com.fastbo.util.DbUtil;   
  
/**  
 * Description: Jdbc相关性能测试,batch处理,PrepareStatement,Statement等。  
 *   
 * <p>  
 * Mysql数据库:表结构为简单的id,name(varchar:255),type(varchar:255)字段,id自增  
 * </p>  
 *   
 * @author Peter Wei Email: <a href="mailto:weigbo@163.com">weigbo@163.com </a>  
 *   
 * @version 1.0 2010-8-21  
 */  
public class JdbcTest {   
  
 /**  
  * 测试数据量  
  */  
 public static int TEST_NUM = 10000;   
  
 /**  
  * 批处理大小  
  */  
 public static int BATCH_SIZE = 300;   
  
 /**  
  * 清空数据表  
  *   
  * @param con  
  */  
 public static void clear(Connection con) {   
  PreparedStatement ps = null;   
  StringBuffer buff = new StringBuffer();   
  try {   
   buff.append("truncate table bobo");   
   ps = con.prepareStatement(buff.toString());   
   ps.executeUpdate();   
   System.out.println("清空表");   
  } catch (SQLException e) {   
   e.printStackTrace();   
  } finally {   
   DbUtil.close(ps);   
  }   
 }   
  
 /**  
  * 普通的Statement插入数据  
  *   
  * @param con  
  */  
 public static int add(Connection con) {   
  Statement stmt = null;   
  int num = 0;   
  String sql = "insert into bobo(name,type) values('Peter Wei','test')";   
  try {   
   stmt = con.createStatement();   
   for (int i = 0; i < TEST_NUM; i++) {   
    num += stmt.executeUpdate(sql);   
   }   
   System.out.println("插入数据量:" + num);   
  } catch (SQLException e) {   
   e.printStackTrace();   
  } finally {   
   DbUtil.close(stmt);   
  }   
  return num;   
  
 }   
  
 /**  
  * 用PrepareStatement插入数据  
  *   
  * @param con  
  */  
 public static void addByPrepareStatement(Connection con) {   
  
  PreparedStatement ps = null;   
  StringBuffer buff = new StringBuffer();   
  int num = 0;   
  try {   
   buff.append("insert into bobo(name,type)");   
   buff.append(" values(?,?)");   
   ps = con.prepareStatement(buff.toString());   
   for (int i = 0; i < TEST_NUM; i++) {   
    int index = 1;   
    ps.setString(index++, "Peter Wei");   
    ps.setString(index++, "test");   
    num += ps.executeUpdate();   
   }   
   System.out.println("插入数据量:" + num);   
  } catch (SQLException e) {   
   e.printStackTrace();   
  } finally {   
   DbUtil.close(ps);   
  }   
 }   
  
 /**  
  * 用批处理插入数据  
  *   
  * @param con  
  */  
 public static void addByBatch(Connection con) {   
  PreparedStatement ps = null;   
  StringBuffer buff = new StringBuffer();   
  int sum = 0;   
  int[] num = null;   
  try {   
   buff.append("insert into bobo(name,type) values(?,?)");   
  
   con.setAutoCommit(false);   
   ps = con.prepareStatement(buff.toString());   
   for (int i = 0; i < TEST_NUM; i++) {   
    int index = 1;   
    ps.setString(index++, "Peter Wei");   
    ps.setString(index++, "test");   
    ps.addBatch();   
    if (i != 0 && i % BATCH_SIZE == 0) {   
     num = ps.executeBatch();   
     sum += num.length;   
     con.commit();   
     // System.out.println("batch:" + i);   
    }   
  
   }   
   num = ps.executeBatch();   
   sum += num.length;   
   con.commit();   
   con.setAutoCommit(true);   
   System.out.println("批量更新成功 " + sum + " 条记录!");   
  } catch (SQLException e) {   
   e.printStackTrace();   
  } finally {   
   DbUtil.close(ps);   
  }   
 }   
  
 public static void main(String[] args) {   
  Connection con = ConnectionFactory.getConnection();   
  clear(con);   
  Clock c = new Clock();   
  // 普通的Statement插入数据   
  System.out.println("普通的Statement插入数据:");   
  c.start();   
  add(con);   
  c.stop();   
  System.out.println(c.toString());   
  c.readMilli();   
  System.out.println(c.read());   
  System.out.println("================================");   
  
  clear(con);   
  // 通过PrepareStatement插入数据   
  System.out.println("通过PrepareStatement插入数据:");   
  c = new Clock();   
  c.start();   
  addByPrepareStatement(con);   
  c.stop();   
  System.out.println(c.toString());   
  c.readMilli();   
  System.out.println(c.read());   
  System.out.println("================================");   
  
  clear(con);   
  // 用批处理插入数据   
  System.out.println("用批处理插入数据:");   
  c = new Clock();   
  c.start();   
  addByBatch(con);   
  c.stop();   
  System.out.println(c.toString());   
  c.readMilli();   
  System.out.println(c.read());   
  System.out.println("================================");   
  
 }   
  
}  
30 楼 niuniuliu 2010-08-21  
你的jDBC肯定是自动提交事务了
可能每插入一条就提交一次

而hibernate只执行了一次提交
29 楼 kylinsoong 2010-08-21  
笑我痴狂 写道
首先得说jdbc肯定比hibernate快
这里楼主的程序写的不好,如果把jdbc改成batch提交  我想时间应该就很短了

现在来具体分析下:

jdbc测试:
没insert一条记录  就跟数据库打交道一次 ,这里一共连接了1000次数据库 光是连接数据库的时间就已经很长了

hibernate测试:
for(int i=0;i<1000;i++){
    。。。。
    session.save() ;
}


在这个过程中session保存对象时 ,是放在了session中 ,此时还没有真正的持久化到数据库,
在最后执行commit的时候,才真正的持久化到数据库 ,所以这1000个对象在同一时候被持久化到数据库中
在效率上来说 可想而知了


简单的说就是一桶水 ,执行jdbc的时候,每一次取一小瓢放到缸里

hibernate执行时,直接把一桶水倒到缸里 ,这样的测试当然是hibernate快了

小弟不才  哈哈哈哈哈哈哈哈哈


这个解释相当详细,学习了,我知道昨天的错误出在哪儿了
28 楼 kylinsoong 2010-08-21  
sswh 写道
chrislee1982 写道
JDBC绝对比hibernate快,毋庸置疑!

zhuyan_zy 写道
此贴体现了框架存在的意义之一

这个例子恰恰说明,hibernate一定比蹩脚程序员写的JDBC代码快!!
 

大家批评让我觉得相当惭愧,刚毕业没有什么经验,闹了个大笑话,大家就当娱乐一下,问题正如这位大哥所讲是JDBC代码写的有问题,修给一下
public static void main(String[] args) throws SQLException {
		Connection conn = getConn();
		conn.setAutoCommit(false);
		Date start = new Date();
		String sql = "insert into corporation(name,type) value(?,?)";   
		PreparedStatement prest = conn.prepareStatement(sql);      
		   for(int x = 0; x < 10000; x++){              
		      prest.setString(1, "Tibco");      
		      prest.setString(2, "IT");           
		      prest.addBatch();      
		   }      
		prest.executeBatch(); 
		conn.commit();
		conn.setAutoCommit(true);
		System.out.println("Total spent " + (new Date().getTime() - start.getTime()));
	}

   这样运行结果是1600毫秒左右,相比较Hibernate的8700毫秒,JDBC还是快的多。
27 楼 sswh 2010-08-21  
chrislee1982 写道
JDBC绝对比hibernate快,毋庸置疑!

zhuyan_zy 写道
此贴体现了框架存在的意义之一

这个例子恰恰说明,hibernate一定比蹩脚程序员写的JDBC代码快!!
 
26 楼 redish 2010-08-21  
感觉楼主还是做个公平的测试,个人觉得你没能比Hibernate更好的应用JDBC才是关键。
25 楼 yangyw 2010-08-21  
kimmking 写道
3923075 写道
lz 你看看 你写的代码
 for(int i = 0 ; i < 10000 ; i ++) {   
            String sql = "insert into corporation(name,type) value('Tibco','IT')";   
            stmt.executeUpdate(sql);   
        }   


1.for 循环有80%的时间都在 IO上..真正执行你sql 的时间很短
2.我们都知道 尽量不要再循环中新建类.

改成这样 试试

         Statement stmt = conn.createStatement();
	StringBuffer sb=new StringBuffer(); 
         for(int i = 0 ; i < 10000 ; i ++)
         {
           sb.append("insert into corporation(name,type) value('Tibco','IT')");   
         }
         stmt.addBatch(sb.toString()); 
         stmt.executeBatch();
  


我估计这个超过mysql运行的最大长度了。


不太明白,为啥不用PreparedStatement的批量提交?
   String sql = "insert into corporation(name,type) value(?,?)";
   PreparedStatement prest = conn.prepareStatement(sql);   
      for(int x = 0; x < size; x++){           
         prest.setString(1, "Tibco");   
         prest.setString(2, "IT");        
         prest.addBatch();   
      }   
  prest.executeBatch();  


24 楼 kimmking 2010-08-21  
3923075 写道
lz 你看看 你写的代码
 for(int i = 0 ; i < 10000 ; i ++) {   
            String sql = "insert into corporation(name,type) value('Tibco','IT')";   
            stmt.executeUpdate(sql);   
        }   


1.for 循环有80%的时间都在 IO上..真正执行你sql 的时间很短
2.我们都知道 尽量不要再循环中新建类.

改成这样 试试

         Statement stmt = conn.createStatement();
	StringBuffer sb=new StringBuffer(); 
         for(int i = 0 ; i < 10000 ; i ++)
         {
           sb.append("insert into corporation(name,type) value('Tibco','IT')");   
         }
         stmt.addBatch(sb.toString()); 
         stmt.executeBatch();
  


我估计这个超过mysql运行的最大长度了。
23 楼 3923075 2010-08-21  
lz 你看看 你写的代码
 for(int i = 0 ; i < 10000 ; i ++) {   
            String sql = "insert into corporation(name,type) value('Tibco','IT')";   
            stmt.executeUpdate(sql);   
        }   


1.for 循环有80%的时间都在 IO上..真正执行你sql 的时间很短
2.我们都知道 尽量不要再循环中新建类.

改成这样 试试

         Statement stmt = conn.createStatement();
	StringBuffer sb=new StringBuffer(); 
         for(int i = 0 ; i < 10000 ; i ++)
         {
           sb.append("insert into corporation(name,type) value('Tibco','IT')");   
         }
         stmt.addBatch(sb.toString()); 
         stmt.executeBatch();
  

22 楼 treblesoftware 2010-08-21  
JDBC架构的系统,1台服务器

HIBERNATE架构的系统,4台服务器。

看看哪个快哦~
21 楼 田智伟 2010-08-21  
个人的看法还是在
for(int i = 0 ; i < 10000 ; i ++) {  
            String sql = "insert into corporation(name,type) value('Tibco','IT')";  
            stmt.executeUpdate(sql);  
}
与hibernate的transction提交的问题上!如果在hibernate的for循环中将session的开启和关闭以及事务的开启提交全部放入其中,我想他们两个的效率比较就比较公平了!说到底,还是写代码的问题!
20 楼 田智伟 2010-08-21  
个人的看法还是在
for(int i = 0 ; i < 10000 ; i ++) {  
            String sql = "insert into corporation(name,type) value('Tibco','IT')";  
            stmt.executeUpdate(sql);  
}
与hibernate的transction提交的问题上!如果在hibernate的for循环中将session的开启和关闭以及事务的开启提交全部放入其中,我想他们两个的效率比较就比较公平了!说到底,还是写代码的问题!

相关推荐

Global site tag (gtag.js) - Google Analytics