使用hibernate的一个疑惑

03-07-20 you.cai

其实这个疑惑应该和直接使用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也是会有问题的。

当项目规模不大时,互相转化的代价应该还是可以接受,如果项目庞大,恐怕在动手之前必须作出决定。

robbin
2003-07-21 08:33

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,否则有严重的性能问题。

robbin
2003-07-21 08:40

转贴一个我以前在别的论坛写的帖子,探讨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数据库的性能问题。

robbin
2003-07-21 08:44

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

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语句,只是参数不同,如果是这样的话,就说明程序员的代码写的有问题。

robbin
2003-07-21 08:56

补充一点:

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的一个很大的优点。

3Go 1 2 3 下一页