从MyBatis代码实现的角度来看,MyBatis的主要的核心部件有以下几个:
Configuration 初始化基础配置,比如MyBatis的别名等,一些重要的类型对象,如,插件拦截器,mapper方法元数据,映射器,ObjectFactory和typeHandler对象,MyBatis所有的配置信息都维持在Configuration对象之中
SqlSessionFactory SqlSession工厂
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维护了一条
datasource 相当于Connection连接或者连接池(第三方连接池),SqlSession(mybatis的接口)是封装了mybatis增删改查的方法,SqlSessionFactory 用于创建SqlSession,Transaction是用什么事务管理。
SqlSession 是一次会话,比如一次事务的提交。而一个 connection可以进行多次session会话。
SqlSession 接口的默认实现类DrfaultSqlSession
在SqlSession执行操作时会从连接池中获取connection
SessionFactory中打开一个SqlSession
原生配置
spring配置
整体流程
或者下图
代码演示
1、主程序(我称之为主操作程序)
主程序的主要任务是创建一个sqlsession:
(1)声明一个值为总配置文件的
String String s="conf.xml";
(2)利用
inputStream=Resources.getResourceAsStream(s);
创建一个InputStream,这是一个通向主配置文件的字节流
(3)创建一个sqlsessionfactory,SqlSession,
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
,这个sqlsession工厂的任务就是打开一个sqlsession
SqlSession sqlSession=sqlSessionFactory.openSession();
(4)利用sqlsession得到接口的对象(动态代理,里面会封装好一个SqlSession):
GetstudentInfo getstudentInfo = sqlSession.getMapper(GetstudentInfo.class);
(这种类型的方法都是通过反射来实现的,即你传了一个对象的class过去,就可以通过反射方式来生成这个对象)
(5)得到所需要的bean对象
Student student = getstudentInfo.getStudent(1);
如果没有接口的话,可以使用
Student student=sqlSession.selectOne("shenzhigongsi.MaiziMybatis.mapper.StudentMapper.selectStudent",1);
来访问这个mapper中的方法,效果是一样的
参考:https://www.cnblogs.com/shenzhi/p/6935071.html
在org.apache.ibatis.session.Configuration类中,存储着所有的mybatis配置信息。其中有一个mappedStatments的Map,存储着所有的 mapper方法 的元数据信息,包括返回结果对象类型,方法的id,sql,参数类型,等。
Mapper.xml与Mapper建立联系主要的入口有三:
1)MapperScannerConfigurer扫描Bean流程中,在调用MapperReigistry#addMapper时如果Mapper对应的映射文件(Mapper.xml)未加载到内存,会触发加载。
2)实例化SqlSessionFactory时,如果配置了mapperLocations。
3)示例化SqlSessionFactory时,如果配置了configLocation。
参考:https://blog.csdn.net/prestigeding/article/details/90488395
(调用getMapper时创建代理对象,并返回,下次调用还会再次创建返回,在spring中以bean单例形式存在,不会重复创建)
(1)使用getMappr方法,传出mapper接口类。
(2)返回mapper接口类被MapperProxy代理后的对象。
主要解析元方法的sql(根据sql所属的方法名(Id)获得MappedStatement对象,此对象中包含一个xml增删改操作(方法)的元数据信息),然后使用SqlSession 进行执行sql,获得返回数据
DefaultSqlSession.java
public T getMapper(Class type) {
return configuration.getMapper(type, this);
}
Configuration.java
public T getMapper(Class type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
MapperRegistry.java
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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);
}
}
MapperProxyFactory.java
public T newInstance(SqlSession sqlSession) {
final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
new MapperProxy
至此,mapper 接口是如何被 mybatis 代理的一目了然。
同时可以看出获取到的代理对象就是 MapperProxy 类,该类实现 InvocationHandler 接口和 invoke 方法。获取到了 MapperProxy 对象之后,在调用该代理对象的接口方法时,只要在这个对象的 invoke 方法里执行相应的 SQL 语句并将结果集返回不就达到我们的目的了吗,因此也就不需要接口实现类了。
参考:https://www.jianshu.com/p/93e18dcc7c10
总结下来Spring 中使用mapper接口类查询的步骤如下:
(1)使用mapper接口对象在spring中的Bean是mapper类的代理类 MapperProxy实例,调用对象查询方法selectABC。
(2)实际上会调用MapperProxy的invoke反射的方法。
(3)在invoke方法中会最终调用
DefaultSqlSession类中的select方法,此方法中configuration对象根据selectABC方法的唯一id,获得MappedStatement元数据信息。并用BaseExecutor(SqlSession中已经注入executor)执行器执执行query并传入 MappedStatement。
(4)接着通过configuration.newStatementHandler会生成一个StatementHandler对象 ,并在构造器中调用生成parameterHandler和resultSetHandler,同时存储在StatementHandler对象中。
(5)然后用handler生成一个java.sql.statement
(6)最后试用statemenet执行sql。
(1)使用mapper类的动态代理实例调用查询方法。
在调用studentMapper.selectWithCondition(studentSelect)的时候,studentMapper只是个接口,我们并没有具体去实现这个接口,这个mybatis使用了动态代理,真正执行的是MapperProxy的invoke方法,如下。
(2)继续跟踪mapperMethod.execute方法
(3) 然后进入到了DefaultSqlSession类中,为SqlSession的实现类
继续追踪到了BaseExecutor类,Executor接口的实现类
(4)最终找到了这样一个方法,创建StatementHandler
(5)继续
最终会把所有StatementHandler 类型的拦截都加进去
继续追踪(5)中new RoutingStatementHandler(),最终看到如下:
在StatementHadler的构造器中调用生成parameterHandler和resultSetHandler,同时存储在StatementHandler对象中。
继续回到步骤(4)看一下java.sql.Statement的生成
继续回到步骤(4) ,看一下query最终的执行,
StatementHandler接口的实现类 org.apache.ibatis.executor.statement.SimpleStatementHandler类最终调用java.sql。Statement的实现类执行sql。
MyBatis 通过提供插件机制,让我们可以根据自己的需要去增强 MyBatis 的功能。需要注意的是,如果没有完全理解 MyBatis 的运行原理和插件的工作方式,最好不要使用插件,因为它会改变系底层的工作逻辑,给系统带来很大的影响。
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
// ExamplePlugin.java
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
private Properties properties = new Properties();
public Object intercept(Invocation invocation) throws Throwable {
// implement pre processing if need
Object returnObject = invocation.proceed();
// implement post processing if need
return returnObject;
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
上面的插件将会拦截在 Executor 实例中所有的 “update” 方法调用, 这里的 Executor 是负责执行底层映射语句的内部对象。
四大对象什么时候被代理,也就是:代理对象是什么时候创建的?
Executor 是 openSession() 的 时 候 创 建 的 ,创建之后即调用 InterceptorChain.pluginAll(),返回层层代理后的对象。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
StatementHandler 是SimpleExecutor.doQuery()创建的,里面包含了处理参数的 ParameterHandler 和处理结果集的 ResultSetHandler 的创建,创建之后即调用 InterceptorChain.pluginAll(),返回层层代理后的对象。
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;
}
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.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
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;
}
具体看看interceptorChain.plginAll(Object target)具体做了什么事情:
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
就是调用了自定义插件列表中每个插件的plugin(Object target)方法:
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
Plugin.wrap(Object target, Interceptor interceptor)方法调用了JDK的动态代理创建了一个目标对象的代理(Plugin类肯定实现了InvocationHandler方法):
public static Object wrap(Object target, Interceptor interceptor) {
Map, Set> signatureMap = getSignatureMap(interceptor);
Class> type = target.getClass();
Class>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
当被拦截的四大对象被执行前,会先调用Plugin的invoke方法,我们看下源码:
public class Plugin implements InvocationHandler {
private final Object target;
private final Interceptor interceptor;
private final Map, Set> signatureMap;
private Plugin(Object target, Interceptor interceptor, Map, Set> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
public static Object wrap(Object target, Interceptor interceptor) {
Map, Set> signatureMap = getSignatureMap(interceptor);
Class> type = target.getClass();
Class>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
private static Map, Set> getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map, Set> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
Set methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
try {
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
private static Class>[] getAllInterfaces(Class> type, Map, Set> signatureMap) {
Set> interfaces = new HashSet<>();
while (type != null) {
for (Class> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class>[interfaces.size()]);
}
}
invoke方法首先会获取插件类上的注解上拦截的方法,若是被拦截的方法,则执行插件的intercept(Invocation invocation)方法,
若不是则执行被代理对象的方法。
Invocation:
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
Invocation对被代理对象进行封装,将被代理对象、方法和参数列表传入Invocation,若插件内容执行完成之后,可以调用Invocation.proceed()方法进行被代理对象方法执行:
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
总结流程:
参考:https://blog.csdn.net/lihaiyang_sz/article/details/106729017
拦截器其他
1. 在mybatis中可被拦截的类型有四种(按照拦截顺序):
多个插件拦截的顺序的问题
需要注意的是,因为拦截器Aa和拦截器Bb均是拦截的StatementHandler对象,所以拦截器B在此获取StatementHandler的时候,获取的是代理对象。
2. 拦截器可拦截的方法:
拦截的类 | 拦截的方法 |
---|---|
Executor | update, query, flushStatements, commit, rollback,getTransaction, close, isClosed |
ParameterHandler | getParameterObject, setParameters |
StatementHandler | prepare, parameterize, batch, update, query |
ResultSetHandler | handleResultSets, handleOutputParameters |
Executor 中会生成 StatementHandler 并调用,在new StatementHandler会生成ParameterHandler和ResultSetHandler