Hibernate注解使用感受

    之前在项目中已经决定使用mongoDB+morphia,这种支持复杂结构的数据库的支持,使得我们完全摆脱DB的束缚,完全进行DDD设计,这样,我们游戏中的所有model、DTO以及数据持久对象均采用相同的Class进行表述,而通过丰富的注解(morphia和我们自己的定义的DTO以及其他注解)来控制Model属性所属域。这么做简直太完美了,除了维护逻辑处理的service外,我们几乎只需要维护一个model工程就可以了。
    但是,噩耗来了。。。由于项目是与小日本的公司合作开发,游戏要挂在人家的平台上,上周经过多次会议讨论后,最终集团CTO决定使用mysql,因为小日本认为mongoDB不够稳定(擦,那么多大型应用在使用,你丫也太保守了。。。)。没办法,毕竟CTO不愿冒险,理解。。。于是乎放弃使用mongoDB转战hibernate。
    由于框架已经形成,AOP编程已经在框架中大行其道,最小的改动就是只换掉morphia,不能让关系型数据库束缚住model的设计,于是自然而然想到hibernate的注解。
    经过一天的折腾之后,对注解方式有了一定的了解,总体来讲hibernate的注解基本可以完全替换HBM方式的配置文件,而且易于程序阅读,加上我们之前自己的注解,OK,一切可行。不过,没了mongoDB的sharding和高性能,mysql的sharding实在没法摆脱逻辑的相关性,悲催啊。。。
    以前用过hibernate,不过只简单的当做数据库访问和映射工具用,没有实际深究,这次项目比较大,且需要在5个项目上同时开工的架构不能大意,于是稍微深入了下。下面简单列下,hibernate中发现了一些原来我没有想到或注意的问题:

1、主键通过@GeneratedValue进行生成,默认为auto的情况下,如果数据使用mysql,那么主键将会使用数据库的自增方式,这样会出现一个问题:
                Session session = sessionFactory.openSession();
                Cat mother = new Cat();
		mother.setSex("female");
		mother.setBirthdate(new Date());
		mother.setColor(ColorType.COLOR);
		mother.setLitterId(-1);
		mother.setWeight(new BigDecimal(123.0D));
		session.save(mother);
		for (int i = 0; i < 10; i++) {
			Cat cat = new Cat();
			cat.setBirthdate(new Date());
			cat.setLitterId(i);
			cat.setMother(mother);
			cat.setSex("male");
			session.save(cat);
		}
		System.out.println(session.getFlushMode());
		session.flush();
                session.close();

在debug上面一段代码(注意代码的数据操作为非事务操作)的时候发现,无论flush是否被调用,只要save执行完成,数据库中就可以看到save的结果已经被同步到数据库,但是按照hibernate官方文档中看,只有session.flush()才执行session的缓存(一级缓存)与数据库同步操作(也就是sql),那问题到底出在哪儿呢?于是,我首先想到了生成主键的问题,因为使用了数据库的生成主键,大家知道,mysql生成的主键的读取是通过一个mysql提供的sql读取的,可以理解为读取上一次自增ID的值,这样,意味着必须insert之后才能拿到自增ID,所以save的同步也就不得不执行了。当然以上只是我的猜想,为了证明,我将cat对象的主键生成方式使用UUIDHexGenerator(在应用本地通过算法计算出一个值),然后再次调用上面的代码,结果证明,只要不进行flush操作,数据库里没有任何结果,而进行flush的时候,通过log可以看到,hibernate进行了一个batch操作,整个batch使用了相同的prepareStatement,供执行了11条sql,也就是说以上的代码中只发送一次sql语句集合给mysql。
由此,我建议大家,如果使用hibernate+mysql,且你的应用在单次逻辑使用了大量的更新操作的时候,且很多sql结构一致的情况下,建议不要使用数据库的自增ID,使用UUID没什么不好,只是稍微长了那么点,哈哈。。。

2、再来说第二个问题,前段时间面试一个开发人员的时候,对方提到了spring集成hibernate的时候注意OpenSessionInViewInterceptor的使用,他认为OpenSessionInViewInterceptor在client用户或server服务器的网络环境不好的情况下使用会导致严重性能问题。
下面让我们分析下,这种说法靠谱不。。。
先说OpenSessionInViewInterceptor的实现吧,顾名思义,它是个拦截器,这个拦截器一般在spring mvc中使用,对应的还有个filter实现我想是给那些不适用spring mvc的框架能够在容器层配置使用吧。那么这个拦截器的拦截点在哪儿呢,经查看源码,其为WebRequestInterceptor的实现,也就是说,是拦截的用户请求开始和处理结束,也就是一次用户请求的生命周期,查看源码后发现,其通过threadlocal保证了整个request的逻辑处理周期中均使用相同的hibernate session,也就是说使用一个jdbc connection,起初在我看来,似乎没有什么问题,尽可能对用户的一次逻辑处理中不频繁的获取连接,即使是连接池,频繁获取也有同步问题,但仔细思考之后,个人认为上文提到的网络环境差的情况下存在性能问题的确会存在。大家都知道,最终用户的request的处理结果会通过一个outpustream流返给用户,而这个流谁提供的实现的呢,那肯定是你的server容器,前文提到,OpenSessionInViewInterceptor最终在request处理结束后进行session的释放,而request的结束标识就是将处理结果输入到output流中,而output流最终会把结果存放到你的server容器的缓冲区中,容器的缓冲区会将结果逐渐发送给OS的缓冲区最终经网络缓冲区发送给用户。基于这样一个过程,问题就来了,当网络环境不好的情况下,如果server内的所有用户的处理结果result都放入缓冲区而没有来得及(因为网络太差了)通过网络返回给用户,这时候很容易造成server容器的缓冲区满,缓冲区满了之后,result通过output流输出的之后就会堵塞,因为满了你塞不进去结果了,如果堵塞就会导致hibernate session没有被释放(因为还没调用到OpenSessionInViewInterceptor的使用session操作),这样,大量的session被占用,导致连接池很快被用光,数据库无法被继续访问,性能问题自然出现。说的有点乱,咱在理一理哈,
网络延迟->网络数据堆积->OS缓冲区满->server容器缓冲区满->向响应的outputstream写入result数据时堵塞->堵塞造成一次request的处理无法完成->无法完成导致OpenSessionInViewInterceptor的释放session操作无法被执行->导致session被很多堵塞线程持有->导致数据库连接池用光->导致无法继续为用户提供服务以及性能问题

猜你喜欢

转载自lvff1314.iteye.com/blog/1119814