使用hibernate的一个疑惑

其实这个疑惑应该和直接使用jdbc时一样的。
前面guty采用使用threadlocal的方式,很方面,但是这意味着,从web开始,后面的所有和数据库打交道的接口都需要hibernate的session,这也就意味着后面的操作都离不开hibernate了,这样使用dao模式的一个好处--可以替换持久层,就丧失了,采用dao的价值就大打折扣了。
我想这也是guty说得不需要dao的一个原因。
比如下面建立dao的接口就没有意义了。
public interface LookupDAO {
public List getRoles(Session ses) throws DAOException;
}

public class LookupDAOHibernate extends BaseDAOHibernate implements LookupDAO {
private Log log = LogFactory.getLog(LookupDAOHibernate.class);
public List getRoles(Session ses) throws DAOException {
List roleList = null;

if (log.isDebugEnabled()) {
log.debug("retrieving all role names...");
}

try {
Query q = ses.createQuery("from r in class " + Role.class + " order by name");
roleList = q.list();
ses.flush();
} catch (Exception e) {
e.printStackTrace();
throw new DAOException(e);
}

return roleList;
}
}
这个问题主要出现在需要事务处理的地方,这也是前面几个帖子讨论的关于事务处理的麻烦的地方。j2ee因为将事务放在配置里面,所以就不要这样传递seesion了。
我觉得选择hibernate某种程度上是排他的。混合使用hibernate和jdbc会导致接口不一致。同样混合使用hibernate和cmp也是会有问题的。
当项目规模不大时,互相转化的代价应该还是可以接受,如果项目庞大,恐怕在动手之前必须作出决定。

1、我想不论使用什么ORM技术,都不应该混合使用,而应该采用单一的方案。

2、在Hibernate中并不是鼓励混合采用JDBC,而是个别情况下,由于ORM的限制,比如说批量update,使用Hibernate性能会比较低,如果在你的应用中这是个瓶颈的话,那么单独开一个Connection使用JDBC,由于不在同一个Connection里面,在Hibernate里面不存在PO同步问题。

3、你的程序我没看明白,而且我也不清楚你为什么要在方法的参数里面传递Session。是不是你理解的ThreadLocal和我们谈的ThreadLocal解决线程singleton的问题不一样。因为你说“从web开始,后面的所有和数据库打交道的接口都需要hibernate的session,这也就意味着后面的操作都离不开hibernate了”其实并不是这样的,除了在Filter里面需要进行一次Session.close之外,其他所有的Session操作全部都在DAO实现类里面,不会出现Session操作扩散到DAO之外的情况。

4、使用ThreadLocal来保持Session,不是Hibernate唯一的选择,严格的事务处理,我本人更倾向于使用SLSB,而不是使用ThreadLocal这样比较粗糙的办法。另外一个使用ThreadLocal的情况是lazy loading,不过lazy loading也可以不用ThreadLocal,另有解决办法。

5、我想guty的意思是Hibernate已经足够好了,他也不准备改用别的ORM了,既然不准备替换ORM,那么还留DAO有何用?

6、BTW,你的程序有经典错误。

Query q = ses.createQuery("from r in class " + Role.class + " order by name");

千万不要这里用!!!
应该改成
"from r in class :role order by name",必须使用Placeholder,否则有严重的性能问题。

转贴一个我以前在别的论坛写的帖子,探讨Statement和PreparedStatement的性能问题的,希望提醒大家注意。

经常可以遇到这样的情况,由于程序员的sql语句写的非常不合理,导致Oracle数据库查询速度缓慢,严重的情况会导致数据库的故障。
举个例子,我以前就遇到过Oracle数据库定期(一周左右)出现 ORA-4031错误(shared pool内存不够,无法连接Oracle数据库),此时数据库已经无法再使用了。必须关闭重起,来释放那些占在shared pool里面无法释放的对象。
所以对于一个Oracle DBA来说,需要定期的检查shared pool中的sql语句占用的内存空间,对于严重占用shared pool,无法释放的sql语句,必须要求程序员修改或优化sql语句。
select sql_text ,sharable_mem from v$sql where sharable_mem > '100000' order by sharable_mem ;

上面的sql语句是查询shared pool中占用内存超过100K的sql语句。

这个sql可以非常有效的检查出Oracle shared pool中那些严重占用内存的sql,根据我的经验,绝大多数有问题的sql语句都会在这里留下痕迹,通过在这里找出有问题的sql语句并进行修改,再反复运行这个sql脚本,直到所以有问题的sql都处理完毕,这就是对Oracle数据库在sql上面的最好的优化,可以保证不会因为程序员的sql语句问题导致Oracle数据库的性能问题。

一个常识问题,却很容易被忽略。比如:

select * from table_name where id = 1;
select * from table_name where id = 2;
对于这种带参数的sql,id = ? 这个地方叫做站位符(Placeholder)。

拿Java为例,很多人是这样写代码的:
String sql = "select * from table_name where id = ";
Statement stmt = conn.createStatement();

rset = stmt.executeQuery(sql+"1");
......
rset = stmt.executeQuery(sql+"2");

这种写法,对于Oracle数据库来说,完全就是两条不同的sql语句,
select * from table_name where id = 1;
select * from table_name where id = 2;
每次查询都要进行sql语句的执行解析,并且每个sql都会分配一个区域来存放sql解析后的二进制可执行代码。试想,要是id不同的10万个sql呢?Oracle就会分配10万个sql区域来分别存放10万个这样的id不同的sql语句。对于一个数据库驱动的Web网站这样情况下,SGA开的再大,也会很快被耗尽share pool的,最后报一个ORA-4031错误。数据库就连接不上了,只好重起。 有人会想到,Oracle会在Shared Pool不够的时候,使用LRU算法清除掉一些不用的sql,但是遗憾的是,一旦Oracle的访问十分频繁的情况下,清除的速度远远跟不上加进来的速度,会很快导致数据库崩溃。

