30

mybatis源码分析:启动过程

 4 years ago
source link: http://www.cnblogs.com/teach/p/12697660.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

mybatis在开发中作为一个ORM框架使用的比较多,所谓ORM指的是Object Relation Mapping,直译过来就是对象关系映射,这个映射指的是java中的对象和数据库中的记录的映射,也就是一个java对象映射数据库中的一条记录。了解了mybatis的背景及作用下面看mybatis的使用及从源码分析启动过程。

一、概述

要使用mybatis必须要引入mybatis的jar包,由于我这里需要查看源码,使用的mybatis源码作为依赖。首先需要下载源码,可执行从github上下载,mybatis下载下来是maven工程,按照maven导入的方式进行导入即可,详细的步骤在这里不在赘述。

引入了mybatis的依赖便可以开发mybatis的程序,我这里使用的源码版本为:3-3.4.x版本。

1、核心配置文件

mybatis核心配置文件,一般命名为mybatis-config.xml,说是核心配置文件一点也不错,这个文件包含了使用mybatis的时候的所有配置,只有正确加载了此文件,mybatis才可正常工作。下面是mybatis-config.xml文件,

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!-- 设置日志输出为LOG4J -->
        <setting name="logImpl" value="LOG4J" />
        <!--将以下画线方式命名的数据库列映射到 Java 对象的驼峰式命名属性中-->
        <setting name= "mapUnderscoreToCamelCase" value="true" />
    </settings>
    <!--简化类命名空间 -->
    <typeAliases>
       
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="UNPOOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url"
                    value="jdbc:mysql://127.0.0.1:3306/test" />
                <property name="username" value="user" />
                <property name="password" value="user" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--常规做法-->
        <mapper resource="cn/com/mybatis/dao/UserMapper.xml"/>
        <mapper resource="cn/com/mybatis/dao/MenuMapper.xml"/>
        <!--第二种做法-->
        <!--  
        <package name="cn.com.mybatis.dao"/>
        -->
    </mappers>
</configuration>

上面是一个mybatis-config.xml文件的实例,在configuration标签中配置了mappers、settings、environments等标签,这些标签代表的意思及如何解析在后面会详细分析。

这里sql的配置方式有注解和映射文件两种方式,这里采用映射文件的方式,所以在mybatis-config.xml文件中配置了Mapper文件,下面看UserMapper.xml文件,

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.com.mybatis.dao.UserMapper">
  <select id="selectUser" resultType="hashmap">
    select * from e_user 
  </select>
</mapper>

上面的UserMapper.xml只有一个select标签,另外在mapper标签中配置了namespace属性,这个属性很关键,代表的是一个应映射文件对应的接口。下面看UserMapper接口,

package cn.com.mybatis.dao;

import java.util.HashMap;
import java.util.List;

public interface UserMapper {

    public List<HashMap> selectUser();
}

细心的读者会发现接口中的方法名和映射文件中的select标签的id是一样的,没错这里必须是一致,必须一一对应,至于为什么要保持一致,后面会通过源码分析,并且在一同一个namespace中不能包含同名的方法,也就是映射文件中的id不允许重复。

有了上面的这些配置,便可以开始mybatis之旅了,下面看下每个文件的位置,

vaaEfeZ.png!web

二、详述

上面已经把mybatis的环境及代码已经分析了,下面看测试代码,

package cn.com.mybatis.test;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import cn.com.mybatis.dao.UserMapper;

public class TestMybatis {

    public static void main(String[] args) throws IOException {
        // TODO Auto-generated method stub
        //加载核心配置文件
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        //生成一个SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder  builder  = new SqlSessionFactoryBuilder();
         //创建一个SqlSessionFactory对象
        SqlSessionFactory  factory  = builder.build(inputStream);
         //获得一个SqlSession对象
        SqlSession session=factory.openSession();
        //获得一个UserMapper
        UserMapper userMapper=session.getMapper(UserMapper.class);
       
        List<HashMap> users=userMapper.selectUser();
        System.out.println(users.size());
    }

}

上面的代码,即使用mybatis的过程,下面来分析。

1、读取配置文件

下面看读取mybatis核心文件的代码,

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

这里就一句话,把mybatis-config.xml转化为InputStream对象,这里mybatis-config.xml文件是在类路径下(WEB-INF/classes)下,这里Resources类是如何读取文件,后续详细分析,只要明白这里会获得InputStream就好。

2、创建SqlSessionFactoryBuilder

下面需要创建一个SqlSessionFactoryBuilder对象,看这个类名可以猜到应该使用的是建造者模式,

SqlSessionFactoryBuilder  builder  = new SqlSessionFactoryBuilder();

看下具体的SqlSessionFactoryBuilder类,下面是其所有的方法,

fy2qQnr.png!web

可以看到都是build方法,那么其构造方法也就是默认的。在SqlSessionFactoryBuilder类中所有的方法都是build方法,这是标准的建造者模式,可以看到返回值都是SqlSessionFactory。在mybatis中很多地方都使用了建造者模式,后边会进行专门的分析。

