一. mybatis的配置与使用


1.1

字符集

首先我们看下这个全局配置文件, 它是mybatis的核心配置,包括数据源, setting, 自定义类型转化, 以及mapper文件等等.

这个配置文件命名是不限定的, 一般会被命名为mybatis-config.xml

<configuration>    <settings>        <setting name="logImpl" value="LOG4J"/>    </settings>     <typeAliases>        <package name="com.coderworld968.model"/>    </typeAliases>    <environments default="development">        <environment id="development">            <transactionManager type="JDBC" />            <dataSource type="UNPOOLED">                <property name="driver" value="org.h2.Driver"/>                <property name="url" value="jdbc:h2:mem:voice;INIT=RUNSCRIPT FROM 'classpath:/h2/schema-h2.sql'"/>                <property name="username" value="sa"/>                <property name="password" value="sa"/>            </dataSource>        </environment>    </environments>
   <mappers>        <mapper resource="mapper/CountryMapper.xml"/>    </mappers></configuration>


全局配置有了, 再看下mapper.xml中的SQL.

<mapper namespace="com.coderworld968.mapper"> <select id="selectAll" resultType="Country">  select id,countryname,countrycode from country </select></mapper>


mybatis的本质是简化SQL处理, 那有了配置文件, 再看如何使用?
使用步骤非常简单, 创建sqlSessionFactory和根据mapper.xml中的sql ID执行对应SQL.

(1) 读取全局配置文件,创建sqlSessionFactory

Reader reader = Resources.getResourceAsReader("mybatis-config.xml");sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

(2) 执行SQL

sqlSessionFactory会创建SqlSession对象, 并调用mapper.xml中的SQL ID执行对应SQL.

SqlSession sqlSession = sqlSessionFactory.openSession();List<Country> countryList = sqlSession.selectList("selectAll");



二. mybatis的配置解析


在上述创建sqlSessionFactory时, 跟进builder()方法就会发现逻辑很简单,就是解析mybatis-config.xml配置文件, 并最终映射成一个Configuration对象.


public SqlSessionFactory build(Reader reader, String environment, Properties properties) {// ...XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);return build(parser.parse());// ...}

那我们完全可以抛开XML配置文件, 直接创建Configuration对象.

public static void init(){try {    UnpooledDataSource dataSource = new UnpooledDataSource(            "org.h2.Driver",            "jdbc:h2:mem:voice;INIT=RUNSCRIPT FROM 'classpath:/h2/schema-h2.sql'",            "sa",            "sa");    TransactionFactory transactionFactory = new JdbcTransactionFactory();    Environment environment = new Environment("Java", transactionFactory, dataSource);
   Configuration configuration = new Configuration(environment);    configuration.getTypeAliasRegistry().registerAliases("com.coderworld968.model");    configuration.setLogImpl(Log4jImpl.class);
   InputStream inputStream = Resources.getResourceAsStream("mapper/CountryMapper.xml");                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, "mapper/CountryMapper.xml", configuration.getSqlFragments());                mapperParser.parse();
   sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);} catch (Exception ignore) {    ignore.printStackTrace();}}

Mapper的注册方式除了示例中解析xml配置文件外,还有包扫描, 指定类等方式, 非常利于mybatis的扩展使用.

public void addMappers(String packageName, Class<?> superType)public void addMappers(String packageName)public <T> void addMapper(Class<T> type)


通过这种无配置文件, 纯代码配置的方式也就能明白spring或者spring boot是如何封装mybatis的了.

与示例代码中有TypeAliasRegistry处理类型别名(TypeAlias)类似的,也会有MapperRegistry,TypeHandlerRegistry,LanguageDriverRegistry,InterceptorChain对相应的功能做处理.

稍稍扩展下mybatis中对常见数据类型的别名(TypeAlias)处理方式是在类的构造方法中完成的. 类似的, TypeHandler处理也是在构造方法中处理的.

public TypeAliasRegistry() {  registerAlias("string", String.class);  registerAlias("byte", Byte.class);  registerAlias("long", Long.class);  // ...}



三. mybatis的SQL执行


3.1

MappedStatement对象

在解析配置文件(或JavaCodeConfig)中的mapper部分时, 不仅会记录SQL本身, 还会结合Configuration配置项生成对应的MappedStatement对象.
创建的MappedStatement对象会存储到Configuration.mappedStatements集合中.


protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")

mappedStatement在存储时, KEY会根据mapper配置分为两种:namespace+SQLID 和 SQLID.
这也是示例中执行查询时, 没有指定namespace也可以找到对应sql的原因了.

sqlSession.selectList("selectAll")


3.2

SqlSession

SqlSession是从sqlSessionFactory中创建的,主要用来处理SQL执行, 事务等功能.
SqlSession中主要包含四个对象:

(1)Executor: 调度执行StatementHandler、ParameterHandler、ResultHandler执行相应的SQL语句;

(2)StatementHandler: 使用数据库中Statement(PrepareStatement)执行操作,即底层是封装好了的prepareStatement;

(3)ParameterHandler: 处理SQL参数;

(4)ResultHandler: 结果集ResultSet封装处理返回;

3.3

SQL执行

下面结合源码一起看下sqlSession.selectList("selectAll")的执行流程

(1) Configuration创建SqlSession, 其中包含封装了拦截器(interceptorChain)的Executor.
拦截器的实现原理可以点这里.
如果是开启了缓存, 还会再封装成CachingExecutor.

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {// ...  executor = new SimpleExecutor(this, transaction);  if (cacheEnabled) {    executor = new CachingExecutor(executor);  }  executor = (Executor) interceptorChain.pluginAll(executor);  return executor;}

(2) 从Configuration中获得MappedStatement对象,并运行执行器(CachingExecutor)执行query()操作


public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {// ...MappedStatement ms = configuration.getMappedStatement(statement);return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);// ...}

(3) 解析SQL和参数, 继续调用query()方法


public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {  BoundSql boundSql = ms.getBoundSql(parameterObject);  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}

(4) 经过多级调用, 会执行SimpleExecutor.doQuery(), 生成StatementHandler对象, 并封装java.sql.Statement对象


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

(5) StatementHandler对象是通过Configuration生成的RoutingStatementHandler类型, 内部根据MappedStatement.getStatementType()会选择具体不同的handler. 本例中用到的是PreparedStatementHandler.

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {  switch (ms.getStatementType()) {    case STATEMENT:      delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);      break;    case PREPARED:      delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);      break;    case CALLABLE:      delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);      break;    default:      throw new ExecutorException("Unknown statement type: " + ms.getStatementType());  }}

(6) PreparedStatementHandler执行query()操作, 并根据resultSetHandler封装SQL执行结果集.

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {  PreparedStatement ps = (PreparedStatement) statement;  ps.execute();  return resultSetHandler.handleResultSets(ps);}

以上就是mybatis的针对一个SQL的主要处理流程.




总结


通过上述流程,大家对mybatis也有了一定的了解, 由于篇幅原因, 文中并没有提到mapper接口相关的动态代理处理, 后续会完善动态代理的详细介绍及使用.