mybatis是一款优秀的持久层框架, 通过配置mybatis-config.xml和mapper.xml就可以轻松完成ORM工作. 在当前流行JavaCodeConfig的情况下, 这些配置项又是如何应用配置的呢? 它的执行过程又是怎样的呢? 带着这两个问题, 一起看下mybatis的真面目.
一. mybatis的配置与使用
1.1
字符集
首先我们看下这个全局配置文件, 它是mybatis的核心配置,包括数据源, setting, 自定义类型转化, 以及mapper文件等等.
这个配置文件命名是不限定的, 一般会被命名为mybatis-config.xml
代码语言:javascript复制<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.
代码语言:javascript复制<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
代码语言:javascript复制Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
(2) 执行SQL
sqlSessionFactory会创建SqlSession对象, 并调用mapper.xml中的SQL ID执行对应SQL.
代码语言:javascript复制SqlSession sqlSession = sqlSessionFactory.openSession();
List<Country> countryList = sqlSession.selectList("selectAll");
二. mybatis的配置解析
在上述创建sqlSessionFactory时, 跟进builder()方法就会发现逻辑很简单,就是解析mybatis-config.xml配置文件, 并最终映射成一个Configuration对象.
代码语言:javascript复制public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
// ...
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
// ...
}
那我们完全可以抛开XML配置文件, 直接创建Configuration对象.
代码语言:javascript复制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的扩展使用.
代码语言:javascript复制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处理也是在构造方法中处理的.
代码语言:javascript复制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集合中.
代码语言:javascript复制protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
mappedStatement在存储时, KEY会根据mapper配置分为两种:namespace SQLID 和 SQLID. 这也是示例中执行查询时, 没有指定namespace也可以找到对应sql的原因了.
代码语言:javascript复制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.
代码语言:javascript复制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()操作
代码语言:javascript复制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()方法
代码语言:javascript复制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对象
代码语言:javascript复制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.
代码语言:javascript复制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执行结果集.
代码语言:javascript复制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接口相关的动态代理处理, 后续会完善动态代理的详细介绍及使用.