下面看生成SqlSessionFactory,

SqlSessionFactory  factory  = builder.build(inputStream);

调用的SqlSessionFactoryBuilder的build(InputStream)方法,

public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

又调用下面的方法,

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

在上面的代码中,使用inputStream生成一个XMLConfigBuilder,这里又是一个建造者模式,看XMLConfigBuilder的构造方法,

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    //调用下面的构造方法
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    //初始化父类BaseBuilder类的configuration
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

下面看其parse()方法,此方法构造的是在这里建造的对象是Configuration对象,

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //解析mybatis-config.xml文件中的<configuration>标签,把该标签中的内容
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

从上面的代码中,可以看出调用parseConfiguration方法,这个方法就是解析<configuration>标签,如下,

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      //解析properties标签    
      propertiesElement(root.evalNode("properties"));
      //解析settings标签
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      //解析别名标签,例<typeAlias alias="user" type="cn.com.bean.User"/>
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析插件标签
      pluginElement(root.evalNode("plugins"));
      //解析objectFactory标签,此标签的作用是mybatis每次创建结果对象的新实例时都会使用ObjectFactory,如果不设置
      //则默认使用DefaultObjectFactory来创建,设置之后使用设置的
      objectFactoryElement(root.evalNode("objectFactory"));
      //解析objectWrapperFactory标签
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //解析reflectorFactory标签
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      //解析environments标签
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析<mappers>标签
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

上面的方法后面会逐一进行分析,主要就是解析核心配置文件mybatis-config.xml中的配置,并放到Configuration对象中。

再回到上面的build(parse.parse())方法,其定义如下,

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

从上面的代码,可以看到使用configuration生成一个DefaultSqlSessionFactory对象。

3、创建SqlSessionFactory

上面分析到SqlSessionFactoryBuilder最后会返回一个DefaultSqlSessionFactory对象,

public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
  }

可以看到,把Configuration对象直接赋值给了DefaultSqlSessionFactory对象的configuration属性。

4、创建SqlSession

SqlSession session=factory.openSession();

上面的代码调用factory的openSession()方法,也就是DefaultSqlSesssionFactory的openSession()方法,

@Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

调用了下面的方法,

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        //获得mybatis核心配置文件中的environment信息,包括dataSource id trasacationFactory
      final Environment environment = configuration.getEnvironment();
      //获得transactionFactory,如果environment中没有则使用ManagedTransactionFactory
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

上面的方法返回了一个DefaultSqlSession对象,具体的过程就是使用上面的参数构造一个DefaultSqlSession对象。

5、获取Mapper对象

使用下面的代码获取一个Mapper对象,有了Mapper对象便可以调用方法,进行数据库操作,

UserMapper userMapper=session.getMapper(UserMapper.class);

上面的代码从DefaultSqlSession中调用getMapper返回一个UserMapper对象,这里UserMapper是一个代理对象,至于为什么是代理对象,先不分析,先了解其过程,

@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

可以看出是从DefaultSqlSession的Configuration中获得该Mapper,下面继续看,

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //返回的是下面的方法
    return mapperRegistry.getMapper(type, sqlSession);
  }
//调用此方法
@SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

从上面中可以看到从knownMappers中根据type,这里就是UserMapper.class返回一个MapperProxyFactory,最后返回MapperProxyFactory的一个实例,

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

下面看newInstance方法,

@SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
   //JDK动态代理
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

到这里我们可以看到最终返回的是一个代理对象,而且是JDK动态代理的一个对象,从我们只编写了接口也可以猜出这里返回的应该是一个JDK动态代理的类,因为JDK动态代理要求必须有接口。

6、执行操作

执行操作,则直接调用其方法即可,

List<HashMap> users=userMapper.selectUser();

从上面的分析制定useMapper是代理对象,那么代理类便是上面的MapperProxy类,那么执行selectUser方法,便会执行MapperProxy的invoke方法,那么该类肯定也会实现InvocationHandler接口,下面,

Ybmyui7.png!web

在看其invoke方法,

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
      //调用的是execute方法
    return mapperMethod.execute(sqlSession, args);
  }

从上面可以看到调用的是mapperMethod.execute方法,并且把sqlSession方法作为参数传进去。那也就是说最后调用的sqlSession的方法,下面看,

j2eAj2b.png!web

可以看到调用的sqlSession的方法,从这里大体可以看出sqlSession是个重要的类。

三、总结

上面分析了mybatis的启动过程,包括加载核心配置文件(mybatis-config.xml)、SqlSessionFactory、SqlSession、执行操作数据库方法。这里仅仅分析了其执行过程,很多细节后续会一一分析,像加载配置文件、Configuration类、DefaultSqlSession以及如何通过接口找到对应的Mapper文件等内容。

有不正之处,欢迎指正。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK