hibernate session开启与数据库物理连接的时间关系

来源:互联网 发布:冰点下载器 mac版 编辑:程序博客网 时间:2024/05/19 14:00

先说结论:

spring openSession获取到的是hibernate session,并没有实际获取绑定数据库连接。
不到最后一刻,不操作db就不会获取实际的数据库连接。当第一次操作db时,hibernate session 才会绑定数据库物理连接。这样降低了session使用数据库连接时间片段,最大化的利用链接资源。

所以spring连接打开的session是hibernate Session,并不是实际数据库连接,可放心使用(提前Open hibernate Session 对数据库链接无消耗,所以spring filter才能帮助用户提前打卡session)

注:以下是基于hibernate和spring集成的前提下进行的。

一般spring的事物是配置在service上,所以来说service和controller上的情况

1. service 层情况

绑定数据库物理连接是在第一次操作数据库的时候发生。操作数据库包括查询,修改,删除,新增,当然还有事物。在service中后续的db操作继续使用第一次绑定的数据库连接,直到service方法退出结束。

  • 对于非事物的查询(PROPAGATION_SUPPORTS)
    不管你在service查询前做了什么复杂的业务,都不会绑定数据库链接
    如下代码:
 @Override public UserInfo getById(long id) {      log.info("操作前sleep 10s,模拟调用外部接口");      this.sleep(10);      log.info("执行操作");      //以上时间段查看链接池,数据库链接未被消耗      UserInfo bean= baseDAO.get(UserInfo.class, id);//链接开始被消耗      //以下时间段,由于未退出方法,不会触发spring自动释放链接      //会一直暂用数据库链接      log.info("操作后sleep 10s");      this.sleep(10);      log.info("方法结束");      return bean;//退出后释放链接  }
  • 对于事物。开启事物时打开链接(PROPAGATION_REQUIRED)
    进入方法就绑定了数据库链接,所以事物里面的逻辑单元应该精简,不要有太多和事物无关的逻辑。比如提交一个http请求,在请求中会浪费掉数据库链接的时间
  @Override    public void saveTest(long id) throws BizException {        //进入方法就绑定了数据库链接        this.sleep(10);        long sn=System.currentTimeMillis();;        baseDAO.executeHql("UPDATE UserInfoEx set address=? where id =?"        ,new Object[]{sn+"",id});//已经处理完了        log.info("延迟10秒,"+sn);        this.sleep(10);//模拟远程调用反馈执行结果        log.info("提交"+sn);        //退出后释放链接    }
  • service方法中调用内部其他方法
    只会绑定一次数据库连接,第二次操作DB使用的是第一次操作DB绑定的链接,同样会把事物特性传递下来。

    @Overridepublic UserInfo getById(long id) {    //第一次操作绑定数据库链接    UserInfo bean= baseDAO.get(UserInfo.class, id);    updateName(id,bean.getName()+System.currentTimeMillis());    // 不会重新绑定链接,继续使用baseDAO.get(UserInfo.class, id)绑定的链接。    // 不会真实的保存,因为链接中没有开启事物    baseDAO.save(bean);    //也不会触发真实的保存,因为没有经过代理拦截,(在自己本身调用本身的方法,即原始目标方法),不会触发事物开启。    this.saveOptLog(id);    return bean;}

    注意:上面的代码 baseDAO.save(bean)和 this.saveOptLog(id)并不会触发真实的保存和提交。事物和自动提交是加在service层,getById不会触发事物的开启和自动提交。如果saveOptLog是其他servcie中注入的方法会自动开启的事物,但是继续使用同一连接,如下代码:

    @Overridepublic UserInfo getById(long id) {    //第一次操作绑定数据库链接    UserInfo bean= baseDAO.get(UserInfo.class, id);    updateName(id,bean.getName()+System.currentTimeMillis());    // 不会真实的保存,因为链接中没有开启事物    baseDAO.save(bean);    //会触发真实的保存,因为注入的logService,是代理对象有spring增强,会自动开启事物和提交    logService.saveOptLog(id);    return bean;}    

    2 controller 情况

  • 每调用一次service都会绑定一个数据库物理连接,并且执行完后释放掉。
    原因:service是spring注入进来的代理service,有事物增强处理。

    @RequestMapping("/loginLog")@ResponseBodypublic JsonResponseBean loginLog(@RequestScope JSONObject jsonParams) throws BizException {    JsonResponseBean responseBean = new JsonResponseBean();    UserInfo userInfo=userService.getById (jsonParams.getLongValue("id"));//绑定数据连接,执行,并释放    this.sleep(10);//模拟复杂的控制处理,此处不会消耗数据连接资源的时间片。    loginService.saveLoginLog(UserLoginLog.TYPE_PASSWORD, userInfo.getId(), userInfo.getName(), "","","","");//第一次调用,重新绑定连接,执行,释放连接    loginService.saveLoginLog(UserLoginLog.TYPE_PASSWORD, userInfo.getId(), userInfo.getName(), "","","","");//重复调用,重新绑定连接,执行,释放连接    return responseBean;}
  • 如果是处理重复业务数据,preparestatement不一定会生效。
    原因:controller重复调用service的方法绑定到的数据库连接不一定是同一个,preparestatement是基于同一连接生效的。如果preparestatement要生效需要放在service内处理。

    //controller代码public JsonResponseBean loginLog(@RequestScope JSONObject jsonParams) throws BizException {    JsonResponseBean responseBean = new JsonResponseBean();    List<Long> ids=JsonUtils.toArray(jsonParams.getJSONArray("id"),Long.class);    for(long id:ids) {        UserInfo userInfo = userService.getById(id);        loginService.saveLoginLog(UserLoginLog.TYPE_PASSWORD, userInfo.getId(), userInfo.getName(), "", "", "", "");//每次都是,重新绑定连接,执行,释放连接。    }    return responseBean;} 

3 控制代码划分规则建议
一般来说service根据模块功能和解耦,里面可能会包含部分的业务逻辑控制代码;controller本身是控制层,也会包含部分逻辑控制代码。那么逻辑控制代码怎么区分是方在service层还是controller呢?

如果是处理比较复杂的业务请求,并且可能消耗的时间比较久(正常情况应该走MQ进行异步处理,但这个地方讨论不能走异步必须面对的情况)。需要把消耗时间的业务逻辑控制上浮,放在controller中处理,即使是同一service。

serivce和controller的规则:

  • 1 根据模块功能划分,并进行解耦
    当仁不让,第一规则,优先考虑。

  • 2 serivce层的逻辑粒度尽量小,尽量单一
    保持service方法的逻辑简洁性,往往一个方法处理逻辑过多会很容易造成代码混乱,并且不容易维护。

  • 3 在小粒度的方法前提下,控制业务逻辑进行上浮
    如果是非事物,可以考虑吧逻辑代码上浮到controller,由controller进行组合调用service接口。
    如果是事物业务,必须把代码逻辑放在service层,但也需要保持每个方法的小粒度,再由一个方法来进行业务逻辑层控制,并暴露给contrller使用。
    当然非业务的事物情况下,也可以把部分contrller里面共同的逻辑,下潜到service实现。(具体根据service具体业务属性进行划分)
    但同上,每个子方法都应该具有小粒度和单一的特性。

    为什么要这样做?
    在上面提到,当第一次操作db后,如果方法一直没执行完并退出,会一直占用链接。这段时间链接是无法被其他人使用的,哪怕链接处于空闲状态。

    这样的结果就是100个复杂的业务来了,service层使用了100个数据链进行长时间处理(连接池只提供100个链接),这时其他一个执行查询的小业务请求来了,由于链接全部被占用了,这时这地101个请求就无法处理,只有等。

    如果service足够简单,让controller来控制部分业务逻辑(由一个一个小粒度的service方法组成),
    A 调用一个service方法结束后会先把链接释放出来;
    B 这时其他业务请求才有可能获取到链接,并进行处理,小请求处理完后会释放链接;(能很快的做出相应)
    C 之前的contrller继续调用serivce方法,绑定到释放的链接,继续处理。(响应速度延迟不大)
    这样100个链接能支持100个以上的请求业务,才能【实现高并发】。否则100个链接提供的并发支持非常有限。

    关键思路:
    1 调用小粒度service方法,让使用完后,及时释放,尽量避免不用但一直占用资源的时间段产生
    2 让使用db资源的时间片尽可能的小,处理过程中可以让其他需要资源的业务请求也能够参与进来
    3 让想用资源的能及时用到资源,占用资源但空闲状态的释放资源,【最大化】利用有限的资源。

以上是大多数情况下的处理方式,但不能一篇而论。需要根据功能模块和业务场景选择性的使用。