Mybatis源码分析

Mybatis

Mybatis是一款持久层框架,其作用是简化了jdbc操作,并且有很强的扩展性。

实现思路

因为Mybatis是和数据库打交道,所以其实现的思路可以简单用以下过程概括。

  1. 确定数据源
    通过在mybatis-config.xml中配置的标签中的内容,封装DataSource
  2. 封装sql语句
  3. 执行sql
    其实质还是需要通过jdbc的操作,包括创建Connection对象,(或是从连接池获取Connection),构建PrapareStatement,最后获取ResultSet。

实现技术

实现可以简单概括为,读取mybatis-config.xml和mapper.xml中的内容,动态代理实现mapper接口,通过门面模式简化操作。

源码分析

常用的使用mybatis代码如下

        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
//        sqlSessionFactory.openSession(false);//关闭自动提交
//        sqlSessionFactory.openSession(TransactionIsolationLevel.READ_COMMITTED);//关闭自动提交,同时配置隔离级别
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        sqlSession.commit();
        List<User> all = mapper.getByName("test");


        all.forEach(System.out::println);

读取资源

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

这一步用一句话概括是,将mybatis-config.xml以及mapper.xml的文件全部读取到mybatis中的Configuartion对象中。
#### Configuartion
Configuartion是整个Mybaits依赖的资源类。
其中包含了很多重要的字段。

public class Configuration {
  protected Environment environment;//用于存放数据源信息
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");//用于存放mapper信息
  ......
}

解析datasource

Environment类就是解析对应mybaits-config.xml中的对应的信息。

        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://127.0.0.1:3306/zg_test?useUnicode=true&amp;characterEncoding=UTF8&amp;zeroDateTimeBehavior=convertToNull&amp;serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>

具体的代码执行为

解析mapper

解析的mapper信息会被封装为一个MappedStatement对象,然后加入Configuration中的mappedStatements(Map)字段中。这个map的key就是mapper接口对应的方法,value为MappedStatement。

解析mapper的流程如下

具体的代码执行为

获取sqlSession

        SqlSession sqlSession = sqlSessionFactory.openSession();

这行代码是通过工厂类获取SqlSession。
SqlSession主要包含了两个重要的成员变量,Configuration和Executor。

  private Configuration configuration;
  private Executor executor;

Configuration就是上文介绍的资源类。这里重点介绍Executor。
我们实际进行的增删改查都是通过这个Executor来进行的。

Executor

首先看看Executor接口的方法定义。

public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;

  int update(MappedStatement ms, Object parameter) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

  List<BatchResult> flushStatements() throws SQLException;

  void commit(boolean required) throws SQLException;

  void rollback(boolean required) throws SQLException;

  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

  boolean isCached(MappedStatement ms, CacheKey key);

  void clearLocalCache();

  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

  Transaction getTransaction();

  void close(boolean forceRollback);

  boolean isClosed();

  void setExecutorWrapper(Executor executor);

}

接口里,主要包含了事务的一些方法,和缓存方法,还有查询和更新等方法。
其继承关系为

首先,最底层的接口是Executor,有两个实现类:BaseExecutor和CachingExecutor,CachingExecutor用于二级缓存,而BaseExecutor则用于一级缓存及基础的操作。并且由于BaseExecutor是一个抽象类,提供了三个实现:SimpleExecutor,BatchExecutor,ReuseExecutor,而具体使用哪一个Executor则是可以在mybatis-config.xml中进行配置的。配置如下:


Executor的实现类

  • SimpleExecutor是最简单的执行器,根据对应的sql直接执行即可,不会做一些额外的操作;
  • BatchExecutor执行器,顾名思义,通过批量操作来优化性能。通常需要注意的是批量更新操作,由于内部有缓存的实现,使用完成后记得调用flushStatements来清除缓存。
  • ReuseExecutor 可重用的执行器,重用的对象是Statement,也就是说该执行器会缓存同一个sql的Statement,省去Statement的重新创建,优化性能。内部的实现是通过一个HashMap来维护* * * * Statement对象的。由于当前Map只在该session中有效,所以使用完成后记得调用flushStatements来清除Map。

获取动态代理类

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

我们获取到的其实是代理类,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;
  }

  @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);
  }

  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;
  }

}

通过MapperProxy代码查看,MapperProxy实现了InvocationHandler接口。
并且在invoke方法中,将Method方法封装为MapperMethod。
于是查看MapperMethod类源码。

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;
  ......
}

MapperMethod类是整个代理机制的核心类,对SqlSession中的操作进行了封装使用。 该类里有两个内部类SqlCommand和MethodSignature。 SqlCommand用来封装CRUD操作,也就是我们在xml中配置的操作的节点。每个节点都会生成一个MappedStatement类。MethodSignature用来封装方法的参数以及返回类型,在execute的方法中我们发现在这里又回到了SqlSession中的接口调用,和我们自己实现UerDao接口的方式中直接用SqlSession对象调用DefaultSqlSession的实现类的方法是一样的,经过一大圈的代理又回到了原地,这就是整个动态代理的实现过程了。

关系映射

执行完sql后,程序会获得ResultSet,最后一步就是将ResultSet封装为我们自己的java类。

debug位置 DefaultResultSetHandler 的getFirstResultSet方法。
主要进行关系ResultSet和我们自己的java类映射的类是ResultSetWrapper

class ResultSetWrapper {

  private final ResultSet resultSet;
  private final TypeHandlerRegistry typeHandlerRegistry;
  private final List<String> columnNames = new ArrayList<String>();
  private final List<String> classNames = new ArrayList<String>();
  private final List<JdbcType> jdbcTypes = new ArrayList<JdbcType>();
  private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<String, Map<Class<?>, TypeHandler<?>>>();
  private Map<String, List<String>> mappedColumnNamesMap = new HashMap<String, List<String>>();
  private Map<String, List<String>> unMappedColumnNamesMap = new HashMap<String, List<String>>();

  public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
    super();
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.resultSet = rs;
    final ResultSetMetaData metaData = rs.getMetaData();
    final int columnCount = metaData.getColumnCount();
    for (int i = 1; i <= columnCount; i++) {
      columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
      jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
      classNames.add(metaData.getColumnClassName(i));
    }
  }

TypeHandlerRegistry
其中TypeHandlerRegistry就是我们一些常用的typeHandler,包括我们自己定义的用于处理枚举的一些。

posted @ 2020-11-01 12:05  刃牙  阅读(186)  评论(0编辑  收藏  举报