Mybatis-基础补充

配置文件加载

在这里插入图片描述
  从SqlsessionFactoryBuild开始先加载配置文件,然后构造cofiguration类,然后执行build方法,构建SqlsessionFactory对象。
  properties配置封装到configuration中的variables变量。
  typeAliases配置封装到configuration中的typeAliasRegistry变量。
  plugins 配置封装到configuration中的interceptorChain变量。
  environments配置封装到configuration中的environment变量。
  mappers配置封装到configuration中的mapperRegistry变量。

mapper配置文件详解

对于mapper配置文件中的statement,元素介绍。

DQL

  1. id:一个mapper.xml下的statement唯一标识。不同mapper.xml下id可以重复。
  2. parameterType:入参的类型。
  3. resultType:返回结果集封装的类型。
  4. resultMap:mapper.xml中定义的resultMap引用。
  5. userCache:对于查询结果是否保存二级缓存。
  6. flushCache:执行sql后会清空一级缓存和当前namespace的二级缓存。
  7. databaseId:标注数据库厂商

DML

区别与DQL的如下

  1. flushCache:默认true,flushCache 会清除全局一级缓存,以及本 namespace 下的二级缓存。
  2. useGeneratedKeys:开启使用,则Mybatis会使用jdbc底层的getGeneratedKeys方法,取出自增主键的值,应用与insert和update。
  3. keyProperty:配合useGeneratedKeys使用,用于指定出传入参数对象的属性名,应用与insert和update。
  4. 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解析配置文件,生成TransactionFactoryTransactionFactory 用来生成TransactionTransaction接口中定义的方法包含了事务的相关操作。

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。对应的TransactionJdbcTransactionJdbcTransaction其中的操作就是通过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();
    }
}

事务的生效原理

一般通过SqlSessionFactoryopenSession开启新的SqlSessionopenSession会调用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>
  1. 不这么写。。。。。
  2. 使用<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提供了可增强的切入点:

  1. Executor ( update, query, flushStatements, commit, rollback, getTransaction, close, isClosed )
  2. ParameterHandler ( getParameterObject, setParameters )
  3. ResultSetHandler ( handleResultSets, handleOutputParameters )
  4. 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 要在Executorupdate方法执行之前进行拦截。然后重写intercept方法。
在mybatis全局配置文件中,标签<plugins>中增加自定义的拦截器。

<plugins>
        <plugin interceptor="com.test.mybatis.plugin.TestInterceptor"/>
</plugins>

拦截器的执行流程

  1. 配置类解析的时候,会将拦截器注册到configuration的interceptorChain属性中
  2. 在SqlSession的方法调用过程中。对于核心组件executorparamHandler,ResultSetHandler,StatementMapper的构建过程中,都通过interceptorChain完成对核心组件的动态代理增强。返回代理后的对象。
  3. 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)调用InterceptorChainpluginAll方法,参数传入对应核心组件对象。比如Executor。

executor = (Executor) interceptorChain.pluginAll(executor);

  (2)pluginAll方法的执行。我们可以看到是遍历所有的Interceptor,分别执行Interceptorplugin方法。
  (3)Interceptorplugin方法,底层是调用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的生命周期

在这里插入图片描述

  1. MyBatis 解析全局配置文件开始,它会将其中定义好的 mapper.xml 、Mapper 接口一并加载,并统一保存到全局 Configuration 配置对象中
  2. SqlSessionFactory 的构建,需要全局的 Configuration 配置,而 SqlSessionFactory 可以创建出 SqlSession 供我们与 MyBatis 交互;
  3. SqlSession 在执行时,底层是通过一个 Executor ,根据要执行的 SQL id (即 statementId ),找到对应的 MappedStatement ,并根据输入的参数组装 SQL
  4. MappedStatement 组装好 SQL 后,底层会操作原生 jdbc 的 API ,去数据库执行 SQL ,如果是查询的话,会返回查询结果集 ResultSet ;
  5. 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的流程

  1. sqlsession根据传入的statement,通过configuration.getMappedStatement(statement);获取到MappedStatement
  2. 委托executor,调用executor.query方法。
executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  1. 请求参数封装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
  1. 执行executor的query方法的前置处理
    (1)通过请求参数通过mapperStatement构造BoundSql。这个过程就是将动态SQL解析转换为可以执行的带占位符的SQL语句BoundSql里面存储了SQL和参数对象
    (2)构造缓存key
    (3)执行query

  2. 执行executor的query方法
    (1)是否存在缓存,存在直接查询返回结果
    (2)如果不存在执行查询数据库

  3. 查询数据库
    (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方法。

  1. statementhandler的query方法。PreparedStatement 执行execute方法。
  2. resulthandler执行handleResultSets方法。resultSetHandler.handleResultSets(ps);它是用来处理结果集和封装的。

在这里插入图片描述

Mybatis中mapper动态代理执行流程

  1. 首先通过sqlsession.getMapper(mapper.class),获取Mapper接口的代理对象
  2. getMapper方法中通过configuration中的MapperRegistry.getMapper(Type type,sqlsession)。获取到MapperProxyFactory对象。
  3. MapperProxyFactory是Mapper代理对象的工厂。通过它生产Mapper代理对象。
  4. 通过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);
}
  1. 接下来我们看看动态代理的增强逻辑。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。

  1. 进入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(通过反射,完成原对象的解析)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值