前言
前面文章主要针对mybatis有个大体的设计,包括 对 整个框架包括对于 mapper的存储,以及 如何应对我们常见的增删改查 如何去 定义 好 注解或者xml的方式来定义,如何进行参数之间的寻找等,都是需要设计的场景,以及 如何去执行sql 这是在 mybatis 框架给我们设计时,需要考虑到的; 这篇文章会继续 实现 mybatis框架 的各个部分,从而知道 mybatis框架 如何实现,最后在来看框架提供为我们做了多少事情。
设计框架的源码的好处
学习以及解析包括mybatis也好spring也好 ,这些热门的框架,都是让自己提升技术,包括在以后的开发中,别老是用什么 if else 这些 耦合性强的代码,而且不易后续功能的扩展以及 后后期维护时相当麻烦的,充分利用 接口 以及抽象类,将逻辑性的代码 进行抽象出来, 真正达到好的代码,单个类的单一原则等等,最后给我们的系统后期进行功能扩展也好 以及 重构也罢,是非常有必要的。 而且 当你学习 mybatis的源码 扩展点,自己也能写出来,通过接口的方式 对于 功能进行增强,这不是对于 我们一个大的提升么,可能 说这些对一些大牛来说都太小儿科了,所以废话不多说继续设计mybatis框架吧。
执行sql类的设计
对于mybatis最重要的一个类 ,Configuration
这里面持有了mappedStatements 这些 数据 ,都是在 mybatis中 给我们 的一个总的类。
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
protected final InterceptorChain interceptorChain = new InterceptorChain();
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
protected final Set<String> loadedResources = new HashSet<String>();
protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
框架完成接口对象的生成,JDBC执行过程。
谁来执行SQL
框架 对象接口的生成,实体类去执行,要做的事情 就是 要做的 sql 并且进行拼接等等。
具体的实现类的。 框架完成接口对象的生成,JDBC执行过程。
UserDao userDao = sqlSession.getMapper(UserDao.class);
userDao.addUser(user);
用户给入一个接口类,DefaultSqlSession中就为它生成一个对象 ,配置阶段扫描、解析Mapper接口时做个存储了
参数判断,类型转换,
这也是配置信息,还是存在Confifiguration 中,就用个Set来存吧。
DefaultSqlSession中需要持有Confifiguration。
对象生成
Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] {
mapperInterface }, invocationHandler);
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
super();
this.configuration = configuration;
}
@Override
public <T> T getMapper(Class<T> type) {
//检查给入的接口
if (!this.configuration.getMappers().contains(type)) {
throw new RuntimeException(type + " 不在Mapper接口列表中!");
}
//得到 InvocationHandler
InvocationHandler ih = null; // TODO 必须要有一个
// 创建代理对象
T t = (T)Proxy.newProxyInstance(type.getClassLoader(), new
Class<?>[] {type}, ih);
return t;
}
}
执行SQL的InvocationHandler
public interface InvocationHandler {
/** @param proxy 生成的代理对象
@param method 被调用的方法
@param args @return Object 方法执行的返回值
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
package com.study.mybatis.session;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MapperProxy implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO 这里需要完成哪些事?
return null;
}
}
这里需要做的事情 ,具体 要执行的。
// 1 、获得方法对应的 SQL 语句// 2 、解析 SQL 参数与方法参数的对应关系,得到真正的 SQL 与语句参数值// 3 、获得数据库连接// 4 、执行语句// 5 、处理结果
可以通过 method参数能得到方法名,但得到的类名不是Mapper接口类名。
@Insert("insert into t_user(id,name,sex,age) values(#{id},#{name},# {sex},#{age})")
void addUser(String id,@Param("name")String xname,String sex,int age);
Parameter[] params = method.getParameters();
public Object invoke(Object proxy, Method method, Object[] args)
所以所有的都要抽象出来,这才是正常的开发 模式。 和系统
我可以一层一层往下传递。 最好不要填写 switch 这样下去。
@Insert("insert into t_user(id,name,sex,age,org_id) values(#{user.id},# {user.name},#{user.sex},#{user.age},#{org.id})")
void addUser(User user,Org org);
解析阶段有它们俩完成这件事:
把MapperProxy的invoke方法填填看:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO 这里需要完成哪些事?
// 1、获得方法对应的SQL语句
String id = this.mapper.getName() + "." + method.getName();
MappedStatement ms = this.configuration.getMappedStatement(id);
// 2、解析SQL参数与方法参数的对应关系,得到真正的SQL与语句参数值
RealSqlAndParamValues rsp = ms.getRealSqlAndParamValues(args);
// 3、获得数据库连接
Connection conn = this.configuration.getDataSource().getConnection();
// 4、执行语句。
PreparedStatement pst = conn.prepareStatement(rsp.getSql());
// 疑问:语句一定是PreparedStatement?
// 设置参数
if (rsp.getParamValues() != null) {
int i = 1;
for (Object p : rsp.getParamValues()) {
pst.setxxx(i++, p);
//这里写不下去了.......如何决定该调用pst的哪 个set方法?
}
}
// 5、处理结果
return null;
}
JavaType、JdbcType转换
pst的set方法中与对应的:
这种情况:
要使用的JDBCType,不然鬼知道他想要什么。
下面这个if-else-if的代码是否可以通过TypeHandler,换成策略模式
int i = 1;
for (Object p : rsp.getParamValues()) {
if (p instanceof Byte) {
pst.setByte(i++, (Byte) p);
} else if (p instanceof Integer) {
pst.setInt(i++, (int) p);
}else if (p instanceof String) {
pst.setString(i++, (String) p);
}... else if(...)
}
MapperProxy.invoke()中 设置 还需要JDBCType。
MapperProxy中的代码就变成下面这样了:
int i = 1;
for (ParamValue p : rsp.getParamValues()) {
TypeHandler th = p.getTypeHandler()
th.setParameter(pst,i++,p.getValue());
}
MappedStatement又从哪里去获取TypeHandler
Map<Type,Map<JDBCType,TypeHandler>> typeHandlerMap;
其实这样设计的方式可以放到 我们开发项目时,这个 应该都明白把。
这个在mybatis中也使用到的,可以采用 注册进去。
用户如何来指定它们的TypeHandler
<!ELEMENT configuration (mappers?, typeHandlers?)+ >
<!ELEMENT mappers (mapper*,package*)>
<!ELEMENT mapper EMPTY>
<!ATTLIST mapper resource CDATA #IMPLIED url CDATA #IMPLIED class CDATA #IMPLIED ><!ELEMENT package EMPTY>
<!ATTLIST package name CDATA #IMPLIED type CDATA #IMPLIED annotation CDATA #IMPLIED ><!ELEMENT typeHandlers (typeHandler*,package*)>
<!ELEMENT typeHandler EMPTY> <!ATTLIST typeHandler class CDATA #REQUIRED >
<configuration>
<mappers>
<mapper resource="com/mike/UserMapper.xml"/>
<mapper url="file:///var/mappers/CourseMapper.xml"/>
<mapper class="com.study.dao.UserDao" />
<package name="com.study.mapper" />
<mappers>
<typeHandlers>
<typeHandler class="com.study.type.XoTypeHandler" />
<package name="com.study.type" />
</typeHandlers>
</configuration>
MappedStatement中来决定TypeHandler,它就需要Confifiguration
ParameterMap中增加typeHandler属性。
执行结果处理
执行结果处理要干的是什么事
这里需要涉及到的 数据进行转换 ,集合的处理, 对应的字段名等处理。
Executor 中 进行 执行 处理 并返回值。
// 6、执行语句并处理结果
switch (ms.getSqlCommandType()) {
case INSERT:
case UPDATE:
case DELETE:
int rows = pst.executeUpdate();
return handleUpdateReturn(rows, ms, method);
case SELECT:
ResultSet rs = pst.executeQuery();
return handleResultSetReturn(rs, ms, method);
}
pst.executeUpate()的返回结果处理。
@Select("select count(1) from t_user where sex = #{sex}")
int query(String sex);
@Select("select id,name,sex,age,address from t_user where id = #{id}")
User queryUser(String id);
@Select("select id,name,sex,age,address from t_user where id = #{id}")
Map queryUser1(String id);
这里也需要 策略模式 ,来根据不同的变换 来走不同的路径 进行转换数据。
@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++;
}
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);
}
private Object handleResultSetReturn(ResultSet rs, MappedStatement ms, Object[] args) {
// TODO Auto-generated method stub
return null;
}
那么在handleResultSetReturn()方法中我们从哪得到ResultHandler呢
这肯定是在解析时,就能获取到,
在handleResultSetReturn方法中只需调用ms中的ResultHandler:
private Object handleResultSetReturn(ResultSet rs, MappedStatement ms, Object[] args) throws Throwable {
return ms.getResultHandler().handle(rs, args);
}
基本数据类型、String 如何处理
针对这种情况,提供对应的ResultHandler实现:
handle方法中的逻辑
public Object handle(ResultSet rs, Object[] args) throws Throwable {
//从rs中取对应值
return rs.getXXX(OOO);
}
该调用rs的哪个get方法
对于mybatis来说 更加麻烦一点,是放到 factory中做的处理。
SimpleTypeResultHandler的handle方法中的代码逻辑如下:
该返回值情况下不允许结果集多列。
if else 的解决办法,利用策略模式 去解决。
public interface TypeHandler<T> {
Type getType(); JDBCType getJDBCType();
void setParameter(PreparedStatement pst, int index, Object paramValue) throws SQLException;
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
}
在启动解析阶段完成结果的TypeHandler选定。
很好,那就可以在SimpleTypeResultHandler中持有对应的TypeHandler。
SimpleTypeResultHandler 的handle方法代码就简单了:
public Object handle(ResultSet rs, Object[] args) throws Throwable {
if (StringUtils.isNotEmpty(columnName)) {
return typeHandler.getResult(rs, columnName);
} else {
return typeHandler.getResult(rs, columnIndex);
}
}
对于对象的情况下。通过 工厂类,进行创建 对象,
这里需要根据具体的 的去判断 到底是那个数据的转换。
Mybatis源码解读
Mybatis整体架构图
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。
将各个部分进行组装起来的。
- XMLConfigBuilder: 主要负责解析mybatis-config.xml;
- XMLMapperBuilder: 主要负责解析映射配置文件;
XMLStatementBuilder: 主要负责解析映射配置文件中的SQL节点;
从 springboot中就能看到
它帮我们创建了 sqlsessionfactory
这几大部分 都是存到Configuration 中的。
里面也也包括了 缓存 , 将事务工厂等,都注入到 容器中去
配置configruation ,配置 的 的信息。
最终会产生sqlsessionfactory
自定义的typehander 等 都需要全需要这里 配置的。 所以我们要自定义去创建 factory ,根据需要去设置。
利用 after proertiesset 方法 加载这个类之后,去创建 xmlbuilder.
将configuration 中创建 的部分。 采用这种方式去加载的。最终返回configration
继承自 basebuilder
Mybatis的初始化
解析所有的节点。
对于所有的节点 ,进行解析。
对于 一一的解析,拿到相关对应的mapper,以及 相关对应的mapper
根据不同的内容进行 解析开来。
解析好了xml 添加进 mapperstatement
根据 langdriver创建 对应 的 source
最终还是添加到 configuration 中去的。
这里面 也有对应的mapper.
Configuration类图解
MappedStatment图解
整个的初始化过程
sqlsessiontemplate
对于 sqlsession 进行功能增强的,统一标准化。
扫描 对应的 对应 的mapper. 找到对应的。
代理生成过程
sqlsession
里面的方法,包括对应的 delete inst commit update commit等等
返回 getmapper。
代理生成过程
MapperProxy
- SqlCommand : 从configuration中获取方法的命名空间.方法名以及SQL语句的类型;
- MethodSignature:封装mapper接口方法的相关信息(入参,返回类型);
- ParamNameResolver: 解析mapper接口方法中的入参;
SqlSession原理
Executor
- SimpleExecutor:默认配置,使用statement对象访问数据库,每次访问都要创建新的statement对象;
- ReuseExecutor:可以重用的执行器,使用预编译PrepareStatement对象访问数据库,访问时,会重用缓存中的statement对象;
- BatchExecutor:实现批量执行多条SQL语句的能力;
参数的处理。
Executor内部运作过程
结果参数绑定
Mybatis事务
MyBatis的事务管理分为两种形式:
事务工厂的创建
TransactionFactory定义
JdbcTransaction
ManagedTransaction
- PooledConnection:使用动态代理封装了真正的数据库连接对象;
- PoolState:用于管理PooledConnection对象状态的组件,通过两个list分别 管理空闲状态的连接资源和活跃状态的连接资源
- PooledDataSource:一个简单,同步的、线程安全的数据库连接池