1. mybatis的骨架

1. mybatis的骨架

MyBatis整体架构

image-20220620125639118

面向sqlsession编程

为什么分层: 提供可读性, 方便团队开发, 提高系统的伸缩性

门面模式: 为了遵循迪米特法则

springmvc的dispatchservelt也是一个门面模式

面向接口编程好处: 实现变化,我们使用的客户端不需要变化, 实现和使用解耦

开闭原则: 需求变化, 通过新增实现, 不要修改代码

迪米特法则: 尽量降低类之间的耦合

1. 日志模块

  • MyBatis没有提供日志的实现类,需要接入第三方的日志组件,但第三方日志组件都有各自的Log级别,且各不相同,而MyBatis统一提供了trace、debug、warn、error四个级别;

  • 自动扫描日志实现,并且第三方日志插件加载优先级如下:slf4J → commonsLoging → Log4J2 → Log4J → JdkLog;

  • 日志的使用要优雅的嵌入到主体功能中;

适配器模式

image-20220620224357838

日志模块的适配器模式类图

image-20220620224512165

LogFactory

//自动扫描日志实现,并且第三方日志插件加载优先级如下:slf4J → commonsLoging → Log4J2 → Log4J → JdkLog
static {
  tryImplementation(LogFactory::useSlf4jLogging);
  tryImplementation(LogFactory::useCommonsLogging);
  tryImplementation(LogFactory::useLog4J2Logging);
  tryImplementation(LogFactory::useLog4JLogging);
  tryImplementation(LogFactory::useJdkLogging);
  tryImplementation(LogFactory::useNoLogging);
}

代理模式

给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用

  1. 屏蔽不必要的复杂性, 2. 给目标代码增强

静态代理会导致代理类的膨胀

Proxy(生成代理类), InvocationHandler(业务增强)

日志模块模式类图

image-20220620230425888

BaseJdbcLogger:

//所有日志增强的抽象基类
public abstract class BaseJdbcLogger {

  //保存preparestatment中常用的set方法(占位符赋值)
  protected static final Set<String> SET_METHODS = new HashSet<>();
  //保存preparestatment中常用的执行sql语句的方法
  protected static final Set<String> EXECUTE_METHODS = new HashSet<>();


  // 为什么要设计成 map + list + list 方式, map方便使用hash算法, list肯定是为了方便使用contains方法或者遍历key,value
  //保存preparestatment中set方法的键值对
  private final Map<Object, Object> columnMap = new HashMap<>();

  //保存preparestatment中set方法的key值
  private final List<Object> columnNames = new ArrayList<>();
  //保存preparestatment中set方法的value值
  private final List<Object> columnValues = new ArrayList<>();

