手写Mybatis源码(小傅哥),个人总结 详细连接
再说mybatis之前,先知道原生jdbc操作,不知道的请链接: 原生jdbc
先说结论:
mybatis主要用了反射和代理实现的,反射用于参数和返回值的处理,代理用于执行接口方法。
章节流程
1:数据源的解析,环境解析
2:连接的池化
3:sql文件的解析(插件的解析处理,mapper文件的解析处理)这是核心逻辑,主要是向Configuration注册各种数据
4:动态sql解析( OGNL 表达式)
5:插件实现原理(动态代理:Executor,StatementHandler,ParameterHandler,ResultSetHandler)
6:一二级缓存(暂时未看)
先看看终极打工仔类:Configuration
//环境数据源
protected Environment environment;
protected boolean useGeneratedKeys = false;
// 映射注册机
protected MapperRegistry mapperRegistry = new MapperRegistry(this);//当前mapper文件中包含了哪些东西
// 映射的语句,存在Map里,Map的key为<mapper namespace="xxx">中的namespace+id,value为当前映射文件的所有信息
protected final Map<String, MappedStatement> mappedStatements = new HashMap<>();
// 结果映射,存在Map里,Map的key为<mapper namespace="xxx">中的namespace+id,value为返回值对象以及其它相关信息
protected final Map<String, ResultMap> resultMaps = new HashMap<>();
//键值生成器接口
protected final Map<String, KeyGenerator> keyGenerators = new HashMap<>();//KeyGenerator
// 类型别名注册机
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
//哪种方式解析sql,默认xml方式
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
// 类型处理器注册机,主要用于Statement的参数设置,ResultSet值获取
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
// 对象处理工厂
protected ObjectFactory objectFactory = new DefaultObjectFactory();
//ObjectWrapper工厂。MetaObject是mybatis自己封装了反射。非常强悍,会涉及关于返回值和参数处理
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
//已经加载了的mapper资源文件,每个mapper加载之后存入到这个集合中,避免重复加载
protected final Set<String> loadedResources = new HashSet<>();
protected String databaseId;
这个类会被到处使用,各种传递,Configuration是mybatis最核心的类没有之一
,就像spring的BeanDefinition是对bean的定义,而这个类是对整个mybatis的mapper定义。
先简单说下Mybtais大致的一个流程:先交代一下配置信息
<?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>
<!--插件-->
<plugins>
<!--下面的plugin里面类路径,看到全路径字符串就知道肯定会被反射创建-->
<!--先说下,这个类TestPlugin(自己定义的)你必须实现Interceptor接口,
以及加上一个注解@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, BoundSql.class})})
属性1:type:type选项有4种class先后顺序:分别是Executor.class,StatementHandler.class,ParameterHandler.class,ResultSetHandler.class
为什么是这四种?因为创建时就只添加了这4中类型,你可以在Configuration中搜interceptorChain.pluginAll
属性2:method:这4个接口里面的方法名称,别瞎写,具体写法是:你想代理哪个class(属性1:type),里面的哪个方法。
属性3:args:这个方法里面的参数类型calss,因为有重载。所以有这个属性,不然鬼知道执行哪个方法。说到这里你有没有想过在mapper接口里面弄个重载方法,看看会发生什么神奇bug。
总结:可以配置多个Signature,每个Signature表示你想拦截哪个接口的哪个方法,然后后面会把对应的参数传递给你Object[] args,你就可以自定义了
-->
<plugin interceptor="cn.bugstack.mybatis.test.plugin.TestPlugin">
<!--设置的值-->
<property name="test00" value="100"/>
<property name="test01" value="200"/>
</plugin>
<plugin interceptor="cn.bugstack.mybatis.test.plugin.TestPlugin2">
</plugin>
</plugins>
<!--你看这个environment是不是和Configuration类里面那个environment一样,没错那个对象就保存的这些东西-->
<environments default="development"><!--environments 表示可以有一堆environment,default默认使用下面的id为哪个的environment -->
<environment id="development">
<transactionManager type="JDBC"/> <!--事务管理器对应Configuration中 typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);-->
<!--连接池,在Configuration中typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);使用每个执行都需要开启连接,非常消耗资源。-->
<!--所以弄了个连接池,说是连接池,不要想的有多高级,往大了说就是一个List,然后根据你的连接池最大数量配置,往集合添加连接。以及加了点多线程的等待机制,状态机制进行检查而已-->
<dataSource type="POOLED">
<!--下面这些配置就不需要我说了噻-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 你的sql XML 配置的位置,待会被加载,就是这个加载完毕后会往Configuration的loadedResources里面添加。 -->
<mapper resource="mapper/Activity_Mapper.xml"/>
</mappers>
</configuration>
Activity_Mapper.xml 配置
<!--非常重要,反射代理执行的接口-->
<mapper namespace="cn.bugstack.mybatis.test.dao.IActivityDao">
<resultMap id="activityMap" type="cn.bugstack.mybatis.test.po.Activity">
<id column="id" property="id"/>
<result column="activity_id" property="activityId"/>
<result column="activity_name" property="activityName"/>
<result column="activity_desc" property="activityDesc"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<select id="queryActivityById" parameterType="cn.bugstack.mybatis.test.po.Activity" resultMap="activityMap">
SELECT activity_id, activity_name, activity_desc, create_time, update_time
FROM activity
<!--trim,if标签都是由NodeHandler处理的,具体有哪些动态标签可以看XMLScriptBuilder的initNodeHandlerMap()方法-->
<trim prefix="where" prefixOverrides="AND | OR" suffixOverrides="and">
<if test="null != activityId"> <!--这个会通过Ognl表达式解析-->
activity_id = #{activityId}
</if>
</trim>
</select>
详细的xml sql文件解析流程在SqlSessionFactoryBuilder的return build(parser.parse());parser.parse()
方法中,parser.parse()这个方法里面直接开始翻云覆雨,翻江倒海。先截个图,后面详细介绍每个细节流程
下面开始解析你的configuration
.xml信息。
下面这些字符串就是你configuration.xml允许出现的标签和每个标签具体的解析操作。
你如果对哪个标签有疑惑就自己点进对应的解析操作中,即可解惑
。本次只带大家看看解析mapper标签,就是我们的sql xml文件
下面就是你的mappers标签里面的每个属性解析。然后就根据你的配置,选择解析。
以上的方法是对整个xml的解析。如果对某些标签有疑惑的话,就自己点击疑惑的对应标签去详细看看。这些解析操作都是在做一个事情,就是向Configuration注册各种属性值
。
接下来我们看看解析完成后,具体又怎么在使用吧!第一步创建代理,第二步执行方法
1:创建接口代理对象 ,IActivityDao dao = sqlSession.getMapper(IActivityDao.class)
请找到DefaultSqlSession中的getMapper方法
@Override
public <T> T getMapper(Class<T> type) {
//开始获取之前解析出来的东西了
return configuration.getMapper(type, this);
}
然后进入到MapperRegistry类中getMapper
//这个map的key就是解析sql xml文件中的namespace的值,代理对象工厂为value
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
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 {
//关键,关键,关键地方了,要开始创建你的接口代理了。后面这个接口的所有方法,都将走代理执行了。
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
接下来就看看怎么创建的代理:如果你想看接口方法中的执行流程就要进入MapperProxy方法
里面看
protected T newInstance(MapperProxy<T> mapperProxy) {
//真正创建了代理MapperProxy<T> mapperProxy这个类必须实现InvocationHandler动态代理.
//重点,重点,重点后面执行接口方法全部都是在走invoke方法了,所以你想看整个接口方法的执行流程就要进入MapperProxy方法里面看
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);
}
这里就把你的代理对象创建出来了IActivityDao dao = sqlSession.getMapper(IActivityDao.class);
2:接下来就调用接口里面的方法了 , Activity res = dao.queryActivityById(req);
代理执行的哈:MapperProxy中的public Object invoke(Object proxy, Method method, Object[] args)方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//如果是Object类,直接走,一般会走else。
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
//我们的接口执行,先把我们的方法缓存一下,避免重复创建返回PlainMethodInvoker,
//然后执行方法.invoke(proxy, method, args, sqlSession)
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
//关键的执行方法。
return mapperMethod.execute(sqlSession, args);
}
}
具体的执行在MapperMethod
类中的execute
方法.
我们先看看SELECT类型,先判断下方法的返回类型,封装不同结果类型,先看下selectOne这个方法吧。在DefaultSqlSession类中selectOne方法,
public <T> T selectOne(String statement, Object parameter) {
//实际上selectOne和selectList调用是一样的。只是判断了下。你看下面这个异常是否熟悉得很。
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
//大于1就抛异常了
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
接下来注意了哈,第一个插件拦截器开始执行了Executor executor
执行器,最先拦截参数少的query方法,后拦截参数较多的query方法。
你可以点进去看下Executor 方法,里面只有query
方法和update
方法,
你所有的dml语句,都将由这两个方法执行,一个负责查询
,一个负责增删改
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
参数较少的query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler),
参数较多的query(ms, parameter, rowBounds, resultHandler, key, boundSql)。
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
//调用query的重载方法,较多参数的
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
//省略部分代码
//query的重载方法,较多参数的。查询数据库操作
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
//省略部分代码,执行查询
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
SimpleExecutor类中的doQuery方法,核心方法 1:stmt = prepareStatement(handler, ms.getStatementLog()); 2:return handler.query(stmt, resultHandler);
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,这个过程会去判断创建哪种StatementHandler,并且会把当前的插件拦截器添加到InterceptorChain中
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//核心方法1。获取连接,设置sql,设置参数
stmt = prepareStatement(handler, ms.getStatementLog());
//核心方法2:真正执行查询
return handler.query(stmt, resultHandler);
} finally {
//关闭资源
closeStatement(stmt);
}
}
// Connection connection = getConnection(statementLog);创建连接
//handler.prepare(connection, transaction.getTimeout()),调用StatementHandler的prepare方法,先会调用StatementHandler拦截器
//handler.parameterize(stmt);参数设置,调用ParameterHandler设置参数,
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//获取连接,这个方法就有点繁琐,如果采用了池(List集合)化技术的话,就会去判断池中满了没,然后加点等待,状态机制实现了。
//所以说如果由慢sql之类的话就会一直把连接占用,导致后面无法获取连接,导致接口响应慢。而连接池的最大连接数需要看你数据库的配置
Connection connection = getConnection(statementLog);
//这里就要设置sql语句。具体的语句select id,activity_id FROM activity where id = ?
stmt = handler.prepare(connection, transaction.getTimeout());
//设置参数。where id = ? ,替换问号 id = 1
handler.parameterize(stmt);
return stmt;
}
PreparedStatementHandler类中的query方法,真正执行查询,和结果封装返回咯。
并且最后一个拦截器ResultSetHandler将被执行
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//执行查询
ps.execute();
//结果处理。
//简单说下处理逻辑
//1:List<Object> multipleResults = new ArrayList<>();准备返回集合
//2:stmt.getResultSet()获取ResultSet
//3:获取返回类型对象
//4:循环处理对象中的每个字段,先自动映射,就是数据库字段和
//5:根据对象字段和映射关系,例如<result column="activity_id" property="activityId"/>标签中的activityId字段的类型
//加上sql xml中的column属性值activity_id,获取对应类型处理器,根据类型处理器,获取activity_id的值,然后利用反射给对象属性赋值。
//6:然后就向返回值设置设置值,并且返回
return resultSetHandler.handleResultSets(ps);
}