`
SquareCome
  • 浏览: 9302 次
  • 性别: Icon_minigender_1
  • 来自: 上海
最近访客 更多访客>>
社区版块
存档分类
最新评论

在 Hibernate 中直接操作 JDBC 接口

阅读更多

简介: Hibernate 在处理多表关联及分组排序等复杂数据库查询操作时,其固有的 O-R 映射机制会产生大量冗余 SQL 操作,系统性能比传统的 JDBC 低很多。本文分析了 Hibernate 产生此类问题的原因,提出了一个在 Hibernate 框架内直接操作 JDBC 的接口的解决方案,在实际项目中验证了该解决方案可以有效提高此类查询的效率。文中提供的示例代码可以直接运用于使用 Hibernate 框架的 J2EE 系统项目。

 

------------------============-----------------------

 (转载自

内容

在 Hibernate 框架中提供直接操作 JDBC 接口的原因

Hibernate 框架在处理复杂查询方面的问题

Hibernate 是一个开放源代码的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,使得 Java 程序员可以随心所欲的使用面向对象编程思维来操纵数据库。Hibernate 的优势在于屏蔽了数据库细节,对于新增修改删除的数据层操作,不再需要跟具体的 SQL 语句打交道,简单的对对象实例进行增删改操作即可。

但是,对于多表关联、分组统计、排序等复杂的查询功能时,由于 Hibernate 自身的 O-R 映射机制,父子表之间关联取数据会产生大量冗余的查询操作,性能低下。此类情况下,直接使用 JDBC 的 SQL 语句反而更加灵活和高效。

Hibernate 框架处理复杂查询问题实例分析

考虑如下数据库实体示例,表 A 为主表,表 B 和表 C 为子表,A 与 B、A 与 C 表均为 1 对多关系,在 B 表和 C 表中以 A_ID 外键字段关联 A 表父记录。


图 1. 数据库实体示例图
图 1. 数据库实体示例图

在 Hibernate 框架中,通常采用以下配置方式完成 A 表与 B,C 表父子实体之间的级联查询操作,Hibernate 实体配置 xml 如下:


清单 1. 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 领域实体类代码示例如下:


清单 2. 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 大小并进行排序得到,其代码示例如下:


清单 3. 排序代码示例

				
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 日志中:


清单 4. 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 语句,即可完成该功能:


清单 5. 直接 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 的 session 机制

我们知道 Hibernate 框架本身也是建立在 JDBC 之上的数据持久层实现,因此,要在框架本身提供操作 JDBC 的接口,需要切入其对 JDBC 封装的细节。

通过研究和查阅 Hibernate 的框架源代码及参考文档,我们发现,Hibernate 的 Session 会话是进行持久化的基础,所有的持久化操作都是在 Session 的基础上进行的,在实现上它是和 JDBC 中的 connection 数据库连接绑定的,也就是说,Hibernate 的会话域基于一个实际的 connection 类实例,二者之间的关系如下图所示:


图 2. Hibernate Session 机制示意图
图 2. Hibernate Session 机制示意图

由上可以看到,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 的接口。设计类图如下图所示:


图 3. 解决方案设计类示意图
图 3. 解决方案设计类示意图

设计中,AbstractHibernateDao 类作为 DAO 操作的基本类,保留原有 Hibenrate 框架下的新增,修改,删除等 API。BaseHibernateDao 类继承 AbstractHibernateDao 类,在此类中增加了直接操作 JDBC 的接口。设计 getConnection 方法获取 JDBC 的数据库连接实例,设计 getObjectsBySql 方法作为对外的主要接口,该方法调用 fetchObjects 方法,这是具体的数据库记录到领域对象的转换操作,需要使用者 override 该方法以完成自有领域对象的填充细节。

实际实现的类代码如下所示:


清单 6. 解决方案实现代码

				
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 取数的代码示例如下:


清单 7. 使用解决方案示例

				
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:

  Hibernate 框架方式采用 JDBC 接口解决方案
查询耗时 1475ms 1096ms
排序耗时 1035ms 0ms
总计: 2110ms 1096ms

A 表数据量 2000 条,B 表数据量 6W,C 表数据量 4000 条情况下,采用 Hibernate 的耗时和用 JDBC 接口解决方案的效率比较:


表 2. Hibernate 框架方式与 JDBC 接口方式效率比较 2:

  Hibernate 框架方式采用 JDBC 接口解决方案
查询耗时 2836ms 1657ms
排序耗时 1568ms 0ms
总计: 4404ms 1657ms

由以上结果可以看出:在数据量递增的情况下,采用 Hibernate 方式下效率与库表数据呈线性增长,且排序的操作的效率也是一样,而直接采用 JDBC 接口解决方案下效率远远高于 Hibernate 方式,且在数据量增长的情况下耗时的增长速度处于合理的区间内。


总结

本文分析了 Hibernate 框架在处理复杂查询功能上的效率问题,提出并实现了一个在 Hibernate 框架内提供直接 JDBC 操作接口的解决方案,并实际验证了该解决方案的有效性,文中的源代码可以直接运用于选择 Hibenrate 框架作为数据持久层实现的 J2EE 项目,使之具备操作底层 JDBC 的功能。

分享到:
评论

相关推荐

    自己写的jdbc对DB2的操作(含jar包在里面)

    经常有人问我java对DB2数据库的连接很麻烦,特别是连接字符串和连接驱动,现在我自己写了个jdbc对DB2的操作(含jar包在里面),非常全,大家下了之后就可以直接拷贝到你项目中应用了

    精通 Hibernate:Java 对象持久化技术详解(第2版).part2

     9.4.4 直接通过JDBC API来进行批量操作  9.5 使用元数据  9.6 通过Hibernate调用存储过程  9.7 小结  9.8 思考题 第10章 映射组成关系  10.1 建立精粒度对象模型  10.2 建立粗粒度关系数据模型  10.3 映射...

    hibernate笔记

    课程内容 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

    基于SpringJDBC的轻量级ORM框架sborm.zip

    3、实现一套简单的ORM(直接使用spring rowmapper,insert自己实现),可以基于对象进行crud和相对复杂(感觉比hibernate强大一点)的sql操作; 4、基于对象指定查询的字段,大部分时候可以忘掉表结构进行业务...

    ssh(structs,spring,hibernate)框架中的上传下载

     Spring强调面向接口编程,所以我们将所有对Tfile的数据操作的方法定义在TfileDAO接口中,这些接口方法分别是:  •findByFildId(String fileId)  •save(Tfile tfile)  •List findAll()  TfileDAOHibernate...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part4

     9.4.4 直接通过JDBC API来进行批量操作  9.5 使用元数据  9.6 通过Hibernate调用存储过程  9.7 小结  9.8 思考题 第10章 映射组成关系  10.1 建立精粒度对象模型  10.2 建立粗粒度关系数据模型  10.3 映射...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part3

     9.4.4 直接通过JDBC API来进行批量操作  9.5 使用元数据  9.6 通过Hibernate调用存储过程  9.7 小结  9.8 思考题 第10章 映射组成关系  10.1 建立精粒度对象模型  10.2 建立粗粒度关系数据模型  10.3 映射...

    jdbc基础和参考

    5.hibernate中用来做持久化操作的 SessionFactory 1.重量级的,创建和销毁需要消耗很大的资源,不建议频繁创建和销毁 2.线程安全的,一个数据库对应一个Sessionfactory(一般一个应用程序对应一个SessionFactory就...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part1.rar

     9.4.4 直接通过JDBC API来进行批量操作  9.5 使用元数据  9.6 通过Hibernate调用存储过程  9.7 小结  9.8 思考题 第10章 映射组成关系  10.1 建立精粒度对象模型  10.2 建立粗粒度关系数据模型  10.3 映射...

    hibernet入门

    关于hibernate的讲解,浅显易懂。 直接使用JDBC操作数据库的步骤很繁琐 JDBC操作的是关系型数据库 ...Hibernate正是在这两种不同的模型之间建立关联,Hibernate给我们提供了利用面向对象的思想来操作关系型数据的接口

    iBATIS技术教程PPT和代码.rar

    恍惚之际,只好再摸出JDBC 准备拼死一搏……,说得未免有些凄凉,直接使用 JDBC进行数据库操作实际上也是不错的选择,只是拖沓的数据库访问代码,乏味的字段读取操作令人厌烦。 “半自动化”的iBATIS,却刚好解决...

    rexdb-tester

    JDBC接口直接调用 测试项目有: 写入性能 批处理性能 查询性能 程序已经集成了各主流数据库的驱动,在修改数据库连接配置后,可以直接使用。已经集成的驱动有: oracle mysql SQL Server DB2 postgresql derby h2 ...

    网站设计方案(完整版).doc

    Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在 Servlet/JSP的Web应用中使用,最具革命意义的是,Hibernate可以在应用EJB的J2EE架 构中取代CMP,完成数据持久化的重任。...

    ibatis 开发指南

    恍惚之际,只好再摸出JDBC 准备拼死一搏……,说得未免有些凄凉,直接使用JDBC 进行数据库操作实际上也是不错的选择,只是拖沓的数据库访问代码,乏味的字段读取操作 令人厌烦。 “半自动化”的ibatis,却...

    Spring面试题

    当Hibernate在查询数据的时候,数据并没有存在与内存中,当程序真正对数据的操作时,对象才存在与内存中,就实现了延迟加载,他节省了服务器的内存开销,从而提高了服务器的性能。 3.Hibernate中怎样实现类之间的...

    Querydsl中文文档翻译.pdf

    改变直接反应在查询和自动查询的构建, 它使查询构建更快且安全。 Querydsl最先支持的是Hibernate的HQL语言, 现如今, Querydsl已经支持JPA, JDO, JDBC, Lucene, Hibernate Search, MangoDB, Collections 和 ...

    JAVA程序开发大全---上半部分

    本书系统全面地介绍了Java中的各项主流技术,以及基于这些技术的商业化应用程序的开发技巧,在讲解过程中以目前最为流行的开发工具MyEclipse为载体,全面系统地介绍了如何在MyEclipse中开发基于Struts、Hibernate、...

    基于J2EE框架的个人博客系统项目毕业设计论文(源码和论文)

    友情链接及网页访问量统计显示:在博客的个人页面中还提供了推荐给普通网络用户的相关友情链接,此外,对个人页面的访问量也在随时进行统计,并在个人页面中进行直观的显示。 博客主页面的用例图如图3所示: 图3 ...

    火炬博客系统7

    使业务逻辑和数据库持久操作解耦,使代码更容易维护,主要处理应用程序的业务逻辑和业务验证,管理事务,预留和其它层交互的接口,管理业务层对象之间的依赖,增加在表现层和持久层之间的灵活性,使它们互不直接通讯...

Global site tag (gtag.js) - Google Analytics