  protected Log statementLog;
  protected int queryStack;

ConnectionLogger:负责打印连接信息和SQL语句,并创建PreparedStatementLogger;

PreparedStatementLogger:负责打印参数信息,并创建ResultSetLogger

ResultSetLogger:负责打印数据结果信息;

org.apache.ibatis.logging.jdbc.ConnectionLogger

org.apache.ibatis.logging.jdbc.PreparedStatementLogger#invoke

源码调式(最好的记忆方式就是debug源码, 没有之一, 其他方式容易忘记, debug随时可以复习)

日志打印其实就是围绕Connection,PreparedStatement,ResultSet类的一系列注册在BaseJdbcLogger的EXECUTE_METHODS和SET_METHODS中的方法做日志逻辑增强,

主要是set*方法和execute,executeUpdate,executeQuery,addBatch方法

所以熟悉jdbc依然很重要, 框架也是围绕jdbc来做的

经典的jdbc代码

//STEP 1. 导入sql相关的包
@Test
public void QueryPreparedStatementDemo() {
    Connection conn = null;
    PreparedStatement stmt = null;
    List<TUser> users = new ArrayList<>();
    try {
        // STEP 2: 注册mysql的驱动
        Class.forName("com.mysql.jdbc.Driver");

        // STEP 3: 获得一个连接
        System.out.println("Connecting to database...");
        conn = DriverManager.getConnection(DB_URL, USER, PASS); // 被mybatis代理的类Connection->ConnectionLogger

        // STEP 4: 创建一个查询
        System.out.println("Creating statement...");
        String sql;
        sql = "SELECT * FROM t_user where userName= ? ";
        stmt = conn.prepareStatement(sql); // 被mybatis代理的类PreparedStatement->PreparedStatementLogger
        stmt.setString(1, "lison");
        System.out.println(stmt.toString());//打印sql
        ResultSet rs = stmt.executeQuery(); // 被mybatis代理的类ResultSet->ResultSetLogger

        //       ResultSet rs = stmt.getResultSet();


        // STEP 5: 从resultSet中获取数据并转化成bean
        while (rs.next()) {
            System.out.println("------------------------------");
            // Retrieve by column name
            TUser user = new TUser();
            user.setId(rs.getInt("id"));
            user.setUserName(rs.getString("userName"));
            user.setRealName(rs.getString("realName"));
            user.setSex(rs.getByte("sex"));
            user.setMobile(rs.getString("mobile"));
            user.setEmail(rs.getString("email"));
            user.setNote(rs.getString("note"));

            System.out.println(user.toString());

            users.add(user);
        }
        // STEP 6: 关闭连接
        rs.close();
        stmt.close();
        conn.close();
    } catch (SQLException se) {
        // Handle errors for JDBC
        se.printStackTrace();
    } catch (Exception e) {
        // Handle errors for Class.forName
        e.printStackTrace();
    } finally {
        // finally block used to close resources
        try {
            if (stmt != null)
                stmt.close();
        } catch (SQLException se2) {
        }// nothing we can do
        try {
            if (conn != null)
                conn.close();
        } catch (SQLException se) {
            se.printStackTrace();
        }
    }
    System.out.println("-------------------------");
    System.out.println("there are "+users.size()+" users in the list!");
}

测试用例

private SqlSessionFactory sqlSessionFactory;

@Before
public void init() throws IOException {
    //--------------------第一阶段---------------------------
    // 1.读取mybatis配置文件创SqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    // 1.读取mybatis配置文件创SqlSessionFactory
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    inputStream.close();
}

@Test
// 快速入门
public void quickStart() throws IOException {
    //--------------------第二阶段---------------------------
    // 2.获取sqlSession  
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 3.获取对应mapper
    TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);

    //--------------------第三阶段---------------------------
    // 4.执行查询语句并返回单条数据
    TUser user = mapper.selectByPrimaryKey(2);
    System.out.println(user);

    System.out.println("----------------------------------");

    // 5.执行查询语句并返回多条数据
    List<TUser> users = mapper.selectAll();
    for (TUser tUser : users) {
        System.out.println(tUser);
    }
}

此方法打断点

org.apache.ibatis.executor.SimpleExecutor#prepareStatement

image-20220624223804711

会发现入口方法是: org.apache.ibatis.executor.SimpleExecutor#doQuery

image-20220624223831872

所以默认的执行器是SimpleExecutor, 我们所有mybatis的核心源码多数会从这里进入

接着看源码

protected Connection getConnection(Log statementLog) throws SQLException {
  Connection connection = transaction.getConnection(); // 拿到原生的connection, 
  if (statementLog.isDebugEnabled()) { // 然后对齐生成具备logger能力的代理对象, jdk代理
    return ConnectionLogger.newInstance(connection, statementLog, queryStack);
  } else {
    return connection;
  }
}

所以我们一定要在org.apache.ibatis.logging.jdbc.ConnectionLogger#invoke打一个断点吧, 后面调试一定会停下来

image-20220624224256854

public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
    try {
        //如果是从Obeject继承的方法直接忽略
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, params);
        }
        //如果是调用prepareStatement、prepareCall、createStatement的方法,打印要执行的sql语句
        //并返回prepareStatement的代理对象,让prepareStatement也具备日志能力,打印参数
        if ("prepareStatement".equals(method.getName())) {
            if (isDebugEnabled()) {
                debug(" Preparing: " + removeBreakingWhitespace((String)params[0]), true);//打印sql语句
            }
            PreparedStatement stmt = (PreparedStatement)method.invoke(connection, params);
            stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);//创建代理对象
            return stmt;
        } else if ("prepareCall".equals(method.getName())) {
            if (isDebugEnabled()) {
                debug(" Preparing: " + removeBreakingWhitespace((String)params[0]), true);//打印sql语句
            }
            PreparedStatement stmt = (PreparedStatement)method.invoke(connection, params);//创建代理对象
            stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
            return stmt;
        } else if ("createStatement".equals(method.getName())) {
            Statement stmt = (Statement)method.invoke(connection, params);
            stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);//创建代理对象
            return stmt;
        } else {
            return method.invoke(connection, params);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
}

