Mybatis源码解析


title: Mybatis源码分析1
date: 2018-7-9 14:18:40
categories:
- Mybatis
tags:
- Mybatis

摘要:Mybatis源码学习1

暑假看完了《深入Mybatis技术原理》,在这里写了一点对Mybatis运行原理的一点总结。

Mybatis的基本构成

主要包含了下面几个重要的组件:

  • SqlSessionFactoryBuilder 这是一个构造器,他会根据我们配置单XML文件来生成SqlSessionFactory(工程类)
  • SqlSessionFactory 来生成回话(SqlSession)
  • SqlSession 可以发送SQL执行并获得结果,可以获取Maper的接口
  • SQLMapper Mybatis最重要的构成,它是由Java Interface 和一个对应的XML文件构成的

构造SqlSessionfactory

原生可以使用这样的方式

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

XML 配置文件(configuration XML)中包含了对 MyBatis 系统的核心设置,包含获取数据库连接实例的数据源(DataSource)和决定事务作用域和控制方式的事务管理器(TransactionManager)。

从SqlSessionFactory 中获取Sqlsession

SqlSession session = sqlSessionFactory.openSession();
try {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
} finally {
  session.close();
}

作用域(Scope)和生命周期

SqlSessionFactoryBuilder

单例,一经创建,就不再需要了。

SqlSessionFactory

单例

SqlSessionFactory

一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建

SqlSession

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。

SqlSession session = sqlSessionFactory.openSession();
try {
  // do work
} finally {
  session.close();
}

映射器实例(Mapper Instances)

映射器是一个你创建来绑定你映射的语句的接口。映射器接口的实例是从 SqlSession 中获得的。

SqlSession session = sqlSessionFactory.openSession();
try {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  // do work
} finally {
  session.close();
}

Mybatis重点

技术重点就是我们经常使用的Mapper了, 其中Mapper.xml的编写,是重中之重,能写出好的xml文件,需要动态SQl, Insert,delete,update,select 还有重要的resultMap。

动态SQL

if 元素
choose,when, otherwise
trim, where, set元素
foreach元素

运行原理

前面说了那么多的废话,熟悉Java设计语言的程序员,看着Mybatis的官方文档,还是比较容易的。但是只会使用这个持久层的框架还远远不够,我们还需要去对她的内部源码一探究竟。
Mybatis的插件使用几乎都是构建在Mybatis的原理之上的,不懂原理基本无法使用Mybatis的插件。
Mybatis的运行分为两大部分, 第一分部是读取配置文件,构建Configration对象,用以创建SqlSessionFactory。第二部分是Sqlsession的执行过程。

动态代理的两种方式

JDK原生的代理,和CGlib的动态代理是Mybatis的实现技术。

public class MyProxy implements InvocationHandler {

	Object obj;
	public Object bind(Object obj) {
		this.obj = obj;
		return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println(" l am a proxy");
		Object res = method.invoke(obj, args);
//		Arrays.copyOf(original, newLength)
		return res;
	}

}

里面的重点自然是Proxy类,来产生一个代理对象。用着代理对象执行真实对象的方法时,invoke方法会进行拦截,方法中的三个参数含义为:

  1. proxy 被代理对象
  2. method 被拦截的方法
  3. args 方法参数

要想使用CGlib,在poem.xml里面加入如下依赖就行了。

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>
public class Intercepter implements MethodInterceptor{

	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		// TODO Auto-generated method stub
		System.out.println("before.........");
		
		Object res = proxy.invokeSuper(obj, args);
		System.out.println("after........");
		return res;
	}

}

Hello h = new Hello();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(h.getClass());
enhancer.setCallback(new Intercepter());
Hello proxy = (Hello)enhancer.create();
proxy.hello(); // 就可以生成真实对象的子类来代理真实对象了

构建Configration

在SqlSessionFactory构建中,Configration是很重要的。

  • 读入配置文件,包括基础配置的XML文件和映射器的XML文件
  • 初始化基础配置,例如别名
  • 提供单例,为后面创建SessionFactory服务提供配置参数
  • 执行一些重要的对象方法,初始化配置信息

