参考:咕泡学院的mybatis源码分析及手写实现教程
1. 运行hello world,提出问题
先自己运行一个demo,以mybatis为例,需要sqlsession,需要mapper,为什么需要呢?
2. 看设计文档,看模块划分,理解大局
需要看其design设计文档或先从模块上来看,大局上是什么样的,之后再跟进去看
看其架构设计图,看其是如何设计其系统的
如果没有架构设计图,那么就看其包组织的结构,
通过上面的包组织结构及基本的hello world demo,基本上就能猜测出来下面这张图
Configuration其实就是我们的全局配置xml文件
通过读取全局配置文件来给configuration来赋值
3. 边看代码,边画UML图
看代码的时候,要学会画UML图,包括类图(对象)和时序图(顺序)
mybatis是一个主架构比较清晰的框架,比较适合来进行阅读
SqlSession可以看做是mybatis的核心
看源码不是要把每一行看清,重点是把流程先看明白,细节可以在后面学习
看源码看得多的,可以看到一个接口设计的基本结构是
先声明一个interface,来定义好规范/模板
之后有一个abstract class,实现共性的功能
再下面有一个Default Class,即默认的实现
支持定制
上面这点也是应该学的,我们一般人就直接写interface--定制实现
如下面的Executor接口,就是这样设计的
源码分析
业务代码1
获取sqlSessionFactory对象
解析文件的每一个信息保存在Configuration中,返回包含Configuration的DefaultSqlSessionFactory
Configuration中两个重要的属性:
1. MappedStatement:代表一个增删改查的详细信息
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
2. mapperRegistry:是一个Map,为每个Mapper绑定了对应的MapperProxyFactory对象,最后就是通过MapperProxyFactory来创建MapperProxy
public MapperRegistry getMapperRegistry() {
return mapperRegistry;
}
代码:
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
源码:
通过XMLConfigBuilder
具体的执行:
private void parseConfiguration(XNode root) {
try {
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
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);
}
}
一层一层来解析,
ConfigurationParser
MapperParser
StatementParser,将sql语句封装为MappedStatement,一个MappedStatement对应我们mapper.xml中的一个增删改查标签
这样就通过XML文件获得了Configuration类,就是向Configuration类中填充内容
把配置文件的信息解析并保存在Configuration对象中
最后返回DefaultSqlSessionFactory
流程图
业务代码2:
获取sqlSession对象
返回一个DefaultSqlSession对象,包含Executor和Configuration;
在这一步会创建Executor对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
源码:
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession()
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
}
可以看到configuration.newExecutor 四大对象中的Executor
CachingExecutor中包含了一个Executor,其将这个executor进行了包装了wrapper
private Executor delegate;
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
delegate(executor)执行所有真正的增删改查,相当于CacheExecutor的门面
这样的好处就是:比如说我们开启了二级缓存,那么你在查询之前,就会有缓存操作
下面这句就比较重要了,与plugin有关
executor = (Executor) interceptorChain.pluginAll(executor);
拿到所有的拦截器,调用每个interceptor的plugin方法
每一个executor创建出来,都要执行这一步,使用每一个拦截器重新包装executor,并返回
接下来就是从全局配置文件中读取值,建立与db的会话session
return new DefaultSqlSession(configuration, executor, autoCommit);
流程图
边读源码变画图,整个流程通了,那么我们就可以自己山寨一个了
SqlSession持有configuration和executor
业务代码3:
获取接口的代理对象(MapperProxy)
getMapper,使用MapperProxyFactory创建一个MapperProxy的代理对象
代理对象里面包含了DefaultSqlSession(Executor),一层一层的包含
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
我在没有看源码的情况下,猜测一下,其如果是简单的实现,那应该类似于从一个hashmap中把EmployeeMapper.class取出来,这个hashmap的填充应该是在业务代码1的build中进行的
源码:
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
xml配置或annotation配置或其他方式,spring框架、mybatis框架等都是这样一个套路,都是映射成一个configuration类
configuration.getMapper,我们可以大胆猜测之前的build里面,由xml生成Configuration的时候,把xml与mapper的interface已经组装好了。由于这里是EmployeeManager.class,那么就猜测其是由一个map进行关联的,map的key就是interface.class,
org.apache.ibatis.session.Configuration#mapperRegistry
return mapperRegistry.getMapper(type, sqlSession);
既然其也是用getMapper方法,那mapperRegistry中应该有一个hashmap,
org.apache.ibatis.binding.MapperRegistry中
我们看到了这个hashmap
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
这里看到HashMap的value是MapperProxyFactory<?>
我们知道,我们定义的interface EmployeeMapper,其是没有实现类的,那么没有实现类,是怎么运行方法的呢?
注意:这里是一个非常有意思的点
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
拿到这个MapperProxyFactory之后,
在MapperRegistry.java中 newInstance
return mapperProxyFactory.newInstance(sqlSession);
MapperProxyFactory.java
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
mapperInterface是com.hfi.dao.EmployeeMapper
下面这段代码,就是我们熟悉的动态代理的代码
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
但是你确定这个就是我们熟悉的动态代理的代码吗?
反正到这里这段业务代码就执行完成了,关于动态代理的事情后面再说
流程图
业务代码4:
1. 调用DefaultSqlSession的增删改查(Executor);
2. 会创建一个StatementHandler对象。
(同时也会创建出ParameterHandler和ResultSetHandler)
3. 调用StatementHandler预编译参数以及设置参数值;使用ParameterHandler来给sql设置参数
4. 调用StatementHandler的增删改查方法;
5. ResultSetHandler封装结果
注意:
四大对象每个创建的时候都有一个interceptorChain.pluginAll(parameterHandler),将我们创建的对象进行包装wrap
Employee employee = mapper.getEmployeeById(1);
源码
我们熟悉的动态代理
首先有一个TargetInterface,如IUserDao,其中提供了save方法。及其一个实现类TargetImpl,如UserDao,我想在其实现类UserDao中方法save的执行的前后,添加逻辑功能,使用动态代理怎么办呢?
MyProxy就是UserSaveHandler(其也可以使用匿名内部类的方式)
在
IUserDao target = new UserDao();
UserSaveHandler userSaveHandler = new UserSaveHandler(target);
IUserDao proxyInstance = (IUserDao) Proxy.newProxyInstance(userSaveHandler.getClass().getClassLoader(),
target.getClass().getInterfaces(), userSaveHandler);
proxyInstance.save();
但是,我们EmployeeMapper没有实现类,用上面的例子来说,就是并没有UserDao这个实现类,那么其是如何运行的呢?
其会执行到MapperProxy的invoke方法中
我们看MapperProxy
class MapperProxy<T> implements InvocationHandler
其invoke方法
@Override
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 t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
第一个参数proxy是真实对象的真实代理对象。正常情况下应该是
com.hfi.mapper.EmployeeMapper@xxxxxx
但我们debug的时候,发现其为org.apache.ibatis.binding.MapperProxy@xxxxxx
这就是trick的点,这样设计就不需要实现类了
因为其使用MapperProxy去做了,其实压根就没有代理实现类,只需要接口的方法的定义,我拿到这个方法的定义,去映射文件xml中找sql就可以了,这整个的过程并不需要实现类。
也就是说,EmployeeMapper只是一个摆设,真正执行功能的是MapperProxy,在这个MapperProxy中是持有映射文件xml的所有的sql的(MapperProxy中持有DefaultSqlSession,而DefaultSqlSession中持有Configuration和Executor),我只要根据EmployeeMapper找到sql,执行后返回就可以了
后面我们想既然用了动态代理,那么应该调用method.invoke方法了吧,并不是
final MapperMethod mapperMethod = cachedMapperMethod(method);
其并没有调用method.invoke,而是直接执行下面的语句
MapperMethod中
就是要从configuration中拿sql去
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
this.command
拿到了hashmap中配置的key,
接下来拿到一个methodSignature
其实就是读xml,拿到返回值、sql语句等等
this.method
其返回值,其参数都读进来了
之后才是运行方法
return mapperMethod.execute(sqlSession, args);
先进行了一个转换
Map<String, Object> param
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
然后执行selectOne方法
最后调用到selectList方法
MappedStatement ms = configuration.getMappedStatement(statement);
MappedStatement结果:
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
用executor.query来真正执行
先还是要对我们传入的parameter进行wrap包装
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;
}
这就是我们所说的,如果我们的参数是collection list array,那么要取出来,就要使用#{list}
之后继续看query方法
在CachingExecutor.java
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);
}
先把sql和参数进行绑定
先获取boundSql对象,包括sql,参数,以及sql和参数的映射关系
cacheKey是很长的一个字符串
-1566216868:1207450854:com.hfi.dao.EmployeeMapper.getEmployeeById:0:2147483647:select id,last_name lastName,email,gender from tbl_employee where id = ?:1:development
这就是我们的二级缓存及一级缓存中保存的key
Cache cache = ms.getCache();
if (cache != null) {
如果二级缓存中没有,那么继续向下执行,如果有,那么就直接执行
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
delegate是SimpleExecutor,即CachingExecutor最终还是调用SimpleExecutor来执行
BaseExecutor
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);
}
先向localCache中put一个占位符(类似于防止缓存击穿的效果),然后执行doQuery方法
之后再把占位符remove掉,再put进去
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;
}
在SimpleExecutor的doQuery方法
@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());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
Statement就是我们熟悉的java.sql包下的Statement
注意:这里涉及到4大对象的StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
在Configuration类中
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;
}
注意:这里又出现了我们熟悉的interceptorChain
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
在org.apache.ibatis.executor.statement.RoutingStatementHandler#RoutingStatementHandler中
默认StatementType是Prepared,会创建一个PreparedStatementHandler对象,通过构造器创建。另外要注意的是,在创建StatementHandler的时候,同时还创建了ParameterHandler和ResultSetHandler
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
newParameterHandler以及newResultSetHandler也是属于4大对象,同样有interceptorChain
至此,4大对象都创建完毕了
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
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:
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());
}
}
SimpleExecutor中doQuery方法
stmt = prepareStatement(handler, ms.getStatementLog());
这个创建是很经典的jdbc创建statement的方式,拿到connection,prepare就是进行 参数预编译
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
BaseStatementHandler中
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
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);
}
}
在org.apache.ibatis.executor.statement.PreparedStatementHandler#instantiateStatement中,就是在这里进行参数预编译的
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
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);
}
}
在BaseStatementHandler中,存在
protected final ParameterHandler parameterHandler;
这个又是4大对象之一。
在DefaultParameterHandler中,这里就看到jdbcType,也就是我们在resultMap中指定的jdbcType
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
typeHandler.setParameter(ps, i + 1, value, jdbcType);
通过调用typeHandler来设置参数
最后到PrepareStatementHandler中
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
执行ps.execute
查询出来的结果,用resultSetHandler做mapping,mapping到我们的pojo里面去
可以看到
进入到DefaultResultSetHandler.java中
//
// 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<Object>();
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++;
}
获取到resultMap
然后拿到我们每个resultMap的每个元素
这就是为什么我们在写xml文件的时候,要指定jdbcType,就是这里映射的时候用的,我只是知道你数据库的类型,但我并不知道你java的数据类型,要把数据库的数据类型和java的数据类型做映射
在org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getPropertyMappingValue方法中,也是通过typeHandler来做的
typeHandler.getResult(rs, column);
各种TypeHandler
完成之后,
在SimpleExecutor的doQuery中
closeStatement(stmt);
在BaseExecutor中,
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
放到缓存中
查询流程总结
至此,我们也就看到了完整的一个mybatis执行的过程
这样一个完整的执行过程,我们边读边画uml,最后得到这样一张图
根据这张图,我们就可以来自己手写我们的mybatis了
对于mybatis源码,我们还缺少一部分关于事务的内容
关于手写实现上面的基本功能,参考:手写实现乞丐版mybatis