环境准备
- mybatis-configuration.xml
最新的完整mybatis每个配置属性含义可参考http://www.mybatis.org/mybatis-3/zh/configuration.html
<?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>
<environments default="development">
<environment id="development">
// mybatis内置提供JDBC和MANAGED两种事务管理方式,
// 前者主要用于简单JDBC模式,后者主要用于容器管理事务,
//一般使用JDBC事务管理方式
// 使用了别名
<transactionManager type="JDBC"/>
// mybatis内置提供JNDI、POOLED、UNPOOLED三种数据源工厂,一般情况下使用POOLED数据源。
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="ConfigMapper.xml"/>
</mappers>
</configuration>
- ConfigMapper.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="com.xxx.springboot.demo.mapper.ConfigMapper">
<resultMap id="BaseResultMap" type="com.xxx.springboot.demo.entity.ConfigBO">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="ckey" jdbcType="VARCHAR" property="ckey" />
<result column="cvalue" jdbcType="VARCHAR" property="cvalue" />
<result column="application" jdbcType="VARCHAR" property="application" />
<result column="profile" jdbcType="VARCHAR" property="profile" />
<result column="label" jdbcType="VARCHAR" property="label" />
<result column="type" jdbcType="INTEGER" property="type" />
</resultMap>
<sql id="Base_Column_List">
id, ckey, cvalue, `application`, profile, `label`, `type`
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from config
where id = #{id,jdbcType=INTEGER}
</select>
</mapper>
3.java 代码
public interface ConfigMapper {
ConfigBO selectByPrimaryKey(Integer id);
}
public static void main(String[] args) {
String resource = "Configuration.xml";
Reader reader;
try {
reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlSession = new SqlSessionFactoryBuilder().build(reader);
// SqlSession就是jdbc连接的代表,openSession()就是获取jdbc连接
SqlSession session = sqlSession.openSession();
try {
// 调试入口
// 在session上可执行各种CRUD操作
ConfigBO config = (ConfigBO) session.selectOne("com.xxx.springboot.demo.mapper.ConfigMapper.selectByPrimaryKey", 1);
} finally {
session.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
开始
1.session.selectOne
此处session的实际类型为:org.apache.ibatis.session.defaults.DefaultSqlSession
public <T> T selectOne(String statement, Object parameter) {
// selectOne方法的底层是通过selectList实现的
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
2. DefaultSqlSession#selectList
public <E> List<E> selectList(String statement, Object parameter) {
// 初始化分页参数
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
// selectList的重载方法
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 从Mybatis的configuration中获取MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
(1)selectList引入RowBounds类,作用:分页功能的封装
(2)什么是MappedStatement?
它在ConfigMapper.xml长这样:
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from config
where id = #{id,jdbcType=INTEGER}
</select>
也就是它代表了sql语句的一个映射关系,从sql配置文件到java实体类的映射。它保存在一个Map中,那么什么是它的key呢?跟踪源码我们可以看到定义:
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
它的key 就是参数statement的值,即:(key = 全类名+方法名)
com.xxx.springboot.demo.mapper.ConfigMapper.selectByPrimaryKey
思考下列问题:
2.1 MappedStatement对象是怎么实例化的?
2.2 上述selectByPrimaryKey方法可以有重载方法吗?
(3)wrapCollection(parameter) 作用是什么?如果参数是集合类,就包装成Map对象。
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("array", object);
return map;
}
return object;
}
很自然,我们要思考为什么这么做?如果大家使用过<foreach>标签,应该知道:
<foreach collection=“list” item="" open="" close="" separator="" >
上面的list就是这里map的key,当然也可以指定为“collection”
StrictMap对HashMap的装饰在于增加了put时防重复的处理,get时取不到值时候的异常处理,这样核心应用层就不需要额外关心各种对象异常处理,简化应用层逻辑。
(4)引入了参数Executor.NO_RESULT_HANDLER
Executor类中定义了一个空的结果处理器:
ResultHandler NO_RESULT_HANDLER = null;
(5)executor.query方法中executor具体类型是什么?
它是在DefaultSqlSession实例化时赋值的,这里实际类型为:CachingExecutor
3.CachingExecutor#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);
}
// 上面query的一个重载方法,增加了MappedStatement级别的缓存功能
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
前面我们知道MappedStatement 是一个sql语句的映射,它包括了sql语句、参数类型、结果集类型等,那么sql语句在MappedStatement 是怎样保存的呢?答案就是SqlSource类,它有2个实现类DynamicSqlSource和RawSqlSource,在解析mapper文件时会根据当前sql语句是动态与否实例化成相应的类。在本例中它被实例成为RawSqlSource。
(1)BoundSql类:封装真实sql字符串和相关sql参数
从sqlSource获得的实际SQL字符串,SQL可能有SQL占位符“?”和一个参数映射的列表(有序的),其中包含每个参数的附加信息。
(2)实例化CacheKey对象,用于Mybatis提供的缓存机制
(3)CachingExecutor#query在MappedStatement级别的实现了缓存功能
Cache cache = ms.getCache();
思考下列问题:
3.1 怎么设置MappedStatement级别的缓存?
在mapper.xml中使用<cache>标签开启
(4)CachingExecutor将执行sql查询的操作委托给SimpleExecutor,就是这里的delegate对象,而CachingExecutor只实现缓存功能。
4.BaseExecutor#query
SimpleExecutor的父类BaseExecutor的query实现了sqlSession级别的缓存,具体实现类为PerpetualCache
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// sqlSession级别的缓存
// resultHandler赋值,前面有提到
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 存储过程相关
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
// 设置sqlSession级别的缓存
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
5. SimpleExecutor#doQuery
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);
}
}
(1) 实例化语句处理器StatementHandler
相关代码如下:
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// StatementType 可在mapper.xml中指定,默认为PREPARED
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());
}
}
RoutingStatementHandler 是对上面3种语句处理器的一个封装。这里真正实例化的语句处理器是PreparedStatementHandler
(2)分析SimpleExecutor#prepareStatement方法
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 这里从transaction中获取connection,然后返回代理过的connection
// connection的具体类型为 org.apache.ibatis.logging.jdbc.ConnectionLogger
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
流程1 BaseStatementHandler#prepare
主要工作就是把我们在mapper.xml中定义的属性转换为JDBC标准的调用
这里handler.prepare方法的具体逻辑委托给BaseStatementHandler#prepare实现:
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// instantiateStatement由子类实现
statement = instantiateStatement(connection);
// 设置查询超时时间
setStatementTimeout(statement, transactionTimeout);
// 设置每次查询抓取的最大size
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
PreparedStatementHandler#instantiateStatement
// 返回值类型为java.sql.PreparedStatement
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
// 处理Jdbc3KeyGenerator,因为它代表的是自增,另外一个是SelectKeyGenerator用于不支持自增的情况
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
}
connection的类型为ConnectionLogger,它代理了JDBC4Connection对象
ConnectionLogger#invoke代码:
public Object invoke(Object proxy, Method method, Object[] params)
throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
if ("prepareStatement".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
// stmt 返回类型为 JDBC42PreparedStatement
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
// PreparedStatementLogger代理了JDBC42PreparedStatement并返回
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("prepareCall".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("createStatement".equals(method.getName())) {
Statement stmt = (Statement) method.invoke(connection, params);
stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else {
return method.invoke(connection, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
实例化PreparedStatementLogger#newInstance对象时,给它的属性
private final PreparedStatement statement;
赋值为 statement = JDBC42PreparedStatement对象。
public static PreparedStatement newInstance(PreparedStatement stmt, Log statementLog, int queryStack) {
InvocationHandler handler = new PreparedStatementLogger(stmt, statementLog, queryStack);
ClassLoader cl = PreparedStatement.class.getClassLoader();
return (PreparedStatement) Proxy.newProxyInstance(cl, new Class[]{PreparedStatement.class, CallableStatement.class}, handler);
}
private PreparedStatementLogger(PreparedStatement stmt, Log statementLog, int queryStack) {
super(statementLog, queryStack);
this.statement = stmt;
}
流程2 PreparedStatementHandler#parameterize
主要功能:将mapper.xml中定义的参数转换为JDBC参数
语句处理器持有参数处理器ResultSetHandler和结果集处理器ParameterHandler
这里parameterHandler的实现类为DefaultParameterHandler,PreparedStatementHandler.parameterize将具体实现委托给了DefaultParameterHandler.setParameters()方法
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
DefaultParameterHandler#setParameters
我们在mapper中定义的所有ParameterType、ParameterMap、内嵌参数映射等在最后都在这里被作为ParameterMapping转换为JDBC参数
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();
// 1.先判断是不是属于语句的AdditionalParameter
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
// 2.参数是不是null
} else if (parameterObject == null) {
value = null;
// 3.判断是不是属于注册类型
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
// 4. 估计参数是object或者map,借助于MetaObject获取属性值
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 {
// jdbc下标从1开始,由具体的类型处理器进行参数的设置, 对于每个jdbcType, mybatis都提供了一个对应的Handler,
//具体可参考TypeHandlerRegistry详解, 其内部调用的是PrepareStatement.setXXX进行设置。
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);
}
}
}
}
}
(1)ParameterMapping 对象:
ParameterMapping{property=‘id’, mode=IN, javaType=class java.lang.Integer, jdbcType=INTEGER, numericScale=null, resultMapId=‘null’, jdbcTypeName=‘null’, expression=‘null’}
(2)TypeHandlerRegistry对象
提供了常见的Jdbc对象的java处理器类型,参考源码
6 PreparedStatementHandler#query
上面第5步中SimpleExecutor#doQuery,将查询操作委托给RoutingStatementHandler#query执行。而RoutingStatementHandler又委托给PreparedStatementHandler#query执行
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 查询语句的真正执行动作
ps.execute();
// 查询结果集的封装
return resultSetHandler.<E> handleResultSets(ps);
}
7 configuration文件解析核心代码
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
(1)入参XNode root对象信息如下:
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="ConfigMapper.xml"/>
</mappers>
</configuration>
(2) <mappers>标签的解析代码
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
8 资料
参考资料:
[1] https://www.cnblogs.com/zhjh256/p/8512392.html
[2] https://blog.csdn.net/isea533/article/details/44002219
[3] https://blog.csdn.net/qq_35525955/article/details/80890926