Configration是通过XMLConfigBuilder来创建的,

映射器的内部组成

  • MapperStatement 保存映射器的一个节点(slect, insert, delete, update),包含了我们配置的SQL,Sql的ID,缓存信息,resultMap,parameterType,resultType等内容
  • SqlSource 是MapperStatement的一个属性,提供BoundSQL的地方
  • BoundSQL,建立SQL和参数,

SqlSession的运行过程

他是通过动态代理实现的,用MapperProxy类生成代理对象,

public class MapperProxyFactory<T> {

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
	// MapperProxy
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

MapperProxy的源码如下

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;
	/// 构造函数
  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

// 需要先判断是 class 还是 接口 
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args); //开始执行mappermethod
  }
`// --> 上面的if如果不成立的话,就会执行下面的方法来创建mappernethod
  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

上面的代码大概就是,执行invoke方法,如果mapper是一个代理对象,就会运行invoke方法,接着判定Mapper是一个接口,不是类,失败通过CacheMapperMethod来生成MAPPMethod对象,执行execute方法,把当前的SqlSession和运行参数传进去,
继续追踪这个Mappermethod,我们终于看到了SQL语句执行的代码

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
      // 我们可以看这一行代码
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else if (method.returnsCursor()) {
        result = executeForCursor(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else if (SqlCommandType.FLUSH == command.getType()) {
        result = sqlSession.flushStatements();
    } else {
      throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

上面都是样板代码,我们看其中一个方法

  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
    // SqlSession已经开始真正的执行了
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

到这里,我们知道了映射器mapper就是一个JDK动态代理对象,进入到了MapperMethod的eexecute方法,他经过简单的判定就进入到了SqlSession的insert, delete,update, select等方法,这些方法如何执行、是我们需要搞清楚的,

mapper的四大组件

  • Executor 执行器,来调度StatementHandle, ParameterHandle,resultHandle,来执行相应的SQL

  • StatementHandle是核心,使用数据库的Statement来操作

  • parameterHandle SQL参数处理

  • ResultHandle 对结果集进行封装处理

  • Executor 执行器,来调度StatementHandle, ParameterHandle,resultHandle,来执行相应的SQL

  • StatementHandle是核心,使用数据库的Statement来操作

  • parameterHandle SQL参数处理

  • ResultHandle 对结果集进行封装处理

执行器(Executot),执行Java和数据库交互的东西,存在三种执行器,可以再配置文件中进行选择。

  • SIMPLE 简单执行器,默认的
  • REUSE 重用预处理
  • BATCH 批量更新的执行器

简单的执行器SimpleExecutor的执行过程

 @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

里面的prepareStatement方法

 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

进过Cnofigration构建StatementHandle,然后再经过里面的prepareStatement方法,
对SQL进行编译处理,他调用了StatementHandle的prepare()进行编译和基础设置,在通过parameterrize()设置参数,

    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
StatementHandle

完成参数的设置之后,跟着就是执行查询,update也是这样的,如果需要结果,再用ParameterHandle和resultHandle在包装结果集。

parameterHandle

前面看到了StatementHandle使用ParameterHandle进行编译和参数设置

public interface ParameterHandler {

  Object getParameterObject();

  void setParameters(PreparedStatement ps)
      throws SQLException;

}

getParameterObject是获取参书,setParameters设置参数,这两个方法都留给默认实现类DefaultParameterHandler去完成。

public class DefaultParameterHandler implements ParameterHandler {

  .......
  @Override
  public Object getParameterObject() {
    return parameterObject;
  }

  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

}

SqlSession运行总结

mybatis

SqlSession通过Executor创建StatementHandle开始,
Statement需要经过散步来进行SQL语句的处理:

  • prepared预编译SQL
  • parameterrize设置参数
  • query/update执行SQL
    其中的parameterrize调用parameterHandle的方法设置的,参数是根据类型处理器typeHandle处理的。query/update是通过resultHandle进行结果封装。
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值