一、准备工作,环境搭建
1.1 mybatis 配置
<?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">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root123"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="xml/TestMapper.xml"/>
</mappers>
</configuration>
1.2 接口及mapper文件
public interface TestMapper {
List<User> findAll();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 指明当前xml对应的Mapper -->
<mapper namespace="com.mybatis.source.mapper.TestMapper">
<select id="findAll" resultType="com.mybatis.source.pojo.User">
select * from user
</select>
</mapper>
1.3 测试类
public class Demo {
public static void main(String[] args) throws IOException {
SqlSession sqlSession = getSqlSession();
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
List<User> all = testMapper.findAll();
all.stream().forEach(System.out::println);
}
private static SqlSession getSqlSession() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory.openSession();
}
}
1.4 测试运行,可以正常查询数据
User{id='1', name='老王', password='12341234', remark='老王是各好邻居'}
User{id='2', name='老王', password='12341234', remark='老王是各好邻居吗?'}
User{id='3', name='老王', password='@cipher@p8fry4MbKGn39vXEEg2adw==', remark='老王是各好邻居吗?'}
User{id='4', name='老王', password='12341234', remark='老王是各好邻居吗?'}
二、debug跟踪源码
2.1 sqlSession的获取
mybatis在操作数据库前首先会先获取sqlSession,它是mybatis和db沟通的桥梁。sqlSession的获取过程如下。
2.1.1 加载资源文件流。
InputStream inputStream = Resources.getResourceAsStream(resource);
2.1.2 根据文件流获取获取SqlSessionFactory,SqlSessionFactory再生产出SqlSession。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory.openSession();
2.2 debug
2.2.1 在sqlSession获取mapper处打断点,debug启动。
F7 下一步,会进入方法内部,但是不会进入方法中嵌套的方法。
F8 下一步,不会进入方法
F9 恢复方法运行,或者跳到下一个断点处。
2.2.2 进入getMapper方法。
SqlSession 是一个接口,F7进入getMapper,进入了DefaultSqlSession方法中,这是sqlSession的默认实现。属性如下定义如下,这里有两个重要对象,包含mybatis配置属性的Configuration对象,和真正操作DB的Executor对象。
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private Executor executor;
private boolean autoCommit;
private boolean dirty;
。。。。。
}
进入了DefaultSqlSession的如下代码,该方法根据传入的泛型返回一个接口对象。具体操作委托给了Configuration对象的getMapper方法。
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
2.2.3 进入Configuration的getMapper方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
发现Configuration又把getMapper的事情委托给了MapperRegistry去做了。
2.2.4 查看 MapperRegistry源码
public class MapperRegistry {
private Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
public MapperRegistry(Configuration config) {
this.config = config;
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
public <T> boolean hasMapper(Class<T> type) {
return this.knownMappers.containsKey(type);
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
this.knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
}
public Collection<Class<?>> getMappers() {
return Collections.unmodifiableCollection(this.knownMappers.keySet());
}
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
resolverUtil.find(new IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
Iterator i$ = mapperSet.iterator();
while(i$.hasNext()) {
Class<?> mapperClass = (Class)i$.next();
this.addMapper(mapperClass);
}
}
public void addMappers(String packageName) {
this.addMappers(packageName, Object.class);
}
}
MapperRegistry 对象有一个Configuration对象属性和一个HashMap属性,key为Class对象,value为一个代理工厂类。还一个getMapper方法和一个addMapper方法。通过对源码的大概浏览及结合mybatis的使用经验,可以推测MapperRegistry 的大概作用为:1、将所有的Mapper文件通过addMapper方法存入一个HashMap中。2、通过getMapper方法从HashMap获取指定类型的Mapper。
2.2.5 进入MapperRegistry的getMapper方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
根据2.2.4对源码的查询,可以看出MapperRegistry 对象的上述getMapper方法首先是根据传入的接口类型从HashMapper中取获取MapperProxyFactory代理工厂对象。如果获取不到代理工厂对象则抛出异常。然后使用该代理工厂对象生产出指定泛型的Mapper对象。
2.2.6 继续进入mapperProxyFactory.newInstance(sqlSession)方法
MapperProxyFactory的重要属性如下:
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
}
newInstance(SqlSession sqlSession)首先构建了一个MapperProxy代理对象,然后调用newInstance(MapperProxy mapperProxy)方法通过反射生成了一个T类型对象。
F8,F8…返回到测试类中。至此,sqlSession对象完成了getMapper动作。
2.2.7 继续debug,调用findAll()方法
重要代码如下:
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 Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
} else {
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
F7 findAll()后进入了MapperProxy代理类对象的invoke()方法中,2.2.6的源码查看中我们也看到了MapperProxy对象会对传入的接口类进行动态代理。invoke()方法会判断传入的方法是否是Object类型的,如果不是则执行cachedMapperMethod方法。在该方法中,首先判断缓存map对象methodCache中是否有该方法了,如果有则直接返回,如果没有则根据传入的参数构建一个MapperMethod 对象返回,并put入methodCache缓存map中。
2.2.8 MapperMethod对象分析
2.2.7 流程执行完后返回了一个MapperMethod对象,F8继续走,F7进入mapperMethod.execute()方法。
MapperMethod方法的部分源码如下,MapperMethod有两个属性SqlCommand和MethodSignature。2.2.7 执行流程中,构建MapperMethod对象调用了其构造函数对其初始化过。execute()方法首先会判断初始化过的SqlCommand 是的type是什么类型:INSERT、UPDATE、DELETE 还是SEELCT类型?F8继续下一步,进入到SELECT判断内,接着就是判断返回值:是否是void,是否有ResultHandler,是否returnMany,是否returnMap。继续F8下一步,进入executeForMany()方法。
public class MapperMethod {
private final MapperMethod.SqlCommand command;
private final MapperMethod.MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new MapperMethod.MethodSignature(config, method);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
if (SqlCommandType.INSERT == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
} else if (SqlCommandType.UPDATE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
} else if (SqlCommandType.DELETE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
} else {
if (SqlCommandType.SELECT != this.command.getType()) {
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
}
}
2.2.9 继续F8 进入executeForMany
判断是否需要分页,然后又把查询的任务委托给了sqlSession的selectList()继续查询。
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
Object param = this.method.convertArgsToSqlCommandParam(args);
List result;
if (this.method.hasRowBounds()) {
RowBounds rowBounds = this.method.extractRowBounds(args);
result = sqlSession.selectList(this.command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(this.command.getName(), param);
}
if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
} else {
return result;
}
}
2.2.10 F7进入sqlSession的默认实现方法DeafaultSession类中
部分源码如下。首先DeafaultSession对象从Configuration中获取到指定的statement,然后DeafaultSession又将任务委托了executor接口对象的query()方法去执行。
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var6;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
List<E> result = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
var6 = result;
} catch (Exception var10) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var10, var10);
} finally {
ErrorContext.instance().reset();
}
return var6;
}
2.2.11 Executor的query()方法
做的三件事情,1、绑定sql参数;2、创建缓存key;3、执行query()方法。
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
2.2.12 进入query方法。
进入的是Executor的实现类BaseExecutor的query()方法。然后调用调用的是queryFromDatabase()方法,该方法又进入了doQuery()方法。后续就是一些类似于JDBC的复杂操作,进行Connection、prepare、resultSet之类的操作,不再跟踪,太复杂了,后面再慢慢学习。
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
List list;
try {
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
this.localCache.removeObject(key);
}
this.localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
this.localOutputParameterCache.putObject(key, parameter);
}
return list;
}