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;
}
前提
XML文件
接口
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);
}
@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
方法逻辑
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
方法使用策略模式
题外话: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;
}
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。