hiberante入门(十六):一些细节理论

十二、一些细节问题分析

1.实体配置文件中的类型问题

在前面的多个实例中配置<property>映射类型时,我们都采取的是hibernate默认的配置,即是说没有在<property>中配置type属性:其实基本类型一般不需要映射文件中配置,只有在一个java类型与多个数据库类型相对应时,交且我们不希望使用默认的配置才会用type来指明类型。举例:java.util.Date与数据库中的DATETIMEDATETIME,TIMESTAMP相对应,如果我们不希望映射成默认的DATATIME,而想映成DATE,便配置成type=DATE”,另需要说明的是我们也可配置成我们自定义的类型,但前提是我们自定义的类型必须实现org.hibernate.UserTypeorg.hibernate.CompositeUserType中的任何一个接口。且在配置时指定类的完整名

2.SessionSessionFactory

Session是非线程安全的,生命周期较短,代表一个数据库的连接,在B/S系统中一般不会超过一个请求,内部维护一级缓存和数据库连接。通常一个Session的生成也就意味着即将结束。

SessionFactory是线程安全的,一个数据库对应一个SessionFactoy,一般会在整个系统生命周期内有效:SessionFactory保存着和数据库连接相关的信息(user,password,url)和映射信息,以及hibernate运行时用到的一些信息。

3.Session中的flush方法

此方法的作用是将一级缓存与数据库同步,通常在查询数据或者是提交前(可能会采取批量更新的方式到数据库)会实现同步,这样可有效保证一级缓存中的数据是有效的数据。这个方法是由hibernate自身来维护调用的,因为它的调用意味着与数据库交互,所以不建议我们手工调用,而事实上hibernate对它的调用主要是为了提升性能和保证数据的有效性且hibernate自身的对它的维护调用已近完美。

4.巧用flush解决内存溢出问题

当我们进行类似如下的操作时:for (int i = 0; i < 10000000; i++) s.save(obj);i足大到我们的内存不足以承受时,便会出现内存溢出问题,对于此可以这样解决:

for (int i = 0; i < 10000000; i++) {

    s.save(obj);

    if (i % 30 == 0) {

    s.flush();

    s.clear();

    }

} 分析:当每保存30个数据时,我们便清掉一级缓存中的内容,但是在清掉内容前我们必须让一级缓存中的数据与数据库进行一次交互,这样可以保证数据能被保存到数据库中。试想下,如果不flush,那么在一级缓存中的数据是不能同步到数据库中的,所以这里flush是非常重要得。 其实只要再来回忆前面所说的一级缓存中的一些知识,便可以发现如果保存的对象的主键是以increme的方式生成的,可以不用flush也能实现数据及时同步到数据库中

5.StatelessSession接口解决批量更新问题:

在进行如上的批量操作时,我们通过flush来解决和数据库的同步更新,但是这样会造成缓存的不断更新,因此在进行类似的批量操作时,我们通常会选择使用StatelessSession接口来代替Session,由于这种无状态的Session具有以下特点:它不和一级缓存、二级缓存交互,也不触发任何事件,监听器,拦截器,通过该接口能把这些批量更新直接发送到数据库。说明:StatelessSession的方法也Session的方法相似。 除了此方法外,我们使用Query.executeUpdate(),也可以执行批量更新,但是此方法会清除相关联的类的二级缓存,也可能造成级联,甚至是和乐观锁不相容。

6.离线查询DetachedCriteria:

package com.asm.hibernate.test; public class DetachedCriteriaTest { public static void main(String[] args) { add(); DetachedCriteria dec = DetachedCriteria.forClass(User.class); String name = "jack"; dec.add(Restrictions.eq("name", name)); List<User> list = detaCri(dec); for (User u : list) System.out.println(u); } static List detaCri(DetachedCriteria dec) { Session s = HibernateUtil.getSession(); Criteria cr = dec.getExecutableCriteria(s); List list = cr.list(); s.close(); return list; } static void add() { ...省略内容:主要作用是在数据库中插入两条名为“jack”的记录,以使查询操作可以进行。 } }

 

 

分析为会么要使用这种离线查询,比如我们可以在业务逻辑层构造出一个DetachedCriteria对象,且它不会依赖于Session,这样当们把这个对象作为参数来传递给M的方法时便可以实现连库查询。这样可以有效的使用Session,避免与数据库的频繁交互。

7.监听器的使用:

下面首通过一个简单的例子来说明此问题:

>>步骤一、编写监听类:

 

package com.asm.hibernate.event; public class SaveUserListener implements SaveOrUpdateEventListener { public void onSaveOrUpdate(SaveOrUpdateEvent event) throws HibernateException { if (event.getObject() instanceof com.asm.hibernate.domain.User) { User user = (User) event.getObject(); System.out.println("find save User:" + user.getName()); user.setDate(new Date()); } } }
 

 

 