正确的写法应该是:

PreparedStatement pstmt = conn.prepareStatement("select * from table_name where id = ?");

pstmt.setInt(1,1);
rset = pstmt.executeQuery();
...
pstmt.setInt(1,2);
rset = pstmt.executeQuery();

这样Oracle数据库就知道你实际上用的都是同一条sql语句,会以这样的形式:
select * from table_name where id = :1
解析执行后存放在sql区域里面,当以后再有一样的sql的时候,把参数替换一下,就立刻执行,不需要再解析sql了。既加快了sql执行速度,也不会占有过多SGA的share pool。

可惜的是,很多程序员明知道这个问题,却意识不到问题的严重性,因为上面那种写法,编程的时候很灵活,sql语句可以动态构造,实现起来很容易,后面那种写法,sql语句是写死的,参数不能再变了,编程经常会非常麻烦。

很多数据库的性能问题都是这样造成的。

有兴趣在一个生产系统中,用上面sql检查一下,看看是否选择出来的是否l有很多都是一样的sql语句,只是参数不同,如果是这样的话,就说明程序员的代码写的有问题。

补充一点:

Hibernate实际上总是向数据库发送PreparedStatement,已经在很大程度上避免了程序员对于Statement的误用。但是即使是PreparedStatement,不用Placehold,非要把常量带入,也会出现同样的性能问题。

当where子句的条件不确定的时候,使用PreparedStatement是非常痛苦的,因为Statment可以简单的用常量代入的方式动态构造sql,而PreparedStatement的的set参数的方法是按照数字索引的,比如:
setString(1,...);
setBoolean(2,...);
就造成了动态构造的sql,你无法确定参数的1,2这样的数字顺序,除非使用很大很麻烦的if else嵌套才能够勉强解决。

但是Hibernate好在可以使用带名的Placehold,就是这样:
select * from table where user = :name

然后set参数的时候,就可以setString("name",...);
由于set参数和顺序无关,就很容易实现动态构造sql。

这也是Hibernate的一个很大的优点。


看了robbin关于PrepareStatment和Statment的解释,受益良多!

但我觉得


Query q = ses.createQuery("from r in class " + Role.class + " order by name");

这一句并不需要用Placeholder吧,这里的Role.class相当于一个表的名字,而sql查询的时候表名一般是不需要用占位符。就好像你举例中的table_name并不需要用问号来置换一样吧。

另外还有一些hibernate查询的问题请教:


Organization org = (Organization)session.load(Organization.class, orgId);

这样的性能和

Query q = session.createQuery("from org in class " + Organization.class + " where org.id = ?");
//......
//find method

哪一个高?

另外

from alias_name in class ****** ...... 

from ******* (as) alias_name ......

这两种方式hibernate解释是否一样?性能一样?
hibernate中的select和from的性能比较呢?


非常感谢robbin的回答
“1、我想不论使用什么ORM技术,都不应该混合使用,而应该采用单一的方案。”
说混合不合适,是因为针对前面的建议说“...可以选择熟悉的持久层技术,然后局部修改成hibernate,熟悉后扩大到全部”。

“2、在Hibernate中并不是鼓励混合采用JDBC,而是个别情况下,由于ORM的限制,比如说批量update,使用Hibernate性能会比较低,如果在你的应用中这是个瓶颈的话,那么单独开一个Connection使用JDBC,由于不在同一个Connection里面,在Hibernate里面不存在PO同步问题。”
这是一个很好的经验。

“3、你的程序我没看...不会出现Session操作扩散到DAO之外的情况。”
例子不是我写的,是从struts-resume里摘出来的。我理解要传递session,是因为需要严格控制事务,事务的开始恐怕是在web层里就要打开了,不同dao之间session共享的话,struts-resume的解决方案就是传递session,不过,看了spring的框架,发现是可以不这样用的,但是解决方案比较复杂没有看懂。另外一种就是采用现在比较热的一种技术aop。

“4、使用ThreadLocal来保持Session,不是Hibernate唯一的选择,严格的事务处理,我本人更倾向于使用SLSB,而不是使用ThreadLocal这样比较粗糙的办法。另外一个使用ThreadLocal的情况是lazy loading,不过lazy loading也可以不用ThreadLocal,另有解决办法。”
对于slsb,是否指无状态回话bean,是否专门作个bean,把所有和事务相关的操作都放在一起?

5,同意
6,牢记在心:)

呵呵,性能好坏一个要测量,一个可以看源码。
我都没有作,但是,我猜:一样(转化的性能可以忽略的化),hiberate会转化成相同的sql来完成。
可以打开log日志检查sql来验证。

>>对于slsb,是否指无状态回话bean,是否专门作个bean,把所有和事务相关的操作都放在一起?

SLSB = Stateless Session Bean

就是完全用SLSB来实现整个业务层逻辑。

对于用placeholder去替换表名, 对oracle来说是影响不大的.

oracle把sql放到sqlarea里是为了提高hit_ridio, 即尽量从内存中得到数据, 而不是每次都从硬盘上. 所以每次我们运行一条sql, oracle都会依照LRU的原则把cursor保存到sga中, 下一次运行类似的sql, oracle先查x$kglob(他是v$sql和v$sqltext的原表), 然后查v$sql_cursor去得到cursor, 但如果cursor的数据源表被写了,即dirty cursor, 这个cursor就会被删除, 如果sql中表的名字是不确定的, 这个cursor也是不会被保存的.

真正占内存的是shared cursor, 而不是用来查cursor的sql.

看错了, oracle不可能接受表名或字段名做为变量传入的.