同样PreparedStatement或者Statement也被代理增强了, 接下来看PreparedStatementLogger

image-20220624224458500

@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
    try {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, params);
        }
        if (EXECUTE_METHODS.contains(method.getName())) {//增强PreparedStatement的execute相关方法
            if (isDebugEnabled()) {
                debug("Parameters: " + getParameterValueString(), true);//当方法执行时,通过动态代理打印参数
            }
            clearColumnInfo();
            if ("executeQuery".equals(method.getName())) {//返回动态代理能力的resultSet
                ResultSet rs = (ResultSet)method.invoke(statement, params);
                return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
            } else {
                return method.invoke(statement, params);
            }
        } else if (SET_METHODS.contains(method.getName())) {//将参数设置到columnMap、columnNames、columnValues,为打印参数做好准备
            // 设置方法比上面的打印日志的方法先打印
            if ("setNull".equals(method.getName())) {
                setColumn(params[0], null); // 往三个容器设置参数
            } else {
                setColumn(params[0], params[1]);
            }
            return method.invoke(statement, params);
        } else if ("getResultSet".equals(method.getName())) {//返回动态代理能力的resultSet
            ResultSet rs = (ResultSet)method.invoke(statement, params);
            return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
        } else if ("getUpdateCount".equals(method.getName())) {//   如果是更新,直接打印影响的行数
            int updateCount = (Integer)method.invoke(statement, params);
            if (updateCount != -1) {
                debug("   Updates: " + updateCount, false);
            }
            return updateCount;
        } else {
            return method.invoke(statement, params);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
}

ResultSet也被代理增强, 看ResultSetLogger代码

image-20220624224618482

public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
  try {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, params);
    }    
    Object o = method.invoke(rs, params);//执行result.next方法,判断是否还有数据
    if ("next".equals(method.getName())) {//如果还有数据,计数器rows加一
      if (((Boolean) o)) {
        rows++;
        if (isTraceEnabled()) {
          ResultSetMetaData rsmd = rs.getMetaData();
          final int columnCount = rsmd.getColumnCount();
          if (first) {
            first = false;
            printColumnHeaders(rsmd, columnCount);
          }
          printColumnValues(columnCount);
        }
      } else {
        debug("     Total: " + rows, false);//如果没有数据了,打印rows,打印查询出来的数据条数
      }
    }
    clearColumnInfo();
    return o;
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
}

从源码可以看出来是一个非常典型的代理模式的应用, 而BaseJdbcLogger类中聚合Log就是一个很好的适配器模式的应用

适配器核心代码

public abstract class BaseJdbcLogger { // 

  //保存preparestatment中常用的set方法(占位符赋值)
  protected static final Set<String> SET_METHODS = new HashSet<>();
  //保存preparestatment中常用的执行sql语句的方法
  protected static final Set<String> EXECUTE_METHODS = new HashSet<>();


  // 为什么要设计成 map + list + list 方式, map方便使用hash算法, list肯定是为了方便使用contains方法或者遍历key,value
  //保存preparestatment中set方法的键值对
  private final Map<Object, Object> columnMap = new HashMap<>();

  //保存preparestatment中set方法的key值
  private final List<Object> columnNames = new ArrayList<>();
  //保存preparestatment中set方法的value值
  private final List<Object> columnValues = new ArrayList<>();

  protected Log statementLog; // 被适配的接口统一的接口
    ...
}
public final class LogFactory {

  /**
   * Marker to be used by logging implementations that support markers
   */
  public static final String MARKER = "MYBATIS";

  //被选定的第三方日志组件适配器的构造方法
  private static Constructor<? extends Log> logConstructor; // 组合log接口
    ...
}
image-20220624225652387
public class Log4jImpl implements Log { // 适配成新产品的接口类: mybatis定义的接口类Log
  
  private static final String FQCN = Log4jImpl.class.getName();

  private final Logger log; // 原来产品的接口类: log4j的接口类 Logger, 被复用的代理
    ...
}

总结: 使用了适配器模式和代理模式

2. 数据源模块分析

数据源模块重点数据源的创建和数据库连接池的源码分析;数据源创建比较复杂,对于复杂对象的创建,可以考虑使用工厂模式来优化,接下来介绍下简单工厂模式和工厂模式;

