配置文件加载
从SqlsessionFactoryBuild开始先加载配置文件,然后构造cofiguration类,然后执行build方法,构建SqlsessionFactory对象。
properties
配置封装到configuration中的variables
变量。
typeAliases
配置封装到configuration中的typeAliasRegistry
变量。
plugins
配置封装到configuration中的interceptorChain
变量。
environments
配置封装到configuration中的environment
变量。
mappers
配置封装到configuration中的mapperRegistry
变量。
mapper配置文件详解
对于mapper配置文件中的statement,元素介绍。
DQL
- id:一个mapper.xml下的statement唯一标识。不同mapper.xml下id可以重复。
- parameterType:入参的类型。
- resultType:返回结果集封装的类型。
- resultMap:mapper.xml中定义的resultMap引用。
- userCache:对于查询结果是否保存二级缓存。
- flushCache:执行sql后会清空一级缓存和当前namespace的二级缓存。
- databaseId:标注数据库厂商
DML
区别与DQL的如下
- flushCache:默认true,flushCache 会清除全局一级缓存,以及本 namespace 下的二级缓存。
- useGeneratedKeys:开启使用,则Mybatis会使用jdbc底层的getGeneratedKeys方法,取出自增主键的值,应用与insert和update。
- keyProperty:配合useGeneratedKeys使用,用于指定出传入参数对象的属性名,应用与insert和update。
- keyClumn:设置useGeneratedKeys生效的值对应到数据库表中的列名,应用与insert和update。
useGeneratedKeys:就是在插入数据时,用数据库的自增id作为主键。如果这个属性为true,则主键可以不用传,mybatis会在底层使用getGeneratedKeys方法帮我们查出id,放入id属性中,回填到实体类。
默认情况下,数据库中建表第一列时主键。但有时候情况例外,那就需要通过keyProperty指定实体类的id,keyClumn指定数据库中表的主键id。
mapper.xml中的缓存
一级缓存
默认情况下Mybatis只会开启基于SqlSesion的一级缓存。二级缓存默认不开启。
二级缓存
基于SqlSessionFactroy级别的缓存。一个namespce对应一块二级缓存。如果需要为namespce开启二级缓存,需要在对应的mapper.xml中声明一个<cache>
标签。
开启二级缓存,我们的pojo要实现Serializable
接口。为了将缓存数据取出执行反序列化操作。因为二级缓存数据可能存储内存业可能存储在磁盘中。如果我们要取这个缓存的话,就需要反序列化了。所以mybatis中的pojo都去实现Serializable接口。
20220619补充。
二级缓存的实现过程:
①解析mapper.xml配置文件,如果有<cache/>
标签,解析,构造cache对象。增加到config类中和mapperbuildassist类中的currentCache属性中。
②解析mapper.xml中的crud标签。封装为mapperStatement对象。将mapperBuildAssist中的本地变量currentCache赋值给mapperStatement中的cache属性。
③也就是说一个mapper.xml如果配置了cache标签。那么这个mapper中的所有crud操作封装的mapperStatement都包含这个cache对象。它们公用二级缓存cache。
二级缓存并不会立即生效。需要事务提交后才生效。
缓存的实现
mybatis
自己定义了一个Cache
接口,里面实现了增删改查。
从结构目录上可以看出,有一个实现类在impl包下,其他都在decorators包下(装饰)。
为什么mybatis要将缓存模型设计为一堆装饰者呢?
装饰者的目的就是为目标对象增加一下额外的特性。mybatis这么设计,就是因为二级缓存使用的是jvm的内存,占用太多不利于应用本身的正常运行。mybatis就增加了针对缓存的各种特性和过期策略
,以此达到动态拼接缓存实现的目的
。
PerpetualCache缓存的实现
可以发现,它内部就是套用了一个HashMap。
缓存的使用呢。是在Excutor
接口的抽象实现类BaseExecutor
中。BaseExecutor
是一个抽象类,内部组合了PerpetualCache
。
缓存是如何工作的呢?
一级缓存起作用的位置,是在向数据库发起查询之前,先拦截检查一下,如果一级缓存中有数据,则直接从缓存中取数据并返回,否则才查询数据库。
Mybatis的事务实现
对于jdbc的事务操作来说,无非就是开启事务,提交事务,回滚事务三个操作。
事务生效,在mybatis配置文件中配置事务管理。
<environments default="development">
<environment id="development">
<!-- 配置了事务管理器 -->
<transactionManager type="JDBC"/>
JDBC–使用JDBC的提交和回滚方法。它依赖从数据源获得的连接来管理事务作用域。
sqlSessionFactory
创建SqlSession
的时候,如果传入的参数为true
,以为着这个sqlsession
不参与任何事务操作。
mybatis解析配置文件,生成TransactionFactory
。TransactionFactory
用来生成Transaction
。Transaction
接口中定义的方法包含了事务的相关操作。
public interface TransactionFactory {
default void setProperties(Properties props) {
// NOP
}
Transaction newTransaction(Connection conn);
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
Integer getTimeout() throws SQLException;
}
关于基于JDBC的事务,Mybatis提供了一个JdbcTransactionFactory
。对应的Transaction
是JdbcTransaction
。JdbcTransaction
其中的操作就是通过Connection
进行相关操作。
public Connection getConnection() throws SQLException {
if (connection == null) {
openConnection();
}
return connection;
}
@Override
public void commit() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
connection.commit();
}
}
@Override
public void rollback() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
connection.rollback();
}
}
@Override
public void close() throws SQLException {
if (connection != null) {
resetAutoCommit();
connection.close();
}
}
事务的生效原理
一般通过SqlSessionFactory
的openSession
开启新的SqlSession
。openSession
会调用openSessionFromDataSource
方法。
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 此处创建新的事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
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();
}
}
从上面代码中可以看到,先拿到Environment
,然后获取TransactionFactory
,最后创建事务Transaction
。创建出来的事务传入到Executor
对象中。Executor
是负责执行statement
的核心组件。
在Executor的实现类BaseExecutor中:
调用transaction
执行相关事务操作:
mapper.xml编写动态sql
if标签
我们有时候会这样些。
where 1=1
<if test="xxx">
and xx=#{xx}
</if>
这样写是为了避免where语句中如果只有一个条件,多一个and的问题。
我们也可以这样写:
定义<where>
标签,这个标签会帮我们构造where,而且会帮我们除去第一个不必要的and。不过and标签都要写在<if>
标签的前面。
如果习惯都写到后面,那么最后一个条件就会在后面多一个and,如何处理?
<where>
<if test="xxx">
xx=#{xx} and
</if>
</where>
- 不这么写。。。。。
- 使用
<trim>
标签
(1) prefix :在整个标签前面附加指定内容
(2) prefixOverrides :去掉标签体内第一个指定的关键字
(3) suffix :在整个标签最后追加指定内容
(4) suffixOverrides :去掉标签体内最后一个指定的关键字
<trim prefix="where" suffix="" suffixOverrides="and">
<if test="xxx">
xx=#{xx} and
</if>
</where>
choose,when,otherwise标签
如果一条查询中有多个条件,每次只让其中一个条件生效。这种情况就需要使用这三个标签了。
表达的含义是:if() else if() else()
<choose>
<when test="id != null and id != ''">
where id = #{id}
</when>
<when test="name != null and name != ''">
where name like concat('%', #{name}, '%')
</when>
<otherwise>
where tel = #{tel}
</otherwise>
</choose>
set标签
在update语句中,使用set标签,可以帮我们去掉最后一个set后面的逗号。
<set>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="tel != null and tel != ''">
tel = #{tel},
</if>
</set>
where id =#{id}
这个里面的最后一个tel=#{tel}后面的逗号,set标签会帮我们去掉。
foreach
主要用于使用in的场景。where xxx in();
where id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
批量更新
Department department = new Department();
department.setName("测试部");
//实体类转Map工具类
BeanMap beanMap = BeanMap.create(department);
Map<String, Object> departmentMap = new HashMap<>(2);
departmentMap.put("id", "123456");
departmentMap.put("beanMap", beanMap);
sqlSession.update("dynamic.updateByMap", departmentMap);
<update id="updateByMap" parameterType="map">
update test
<foreach collection="beanMap" index="key" item="value" open="set " separator=",">
<if test="value != null">
${key} = #{value}
</if>
</foreach>
where id = #{id}
</update>
foreach 在循环 Map 时,键值对的 key 是 index ,value 是 item 。
sql标签
有一些通用的sql语句,我们可以通过<sql>
标签来进行抽取。
<sql id="columns">
id, name, sex
</sql>
后面的sql可以通过引用sql,获取对应的列。
<select id="testSql" resultType="map">
select <include refid="columns"/> from test2
</select>
SQL 语句片段也是可以接收 <include>
标签传递的参数值的!<include>
标签并不是完全自闭合的,它里面有 <property>
标签可以写。
<sql id="columns">
name, tel
<if test="${testValue} != null and ${testValue} == true">
, id
</if>
</sql>
<select id="testSql" resultType="map">
select
<include refid="columns">
<property name="testValue" value="true"/>
</include>
from test2
</select>
mybatis的插件
mybatis框架预留了扩展点。插件也可以说是拦截器。mybatis提供了可增强的切入点:
- Executor ( update, query, flushStatements, commit, rollback, getTransaction, close, isClosed )
- ParameterHandler ( getParameterObject, setParameters )
- ResultSetHandler ( handleResultSets, handleOutputParameters )
- StatementHandler ( prepare, parameterize, batch, update, query )
Executor
:它是执行 statement 的核心组件,它负责整体的执行把控。拦截 Executor ,则意味着要干扰 / 增强底层执行的 CRUD 等动作
。
ParameterHandler
:处理 SQL 注入参数的处理器。拦截 ParameterHandler ,则意味着要干扰 / 增强 SQL 参数注入 / 读取的动作
ResultSetHandler
:处理原生 jdbc 的 ResultSet 的处理器。拦截 ResultSetHandler ,则意味着要干扰 / 增强封装结果集的动作
。
StatementHandler
:处理原生 jdbc 的 Statement 的处理器。拦截 StatementHandler ,则意味着要干扰 / 增强 Statement 的创建和执行的动作
。
自定义拦截器。
要实现org.apache.ibatis.plugin.Interceptor
接口。并且在自定义拦截器类上标注@Intercepts
注解。用于声明要拦截哪个组件
的哪个方法
。
举例:
@Intercepts(@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}))
public class TestInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("TestInterceptor intercept run ......");
// 把这个Invocation中的东西也打印出来吧
System.out.println(invocation.getTarget());
System.out.println(invocation.getMethod().getName());
System.out.println(Arrays.toString(invocation.getArgs()));
return invocation.proceed();
}
}
这个含义就是TestInterceptor
要在Executor
的update
方法执行之前进行拦截。然后重写intercept方法。
在mybatis全局配置文件中,标签<plugins>
中增加自定义的拦截器。
<plugins>
<plugin interceptor="com.test.mybatis.plugin.TestInterceptor"/>
</plugins>
拦截器的执行流程
- 配置类解析的时候,
会将拦截器注册到configuration的interceptorChain属性中
。 - 在SqlSession的方法调用过程中。对于核心组件
executor
,paramHandler
,ResultSetHandler
,StatementMapper
的构建过程中,都通过interceptorChain
完成对核心组件的动态代理增强。返回代理后的对象。 - interceptorChain的增强过程如下
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
(1)调用InterceptorChain
的pluginAll
方法,参数传入对应核心组件对象。比如Executor。
executor = (Executor) interceptorChain.pluginAll(executor);
(2)pluginAll
方法的执行。我们可以看到是遍历所有的Interceptor
,分别执行Interceptor
的plugin
方法。
(3)Interceptor
的plugin
方法,底层是调用Plugin的wrap方法。里面的逻辑也比较清晰。首先获取拦截器中配置的要增强的方法。然后获取增强组件的加载器,实现接口。最后通过JDK动态代理构建代理对象。
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// Plugin
public static Object wrap(Object target, Interceptor interceptor) {
// 1.3.1 获取所有要增强的方法
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 1.3.2 注意这个Plugin就是自己
return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap));
}
return target;
}
(4)构建代理对象的增强逻辑为:new Plugin(target, interceptor, signatureMap))
;
public class Plugin implements InvocationHandler {
// 目标对象
private final Object target;
// 拦截器对象
private final Interceptor interceptor;
// 记录了@Signature注解的信息
private final Map<Class<?>, Set<Method>> signatureMap
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 检查@Signature注解的信息中是否包含当前正在执行的方法
Set<Method> 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);
}
}
我们可以看到Plugin本身就是一个InvocationHandler的实现类。内部包含了要代理的对象,拦截器对象,以及拦截器中标注要拦截的方法。
我们可以看下方法的执行逻辑invoke。首先判断当前执行的方法是否是拦截器要增强的方法。如果不是直接放行执行。然后对于是拦截要执行的方法,执行拦截器的intercept方法,传入的参数是封装好的Invocation对象(Invocation对象封装了target对象,Method,args
,可以很方便进行反射方法调用)。
public class Invocation {
private final Object target;
private final Method method;
private final Object[] args;
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
// getter
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}
Mybatis的生命周期
- 从
MyBatis 解析全局配置文件开始
,它会将其中定义好的 mapper.xml 、Mapper 接口一并加载,并统一保存到全局 Configuration 配置对象中 SqlSessionFactory 的构建
,需要全局的 Configuration 配置,而 SqlSessionFactory 可以创建出 SqlSession 供我们与 MyBatis 交互;SqlSession 在执行
时,底层是通过一个 Executor ,根据要执行的 SQL id (即 statementId ),找到对应的 MappedStatement ,并根据输入的参数组装 SQL
;- MappedStatement 组装好 SQL 后,底层会操作
原生 jdbc 的 API ,去数据库执行 SQL
,如果是查询的话,会返回查询结果集 ResultSet ; - MyBatis 拿到 ResultSet 后,由
ResultHandler 负责封装结果集
,根据我们事先定义好的结果集类型,封装好结果集后返回。
Executor
Executor从自卖你意思看,它表示的就是执行器。sqlsession的所有sql执行,都是委托给executor执行
。Executor是直接与底层jdbc的api打交道的。
Executor中定义了好多方法,包括事务的执行,sql的crud操作(query,update)。
query
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler) throws SQLException {
// 获取要执行查询的SQL
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
上面的方法,它的主要的工作是获取要发送的SQL,根据要去往数据库的查询请求,构造出缓存标识(CacheKey)。接下来执行下面代码。
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.");
}
// 如果是刚开始查询,并且statement定义的需要刷新缓存,则前置清空一次一级缓存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
// 开始查询,计数器+1
queryStack++;
// 先检查一级缓存中是否有查询结果
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 如果有,直接返回
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 否则,查询数据库
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
// 查询完成后计数器-1
queryStack--;
}
// 计数器归零时,证明所有查询都已经完成,处理后续动作
if (queryStack == 0) {
// 处理延迟加载
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear();
//如果全局配置文件中声明的一级缓存作用域是statement,则应该清空一级缓存(因为此时statement已经处理完毕了)
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}
update
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();
// xxx -> doXxx
return doUpdate(ms, parameter);
}
insert update delete 语句都可以使用 update 的动作完成,所以 Executor 也只设计了一个 update 方法完事。
MappedStatement
mapper.xml中配置的一个一个的<select> <insert><update>
底层都会封装为一个一个的MapperdStatement
。
public final class MappedStatement {
// 当前mapper的来源(mapper.xml / Mapper.class的路径)
private String resource;
private Configuration configuration;
private String id;
// statement内部封装的SQL
private SqlSource sqlSource;
// 当前statement对应的mapper.xml或Mapper接口的namespace下的二级缓存
private Cache cache;
// 如果是select,则此处存放返回值的映射(resultMap和resultType都在这里)
private List<ResultMap> resultMaps;
// 执行此条SQL之前是否需要清空二级缓存
private boolean flushCacheRequired;
// 当前SQL是否使用二级缓存
private boolean useCache;
// ......
SqlSource是封装sql。为什么sql要被封装一下呢?因为动态SQL在实际查询的时候,应该根据传入的参数,动态的组合if标签来生成SQL。所以用String来记录SQL是不现实的。
我们可以认为,SqlSource
是带动态SQL标签的SQL定义,在程序执行期间,根据SqlSource传入SQL必须的参数后
,它会解析这些动态SQL,并生成一条真正可用于preparedStatement执行的sql
,并且把这些下参数都保存好。而保存SQL和参数的载体
就是BoundSql
。
query的流程
- sqlsession根据传入的statement,通过
configuration.getMappedStatement(statement);
获取到MappedStatement - 委托executor,调用executor.query方法。
executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
- 请求参数封装wrapCollection。如果传入的参数是list类型,封装为
map.put("list", object)
;如果是集合,封装为
map.put("collection", object)
;如何是数组封装为map.put("array", object)
;
private Object wrapCollection(final Object object) {
// 这个方法是3.5.5抽出来的,之前都是直接在此处编写的逻辑
return ParamNameResolver.wrapToMapIfCollection(object, null);
}
// 3.5.5 新抽的方法
public static Object wrapToMapIfCollection(Object object, String actualParamName) {
if (object instanceof Collection) {
ParamMap<Object> map = new ParamMap<>();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
return map;
} else if (object != null && object.getClass().isArray()) {
ParamMap<Object> map = new ParamMap<>();
map.put("array", object);
Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
return map;
}
return object;
}a
-
执行executor的query方法的前置处理
(1)通过请求参数通过mapperStatement构造BoundSql。这个过程就是将动态SQL解析转换为可以执行的带占位符的SQL语句
。BoundSql里面存储了SQL和参数对象
。
(2)构造缓存key
(3)执行query -
执行executor的query方法
(1)是否存在缓存,存在直接查询返回结果
(2)如果不存在执行查询数据库 -
查询数据库
(1)通过mapperStatement获取configuration。MappedStatement.getConfiguration()
;
(2)通过configuration获取statementhandler对象。
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
(3)利用statementhandler创建出真实的statement对象(jdbc的statement),这个通过statementhandler构造preparestatement对象后,还进行执行sql的参数设置
,通过parameterHandler完成参数设置
。BoundSql中的ParameterMappings
封装了执行sql中的参数列表(比如select * from user where name = #{name} 。ParameterMappings封装的就是name)。然后执行statementhandler的query方法。
- statementhandler的query方法。PreparedStatement 执行execute方法。
- resulthandler执行handleResultSets方法。
resultSetHandler.handleResultSets(ps)
;它是用来处理结果集和封装的。
Mybatis中mapper动态代理执行流程
- 首先通过
sqlsession.getMapper(mapper.class),
获取Mapper接口的代理对象
。 - getMapper方法中通过configuration中的
MapperRegistry.getMapper(Type type,sqlsession)
。获取到MapperProxyFactory
对象。 MapperProxyFactory
是Mapper代理对象的工厂。通过它生产Mapper代理对象。- 通过
MapperProxyFactory的newInstance
方法实例化代理对象。底层就是基础的jdk动态代理。
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
- 接下来我们看看动态代理的增强逻辑。invocationHandler的实现:
mapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
// 内部要组合SqlSession,不然没法执行SqlSession的方法
private final SqlSession sqlSession;
// 记录当前代理了哪个接口
private final Class<T> mapperInterface;
// 缓存MapperMethod
private final Map<Method, MapperMethodInvoker> methodCache;
其中的methodCache是一个Map结构,key为接口中的method对象。说明这个MapperProxy会将Mapper接口中的每个方法都缓存一遍,回头Mapper代理对象执行时,只需要知道当前执行哪个方法,就可以从Map中取出对应的Invoker,调用其方法就ok。
- 进入mapperProxy的invoke方法。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果是Object类中的方法,默认不代理
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// Mapper接口自己定义的方法,需要找对应的MapperMethodInvoker
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} // catch ......
}
看逻辑我们可以了解到,对于Object类型的方法,不做增强处理。
对于Mapper接口自己定义的方法,需要找对应的MapperMethodInvoker。
(1) cachedInvoker(method)
先从mapperProxy中的属性methodCache中取是否有这个方法的缓存。有的话直接返回invoker。
构建方法执行器invoker(PlainMethodInvoker
)。放入缓存。
(2)invoke(proxy, method, args, sqlSession);
调用MapperMehtodInvoker的invoke方法。底层就是调用MapperMethod的execute方法。
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方法。判断当前调用的SQL的类型
,而这个类型藏在SqlCommand中。然后根据SQL类型,处理查询参数
,最后调用SqlSession的方法
。
private final SqlCommand command;
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 2.2.2.1 判断一下当前调用的SQL的类别
switch (command.getType()) {
case INSERT: {
// 2.2.2.2 处理参数
Object param = method.convertArgsToSqlCommandParam(args);
// 此处调用SqlSession的insert方法
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
// 此处调用SqlSession的update方法
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
// 此处调用SqlSession的delete方法
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
// 使用ResultHandler处理查询动作
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// selectList
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
// selectMap
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
// selectCursor
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
// selectOne
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
// flush动作
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
// throw ex ......
}
return result;
}
总结
动态代理Mapper的执行流程就是构造Mapper接口的代理对象,代理对象中根据执行方法的类型,底层还是调用SqlSesison的方法,完成sql的执行。
mybatis的pooledDataSource
数据库连接池
1. 空闲连接数不为空
从空闲连接中获取一个连接
2. 当前活跃连接数 < 连接池的最大活跃连接数
创建一个新的连接
3. 当前活跃连接数 >= 连接池的最大活跃连接数
(1) 获取当前活跃连接的最早一个连接,判断超时时间是否大于连接池设置的最大超时时间
超过,最早的活跃连接回滚,从活跃连接列表中移除
不超过。当前请求阻塞挂起。
4. 当连接使用结束,调用close方法。执行connontion的代理类的invoke方法。PooledDataSource的pushconnection方法。
(1) 当前连接放入空闲连接集合
(2) 唤醒阻塞的线程
mybatis的原对象工具MetaObject
MetaObject工具,我们可以对任意对象进行获取属性和设置属性的操作。
实现调用链路:MetaObject(传入对象获取对象的原对象) —> ObjectWrapper的实现(根据原对象类型,选择对应的ObejectWrapper的实现)—>MetaClass–>Reflect(通过反射,完成原对象的解析)