Mybatis调用流程
准备
sql语句
create table `t_user`
(
id int not null auto_increment,
`name` varchar(255) default null,
`pwd` varchar(255) default null,
gender int default null,
age int default null,
primary key (id)
) engine = innodb
auto_increment = 1
charset = utf8mb4;
启动类
public class MybatisApplication {
public static void main(String[] args) {
final String configFileName = "mybatis-config.xml";
try {
InputStream inputStream = Resources.getResourceAsStream(configFileName);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession sqlSession = sessionFactory.openSession()) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectById(1);
System.out.println(user);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
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="msa.dao.UserMapper">
<resultMap id="User" type="msa.entity.User">
<id column="id" property="id"/>
<result column="name" property="name"/>
</resultMap>
<select id="selectById" resultMap="User">
select * from `t_user`
<where>
<if test="id != null">
id = #{id}
</if>
</where>
</select>
</mapper>
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>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.56.10:3306/msa?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="ba7QhVdN0GZuATUq"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
UserMapper接口
public interface UserMapper {
User selectById(Integer id);
}
User实体类
@Data
public class User {
private Integer id;
private String name;
private String pwd;
private Integer gender;
private Integer age;
}
前提
Mybatis是基于XML + Mapper接口来作为用户调用的DAO层。
XML文件
接口
使用SqlSession获取接口的代理类
Mapper接口的代理类为
org.apache.ibatis.binding.MapperProxy
MapperProxy
实现了InvocationHandler
接口
这也是为什么Mybatis不支持使用类来写Mapper文件对应的接口,因为Mybatis的代理是基于JDK的,而不是基于CGLIB。
现在已经知道Mapper接口的是由MapperProxy这个类的代理的,那这个MapperProxy类又是如何获取的呢?
SqlSession创建MapperProxy
SqlSession的默认实现类为
org.apache.ibatis.session.defaults.DefaultSqlSession
DefaultSqlSession
类的getMapper
方法
DefaultSqlSession
类内部的getMapper
方法又调用了org.apache.ibatis.session.Configuration
的getMapper
方法。注意:
Configuration
没有实现任何接口和继承任何类。
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
Configuration
类的getMapper
方法
@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 {
// mapperProxyFactory使用了工厂模式,在需要时new一个MapperProxy
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
MapperProxyFactory
类源码
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
// 同一个SqlSession创建的MapperProxy共用一个方法缓存
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
// 使用了JDK的动态代理
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
MapperProxy执行流程
MapperProxy的代理逻辑:invoke
方法
MapperProxy实际上只是对
InvocationHandler
接口的一层封装,实际的方法调用逻辑由MapperMethod
实现。
public class MapperProxy<T> implements InvocationHandler, Serializable {
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) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
// 如果Object类定义的方法,则直接调用
return method.invoke(this, args);
} else if (method.isDefault()) {
// 调用接口的默认方法
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 从缓存中查找对应的MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 通过MapperMethod来执行对应的逻辑
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
}
MapperMethod的execute
方法逻辑
execute
方法实际还是调用了SqlSession
类。
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:
// select语句走这个逻辑
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
// 转换方法参数为xml里面对应的参数
Object param = method.convertArgsToSqlCommandParam(args);
// 调用SqlSession来获取结果
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("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
DefaultSqlSession类的selectList
方法
DefaultSqlSession内部又调用了
org.apache.ibatis.executor.CachingExecutor
类的query
方法。
CachingExecutor类的query
方法
@Override
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.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 缓存中不存在,则使用实际处理类来查询(这里的delegate是SimpleExecutor类)
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
SimpleExecutor类的query
方法
@Override
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++;
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;
}
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;
}
@Override
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());
// 这里就会去打开数据库连接并获取对应的PreparedStatement去执行sql语句
// 详情: PreparedStatementHandler#query
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
PreparedStatementHandler类的query
方法
这个方法内部调用
resultSetHandler
的默认实现类为org.apache.ibatis.executor.resultset.DefaultResultSetHandler
DefaultResultSetHandler类的handleResultSets
方法
//
// HANDLE RESULT SETS
//
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
MapperMethod类的execute
方法使用策略模式
MapperMethod的
execute
方法使用SqlCommand来区分执行哪种逻辑(增删改查)
题外话:Mybatis如何把接口参数注入XML中对应的参数?
实现逻辑在
org.apache.ibatis.reflection.ParamNameResolver
的getNamedParams
中。我们知道通过上面的分析知道了
MapperProxy
代理类是调用MapperMethod
类的execute
来间接调用SqlSession
,那么在MapperMethod
到SqlSession
这个过程中是如何注入这些参数的呢?这就要看
MapperMethod
里面的MethodSignature
的convertArgsToSqlCommandParam
方法。
MethodSignature
内部有一个ParamNameResolver
的成员,convertArgsToSqlCommandParam
方法就调用了ParamNameResolver
的getNameParams
。
public class ParamNameResolver {
private static final String GENERIC_NAME_PREFIX = "param";
/**
* 保存接口方法的参数索引和其参数名称的对应关系
* key: 方法参数在方法中的索引, 例如:0, 1, ...
* value: 对应的参数名称, 如果参数上存在@Param则使用@Param注解的值,否则该值等于调用key.toString()
* 注意: RowBounds和ResultHandler接口的方法参数会被忽略
* aMethod(@Param("M") int a, @Param("N") int b) -> {{0, "M"}, {1, "N"}}
* aMethod(int a, int b) -> {{0, "0"}, {1, "1"}}
* aMethod(int a, RowBounds rb, int b) -> {{0, "0"}, {2, "1"}}
*/
private final SortedMap<Integer, String> names;
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
// 如果没有方法参数,则返回null
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
// 没有@Param且只有一个参数,则返回对应的参数值
return args[names.firstKey()];
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// 通用的方法名称param1, prarm2, ...
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}