简单工厂模式和工厂模式略

1. 数据源的创建

数据源对象是比较复杂的对象,其创建过程相对比较复杂,对于 MyBatis 创建一个数据源, 具体来讲有如下难点:

  1. 常见的数据源组件都实现了 javax.sql.DataSource 接口;

  2. MyBatis 不但要能集成第三方的数据源组件,自身也提供了数据源的实现;

  3. 一般情况下,数据源的初始化过程参数较多,比较复杂;

综上所述,数据源的创建是一个典型使用工厂模式的场景

image-20220624232528593

  • DataSource:数据源接口,JDBC 标准规范之一,定义了获取获取 Connection 的方法;
  • UnPooledDataSource:不带连接池的数据源,获取连接的方式和手动通过 JDBC 获取连接的方式是一样的;
  • PooledDataSource:带连接池的数据源,提高连接资源的复用性,避免频繁创建、关闭连接资源带来的开销;
  • DataSourceFactory:工厂接口,定义了创建 Datasource 的方法;
  • UnpooledDataSourceFactory:工厂接口的实现类之一,用于创建 UnpooledDataSource(不 带连接池的数据源);
  • PooledDataSourceFactory:工厂接口的实现类之一,用于创建 PooledDataSource(带连 接池的数据源);

2. 数据库连接池技术解析

数据库连接池技术是提升数据库访问效率常用的手段,使用连接池可以提高连接资源的复用 性,避免频繁创建、关闭连接资源带来的开销。MyBatis 内 部就带了一个连接池的实现,接下来重点解析连接池技术的数据结构和算法;先重点分析下 跟连接池相关的关键类:

  • PooledDataSource:一个简单,同步的、线程安全的数据库连接池
  • PooledConnection:使用动态代理封装了真正的数据库连接对象,在连接使用之前和关闭时进行增强;
  • PoolState:用于管理 PooledConnection 对象状态的组件,通过两个 list 分别管理空闲状态的连接资源和活跃状态的连接资源,如下图,需要注意的是这两个 List 使用 ArrayList实现,存在并发安全的问题,因此在使用时,注意加上同步控制;

重点解析获取资源和回收资源的流程,获取连接资源的过程如下图:

参考代码:org.apache.ibatis.datasource.pooled.PooledDataSource.popConnection(String, String)

**popConnection(): ** 获取连接资源

image-20220624233558491

回收资源参考代码:

org.apache.ibatis.datasource.pooled.PooledDataSource.pushConnection(PooledConnection)

image-20220624233730713

org.apache.ibatis.datasource.pooled.PooledConnection#isValid

检测状态和ping连接

public boolean isValid() {
  return valid && realConnection != null && dataSource.pingConnection(this);
}

3. 缓存模块分析

基于map实现的

除核心功能之外,有很多额外的附加功能,如:防止缓存击穿,添加缓存清空策略(fifo、lru)、序列化功能、日志能力、定时清空能力等;

怎么样优雅的为核心功能添加多种附加能力?使用动态代理或继承的办法扩展多种附加功能?

这些方式是静态的,用户不能控制增加行为的方式和时机。另外,新功能的存在多种组合,使用继承可能导致大量子类存在;

优化思路:装饰器模式是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀;

装饰器模式类图:

image-20220626174824125

mybatis 的缓存装饰模式类图

image-20220626175911045

Cache:Cache接口是缓存模块的核心接口,定义了缓存的基本操作;

PerpetualCache:在缓存模块中扮演ConcreteComponent角色,使用HashMap来实现cache的相关操作;

BlockingCache:阻塞版本的缓存装饰器,保证只有一个线程到数据库去查找指定的key对应的数据;

缓存雪崩问题

  1. 当缓存失效, 竞争锁然后查询数据库(粗粒度锁和细粒度锁)
  2. 不同失效时间

BlockingCache通过ConcurrentHashMap实现细粒度锁

核心代码:
org.apache.ibatis.cache.decorators.BlockingCache#getLockForKey

private ReentrantLock getLockForKey(Object key) {
  ReentrantLock lock = new ReentrantLock();//创建锁
  ReentrantLock previous = locks.putIfAbsent(key, lock);//把新锁添加到locks集合中,如果添加成功使用新锁,如果添加失败则使用locks集合中的锁
  return previous == null ? lock : previous; // 如果为null使用新锁, 如果不为null, 使用老锁, 新锁会被jvm回收
}

