mybatis源码解析1——SqlSession

先从日常工作流程下手吧,在看其他项目的源码前,最好对项目有一个充分的了解,如具体的实现步骤、配置、应用等,另外看源码是对所使用工具的一种深究,只有对自身掌握的工具知根知底,在进行架构设计、选型的时候才能够做出合理的判断,并且一旦你对源码有一定的熟悉程度,还可以在必要的时候进行改造或者扩展,已满足自身的需要,这是非常重要的。某些项目的设计模式非常优秀,借鉴这些设计有助于自身的成长。

操作流程

我们通常按照如下流程使用mybatis的:

读取配置文件-->buildsessionfactory-->opensession-->selectone-->commit-->closesession,如果我们使用容器事务管理而非JDBC事务管理,那么close也是不需要的。那么接下来通过逆向推理和追溯来了解其来龙去脉。

	public int update(int id) {
		SqlSession session = SQLSessionFactory.getSessoinFactory().openSession();
		int i = session.update(namespace+"updateOne",id);
		session.commit();
		session.close();
		return i;
	}

逆向推导

上面一段代码是dao层调用sqlsession查询blog对象的操作,可以看到操作中设计若干类型和方法的调用,但是我们不能随意的选择一个类型或者方法下手,否则不容易缕清他们的关系。如果你知道数据结构的树,那么应该知道前序遍历和后序遍历的区别,采用前序遍历,可以尽快知道树的整体结构和规模,而采用后序遍历,则可以快速知道父节点直到根节点。因此,在看源码的时候亦是如此,当我们主要了解执行过程而不是设计模式和结构的情况下,一般用后序遍历,也就是从最后的操作往前推导。

根据上述概念,我们先看sqlsession的close:


使用IDE的outline可以附加的了解这个接口或类型的facades,由于sqlsession是一个接口,这个接口或没有实现的方法说明,会有一个或多个子类构成一种策略模式。查看sqlsession的实现子类:


由两个子类完成实现,下一步:DefaultSqlSession

  @Override
  public void close() {
    try {
      executor.close(isCommitOrRollbackRequired(false));
      closeCursors();
      dirty = false;
    } finally {
      ErrorContext.instance().reset();
    }
  }


可以看到在DefaultSqlSession中实现的close方法实际上是调用executor的close方法,这个executor是一个成员属性,这就需要注意了,通篇概览一下DefaultSqlSession的方法,会发现实际上这些方法真正的工作者就是executor,而DefaultSqlSession基本上是对executor的一个包装,没有对executor做任何线程安全处理也说明了一点:sqlsession是多线程不安全的。

sqlsession的另一个实现子类:sqlSessionManager,这个类专门用于应用程序对sqlSession进行管理:

  private final SqlSessionFactory sqlSessionFactory;
  private final SqlSession sqlSessionProxy;

  private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();

  private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(//持有一个通过动态代理创建的SqlSession
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
  }

这个类中持有一个通过动态代理创建的SqlSession目的何在?继续观察发现,因为这个类是单例的,为了保证线程独立,使用了本地线程ThreadLocal,并通过代理来拦截SqlSession的方法调用,减少空对象的判断语句:

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
      if (sqlSession != null) {
        try {
          return method.invoke(sqlSession, args);//如果session已创建则直接返回方法调用
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      } else {
        final SqlSession autoSqlSession = openSession();//如果session未创建则创建并调用方法并且提交
        try {
          final Object result = method.invoke(autoSqlSession, args);
          autoSqlSession.commit();
          return result;
        } catch (Throwable t) {
          autoSqlSession.rollback();
          throw ExceptionUtil.unwrapThrowable(t);
        } finally {
          autoSqlSession.close();
        }
      }
    }

sqlsession的两个实现子类功能并没有重复,DefaultSqlSession负责完成对executor的包装,而sqlSessionManager进行对DefaultSqlSession的创建和调用。也就是说实际中我们可以通过以下方式创建session,但我们日常接触更多的方式是后一种:

SqlSession session = SqlSessionManager.newInstance(Resources.getResourceAsReader("com/config/mybatis-config.xml"));
private static SqlSessionFactory sessionFactory;
	static{
		String resource = "com/config/mybatis-config.xml";
		try {
			sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

两种方式都不能保证factory单例,所以还是要自己实现单例。

mybatis的开发者对于“一个规则只实现一次”是有严格的遵守的,从sqlsession的两个实现子类以及他们的方法,没有冗余的逻辑,这样对于维护和二次开发提供了良好的环境,我们在开发的时候应该多点借鉴。

到了这一步,点到为止,因为我们要追踪的是工作过程,而非结构和模式,虽然有一部分结构和模式是附加的产出。接下来针对DefaultSqlSession进行解析。

close方法:

 @Override
  public void close() {
    try {
      executor.close(isCommitOrRollbackRequired(false));
      closeCursors();//释放资源
      dirty = false;
    } finally {
      ErrorContext.instance().reset();//重置错误上下文
    }
  }

  private void closeCursors() {
    if (cursorList != null && cursorList.size() != 0) {
      for (Cursor<?> cursor : cursorList) {//释放所有资源
        try {
          cursor.close();
        } catch (IOException e) {
          throw ExceptionFactory.wrapException("Error closing cursor.  Cause: " + e, e);
        }
      }
      cursorList.clear();
    }
  }

上面的close方法代码中,先调用executor执行核心方法,然后通过closeCursors方法来释放所有资源,该方法中的coursor对象是一种实现了Closeable接口的对象,该接口是jdk1.5就有的,表示任何对象实现接口的close方法都必须先释放所占用的资源。在异常处理方面,由于close是顶层方法,因此mybatis选择在这个方法里面对异常进行了处理,将前面调用方法的异常统一捕获然后抛出,这种处理手段在我们日常开发中也是很常见的,除此之外,使用了一个异常上下文对象(ErrorContext)在异常抛出时将异常有关的信息记录:

public class ErrorContext {

  private static final String LINE_SEPARATOR = System.getProperty("line.separator","\n");
  private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();//多线程安全

  private ErrorContext stored;
  private String resource;//来源信息
  private String activity;//动作信息
  private String object;//对象信息
  private String message;//错误信息
  private String sql;//sql语句
  private Throwable cause;//异常对象

  private ErrorContext() {
  }

这个类封装了错误有关的信息,通过他来反馈错误状态。

总的来说,sqlsession是一个接口,他的实现子类也相对比较简单,他们的关系类图如下:


看图可以看出的模式:组合+依赖=包装器(装饰/包装模式)

除上述内容外,没别的特别之处了,下一篇再分析其他组件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值