mybatis解析和基本运行原理
Mybatis的运行过程分为两大步:
- 第1步,读取配置文件缓存到Configuration对象,用于创建SqlSessionFactory;
- 第2步,SqlSession的执行过程。相对而言,SqlSessionFactory的创建还算比较容易理解,而SqlSession的执行过程就不那么简单了,它包括许多复杂的技术,要先掌握反射技术和动态代理,这里主要用到的是JDK动态代理.
一个简单使用的例子
public class TestMybatis {
public static void main(String[] args) {
String resource = "mybatis-config.xml";
Reader reader = null;
try {
reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.findById(2);
System.out.println("name:" + user.getName());
session.close();
SqlSession session1 = sqlSessionFactory.openSession();
List<User> users = session1.selectList("findAll");
session1.commit();
System.out.println("allSize:" + users.size());
session1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
MyBatis的主要构件及其相互关系
从MyBatis代码实现的角度来看,MyBatis的主要的核心部件有以下几个:
- SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
- Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
- StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
- ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数,
- ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
- TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换
- MappedStatement MappedStatement维护了一条<select|update|delete|insert>节点的封装,
- SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
- BoundSql 表示动态生成的SQL语句以及相应的参数信息
- Configuration MyBatis所有的配置信息都维持在Configuration对象之中。
构建SqlSessionFactory过程
构建主要分为2步:
- 通过XMLConfigBuilder解析配置的XML文件,读出配置参数,包括基础配置XML文件和映射器XML文件;
- 使用Configuration对象创建SqlSessionFactory,SqlSessionFactory是一个接口,提供了一个默认的实现类DefaultSqlSessionFactory。
说白了,就是将我们的所有配置解析为Configuration对象,在整个生命周期内,可以通过该对象获取需要的配置。
SqlSession运行过程
我们与mybatis交互主要是通过配置文件或者配置对象,但是我们最终的目的是要操作数据库的,所以mybatis为我们提供了sqlSession这个对象来进行所有的操作,也就是说我们真正通过mybatis操作数据库只要对接sqlSession这个对象就可以了。那么问题来了,我们怎么样通过sqlSession来了操作数据库的呢?
问题1:如何获取sqlSession?
public interface SqlSessionFactory { SqlSession openSession(); SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level); SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection); Configuration getConfiguration(); }
由上面代码我们可知我们可以通过SqlSessionFactory的openSession去获取我们的sqlSession,只要我们实例化了一个SqlSessionFactory,就能得到一个DefaultSqlSession对象。
问题2:Mapper对象怎么来的?
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
通过调用DefaultSqlSession的getMapper方法并且传入一个类型对象获取,底层呢调用的是配置对象configuration的getMapper方法,configuration对象是我们在加载DefaultSqlSessionFactory时传入的。
然后我们再来看下这个配置对象的getMapper,传入的是类型对象(补充一点这个类型对象就是我们平时写的DAO层接口,里面是一些数据库操作的接口方法。),和自身也就是DefaultSqlSession。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
我们看到这个configuration的getMapper方法里调用的是mapperRegistry的getMapper方法,参数依然是类型对象和sqlSession。这里呢,我们要先来看下这个MapperRegistry即所谓Mapper注册器是什么。
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap(); public MapperRegistry(Configuration config) {
this.config = config;
}
....
}
从这里我们可以知道其实啊这个MapperRegistry就是保持了一个Configuration对象和一个HashMap,而这个HashMap的key是类型对象,value呢是MapperProxyFactory。我们这里先不管MapperProxyFactory是什么东西,我们现在只需要知道MapperRegistry是这么一个东西就可以了。这里有人会问MapperRegistry对象是怎么来的,这里呢是在初始化Configuration对象时初始化了这个MapperRegistry对象的,代码大家可以去看,为了避免混乱,保持贴出来的代码是一条线走下来的,这里就不贴出来了。接下来我们继续看下这个MapperRegistry的getMapper方法。
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);
}
}
这里我们可以看到从knownMappers中获取key为类型对象的MapperProxyFactory对象。然后调用MapperProxyFactory对象的newInstance方法返回,newInstance方法传入sqlSession对象。到这里我们可能看不出什么端倪,那我们就继续往下看这个newInstance方法做的什么事情吧。
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
这里我们可以看到MapperProxyFactory直接new了一个MapperProxy对象,然后调用另外一重载的newInstance方法传入MapperProxy对象。这里我们可以看出一些东西了,通过调用Proxy.newProxyInstance动态代理了我们的mapperProxy对象!这里的mapperInterface即我们的dao层(持久层)接口的类型对象。
所以总结下就是我们通过sqlSesssion.getMapper(clazz)得到的Mapper对象是一个mapperProxy的代理类!
所以也就引出下面的问题。
问题3:为什么我调用mapper对象方法就能发出sql操作数据库?
通过上面的讲解,我们知道了这个mapper对象其实是一个一个mapperProxy的代理类!所以呢这个mapperProxy必然实现了InvocationHandler接口。所以当我们调用我们的持久层接口的方法时必然就会调用到这个MapperProxy对象的invoke方法,所以接下来我们进入这个方法看看具体mybatis为我们做了什么。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
从代码中我们可以看到前面做了一个判断,这个判断主要是防止我们调用像toString方法或者equals方法时也能正常调用。然后我们可以看到它调用cachedMapperMethod返回MapperMethod对象,接着就执行这个MapperMethod对象的execute方法。这个cachedMapperMethod方法主要是能缓存我们使用过的一些mapperMethod对象,方便下次使用。这个MapperMethod对象主要是获取方法对应的sql命令和执行相应SQL操作等的处理,具体细节同学们可以抽空研究。
继续跟踪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:
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 {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
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;
}
我们可以清晰的看到这里针对数据库的增删改查做了对应的操作,这里我们可以看下查询操作。我们可以看到这里针对方法的不同返回值作了不同的处理,我们看下其中一种情况。
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
这里我们可以看到它将方法参数类型转换成数据库层面上的参数类型,最后调用sqlSession对象的selectOne方法执行。所以我们看到最后还是回到sqlSession对象上来,也就是前面所说的sqlSession是mybatis提供的与数据库交互的唯一对象。
接下来我们看下这个selectOne方法做了什么事,这里我们看的是defaultSqlSession的selectOne方法。
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
我们看到它调用selectList方法,通过去返回值的第一个值作为结果返回。那么我们来看下这个selectList方法。
@Override
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();
}
}
我们可以看到这里调用了executor的query方法,我们再进入到query里看看。这里我们看的是BaseExecutor的query方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 1.根据具体传入的参数,动态地生成需要执行的SQL语句,用BoundSql对象表示
BoundSql boundSql = ms.getBoundSql(parameter);
// 2.为当前的查询创建一个缓存Key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
} @SuppressWarnings("unchecked")
@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 {
// 3.缓存中没有值,直接从数据库中读取数据
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 {
//4. 执行查询,返回List 结果,然后 将查询的结果放入缓存之中
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;
}
我们看到有个一个方法doQuery,进入方法看看做了什么。点进去后我们发现是抽象方法,我们选择simpleExecutor子类查看实现。
@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();
//5. 根据既有的参数,创建StatementHandler对象来执行查询操作
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//6. 创建java.Sql.Statement对象,传递给StatementHandler对象
stmt = prepareStatement(handler, ms.getStatementLog());
//7. 调用StatementHandler.query()方法,返回List结果集
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
//对创建的Statement对象设置参数,即设置SQL 语句中 ? 设置为指定的参数
handler.parameterize(stmt);
return stmt;
}
我们可以看到通过configuration对象的newStatementHandler方法构建了一个StatementHandler,然后在调用prepareStatement方法中获取连接对象,通过StatementHandler得到Statement对象。另外我们注意到在获取了Statement对象后调用了parameterize方法。继续跟踪下去(自行跟踪哈)我们可以发现会调用到ParameterHandler对象的setParameters去处理我们的参数。所以这里的prepareStatement方法主要使用了StatementHandler和ParameterHandler对象帮助我们处理语句集和参数的处理。最后还调用了StatementHandler的query方法,我们继续跟踪下去。
这里我们进入到PreparedStatementHandler这个handler查看代码。这里我们进入到PreparedStatementHandler这个handler查看代码。
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
//使用ResultHandler来处理ResultSet
return resultSetHandler.<E>handleResultSets(statement);
}
看到这里,我们终于找到了操作数据库的地方了,就是ps.execute()这句代码。底层我们可以发现就是我们平时写的JDBC!然后将这个执行后的PreparedStatement交给resultSetHandler处理结果集,最后返回我们需要的结果集。
mybatis解析和基本运行原理的更多相关文章
- 互联网轻量级框架SSM-查缺补漏第七天(MyBatis的解析和运行原理)
第七章MyBatis的解析和运行原理 SqlSessionFactory是MyBatis的核心类之一,其最重要的功能就是提供创建MyBatis的核心借口SqlSession,所以要先创建SqlSess ...
- Mybatis的解析和运行原理
Mybatis的解析和运行原理 Mybatis的运行过程大致分为两大步:第一步,读取配置文件缓存到Configuration对象,用以创建 SqlSessionFactory:第二步,SqlSessi ...
- mybatis运行原理
mybatis运行原理 运行过程中涉及到的类或者接口 Resources(c) :用于加载mybatis核心配置文件 XMLConfigBuilder(c) :用于解析xml文件(核心配置文件) Co ...
- .NET/ASP.NET MVC Controller 控制器(深入解析控制器运行原理)
阅读目录: 1.开篇介绍 2.ASP.NETMVC Controller 控制器的入口(Controller的执行流程) 3.ASP.NETMVC Controller 控制器的入口(Controll ...
- 二、ASP.NET MVC Controller 控制器(一:深入解析控制器运行原理)
阅读目录: 1.开篇介绍 2.ASP.NETMVC Controller 控制器的入口(Controller的执行流程) 3.ASP.NETMVC Controller 控制器的入口(Controll ...
- JavaScript运行原理解析
原文:1.http://blog.csdn.net/liaodehong/article/details/50488098 2.Stack的三种含义 (阮一峰) 3. http://lib.csdn. ...
- 属性动画 ValueAnimator 运行原理全解析
最近下班时间都用来健身还有看书了,博客被晾了一段时间了,原谅我~~~~ 提问环节 好,废话不多说,之前我们已经分析过 View 动画 Animation 运行原理解析,那么这次就来学习下属性动画的运行 ...
- (转)Apache和Nginx运行原理解析
Apache和Nginx运行原理解析 原文:https://www.server110.com/nginx/201402/6543.html Web服务器 Web服务器也称为WWW(WORLD WID ...
- NET/ASP.NET MVC Controller 控制器(一:深入解析控制器运行原理)
阅读目录: 1.开篇介绍 2.ASP.NETMVC Controller 控制器的入口(Controller的执行流程) 3.ASP.NETMVC Controller 控制器的入口(Controll ...
随机推荐
- __str__,__repr__,__add__
class School: def __init__(self,name,addr,type): self.name=name self.addr=addr self.type=type def __ ...
- webdriervAPI(元素等待)
from selenium import webdriver from selenium.common.exceptions import NoSuchElementException driv ...
- Spring 中的几个常用的钩子接口
1.Aware接口 Awear 这个单词的意思是知道的,所以可以猜想以Awear 结尾的接口意思可以把他知道的东西告诉我们.常用的Awear接口有 ApplicationContextAware和 B ...
- sql server备份损坏
sql server备份损坏 转自:https://www.cnblogs.com/zhijianliutang/p/4080916.html 1.备份文件和数据库放在同一个(或一组)的物理磁盘上.磁 ...
- 关于maven自动部署tomcat9 步骤
maven 自动部署tomcat9 (远程方法) 1.首先要去配置用户,在tomcat的conf中有tomcat_users.xml,在其中有tomcat-user的配置 配置:<tommcat ...
- Codeforces 817+818(A~C)
(点击题目即可查看原题) 817A Treasure Hunt 题意:给出起点和终点,每次移动只能从 (a,b)移动至(a+x,b+y) , (a+x,b-y) , (a-x,b+y) , (a-x, ...
- php设计模式之注册模式
注册模式,解决全局共享和交换对象.已经创建好的对象,挂在到某个全局可以使用的数组上,在需要使用的时候,直接从该数组上获取即可.将对象注册到全局的树上.任何地方直接去访问. <?php class ...
- 从入门到自闭之Python入门
python是一门解释型编程语言 变量名命名的规则: 变量名由字母,数字,下划线组成 变量名不能以数字开头 变量名要具有可描述性 变量名要区分大小写 变量名禁止使用python关键字 变量名不能使用中 ...
- Maximum path CodeForces - 762D
大意: 3*n矩阵, 求从(1,1)->(3,n)路径最大点权和. 核心观察是每个点回头一定不会超过1, 这是因为只有三行, 若回头两格一定是$9$个位置全走, 显然可以找到一种只会头一格的方案 ...
- wpf ActualWidth为0解决方法
LocalNewsControl() { var descriptor = DependencyPropertyDescriptor.FromProperty(ActualWidthProperty, ...