当前线程才能解锁(和redis锁的防止锁误删一样思路)

private void releaseLock(Object key) {
  ReentrantLock lock = locks.get(key);
  if (lock.isHeldByCurrentThread()) {
    lock.unlock();
  }
}

为什么使用CacheKey作为缓存key, 不用String

MyBatis中涉及到动态SQL的原因,缓存项的key不能仅仅通过一个String来表示,所以通过CacheKey来封装缓存的Key值,CacheKey可以封装多个影响缓存项的因素;判断两个CacheKey是否相同关键是比较两个对象的hash值是否一致;

构成CacheKey的对象四个要素

  • mappedStatment的id

  • 查询所使用的SQL语句

  • 指定查询结果集的范围(分页信息)

  • 用户传递给SQL语句的实际参数值

四个关键的属性

public class CacheKey implements Cloneable, Serializable {
    ...
    private final int multiplier;//参与hash计算的乘数, 固定值37
    // 以下三个值为了提高比较的效率
    private int hashcode;//CacheKey的hash值,在update函数中实时运算出来的
    private long checksum;//校验和,hash值的和
    private int count;//updateList的中元素个数, 最终会有四个
    ...
    public CacheKey() {
        this.hashcode = DEFAULT_HASHCODE;
        this.multiplier = DEFAULT_MULTIPLYER;
        this.count = 0;
        this.updateList = new ArrayList<>();
    }
    ...
}

重点方法update和equals

org.apache.ibatis.cache.CacheKey#update

帮助计算hashCode,checkSum和count三个值的

// 每次变化一个要素就会执行一次update
public void update(Object object) {
    //获取object的hash值
    int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
    //更新count、checksum以及hashcode的值
    count++;
    checksum += baseHashCode;
    baseHashCode *= count;
    hashcode = multiplier * hashcode + baseHashCode;
    //将对象添加到updateList中
    updateList.add(object); // 最终会有四个值
}

org.apache.ibatis.cache.CacheKey#equals

@Override
public boolean equals(Object object) {
    if (this == object) {//比较是不是同一个对象
        return true;
    }
    if (!(object instanceof CacheKey)) {//是否类型相同
        return false;
    }

    final CacheKey cacheKey = (CacheKey)object;

    if (hashcode != cacheKey.hashcode) {//hashcode是否相同
        return false;
    }
    if (checksum != cacheKey.checksum) {//checksum是否相同
        return false;
    }
    if (count != cacheKey.count) {//count是否相同
        return false;
    }

    //以上都相同,才按顺序比较updateList中元素的hash值是否一致: 提高效率
    for (int i = 0; i < updateList.size(); i++) {
        Object thisObject = updateList.get(i);
        Object thatObject = cacheKey.updateList.get(i);
        if (!ArrayUtil.equals(thisObject, thatObject)) {
            return false;
        }
    }
    return true;
}

二级缓存实现类: CachingExecutor

mapper.xml中通过cache开启二级缓存

<mapper namespace="...">
   <cache></cache>
</mapper>
入口方法: query

