- 浏览: 9302 次
- 性别:
- 来自: 上海
最新评论
简介: Hibernate 在处理多表关联及分组排序等复杂数据库查询操作时,其固有的 O-R 映射机制会产生大量冗余 SQL 操作,系统性能比传统的 JDBC 低很多。本文分析了 Hibernate 产生此类问题的原因,提出了一个在 Hibernate 框架内直接操作 JDBC 的接口的解决方案,在实际项目中验证了该解决方案可以有效提高此类查询的效率。文中提供的示例代码可以直接运用于使用 Hibernate 框架的 J2EE 系统项目。
------------------============-----------------------
(转载自)
内容
在 Hibernate 框架中提供直接操作 JDBC 接口的原因
Hibernate 是一个开放源代码的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,使得 Java 程序员可以随心所欲的使用面向对象编程思维来操纵数据库。Hibernate 的优势在于屏蔽了数据库细节,对于新增修改删除的数据层操作,不再需要跟具体的 SQL 语句打交道,简单的对对象实例进行增删改操作即可。
但是,对于多表关联、分组统计、排序等复杂的查询功能时,由于 Hibernate 自身的 O-R 映射机制,父子表之间关联取数据会产生大量冗余的查询操作,性能低下。此类情况下,直接使用 JDBC 的 SQL 语句反而更加灵活和高效。
考虑如下数据库实体示例,表 A 为主表,表 B 和表 C 为子表,A 与 B、A 与 C 表均为 1 对多关系,在 B 表和 C 表中以 A_ID 外键字段关联 A 表父记录。
在 Hibernate 框架中,通常采用以下配置方式完成 A 表与 B,C 表父子实体之间的级联查询操作,Hibernate 实体配置 xml 如下:
A.hbm.xml: <hibernate-mapping> <class name="XXX.XXX.A" table="A" > <id name="id" type="long"> <column name="ID"/> <generator class="assigned"> </generator> </id> <set name="children_B" cascade="delete" inverse="true" lazy="false"> <key column="A_ID"></key> <one-to-many class="XXX.XXX.B"/> </set> <set name="children_C" cascade="delete" inverse="true" lazy="false"> <key column="A_ID"></key> <one-to-many class="XXX.XXX.C"/> </set> </class> </hibernate-mapping> B.hbm.xml: <hibernate-mapping> <class name="XXX.XXX.B" table="B" > <id name="id" type="long"> <column name="ID"/> <generator class="assigned"> </generator> </id> <property name="a_id" type="long"> <column name="A_ID"> </column> </property> </class> </hibernate-mapping> C.hbm.xml <hibernate-mapping> <class name="XXX.XXX.C" table="C" > <id name="id" type="long"> <column name="ID"/> <generator class="assigned"> </generator> </id> <property name="a_id" type="long"> <column name="A_ID"> </column> </property> </class> </hibernate-mapping> |
对应的 Hibernate 领域实体类代码示例如下:
A.java: public class A implements java.io.Serializable,Comparable { private long id; private Set children_b = new HashSet<B>(); private Set children_c = new HashSet<C>(); public A(long id) { this.id = id; } public long getId() { return id; } public void setId(long id) { this.id = id; } public Set getChildern_b() { return children_b; } public void setChildren_b (Set children_b) { this.children_b = children_b; } public Set getChildern_c() { return children_c; } public void setChildren_c (Set children_c) { this.children_c = children_c; } public int compareTo(Object other) { A otherSubject = (A)other; long curAmount=this.getChildren_b().size()+this.getChildren_c().size(); long otherAmount =otherSubject.getChildren_b().size() + otherSubject.getChildren_c().size(); if(curAmount<otherAmount) { return -1; } else if(curAmount>otherAmount) { return 1; } else { return 0; } } } B.java: public class B implements java.io.Serializable,Comparable { private long id; private long a_id; public long getId() { return id; } public void setId(long id) { this.id = id; } public long getA_id() { return a_id; } public void setA_id(long a_id) { this.a_id = a_id; } public B(long id) { this.id=id; } } C.java: public class C implements java.io.Serializable,Comparable { private long id; private long a_id; public long getId() { return id; } public void setId(long id) { this.id = id; } public long getA_id() { return a_id; } public void setA_id(long a_id) { this.a_id = a_id; } public C(long id) { this.id=id; } } |
假设现在要统计 A 表中从属的 B 表和 C 表记录之和最高的 top10 的 A 表记录,在 Hibernate 框架下,由于取 A 表对应的数据库记录时,已关联取出了对应的 B、C 表子记录存放于 A 实体类的 children_a,children_c 的属性中,因此 top10 的功能可以通过比较每个 A 表实体类中 children_a、children_c 的 Set 的 size 大小并进行排序得到,其代码示例如下:
private ArrayList<A> sortAByAmount(ArrayList<A> all) { for(int i=0;i<all.size();i++) { for(int j=0;j<all.size()-i-1;++j) { if(all.get(j).compareTo(all.get(j+1))<=0) { A temp = all.get(j); all.set(j,all.get(j+1)); all.set(j+1,temp); } } } return all; } |
表面看来很方便,但是由于 Hibernate 是面向对象的 O-R 映射机制,每一条 A 表记录的查询实际都关联有两条 B、C 表查询的 SQL 产生,我们可以看到 Hibernate 的 SQL 日志中:
Hibernate: select a0_.ID as ID2_ from A a0_ where a0_.ID='1' Hibernate: select b0_.ID as ID2_,b0_.A_ID as A_ID2_ from B b0_ where b0_.ID=? Hibernate: select c0_.ID as ID2_,c0_.A_ID as A_ID2_ from C c0_ where c0_.ID=? |
由上述 Sql 日志可以看出,每一条 A 表记录的取出,都伴随以 A 表 ID 为查询条件关联 B,C 表中 A_ID 外键字段的 2 条取子记录的 sql,这是由 A.hbm.xml 配置中“lazy=false”决定的。
这种情况下,当 A 和 B、C 表中数据量越来越大时,A 表取实体的操作开销将随着 sql 查询的增多而增大,并且在紧接着的排序过程中,即使采用业界最快的快速排序算法,排序时间依然是随原始排序实体数量的线性关系(O(n lg n)),效率会线性下降,最终无法满足客户的前台查询的效率要求。
此类情况下如直接采用 JDBC,则只需一条如下的 SQL 语句,即可完成该功能:
select tab1.ID, tab1.sumCol+tab2.sumCol from ( select a.ID, count(b.ID) sumCol from A a left join B b on a.ID=b.ID GROUP BY a.ID )tab1, ( select a.ID, count(c.ID) sumCol from A a left join C c on a.ID=c.ID GROUP BY a.ID )tab2 where tab1.ID=tab2.ID order by tab1.sumCol+tab2.sumCol desc |
在以上 JDBC 方式下,即使 A、B、C 表的数据量持续增长,仍然只有 1 条 SQL 的开销,不会出现 SQL 递增的情况,因此耗时是在可控制的区间内的。并且读者可以注意到上述 SQL 将 3 表关联拆分成了 2 个子查询,这样避免了 3 表做笛卡尔积的数量和,进一步提高了查询效率。由此可见,直接操作 JDBC,除 SQL 的开销可控外,还可以利用数据库层各种机制,如上述查询语句中的 left join、子查询、索引…,灵活的调整 SQL 语句,以达到最佳的查询性能。
由上可实例可看出,在多表关联、排序等复杂的查询情况下,Hibernate 框架由于其自身对象封装的特殊性,不能像 JDBC 直接操作 SQL 那样很好的解决查询中高效性和灵活性方面的需求,且由于其屏蔽了数据库的底层,开发人员看到的只是 Hibernate 提供的数据层 API,无法与灵活的使用 SQL 语句等数据库底层细节。因此,有必要在 Hibernate 框架中提供直接操作 JDBC 的接口。
在 Hibernate 框架中提供操作 JDBC 的接口的解决方案
我们知道 Hibernate 框架本身也是建立在 JDBC 之上的数据持久层实现,因此,要在框架本身提供操作 JDBC 的接口,需要切入其对 JDBC 封装的细节。
通过研究和查阅 Hibernate 的框架源代码及参考文档,我们发现,Hibernate 的 Session 会话是进行持久化的基础,所有的持久化操作都是在 Session 的基础上进行的,在实现上它是和 JDBC 中的 connection 数据库连接绑定的,也就是说,Hibernate 的会话域基于一个实际的 connection 类实例,二者之间的关系如下图所示:
由上可以看到,Hibernate 中的 session 是单线程的,代表了一次会话的过程。实际上是把一个 JDBC Connection 打包了,每一个 Session 实例和一个数据库事务绑定。其生命周期是与与之关联的 connection 实例的生命周期一致的。
由上面的 Hibernate 的 Session 机制我们意识到,只要能获取到 Hibernate 当前会话中的 Connection,则获得了 JDBC 的底层数据库连接实例,剩下就都是 JDBC 的范畴了。再查阅 Hibernate 的 API,发现 HibernateTemplate 类中 SessionFactory 成员的 getCurrentSession() 方法即可获得 Hibernate 环境下的当前活动的 Session 会话,而 Hibernate 中 Session 实例的 connection() 方法即可获得该会话中绑定的 Connection 数据库连接实例。
问题迎刃而解了,既然可以操作 Connection 实例,那与之关联的 Statement、ResultSet 等基本 JDBC 类均在我们控制范围中了,我们采用接口模式设计一个轻量级解决方案,使其在保持原 Hibernate 的增删改操作方式前提下灵活提供操作 JDBC 的接口。设计类图如下图所示:
设计中,AbstractHibernateDao 类作为 DAO 操作的基本类,保留原有 Hibenrate 框架下的新增,修改,删除等 API。BaseHibernateDao 类继承 AbstractHibernateDao 类,在此类中增加了直接操作 JDBC 的接口。设计 getConnection 方法获取 JDBC 的数据库连接实例,设计 getObjectsBySql 方法作为对外的主要接口,该方法调用 fetchObjects 方法,这是具体的数据库记录到领域对象的转换操作,需要使用者 override 该方法以完成自有领域对象的填充细节。
实际实现的类代码如下所示:
AbstractHibernateDao.java: abstract public class AbstractHibernateDao extends HibernateDaoSupport { protected Log logger = LogFactory.getLog(getClass()); protected Class entityClass; protected Class getEntityClass() { return entityClass; } public List getAll() { return getHibernateTemplate().loadAll(getEntityClass()); } public void save(Object o) { getHibernateTemplate().saveOrUpdate(o); } public void removeById(Serializable id) { remove(get(id)); } public void remove(Object o) { getHibernateTemplate().delete(o); } } BaseHibernateDao.java: abstract public class BaseHibernateDao extends AbstractHibernateDao{ public Connection getConnection() { try { Session curSeesion =null; Connection con =null; curSeesion = super.getHibernateTemplate().getSessionFactory() .getCurrentSession(); con = curSeesion.connection(); return con; } catch(Exception es) { System.out.println(es.getMessage()); return null; } } public ArrayList<Object> fetchObjects(ResultSet rs) { ArrayList<Object> ret = new ArrayList<Object>(); //example: //while(rs.next()) //{ //Object object = new Object(); //rs.getString(1); //rs.getString(2); //ret.add(object); //} return ret; } public ArrayList<Object> getObjectsBySql(String pureSql) { Connection con = curSeesion.connection(); ps = con.prepareStatement(sqlbuf.toString()); rs = ps.executeQuery(); try { return this.fetchObjects(rs); } catch(Exception es) { System.out.println(es.getMessage()); return null; } finally { try { ps.close(); rs.close(); con.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } |
使用该解决方案时,只需要将代码包解压至项目源代码目录,在想要直接操作 JDBC 接口的 DAO 模块继承 BaseHibernateDao 类,然后重写 fetchObjects 方法填入从自身数据库表字段填充到领域对象的操作 , 即可轻松调用 getObjectsBySql 传入原生 SQL 语句,返回具体的领域对象实体集合,当然使用者也可以通过 getConnection 获得 JDBC 的 Connection 实例来进行自己需要的特定的 JDBC 底层操作。
仍然以上文中的 A、B、C 表为例,采用该解决方案完成 top10 取数的代码示例如下:
public class testDAO extends BaseHibernateDao{ private String sqlQuery = " select tab1.ID,tab1.sumCol+tab2.sumCol"+ " from(select a.ID, count(b.ID) sumCol"+ " from A a left join B b on a.ID=b.ID"+ " GROUP BY a.ID)tab1, "+ " (select a.ID,count(c.ID) sumCol"+ " from A a left join C c on a.ID=c.ID"+ " GROUP BY a.ID)tab2"+ " where tab1.ID=tab2.ID"+ " order by tab1.sumCol+tab2.sumCol desc"; @override public ArrayList<A> fetchObjects(ResultSet rs) { ArrayList<A> ret = new ArrayList<A>(); int count=1; while(rs.next()) { A a = new A(); a.setId(rs.getLong(1)); System.out.println("top"+(count++)+" amount:"+rs.getLong(2)); ret.add(object); } return ret; } } |
在实际 mySql 数据库环境中,以 A 表数据量 1000 条,B 表数据量 3W 多条,C 表数据量 2000 条情况下进行上文中提到的 top10 的操作,采用 Hibernate 的耗时和用 JDBC 接口解决方案的效率比较如下:
表 1. Hibernate 框架方式与 JDBC 接口方式效率比较 1:
查询耗时 | 1475ms | 1096ms |
排序耗时 | 1035ms | 0ms |
总计: | 2110ms | 1096ms |
A 表数据量 2000 条,B 表数据量 6W,C 表数据量 4000 条情况下,采用 Hibernate 的耗时和用 JDBC 接口解决方案的效率比较:
表 2. Hibernate 框架方式与 JDBC 接口方式效率比较 2:
查询耗时 | 2836ms | 1657ms |
排序耗时 | 1568ms | 0ms |
总计: | 4404ms | 1657ms |
由以上结果可以看出:在数据量递增的情况下,采用 Hibernate 方式下效率与库表数据呈线性增长,且排序的操作的效率也是一样,而直接采用 JDBC 接口解决方案下效率远远高于 Hibernate 方式,且在数据量增长的情况下耗时的增长速度处于合理的区间内。
本文分析了 Hibernate 框架在处理复杂查询功能上的效率问题,提出并实现了一个在 Hibernate 框架内提供直接 JDBC 操作接口的解决方案,并实际验证了该解决方案的有效性,文中的源代码可以直接运用于选择 Hibenrate 框架作为数据持久层实现的 J2EE 项目,使之具备操作底层 JDBC 的功能。
相关推荐
经常有人问我java对DB2数据库的连接很麻烦,特别是连接字符串和连接驱动,现在我自己写了个jdbc对DB2的操作(含jar包在里面),非常全,大家下了之后就可以直接拷贝到你项目中应用了
9.4.4 直接通过JDBC API来进行批量操作 9.5 使用元数据 9.6 通过Hibernate调用存储过程 9.7 小结 9.8 思考题 第10章 映射组成关系 10.1 建立精粒度对象模型 10.2 建立粗粒度关系数据模型 10.3 映射...
课程内容 5 1 HelloWorld 5 2 Hibernate原理模拟 - 什么是O/R Mapping以及为什么要有O/R Mapping 5 3 常见的0/R框架(了解) 5 ...9 写测试类Main,在Main中对Student对象进行直接的存储测试 6 10 FAQ: 6
3、实现一套简单的ORM(直接使用spring rowmapper,insert自己实现),可以基于对象进行crud和相对复杂(感觉比hibernate强大一点)的sql操作; 4、基于对象指定查询的字段,大部分时候可以忘掉表结构进行业务...
Spring强调面向接口编程,所以我们将所有对Tfile的数据操作的方法定义在TfileDAO接口中,这些接口方法分别是: •findByFildId(String fileId) •save(Tfile tfile) •List findAll() TfileDAOHibernate...
9.4.4 直接通过JDBC API来进行批量操作 9.5 使用元数据 9.6 通过Hibernate调用存储过程 9.7 小结 9.8 思考题 第10章 映射组成关系 10.1 建立精粒度对象模型 10.2 建立粗粒度关系数据模型 10.3 映射...
9.4.4 直接通过JDBC API来进行批量操作 9.5 使用元数据 9.6 通过Hibernate调用存储过程 9.7 小结 9.8 思考题 第10章 映射组成关系 10.1 建立精粒度对象模型 10.2 建立粗粒度关系数据模型 10.3 映射...
5.hibernate中用来做持久化操作的 SessionFactory 1.重量级的,创建和销毁需要消耗很大的资源,不建议频繁创建和销毁 2.线程安全的,一个数据库对应一个Sessionfactory(一般一个应用程序对应一个SessionFactory就...
9.4.4 直接通过JDBC API来进行批量操作 9.5 使用元数据 9.6 通过Hibernate调用存储过程 9.7 小结 9.8 思考题 第10章 映射组成关系 10.1 建立精粒度对象模型 10.2 建立粗粒度关系数据模型 10.3 映射...
关于hibernate的讲解,浅显易懂。 直接使用JDBC操作数据库的步骤很繁琐 JDBC操作的是关系型数据库 ...Hibernate正是在这两种不同的模型之间建立关联,Hibernate给我们提供了利用面向对象的思想来操作关系型数据的接口
恍惚之际,只好再摸出JDBC 准备拼死一搏……,说得未免有些凄凉,直接使用 JDBC进行数据库操作实际上也是不错的选择,只是拖沓的数据库访问代码,乏味的字段读取操作令人厌烦。 “半自动化”的iBATIS,却刚好解决...
JDBC接口直接调用 测试项目有: 写入性能 批处理性能 查询性能 程序已经集成了各主流数据库的驱动,在修改数据库连接配置后,可以直接使用。已经集成的驱动有: oracle mysql SQL Server DB2 postgresql derby h2 ...
Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在 Servlet/JSP的Web应用中使用,最具革命意义的是,Hibernate可以在应用EJB的J2EE架 构中取代CMP,完成数据持久化的重任。...
恍惚之际,只好再摸出JDBC 准备拼死一搏……,说得未免有些凄凉,直接使用JDBC 进行数据库操作实际上也是不错的选择,只是拖沓的数据库访问代码,乏味的字段读取操作 令人厌烦。 “半自动化”的ibatis,却...
当Hibernate在查询数据的时候,数据并没有存在与内存中,当程序真正对数据的操作时,对象才存在与内存中,就实现了延迟加载,他节省了服务器的内存开销,从而提高了服务器的性能。 3.Hibernate中怎样实现类之间的...
改变直接反应在查询和自动查询的构建, 它使查询构建更快且安全。 Querydsl最先支持的是Hibernate的HQL语言, 现如今, Querydsl已经支持JPA, JDO, JDBC, Lucene, Hibernate Search, MangoDB, Collections 和 ...
本书系统全面地介绍了Java中的各项主流技术,以及基于这些技术的商业化应用程序的开发技巧,在讲解过程中以目前最为流行的开发工具MyEclipse为载体,全面系统地介绍了如何在MyEclipse中开发基于Struts、Hibernate、...
友情链接及网页访问量统计显示:在博客的个人页面中还提供了推荐给普通网络用户的相关友情链接,此外,对个人页面的访问量也在随时进行统计,并在个人页面中进行直观的显示。 博客主页面的用例图如图3所示: 图3 ...
使业务逻辑和数据库持久操作解耦,使代码更容易维护,主要处理应用程序的业务逻辑和业务验证,管理事务,预留和其它层交互的接口,管理业务层对象之间的依赖,增加在表现层和持久层之间的灵活性,使它们互不直接通讯...