1. mybatis的骨架
MyBatis整体架构
面向sqlsession编程
为什么分层: 提供可读性, 方便团队开发, 提高系统的伸缩性
门面模式: 为了遵循迪米特法则
springmvc的dispatchservelt也是一个门面模式
面向接口编程好处: 实现变化,我们使用的客户端不需要变化, 实现和使用解耦
开闭原则: 需求变化, 通过新增实现, 不要修改代码
迪米特法则: 尽量降低类之间的耦合
1. 日志模块
-
MyBatis没有提供日志的实现类,需要接入第三方的日志组件,但第三方日志组件都有各自的Log级别,且各不相同,而MyBatis统一提供了trace、debug、warn、error四个级别;
-
自动扫描日志实现,并且第三方日志插件加载优先级如下:slf4J → commonsLoging → Log4J2 → Log4J → JdkLog;
-
日志的使用要优雅的嵌入到主体功能中;
适配器模式
日志模块的适配器模式类图
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);
}
代理模式
给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用
- 屏蔽不必要的复杂性, 2. 给目标代码增强
静态代理会导致代理类的膨胀
Proxy(生成代理类), InvocationHandler(业务增强)
日志模块模式类图
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
会发现入口方法是: org.apache.ibatis.executor.SimpleExecutor#doQuery
所以默认的执行器是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打一个断点吧, 后面调试一定会停下来
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
@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代码
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接口
...
}
public class Log4jImpl implements Log { // 适配成新产品的接口类: mybatis定义的接口类Log
private static final String FQCN = Log4jImpl.class.getName();
private final Logger log; // 原来产品的接口类: log4j的接口类 Logger, 被复用的代理
...
}
总结: 使用了适配器模式和代理模式
2. 数据源模块分析
数据源模块重点数据源的创建和数据库连接池的源码分析;数据源创建比较复杂,对于复杂对象的创建,可以考虑使用工厂模式来优化,接下来介绍下简单工厂模式和工厂模式;
简单工厂模式和工厂模式略
1. 数据源的创建
数据源对象是比较复杂的对象,其创建过程相对比较复杂,对于 MyBatis 创建一个数据源, 具体来讲有如下难点:
-
常见的数据源组件都实现了 javax.sql.DataSource 接口;
-
MyBatis 不但要能集成第三方的数据源组件,自身也提供了数据源的实现;
-
一般情况下,数据源的初始化过程参数较多,比较复杂;
综上所述,数据源的创建是一个典型使用工厂模式的场景
- 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(): ** 获取连接资源
回收资源参考代码:
org.apache.ibatis.datasource.pooled.PooledDataSource.pushConnection(PooledConnection)
org.apache.ibatis.datasource.pooled.PooledConnection#isValid
检测状态和ping连接
public boolean isValid() {
return valid && realConnection != null && dataSource.pingConnection(this);
}
3. 缓存模块分析
基于map实现的
除核心功能之外,有很多额外的附加功能,如:防止缓存击穿,添加缓存清空策略(fifo、lru)、序列化功能、日志能力、定时清空能力等;
怎么样优雅的为核心功能添加多种附加能力?使用动态代理或继承的办法扩展多种附加功能?
这些方式是静态的,用户不能控制增加行为的方式和时机。另外,新功能的存在多种组合,使用继承可能导致大量子类存在;
优化思路:装饰器模式是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀;
装饰器模式类图:
mybatis 的缓存装饰模式类图
Cache:Cache接口是缓存模块的核心接口,定义了缓存的基本操作;
PerpetualCache:在缓存模块中扮演ConcreteComponent角色,使用HashMap来实现cache的相关操作;
BlockingCache:阻塞版本的缓存装饰器,保证只有一个线程到数据库去查找指定的key对应的数据;
缓存雪崩问题
- 当缓存失效, 竞争锁然后查询数据库(粗粒度锁和细粒度锁)
- 不同失效时间
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);
}
}
二级缓存类打断点
被装饰的实例图
其中SynchronozedCache没有做任何逻辑补充, 只是对每个方法加了Synchronized关键字, 为了同步控制
其中典型的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框架查询数据过程
- 实例化对象是 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反射模块的核心类图
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
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());
}