分析:当有保存/更新事件发生时,便被此监听类监听到,而我们处理的只是保存/更新User对象,即是说当发生保存/更新一个User对象时,才会被这个监听器监听操作;其它对象的保存/更新操作尽管也会被它监听,但不会有处理动作。

>>步骤二、编写测试类:

 

package com.asm.hibernate.event; public class TestListener { public static void main(String[] args) { addUser(); } static void addUser() { Session s = null; Transaction tx = null; try { User u = new User(); u.setName("jack"); s = HibernateUtil.getSession(); tx = s.beginTransaction(); s.save(u); tx.commit(); } finally { if (s != null) s.close(); } } }

 

 

 分析:执行后,发现没有任何作用。原因是未配置监听器,联想到gui程序监听时,总会在程序中给出相应的代码,而这里我们只需要在主配置文件中配置这个监听器,在由hibernate把这个监听器写成代码到程序中去。在主配置文件中配置的内容如下

 

 

这样配置后,尽管监听器类中的方法能实现,但是发现这些数据没有保存到数据库中。因为当们配置一监听器时,相对应的默认监听器就失效了,即是说org.hibernate.event.def.DefaultSaveOrUpdateEventListener这个默认的监听器不起作用了,为了能继续使用默认的监听器,应再配置上这个监听器,即配置成这样:

<event type="save"> <listener class="com.asm.hibernate.event.SaveUserListener" /> <listener class="org.hibernate.event.def.DefaultSaveOrUpdateEventListener" /> </event>

 

 

 

这样配置后,即可以让我们自己的监听器生效,又可以让默认监听器起作用。

8.使用原始的sql查询:

代码如下:

package com.asm.hibernate.test; public class JdbcSqlSelectTest { public static void main(String[] args) { addUsers(); query(); } static void query() { Session s = HibernateUtil.getSession(); Query q = s.createSQLQuery("select * from user").addEntity(User.class); List<User> rs = q.list(); for (User u : rs) { System.out.println("Result:"+u.getName() + "---" + u.getDate()); } } static void addUsers() { ...增加一些记录,以检验查询 } }

 

 分析:关键是看查询方法:中间用到的查询语句from user,user是指表名,后面增加addEntity方法的主要是了了保证返回的list对象中保存的User,这样方便foreach遍历。 需要说明的是尽管hibernate支持这种sql查询,但是它不具有通用性,即是说我们在写这些查询语句时总会依赖于一个具体的数据库,一旦换了数据库就可能出现问题,因此最好不要使用这种JdbcSql查询。

9.命名查询:

>>步骤一:在User的实体配置文件</hibernate-mapping>的元素下增加如下内容:

<query name="selectUserbyId"> <![CDATA[from User where id=:id]]> </query>

 

 

>>步骤二、编写测试:

 

 

static List namedQuery(int id) { Session s = HibernateUtil.getSession(); Query q = s.getNamedQuery("selectUserbyId"); //Query q=s.getNamedQuery("com.asm.hibernate.domain.User.selectUserbyIdTheSecond"); q.setInteger("id", id); return q.list(); }

 

 

说明这种方式虽然简单,但是容易在整个系统中引起重名,所以最好的方式是在Use的实体配置文件的<class>元素下配置这个属性,比如我们在<class>元素的最后增加如下内容:

<query name="selectUserbyIdTheSecond"> <![CDATA[from User where id=:id]]> </query>

 

 

则在上面的代码中要想用到这个命名查询,则应写成注释掉的代码那样。如果项目中的命名查询不多,建议写在</hibernate-mapping>元素下,这样引用较方便。

补充说明:我们也可以配置SQl的命名查询,步骤是先在</hibernate-mapping>元素下配置如下内容:

<sql-query name="selectUserSql"> <![CDATA[select * from user]]> </sql-query>

 

 

 然后执行下面的代码:

static List namedSqlQuery(int id) { Session s = HibernateUtil.getSession(); Query q = s.getNamedQuery("selectUserSql"); return q.list(); }

 

 

10.N+1查询:

N+1查询由于缓存,或者lazyload(懒加载)等原因,在查询n个结果,可能要执行n+1次查询数据库操作。 在HibernateN+1查询主要表现在:(1)使用iterate查询的时候 (2)查询子对象的时候。其实N+1曾引发了关于效率上的讨论,因此建议到网上查阅有关的资料学习。

11.HQL QBC:

在前面曾应用过HQL,QBC,所以对于它们的进一步学习,应该很容易上手,所以同样建议参看官方手册或者查阅其它相关资料进行更深入的学习。

 

 

 

 

 

你可能感兴趣的:(sql,Hibernate,中间件)