org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
    ResultHandler resultHandler) throws SQLException {
    //获取sql语句信息,包括占位符,参数等信息
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //拼装缓存的key值
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    // 执行查询
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

执行查询: 二级缓存->一级缓存

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
    ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    //从MappedStatement中获取二级缓存
    Cache cache = ms.getCache(); // 不开启二级缓存, 这里将会直接返回null, 会调用一级缓存的query方法
    if (cache != null) {
        flushCacheIfRequired(ms);
        if (ms.isUseCache() && resultHandler == null) {
            ensureNoOutParams(ms, boundSql);
            @SuppressWarnings("unchecked") List<E> list = (List<E>)tcm.getObject(cache, key);//从二级缓存中获取数据
            if (list == null) {
                //二级缓存为空,才会调用BaseExecutor.query
                list = delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                tcm.putObject(cache, key, list); // issue #578 and #116
            }
            return list;
        }
    }
    return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

MappedStatement

public final class MappedStatement {

    private String resource;//节点的完整的id属性,包括命名空间
    private Configuration configuration;
    private String id;//节点的id属性
    private Integer fetchSize;//节点的fetchSize属性,查询数据的条数
    private Integer timeout;//节点的timeout属性,超时时间
    private StatementType statementType;//节点的statementType属性,默认值:StatementType.PREPARED;
    private ResultSetType resultSetType;//节点的resultSetType属性,jdbc知识
    private SqlSource sqlSource;//节点中sql语句信息
    private Cache cache;//对应的二级缓存
    private ParameterMap parameterMap;//已废弃
    private List<ResultMap> resultMaps;//节点的resultMaps属性
    private boolean flushCacheRequired;//节点的flushCache属性是否刷新缓存
    private boolean useCache;//节点的useCache属性是否使用二级缓存
    private boolean resultOrdered;
    private SqlCommandType sqlCommandType;//sql语句的类型,包括:INSERT, UPDATE, DELETE, SELECT
    private KeyGenerator keyGenerator;//节点keyGenerator属性
    private String[] keyProperties;
    private String[] keyColumns;
    private boolean hasNestedResultMaps;//是否有嵌套resultMap
    private String databaseId;
    private Log statementLog;
    private LanguageDriver lang;
    private String[] resultSets;//多结果集使用

一级缓存类: BaseExecutor

一级缓存的核心实现类: PerpetualCache被BlockingCache装饰的类

public abstract class BaseExecutor implements Executor {

    private static final Log log = LogFactory.getLog(BaseExecutor.class);

    protected Transaction transaction;//事务对象
    protected Executor wrapper;//封装的Executor对象

    protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;//延迟加载的队列
    protected PerpetualCache localCache;//一级缓存的实现,PerpetualCache
    protected PerpetualCache localOutputParameterCache;//一级缓存用于缓存输出的结果
    protected Configuration configuration;//全局唯一configuration对象的引用

    protected int queryStack;//用于嵌套查询的的层数
    private boolean closed;
public class PerpetualCache implements Cache {

    private final String id;

    private Map<Object, Object> cache = new HashMap<>(); // hashmap

org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
    throws SQLException {
    //获取sql语句信息,包括占位符,参数等信息
    BoundSql boundSql = ms.getBoundSql(parameter);
    //拼装缓存的key值
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

测试用例

记得要开启
<cache></cache>
// 快速入门
public void quickStart() throws IOException {
    //--------------------第二阶段---------------------------
    // 2.获取sqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 3.获取对应mapper 被代理
    TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);

    //--------------------第三阶段---------------------------
    // 4.执行查询语句并返回单条数据
    TUser user = mapper.selectByPrimaryKey(2);
    System.out.println(user);

    System.out.println("----------------------------------");

    // 5.执行查询语句并返回多条数据
    List<TUser> users = mapper.selectAll();
    for (TUser tUser : users) {
        System.out.println(tUser);
    }
}

二级缓存类打断点

image-20220626220322493

被装饰的实例图

image-20220626220502139

其中SynchronozedCache没有做任何逻辑补充, 只是对每个方法加了Synchronized关键字, 为了同步控制

image-20220626220701209

其中典型的LoggingCache也是记录日志作用, 只有getObject加了日志

@Override
public Object getObject(Object key) {
    requests++;
    final Object value = delegate.getObject(key);
    if (value != null) {
        hits++;
    }
    if (log.isDebugEnabled()) {
        log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
    }
    return value;
}

4. 反射模块分析

1. orm框架查询数据过程

image-20220626221403445

  • 实例化对象是 ReflectorFactory和Reflector完成
  • 包装对象是ObjectWrapperFactory和ObjectWrapper完成
  • MetaObject封装了api的使用细节

2. Mybatis反射模块的核心类

  • ObjectFactory:MyBatis每次创建结果对象的新实例时,它都会使用对象工厂(ObjectFactory)去构建POJO;

  • ReflectorFactory:创建Reflector的工厂类,Reflector是MyBatis反射模块的基础,每个Reflector对象都对应一个类,在其中缓存了反射操作所需要的类元信息;

  • ObjectWrapperFactory: ObjectWrapper 的工厂类,用于创建ObjectWrapper ;

  • ObjectWrapper:对对象的包装,抽象了对象的属性信息,他定义了一系列查询对象属性信息的方法,以及更新属性的方法;

  • MetaObject:封装了对象元信息,包装了MyBatis中五个核心的反射类。也是提供给外部使用的反射工具类,可以利用它可以读取或者修改对象的属性信息;

3. Mybatis反射模块的核心类图

image-20220626222159215

4. DefaultObjectFactory

创建对象

public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    //判断类是不是集合类,如果是集合类指定具体的实现类
    Class<?> classToCreate = resolveInterface(type);
    // we know types are assignable
    return (T)instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
}

5. Reflector

image-20220626222644291

public class Reflector {

    // 1. 类
    private final Class<?> type;//对应的class
    // 2. get/set属性
    private final String[] readablePropertyNames;//可读属性的名称集合,存在get方法即可读
    private final String[] writeablePropertyNames;//可写属性的名称集合,存在set方法即可写
    // 3. get/set方法
    private final Map<String, Invoker> setMethods = new HashMap<>();//保存属性相关的set方法
    private final Map<String, Invoker> getMethods = new HashMap<>();//保存属性相关的get方法
    // 4. get返回值/set入参
    private final Map<String, Class<?>> setTypes = new HashMap<>();//保存属性相关的set方法入参类型
    private final Map<String, Class<?>> getTypes = new HashMap<>();//保存属性相关的get方法返回类型
    // 5. 构造器
    private Constructor<?> defaultConstructor;//class默认的构造函数

    // 6. 记录所有属性的名称集合
    private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();
    
    // 通过反射去创建get方法的MethodInvoker
    private void addGetMethod(String name, Method method) {
        if (isValidPropertyName(name)) {
            getMethods.put(name, new MethodInvoker(method));
            Type returnType = TypeParameterResolver.resolveReturnType(method, type);
            getTypes.put(name, typeToClass(returnType));
        }
    }

    private void addSetMethods(Class<?> cls) {
        Map<String, List<Method>> conflictingSetters = new HashMap<>();
        Method[] methods = getClassMethods(cls);
        for (Method method : methods) {
            String name = method.getName();
            if (name.startsWith("set") && name.length() > 3) {
                if (method.getParameterTypes().length == 1) {
                    name = PropertyNamer.methodToProperty(name);
                    addMethodConflict(conflictingSetters, name, method);
                }
            }
        }
        resolveSetterConflicts(conflictingSetters);
    }

6. DefaultReflectorFactory

package org.apache.ibatis.reflection;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

// reflect的简单工厂类
public class DefaultReflectorFactory implements ReflectorFactory {
    private boolean classCacheEnabled = true;
    //用于缓存pojo的元数据,避免反射性能较低的问题
    private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();

    public DefaultReflectorFactory() {
    }

    @Override
    public boolean isClassCacheEnabled() {
        return classCacheEnabled;
    }

    @Override
    public void setClassCacheEnabled(boolean classCacheEnabled) {
        this.classCacheEnabled = classCacheEnabled;
    }

    @Override
    public Reflector findForClass(Class<?> type) {
        if (classCacheEnabled) {
            // synchronized (type) removed see issue #461
            return reflectorMap.computeIfAbsent(type, Reflector::new);
        } else {
            return new Reflector(type);
        }
    }

}
测试用例
@Test
public void reflectionTest() {

    //反射工具类初始化
    ObjectFactory objectFactory = new DefaultObjectFactory();
    TUser user = objectFactory.create(TUser.class);
    ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
    ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
    MetaObject metaObject = MetaObject.forObject(user, objectFactory, objectWrapperFactory, reflectorFactory);

    //使用Reflector读取类元信息
    Reflector findForClass = reflectorFactory.findForClass(TUser.class);
    Constructor<?> defaultConstructor = findForClass.getDefaultConstructor();
    String[] getablePropertyNames = findForClass.getGetablePropertyNames();
    String[] setablePropertyNames = findForClass.getSetablePropertyNames();
    System.out.println(defaultConstructor.getName());
    System.out.println(Arrays.toString(getablePropertyNames));
    System.out.println(Arrays.toString(setablePropertyNames));

    //使用ObjectWrapper读取对象信息,并对对象属性进行赋值操作
    TUser userTemp = new TUser();
    ObjectWrapper wrapperForUser = new BeanWrapper(metaObject, userTemp);
    String[] getterNames = wrapperForUser.getGetterNames();
    String[] setterNames = wrapperForUser.getSetterNames();
    System.out.println(Arrays.toString(getterNames));
    System.out.println(Arrays.toString(setterNames));

    PropertyTokenizer prop = new PropertyTokenizer("userName");
    wrapperForUser.set(prop, "lison");
    System.out.println(userTemp);
}

pojo没有按照规范生成getset方法, 同样会生成对应的方法

调试的核心代码入口

public Reflector(Class<?> clazz) {
    type = clazz;
    addDefaultConstructor(clazz);//获取clazz的默认构造函数
    addGetMethods(clazz);//处理clazz中的get方法信息,填充getMethods、getTypes
    addSetMethods(clazz);//处理clazz中的set方法信息,填充setMethods、setTypes
    addFields(clazz);//处理没有get、set方法的属性
    //根据get、set方法初始化可读属性集合和可写属性集合
    readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
    writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
    //初始化caseInsensitivePropertyMap
    for (String propName : readablePropertyNames) {
        caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
    for (String propName : writeablePropertyNames) {
        caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
}

生成set方法的核心类

public class SetFieldInvoker implements Invoker {
    private final Field field;

    public SetFieldInvoker(Field field) {
        this.field = field;
    }

    @Override
    public Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
        field.set(target, args[0]);
        return null;
    }

    @Override
    public Class<?> getType() {
        return field.getType();
    }
}

7. BeanWrapper

用例给pojo赋值的对象

public class BeanWrapper extends BaseWrapper {

    private final Object object;//实例对象
    private final MetaClass metaClass;//类的元数据

    public BeanWrapper(MetaObject metaObject, Object object) {
        super(metaObject);
        this.object = object;
        this.metaClass = MetaClass.forClass(object.getClass(), metaObject.getReflectorFactory());
    }
    
    @Override
    public void set(PropertyTokenizer prop, Object value) {
        if (prop.getIndex() != null) {
            Object collection = resolveCollection(prop, object);
            setCollectionValue(prop, collection, value);
        } else {
            setBeanProperty(prop, object, value);
        }
    }

CustomObjectWrapperFactory

public class CustomObjectWrapperFactory implements org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory {

    @Override
    public boolean hasWrapperFor(Object object) {
        return object.getClass().equals(CustomCollection.class);
    }

    @Override
    public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) {
        return new org.apache.ibatis.submitted.custom_collection_handling.CustomObjectWrapper((CustomCollection)object);
    }

}

8. MetaObject

封装了对象元信息,包装了MyBatis中五个核心的反射类。也是提供给外部使用的反射工具类,可以利用它可以读取或者修改对象的属性信息;

public class MetaObject {

    //原始的java对象
    private final Object originalObject;
    //对对象的包装,抽象了对象的属性信息,他定义了一系列查询对象属性信息的方法,以及更新属性的方法;
    private final ObjectWrapper objectWrapper;
    private final ObjectFactory objectFactory;
    private final ObjectWrapperFactory objectWrapperFactory;
    private final ReflectorFactory reflectorFactory;

使用例子

{
    //模拟数据库行数据转化成对象
        ObjectFactory objectFactory = new DefaultObjectFactory();
        TUser user = objectFactory.create(TUser.class);
        MetaObject metaObject = SystemMetaObject.forObject(user);

        //1.模拟从数据库读取数据
        Map<String, Object> dbResult = new HashMap<>();
        dbResult.put("id", 1);
        dbResult.put("userName", "lison");
        dbResult.put("realName", "李晓宇");
        TPosition tp = new TPosition();
        tp.setId(1);
        dbResult.put("position_id", tp);
        //2.模拟映射关系
        Map<String, String> mapper = new HashMap<String, String>();
        mapper.put("id", "id");
        mapper.put("userName", "userName");
        mapper.put("realName", "realName");
        mapper.put("position", "position_id");

        //3.使用反射工具类将行数据转换成pojo

        //获取BeanWrapper,既包括类元数据,同时还能对对象的属性赋值
        BeanWrapper objectWrapper = (BeanWrapper)metaObject.getObjectWrapper();

        Set<Entry<String, String>> entrySet = mapper.entrySet();
        //遍历映射关系
        for (Entry<String, String> colInfo : entrySet) {
            String propName = colInfo.getKey();//获得pojo的字段名称
            Object propValue = dbResult.get(colInfo.getValue());//模拟从数据库中加载数据对应列的值
            PropertyTokenizer proTokenizer = new PropertyTokenizer(propName);
            objectWrapper.set(proTokenizer, propValue);//将数据库的值赋值到pojo的字段中
        }
        System.out.println(metaObject.getOriginalObject());
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

岁月玲珑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值