一. JDBC式编程
在 jdbc 编程中,我们最常用的是 PreparedStatement 式的编程,我们看下面这个例子;
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
String url = "jdbc:mysql://localhost:3306/zhuce?serverTimezone=UTC";
String user = "root";
String password = "19991121zq";
conn = DriverManager.getConnection(url, user, password);
String sql = "select * from t_user order by id ?";
// 3. 获取数据库连接对象
ps = conn.prepareStatement(sql);
ps.setString(1, keyWords);
// 4. 执行 sql 语句
rs = ps.executeQuery();
// 5. 处理查询结果集
while(rs.next()){
System.out.println(rs.getInt("id"));
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
//6.关闭资源
if (rs != null) {
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (ps != null) {
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (conn != null) {
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
二. 解析Configuration
如果我们的 mybatis 配置文件使用 xml 的方式,没有使用 Spring yaml 配置项的形式,可以通过 XMLMapperBuilder 来看解析 Configuration 的过程;
//---------------------------------XMLConfigBuilder----------------------------
public Configuration parse() {
// 确保每个 xml 配置文件只被解析一次
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 解析 xml 配置文件中的根节点: configuration
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
//---------------------------------XMLConfigBuilder-----------------------------
private void parseConfiguration(XNode root) {
try {
// 可以看出顺序是固定好的
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
// 解析 plugins 标签
pluginElement(root.evalNode("plugins"));
// 解析 MetaObject 中会使用到的几个 Factory,了解
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// 解析 environments 标签
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析 mappers 标签
// 我们主要看此处
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException();
}
}
1. 解析mappers
我们为什么要重点看解析 mappers 的地方,因为这里有一个很重要的对象 MappedStatement,我们在 Executor 中全程都会使用到该对象;
我们简单看下:
//-----------------------------XMLMapperBuilder-----------------------------
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
// 解析 mapper 标签中的 select|insert|update|delete 标签
// 会把每个 select、update 等解析成一个个 MappedStatement 对象
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
//...
}
}
//-----------------------------XMLMapperBuilder-----------------------------
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
//-----------------------------XMLMapperBuilder-----------------------------
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
XMLStatementBuilder statementParser =
new XMLStatementBuilder(configuration, builderAssistant, context);
try {
// 开始解析 select、update、insert 成 MappedStatement
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
我们以如下 StudentMapper.xml 为例来看 MappedStatement 的生成;
该 select 标签的 id 为 “selectStudent”,namespace 为 “com.bjpowernode.dao.StudentDao”;
<mapper namespace="com.bjpowernode.dao.StudentDao">
<select id="selectStudent" resultType="com.bjpowernode.domain.Student">
select id,name,email,age from student where id=#{id}
</select>
</mapper>
解析如上的 select 标签如下:
// --------------------------- XMLStatementBuilder -------------------------
public void parseStatementNode() {
// 1. 此时该 id = "selectStudent"
String id = context.getStringAttribute("id");
// ...
// 2. 生成 SqlSource 对象
// 该 sqlSource 包含了原始sql、参数映射等
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
// 3. 调用 builderAssistant 协助创建 MappedStatement 对象
builderAssistant.addMappedStatement(id, sqlSource, statementType,
sqlCommandType, fetchSize, timeout,
parameterMap, parameterTypeClass,
resultMap, resultTypeClass,
resultSetTypeEnum, flushCache,
useCache, resultOrdered,
keyGenerator, keyProperty,
keyColumn, databaseId,
langDriver, resultSets);
}
// --------------------------- XMLStatementBuilder -------------------------
public MappedStatement addMappedStatement(
String id, SqlSource sqlSource, StatementType statementType,
SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout,
String parameterMap, Class<?> parameterType, String resultMap,
Class<?> resultType, ResultSetType resultSetType, boolean flushCache,
boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator,
String keyProperty, String keyColumn, String databaseId,
LanguageDriver lang, String resultSets) {
// 1. 重新生成 id
// 此时的 id = id + namespace
// 也就是 id = "com.bjpowernode.dao.StudentDao.selectStudent"
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
// 2. 创建出 MappedStatement 对象
MappedStatement statement = statementBuilder.build();
// 3. 将当前 MappedStatement 对象加入到 configuration 配置类中
configuration.addMappedStatement(statement);
return statement;
}
// ----------------------------- Configuration --------------------------------
public void addMappedStatement(MappedStatement ms) {
// 将 MappedStatement 对象放入到 configuration 中的 mappedStatements 中
// mappedStatements 是一个 Map 对象,可以看到 key 是 MappedStatement.id
mappedStatements.put(ms.getId(), ms);
}
可以把 MappedStatement 认为是一条 select、insert、update 等标签的映射对象;
我们看下 SqlSource 和 MappedStatement 对象,MappedStatement 对象中聚合了 SqlSource 对象;共同点是两个对象中都聚合了 Configuration 对象;
SqlSource;
MappedStatement;
三. Executor执行器
1. Executor执行器的种类
对于 Executor,我们需要知道一点,SqlSession 只是门面而已,它聚合了一个 Executor 对象,真正执行 sql 的是 Executor 而非 SqlSession;
- CachingExecutor:处理二级缓存,采用了装饰者模式;CachingExecutor 也实现了 Executor 接口,并且内部聚合了一个 BaseExecutor 的实现类;
- BaseExecutor:处理一级缓存,它是一个抽象类,其他的 Executor 都是继承自该类;
- 该类定义了一个抽象方法如 query(),真正执行查询逻辑的是子类的 doQuery();
- 该类最大的作用就是用来执行一级缓存的查询,该执行器聚合了一个一级缓存 Cache;
- SimpleExecutor:简单执行器,默认使用的是这个执行器,我们重点关注这个;
- ReuseExecutor:可重用的执行器;
- BatchExecutor:批处理的执行器;
类图如下:
2. Executor和SqlSession的对应关系
一个 SqlSession 对应一个 Executor;每创建一个 SqlSession,都会创建一个对应的 Executor 对象;
这时为啥呢?我们直接看 DefaultSqlSessionFactory.openSession() 创建 SqlSession 对象的方法就知道了;
// ------------------------ DefaultSqlSessionFactory -------------------------
public SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null, false);
}
// ------------------------ DefaultSqlSessionFactory -------------------------
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Environment environment = configuration.getEnvironment();
TransactionFactory transactionFactory =
getTransactionFactoryFromEnvironment(environment);
Transaction tx = transactionFactory
.newTransaction(environment.getDataSource(), level, autoCommit);
// 1. 通过 configuration 创建出 Executor 对象
Executor executor = configuration.newExecutor(tx, execType);
// 2. 根据 executor 创建出 SqlSession 对象
return new DefaultSqlSession(configuration, executor, autoCommit);
}
// ----------------------------- Configuration -------------------------------
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 1. 根据 executorType 的类型创建出不同的 Executor 对象
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 如果开启了二级缓存的话,还会包装一层,包装为 CachingExecutor
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 2. 如果需要插件增强的话,给 executor 做一层增强,并返回增强后的 executor 对象
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
四. 一级缓存
1. 一级缓存PerpetualCache
一级缓存是在 BaseExecutor 类中作为成员变量存在的;
// ------------------------- BaseExecutor -----------------------------
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
protected Transaction transaction;
protected Executor wrapper;
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
// localCache 就是我们常说的一级缓存
// 可以看到它是一个 PerpetualCache 类对象
protected PerpetualCache localCache;
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
protected int queryStack;
private boolean closed;
// ...
}
我们看下这个 PerpetualCache 类;
可以看到它内部有一个 HashMap,非常简单,Mybatis 的一级缓存可以看做就是一个简单的 HashMap;
public class PerpetualCache implements Cache {
private final String id;
private Map<Object, Object> cache = new HashMap<>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
}
2. 一级缓存的命中场景
一级缓存生效的几个条件,其实和 CacheKey 有关:
1、mappedStatementId 必须一样;
2、sql 必须一样;
3、RowBounds.offset 分页 offset 必须一样;
4、RowBounds.limit 分页 limit 必须一样;
5、查询参数 param 必须一样;
6、enviroment 环境参数必须一样,Mybatis 配置文件里的 enviroment,一般都是唯一值;
举例如下:
public static void test1(){
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
// 1. sql和参数相同
Student student = mapper.selectStudent(1001);
Student student1 = mapper.selectStudent(1001);
System.out.println(student == student1); //true
// 2. 必须是相同的 statementId
// com.bjpowernode.dao.StudentDao.selectById
// com.bjpowernode.dao.StudentDao.selectById3
Student student2 = mapper.selectById(1001);
Student student3 = mapper.selectById3(1001);
System.out.println(student2 == student3); //false
// 3. sqlSession 必须一样
// 严格来说应该是 sqlSession 里的 Executor 必须一样
// 因为一级缓存 cache 是在 Executor 里的
// 调用 openSession() 会创建出新的 DefaultSqlSession 和 Executor 对象
Student s4 = mapper.selectById(1001);
Student s5 = factory.openSession().getMapper(StudentDao.class).selectById3(1001);
System.out.println(s4 == s5); //false
// 4. RowBounds 返回行的范围必须相同
// RowBounds 的默认值是:RowBounds.DEFAULT
Student student6 = mapper.selectById(1001);
String statementId = "com.bjpowernode.dao.StudentDao.selectById";
RowBounds rowBounds = new RowBounds(0, 10);
List<Student> stus = sqlSession.selectList(statementId, 1001, rowBounds);
System.out.println(student6 == stus.get(0)); //false
}
2.1 CacheKey
我们上面讲的缓存命中场景,其实都是和 CacheKey 有关的,下面我们简单看下这个生成 CacheKey 类对象的地方;
// ------------------------------ BaseExecutor --------------------------------
public <E> List<E> query(MappedStatement ms,
Object parameter,
RowBounds rowBounds,
ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
// 生成 CacheKey 对象
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
// ------------------------------ BaseExecutor --------------------------------
public CacheKey createCacheKey(MappedStatement ms,
Object parameterObject,
RowBounds rowBounds,
BoundSql boundSql) {
CacheKey cacheKey = new CacheKey();
// 1. mappedStatementId
cacheKey.update(ms.getId());
// 2. RowBounds.offset
cacheKey.update(rowBounds.getOffset());
// 3. RowBounds.limit
cacheKey.update(rowBounds.getLimit());
// 4. sql
cacheKey.update(boundSql.getSql());
// ....
// 5. 参数 params
if (configuration.getEnvironment() != null) {
// 6. environment
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
可以看到 CacheKey 和 6 个变量的值有关,我们举一个例子看看:
3. 一级缓存的失效场景
一级缓存的失效,大致有以下几种场景:
1、手动清空了一级缓存 cache:sqlSession 的 clearCache()、commit()、rollback() 都会清空一级缓存;
2、执行了 sqlSession.update(),只要执行了 sqlSession.update(),都会清空一级缓存,因为 update() 方法中会做一个清空一级缓存的动作;
3、全局将一级缓存的作用域改为了 STATEMENT,默认是 SESSION 级别的,也就是默认是 SqlSession 级别的;了解即可;
举例如下:
public static void test2(){
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
// 1. 手动清空一级缓存
Student student = mapper.selectById(1001);
sqlSession.clearCache();
// sqlSession.commit();
// sqlSession.rollback();
Student student1 = mapper.selectById(1001);
System.out.println(student == student1); // false
// 2.执行了 sqlSession.update()
// 只要执行过 update 语句,都会清空当前一级缓存
Student student2 = mapper.selectById3(1001);
mapper.updateStudent(1002,"zq");
Student student3 = mapper.selectById3(1001);
System.out.println(student2 == student3; // false
}
4. 一级缓存的调用流程
从上面我们知道一级缓存是在 BaseExecutor 中的,我们直接看 BaseExecutor 的 query();
// ------------------------------ BaseExecutor --------------------------------
public <E> List<E> query(MappedStatement ms,
Object parameter,
RowBounds rowBounds,
ResultHandler resultHandler,
CacheKey key,
BoundSql boundSql) throws SQLException {
if (queryStack == 0 && ms.isFlushCacheRequired()) {
// 如果当前为第一次调用,且当前 MappedStatement 需要清空缓存,则清空一级缓存
// queryStack 是用于嵌套子查询的,后面会讲
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 1. 先去一级缓存中查,如果缓存中有值直接返回
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 2. 一级缓存中没有值,去查数据库
list =
queryFromDatabase(ms,parameter,rowBounds,resultHandler,key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// 如果当前的缓存范围是 STATEMENT, 执行完查询后清空缓存
clearLocalCache();
}
}
return list;
}
// ------------------------------ BaseExecutor --------------------------------
private <E> List<E> queryFromDatabase(MappedStatement ms,
Object parameter,
RowBounds rowBounds,
ResultHandler resultHandler,
CacheKey key,
BoundSql boundSql) throws SQLException {
List<E> list;
// 1. 将 key 和对应的占位符放入一级缓存
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 2. 调用 doQuery() 获取查询结果
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 3. 将 key 对应的占位符移除
localCache.removeObject(key);
}
// 4. 将查询结果放入一级缓存
localCache.putObject(key, list);
// 5. 返回查询结果
return list;
}
// ------------------------------ BaseExecutor --------------------------------
public int update(MappedStatement ms, Object parameter) throws SQLException {
// 1. 先清空一级缓存
// 为什么说执行了 sqlSession.update() 一级缓存会失效,因为会先清空一级缓存
clearLocalCache();
// 2. 执行 doUpdate() 并返回值
return doUpdate(ms, parameter);
}
五. 二级缓存
二级缓存是在 CachingExecutor 中的,二级缓存和一级缓存不一样,二级缓存需要开启才会使用;
我们暂时不去看二级缓存,了解即可;
六. StatementHandler
1. 种类和结构
StatementHandler 的种类和结构大致如下:
我们主要看 PreparedStatementHandler 这个对象:创建 PreparedStatement、设置参数,执行 sql 都是咋 StatementHandler 中执行的;
2. 分析
我们直接进入到 SimpleExecutor 的 doQuery();
// ------------------------- SimpleExecutor -------------------------------
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();
// 1. 创建出 StatementHandler 对象
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 2. 得到预编译对象 PreparedStatement
// 该方法还会进行参数的映射和设置
stmt = prepareStatement(handler, ms.getStatementLog());
// 3. 通过 statementHandler.query() 执行 sql
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
下面我们对 doQuery() 中三个步骤做简要分析;
2.1 创建出StatementHandler对象
通过 Configuration 对象创建出 StatementHandler 对象;
// ------------------------- Configuration -------------------------------
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 1. 创建出 RoutingStatementHandler
// 这里又是装饰器模式
// 其实 RoutingStatementHandler 中真正作用的是 PreparedStatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 2. 插件增强
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
// ------------------------- RoutingStatementHandler -----------------------------
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:
// 默认创建的就是 PreparedStatementHandler 对象
// 这里又是装饰器模式
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");
}
}
2.2 得到PreparedStatement对象
我们看一下 prepareStatement() 干了啥;
// ------------------------- SimpleExecutor -------------------------------
private Statement prepareStatement(StatementHandler handler, Log statementLog) {
Statement stmt;
Connection connection = getConnection(statementLog);
// 1. 执行 PreparedStatementHandler.prepare() 获得 PreparedStatementHandler 对象
stmt = handler.prepare(connection, transaction.getTimeout());
// 2. 对参数进行映射处理和设值
// 这里后面我们会重点看!!!
handler.parameterize(stmt);
return stmt;
}
// ------------------------- BaseStatementHandler -------------------------------
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 实例化 statement 对象
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
// ------------------------- PreparedStatementHandler -----------------------------
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
// ...
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
// 直接执行 connection.prepareStatement(sql)
// 预编译 sql 得到 PreparedStatement 对象
// 这里就是我们 jdbc 中预编译得到 PreparedStatement 的方式
return connection.prepareStatement(sql);
} else {
// ...
}
}
2.3 执行statementHandler.query()
我们简单看下 statementHandler.query() 干了啥;
public <E> List<E> query(Statement statement, ResultHandler resultHandler) {
PreparedStatement ps = (PreparedStatement) statement;
// 1. 直接执行 ps.execute()
ps.execute();
// 2. 通过 resultSetHandler 处理结果集
return resultSetHandler.handleResultSets(ps);
}
七. Mapper代理类对象
1. UserMapper分析
Spring 为我们生成的 UserMapper 是一个代理类;我们在分析 Mybatis 集成 Spring 后一级缓存失效的场景中已经给 UserMapper 做过一个分析了,我们直接看 UserMapper 的结构;
1、这个 UserMapper 是一个代理类,它的 InvocationHandler 是 MapperProxy 类;
2、MapperProxy 内聚合了一个 SqlSessionTemplate 类,SqlSessionTemplate 实现了 SqlSession 类;
3、MapperProxy 的 invoke() 最终会调用目标类 sqlSessionTemplate 的方法;
4、SqlSessionTemplate 内聚合了一个 sqlSessionProxy,sqlSessionProxy 也是一个代理类,它实现了 SqlSession 类,它的 invocationHandler 是 SqlSessionInterceptor 类;
类图分析如下,其实代理的目标类是 SqlSession,也就是说干活的类是 SqlSession;
需要注意两个 InvocationHandler:MapperProxy 和 SqlSessionInterceptor,SqlSessionInterceptor 我们在一级缓存失效的场景中分析过了,这次我们重点看 MapperProxy;
2. MapperProxy
单看 MapperProxy 这个类名,可能认为它是一个代理类对象,其实它是一个 InvocationHandler 对象,我们看下它的 invoke();
// ------------------------------- MapperProxy ---------------------------------
public class MapperProxy<T> implements InvocationHandler {
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) {
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);
}
// 1. 创建或从缓存中得到 MapperMethod 对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 2. 执行 mapperMethod.execute()
return mapperMethod.execute(sqlSession, args);
}
}
主要是执行 MapperMethod.execute(sqlSession, args);
我们先看下 MapperMethod 的结构,其中 paramNameResolver 用于解析参数对象,后面我们会重点看;
mapperMethod.execute() 如下:
// ------------------------------- MapperMethod ---------------------------------
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
// ...
} else {
// 我们直接看这个地方
// 1. 处理参数 Object[] args,得到处理后的参数 Object param
Object param = method.convertArgsToSqlCommandParam(args);
// 2. 调用 sqlSession.selectxxx() 得到 result
// 可以看到参数已经是我们上面得到的 param,不再是 args 了
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null ||
!method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException();
}
if (result == null
&& method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException();
}
return result;
}
// ------------------------------- MethodSignature ---------------------------------
public Object convertArgsToSqlCommandParam(Object[] args) {
// 调用 paramNameResolver.getNamedParams(args) 解析得到新的参数对象
return paramNameResolver.getNamedParams(args);
}
参数解析是在 ParamNameResolver 中做的,我们重点看下 ParamNameResolver;
2.1 参数解析ParamNameResolver
2.1.1 构造函数
我们先看下它的构造函数;
// ------------------------- ParamNameResolver ---------------------------
public ParamNameResolver(Configuration config, Method method) {
final Class<?>[] paramTypes = method.getParameterTypes();
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
// 轮询解析方法中的参数
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
continue;
}
String name = null;
// 1. 如果该参数前有 @Param 注解,name 值为 @Param 注解的 value 值
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
// 方法参数上有 @Param 注解的情况下,hasParamAnnotation 置为 true
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
// 2. 如果参数前没有 @Param 注解
if (name == null) {
// 3. 默认情况下 Mybatis 中 config 的 useActualParamName 默认值为 true
// 所以默认情况下 name 值为方法参数名
if (config.isUseActualParamName()) {
name = getActualParamName(method, paramIndex);
}
if (name == null) {
name = String.valueOf(map.size());
}
}
// map 中值的形式如 {{0, "id"}, {1, "name"}}
map.put(paramIndex, name);
}
// 4. names 也是一个 Map,所以 names 中值的形式如 {{0, "id"}, {1, "name"}}
names = Collections.unmodifiableSortedMap(map);
}
names 的值跟有无 @Param 注解有关,我们可以看如下场景中 names 的值;
1、没有 @Param 注解,但是给 Java 加上了 -parameters,names 值为:{{0, “id”}, {1, “username”}}
2、没有 @Param 注解,没有给 Java 加上了 -parameters,names 值为:{{0, “arg0”}, {1, “arg1”}}
User selectByIdAndName(int id, String username);
3、有 @Param 注解,names 值为:{{0, “id”}, {1, “name”}}
User selectByIdAndName(int id, @Param("name") String username);
2.1.2 参数解析
我们再看它的参数解析方法:getNamedParams();
// ------------------------- ParamNameResolver ---------------------------
public Object getNamedParams(Object[] args) {
int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
// 1. 如果方法参数没有 @Param 注解,且只有一个参数
// 直接返回该参数,不做任何处理
return args[names.firstKey()];
} else {
// 2. 此时,参数前有 @Param 注解 || 参数不止一个
// 创建一个 Map<String, Object> param,该 Map 的类型为 ParamMap
final Map<String, Object> param = new ParamMap<>();
int i = 0;
// 3. 遍历我们构造函数中得到的 names, 构造 param
for (Map.Entry<Integer, String> entry : names.entrySet()) {
// 3.1 给 param 添加值
// {names.entry.value0, arg[0]}, {names.entry.value1, arg[1]}
param.put(entry.getValue(), args[entry.getKey()]);
// 3.2 再往 param 中添加 {"param1":args[0]}, {"param2":args[1]}
String genericParamName = "param" + String.valueOf(i + 1);
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
// 4. 该情况下,返回得到的 param 对象,该 param 是一个 Map 对象
return param;
}
}
param 的值跟有无 @Param 注解有关,我们可以看如下场景中 param 的值;
我们默认没有给 Java 加上 -parameters 参数,看 param 的值:
1、当参数有 @Param 注解标注;此时 param 的 key 列表为 id、name、param1、param2;
// 此时 param 的 key 列表为 id, name, param1, param2
@Select("select * from student where id=#{id} and name=#{name}")
public Student selectByIdAndName(@Param("id") int id, @Param("name") String name);
2、当参数没有 @Param 注解标注;此时 param 的 key 列表为 arg0、arg1、param1、param2;
// 此时 param 的 key 列表为 arg0, arg1, param1, param2
@Select("select * from student where id=#{param1} and name=#{param2}")
public Student selectByIdAndName(int id, String name);
3、当参数不全有 @Param 注解标注(了解即可,一般我们不会写这么脑瘫的写法);
// 此时 param 的 key 列表为 id, arg1, param1, param2
@Select("select * from student where id=#{id} and name=#{param2}")
public Student selectByIdAndName(@Param("id") int id, String name);
4、当只有一个参数时,且没有 @Param 注解,显然 getNamedParams() 会直接返回该参数;
// 此时参数解析结果就是原参数 Student 对象
// 很显然不能使用 #{stu.id},因为根本没有 stu 这个索引,只能使用 #{id}
@Select("select * from student where id=#{id}")
public Student selectByStudentId2(Student stu);
5、当只有一个参数时,且有 @Param 注解;此时 param 的 key 列表为 stu、param1;
// 此时的参数解析结果是 param 对象:{"stu": student, "param1": student}
// 很显然如果使用 #{id} 来取值的话,Mybatis 不知道该用哪个
// 只有我们明确指出 #{stu.id} 才行,或者也可使用 #{param1.id}
@Select("select * from student where id=#{stu.id}")
public Student selectByStudentId(@Param("stu") Student stu);
此时 param 参数如下:
至此,参数解析完毕;
八. 参数映射处理
我们在第五大点中 prepareStatement() 创建 PreparedStatement 对象中知道,该方法内还会做参数的映射处理;
// ------------------------- SimpleExecutor -------------------------------
private Statement prepareStatement(StatementHandler handler, Log statementLog) {
Statement stmt;
Connection connection = getConnection(statementLog);
// 1. 执行 PreparedStatementHandler.prepare() 获得 PreparedStatementHandler 对象
stmt = handler.prepare(connection, transaction.getTimeout());
// 2. 对参数进行映射处理
handler.parameterize(stmt);
return stmt;
}
下面我们对 PreparedStatementHandler.parameterize() 做一个简单的分析;
// ----------------------- PreparedStatementHandler -----------------------------
public void parameterize(Statement statement) throws SQLException {
// 调用 parameterHandler.setParameters() 对参数进行映射
parameterHandler.setParameters((PreparedStatement) statement);
}
Mybatis 中只有一个默认的 ParameterHandler,是 DefaultParameterHandler,我们看下这个类的 setParameters();
1. DefaultParameterHandler
DefaultParameterHandler 的 setParameters() 流程不长,我们直接往下看;
// ------------------------ DefaultParameterHandler ---------------------------
public void setParameters(PreparedStatement ps) {
// 1. 获取 sql 中所有的 parameterMapping
// 其实就是我们 sql 中 #{id} 中对应值
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
// 2. 遍历处理 parameterMapping
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
Object value;
// 获取 parameterMapping 的属性 property
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry
.hasTypeHandler(parameterObject.getClass())) {
// 3. parameterObject 就是我们上文解析得到的参数对象
// 如果参数只有一个且没有 @Param 的情况下,value 直接等于 parameterObject
// 当然这里是简单参数的情况
// 如果 parameterObject 是 JavaBean 类型,还是会走到第 4 步去解析参数
value = parameterObject;
} else {
// 4. 参数不止一个的情况,parameterObject 是一个 map 对象
// 此时需要通过辅助的 MetaObject 根据 propertyName 获取参数值
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 5. 获取 parameterMapping 的 TypeHandler
// 我们在 sql 中一般不指定 TypeHandler
// 此处获取的 typeHandler 一般都为 UnknownTypeHandler
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 6. 通过 typeHandler.setParameter() 给 sql 中参数进行设值
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping");
}
}
}
}
// -------------------------- UnknownTypeHandler ----------------------------
public void setNonNullParameter(PreparedStatement ps, int i,
Object parameter, JdbcType jdbcType) {
// 1. 根据 parameter、jdbcType 获取一个合适的 TypeHandler
// 一般 int 对应 IntegerTypeHandler、String 对应 StringTypeHandler
TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
// 2. 再次调用 typeHandler.setParameter()
// 我们以 IntegerTypeHandler 为例来看
handler.setParameter(ps, i, parameter, jdbcType);
}
// -------------------------- IntegerTypeHandler ----------------------------
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
Integer parameter, JdbcType jdbcType) {
// 调用我们熟悉的 jdbc 中的 ps.setInt()
// TypeHandlerRegistry 中有所有注册的 TypeHandler
ps.setInt(i, parameter);
}
}
我们举两个例子来看 DefaultParameterHandler.setParameters();
1.1 参数只有一个且没有@Param
parameterObject 是简单类型,此时 value = parameterObject;parameterMappings 只有一个参数;
1.2 参数有两个
此时 parameterObject 是一个 Map 对象,parameterMappings 也有两个参数,需要轮询进行处理;
处理逻辑其实就是取 parameterObject 中对应 key 的 value 值;
至此,参数映射完毕;