小编给大家分享一下mybatis运行原理是什么,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!
一、Mybatis基本认识
1.1、动态代理
JDK实现首先我们需要提供一个接口 , 这个接口是对我们程序员的一个抽象。 拥有编码和改BUG的本领
public interface Developer {
/**
* 编码
*/
void code();
/**
* 解决问题
*/
void debug();
}
关于这两种本领每个人处理方式不同。这里我们需要一个具体的实例对象
public class JavaDeveloper implements Developer {
@Override
public void code() {
System.out.println("java code");
}
@Override
public void debug() {
System.out.println("java debug");
}
}
我们传统的调用方式是通过java提供的new 机制创造一个JavaDeveloper对象出来。而通过动态代理是通过java.lang.reflect.Proxy
对象创建对象调用实际方法的。
通过newProxyInstance
方法获取接口对象的。而这个方法需要三个参数
ClassLoader loader : 通过实际接口实例对象获取ClassLoader
Class<?>[] interfaces : 我们抽象的接口
InvocationHandler h : 对我们接口对象方法的调用。在调用节点我们可以进行我们的业务拦截
JavaDeveloper jDeveloper = new JavaDeveloper();
Developer developer = (Developer) Proxy.newProxyInstance(jDeveloper.getClass().getClassLoader(), jDeveloper.getClass().getInterfaces(), (proxy, method, params) -> {
if (method.getName().equals("code")) {
System.out.println("我是一个特殊的人,code之前先分析问题");
return method.invoke(jDeveloper, params);
}
if (method.getName().equals("debug")) {
System.out.println("我没有bug");
}
return null;
});
developer.code();
developer.debug();
CGLIB动态代理
cglib动态代理优点在于他不需要我们提前准备接口。他代理的实际的对象。这对于我们开发来说就很方便了。
public class HelloService {
public HelloService() {
System.out.println("HelloService构造");
}
final public String sayHello(String name) {
System.out.println("HelloService:sayOthers>>"+name);
return null;
}
public void sayHello() {
System.out.println("HelloService:sayHello");
}
}
下面我们只需要实现cglib提供的MethodInterceptor接口,在初始化设置cglib的时候加载这个实例化对象就可以了
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("======插入前置通知======");
Object object = methodProxy.invokeSuper(o, objects);
System.out.println("======插入后者通知======");
return object;
}
}
下面我们就来初始化设置cglib
public static void main(String[] args) {
//代理类class文件存入本地磁盘方便我们反编译查看源代码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/root/code");
//通过CGLIB动态代理获取代理对象过程
Enhancer enhancer = new Enhancer();
//设置enhancer对象的父类
enhancer.setSuperclass(HelloService.class);
// 设置enhancer的回调对象
enhancer.setCallback(new MyMethodInterceptor());
//创建代理对象
HelloService helloService = (HelloService) enhancer.create();
//通过代理对象调用目标方法
helloService.sayHello();
}
仔细看看cglib和spring的aop特别像。针对切点进行切面拦截控制。
总结:
通过对比两种动态代理我们很容易发现,mybatis就是通过JDK代理实现Mapper调用的。我们Mapper接口实现通过代理到xml中对应的sql执行逻辑
1.2、反射
二、Configuration对象作用
Configuration对象存储了所有Mybatis的配置。主要初始化一下参数
properties
settings
typeAliases
typeHandler
ObjectFactory
plugins
environment
DatabaseIdProvider
Mapper映射器
三、映射器结构
BoundSql提供三个主要的属性 parameterMappings 、parameterObject、sql
parameterObject参数本身。我们可以传递java基本类型、POJO、Map或者@Param标注的参数。
当我们传递的是java基本类型mybatis会转换成对应的包装对象 int -> Integer
如果我们传递POJO、Map。就是对象本身
我们传递多个参数且没有@Param指定变量名则parameterObject 类似
{"1":p1,"2":p2,"param1":p1,"param2":p2}
我们传递多个参数且@Param指定变量名 则parameterObject类似
{"key1":p1,"key2":p2,"param1":p1,"param2":p2}
parameterMapping 是记录属性、名称、表达式、javaType,jdbcType、typeHandler这些信息
sql 属性就是我们映射器中的一条sql. 正常我们在常见中对sql进行校验。正常不需要修改sql。
四、sqlsession执行流程(源码跟踪)
首先我们看看我们平时开发的Mapper接口是如何动态代理的。这就需要提到MapperProxyFactory
这个类了。该类中的newInstance
方法
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
通过上满代码及上述对jdk动态代理的表述。我们可以知道mapperProxy是我们代理的重点。MapperProxy是InvocationHandler的实现类。他重写的invoke方法就是代理对象执行的方法入口。
@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);
}
private boolean isDefaultMethod(Method method) {
return (method.getModifiers()
& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
&& method.getDeclaringClass().isInterface();
}
通过源码发现。invoke内部首先判断对象是否是类 。 通过打断点发现最终会走到cacheMapperMethod这个方法去创建MapperMethod对象。继续查看MapperMethod中execute方法我们可以了解到内部实现其实是一个命令行模式开发。通过判断命令从而执行不同的语句。判断到具体执行语句然后将参数传递给sqlsession进行sql调用并获取结果。到了sqlsession就和正常jdbc开发sql进行关联了。sqlsession中Executor
、StatementHandler
、ParameterHandler
、Resulthandler
四大天王
4.1、Executor
顾名思义他就是一个执行器。将java提供的sql提交到数据库。Mybatis提供了三种执行器。
Configuration.class
中newExecutor
源码
根据uml我们不难看出mybatis中提供了三类执行器分别SimpleExecutor、ReuseExecutor、BatchExecutor
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 得到configuration 中的environment
final Environment environment = configuration.getEnvironment();
// 得到configuration 中的事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 获取执行器
final Executor executor = configuration.newExecutor(tx, execType);
// 返回默认的SqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
通过上述源码我们知道在sqlsession获取一个数据库session对象时我们或根据我们的settings配置加载一个Executor对象。在settings中配置也很简单
<settings>
<!--取值范围 SIMPLE, REUSE, BATCH -->
<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>
我们也可以通过java代码设置
factory.openSession(ExecutorType.BATCH);
4.2、StatementHandler
顾名思义,StatementHandler就是专门处理数据库回话的。这个对象的创建还是在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;
}
很明显Mybatis中StatementHandler使用的是RoutingStatementHandler这个class
关于StatementHandler和RoutingStatementHandler之间的关系我们通过源码可以看出这里和Executor一样都是适配器模式。采用这种模式的好处是方便我们对这些对象进行代理。这里读者可以猜测一下是使用了哪种动态代理。给点提示 这里使用了接口哦
在查看BaseStatementHandler结构我们会发现和Executor一模一样。同样的Mybatis在构造RoutingStatementHandler的时候会根据setting中配置来加载不同的具体子类。这些子类都是继承了BaseStatementHandler.
前一节我们跟踪了Executor。 我们知道Mybatis默认的是SimpleExecutor。 StatementHandler我们跟踪了Mybaits默认的是PrePareStatementHandler。在SimpleExecutor执行查询的源码如下
我们发现在executor查询钱会先让statementHandler构建一个Statement对象。最终就是StatementHandler中prepare方法。这个方法在抽象类BaseStatmentHandler中已经封装好了。
这个方法的逻辑是初始化statement和设置连接超时等一些辅助作用
然后就是设置一些参数等设置。最后就走到了执行器executor的doquery
PrepareStatement在我们jdbc开发时是常见的一个类 。 这个方法执行execute前我们需要设置sql语句,设置参数进行编译。这一系列步骤就是刚才我们说的流程也是PrepareStatementHandler.prepareStatement帮我们做的事情。那么剩下的我们也很容易想到就是我们对数据结果的封装。正如代码所示下马就是resultSetHandler帮我们做事情了。
4.3、结果处理器(ResultSetHandler)
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
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);
}
这个方法我们可以导出来是结果xml中标签配置对结果的一个封装。
4.4、总结
SqlSession在一个查询开启的时候会先通过CacheExecutor查询缓存。击穿缓存后会通过BaseExector子类的SimpleExecutor创建StatementHandler。PrepareStatementHandler会基于PrepareStament执行数据库操作。并针对返回结果通过ResultSetHandler返回结果数据
以上是“mybatis运行原理是什么”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注天达云行业资讯频道!