Mybatis-01 原理

一. JDBC式编程

在 jdbc 编程中,我们最常用的是 PreparedStatement 式的编程,我们看下面这个例子;

Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;try {// 1. 注册驱动Class.forName("com.mysql.jdbc.Driver");// 2. 获取连接String url = "jdbc:mysql://localhost:3306/zhuce?serverTimezone=UTC";String user = "root";String password = "19991121zq";conn = DriverManager.getConnection(url, user, password);String sql = "select * from t_user order by id ?";// 3. 获取数据库连接对象ps = conn.prepareStatement(sql);ps.setString(1, keyWords);// 4. 执行 sql 语句rs = ps.executeQuery();// 5. 处理查询结果集while(rs.next()){System.out.println(rs.getInt("id"));}
} catch (ClassNotFoundException e) {e.printStackTrace();
} catch (SQLException throwables) {throwables.printStackTrace();
} finally {//6.关闭资源if (rs != null) {try {rs.close();} catch (SQLException throwables) {throwables.printStackTrace();}}if (ps != null) {try {rs.close();} catch (SQLException throwables) {throwables.printStackTrace();}}if (conn != null) {try {rs.close();} catch (SQLException throwables) {throwables.printStackTrace();}}
}

二. 解析Configuration

如果我们的 mybatis 配置文件使用 xml 的方式,没有使用 Spring yaml 配置项的形式,可以通过 XMLMapperBuilder 来看解析 Configuration 的过程;

//---------------------------------XMLConfigBuilder----------------------------
public Configuration parse() {// 确保每个 xml 配置文件只被解析一次if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;// 解析 xml 配置文件中的根节点: configurationparseConfiguration(parser.evalNode("/configuration"));return configuration;
}//---------------------------------XMLConfigBuilder-----------------------------
private void parseConfiguration(XNode root) {try {// 可以看出顺序是固定好的propertiesElement(root.evalNode("properties"));Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(settings);loadCustomLogImpl(settings);typeAliasesElement(root.evalNode("typeAliases"));// 解析 plugins 标签pluginElement(root.evalNode("plugins"));// 解析 MetaObject 中会使用到的几个 Factory,了解objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));reflectorFactoryElement(root.evalNode("reflectorFactory"));settingsElement(settings);// 解析 environments 标签environmentsElement(root.evalNode("environments"));databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));// 解析 mappers 标签// 我们主要看此处mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException();}
}

1. 解析mappers

我们为什么要重点看解析 mappers 的地方,因为这里有一个很重要的对象 MappedStatement,我们在 Executor 中全程都会使用到该对象;

我们简单看下:

//-----------------------------XMLMapperBuilder-----------------------------
private void configurationElement(XNode context) {try {String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);cacheRefElement(context.evalNode("cache-ref"));cacheElement(context.evalNode("cache"));parameterMapElement(context.evalNodes("/mapper/parameterMap"));resultMapElements(context.evalNodes("/mapper/resultMap"));sqlElement(context.evalNodes("/mapper/sql"));// 解析 mapper 标签中的 select|insert|update|delete 标签// 会把每个 select、update 等解析成一个个 MappedStatement 对象buildStatementFromContext(context.evalNodes("select|insert|update|delete"));} catch (Exception e) {//...}
}//-----------------------------XMLMapperBuilder-----------------------------
private void buildStatementFromContext(List<XNode> list) {if (configuration.getDatabaseId() != null) {buildStatementFromContext(list, configuration.getDatabaseId());}buildStatementFromContext(list, null);
}//-----------------------------XMLMapperBuilder-----------------------------
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context);try {// 开始解析 select、update、insert 成 MappedStatementstatementParser.parseStatementNode();} catch (IncompleteElementException e) {configuration.addIncompleteStatement(statementParser);}}
}

我们以如下 StudentMapper.xml 为例来看 MappedStatement 的生成;

该 select 标签的 id 为 “selectStudent”,namespace 为 “com.bjpowernode.dao.StudentDao”;

<mapper namespace="com.bjpowernode.dao.StudentDao"><select id="selectStudent" resultType="com.bjpowernode.domain.Student">select id,name,email,age from student where id=#{id}</select></mapper>

解析如上的 select 标签如下:

// --------------------------- XMLStatementBuilder -------------------------
public void parseStatementNode() {// 1. 此时该 id = "selectStudent"String id = context.getStringAttribute("id");// ...// 2. 生成 SqlSource 对象// 该 sqlSource 包含了原始sql、参数映射等SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");String parameterMap = context.getStringAttribute("parameterMap");String resultType = context.getStringAttribute("resultType");Class<?> resultTypeClass = resolveClass(resultType);String resultMap = context.getStringAttribute("resultMap");String resultSetType = context.getStringAttribute("resultSetType");ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");String resultSets = context.getStringAttribute("resultSets");// 3. 调用 builderAssistant 协助创建 MappedStatement 对象builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}// --------------------------- XMLStatementBuilder -------------------------
public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout,String parameterMap, Class<?> parameterType, String resultMap,Class<?> resultType, ResultSetType resultSetType, boolean flushCache,boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator,String keyProperty, String keyColumn, String databaseId,LanguageDriver lang, String resultSets) {// 1. 重新生成 id// 此时的 id = id + namespace// 也就是 id = "com.bjpowernode.dao.StudentDao.selectStudent"id = applyCurrentNamespace(id, false);boolean isSelect = sqlCommandType == SqlCommandType.SELECT;MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType).resource(resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired(valueOrDefault(flushCache, !isSelect)).useCache(valueOrDefault(useCache, isSelect)).cache(currentCache);ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);if (statementParameterMap != null) {statementBuilder.parameterMap(statementParameterMap);}// 2. 创建出 MappedStatement 对象MappedStatement statement = statementBuilder.build();// 3. 将当前 MappedStatement 对象加入到 configuration 配置类中configuration.addMappedStatement(statement);return statement;
}// ----------------------------- Configuration --------------------------------
public void addMappedStatement(MappedStatement ms) {// 将 MappedStatement 对象放入到 configuration 中的 mappedStatements 中// mappedStatements 是一个 Map 对象,可以看到 key 是 MappedStatement.idmappedStatements.put(ms.getId(), ms);
}

可以把 MappedStatement 认为是一条 select、insert、update 等标签的映射对象;

我们看下 SqlSource 和 MappedStatement 对象,MappedStatement 对象中聚合了 SqlSource 对象;共同点是两个对象中都聚合了 Configuration 对象

SqlSource;

在这里插入图片描述

MappedStatement;

在这里插入图片描述

三. Executor执行器

1. Executor执行器的种类

对于 Executor,我们需要知道一点,SqlSession 只是门面而已,它聚合了一个 Executor 对象,真正执行 sql 的是 Executor 而非 SqlSession;

  • CachingExecutor:处理二级缓存,采用了装饰者模式;CachingExecutor 也实现了 Executor 接口,并且内部聚合了一个 BaseExecutor 的实现类;
  • BaseExecutor:处理一级缓存,它是一个抽象类,其他的 Executor 都是继承自该类;
    • 该类定义了一个抽象方法如 query(),真正执行查询逻辑的是子类的 doQuery();
    • 该类最大的作用就是用来执行一级缓存的查询,该执行器聚合了一个一级缓存 Cache;
  • SimpleExecutor:简单执行器,默认使用的是这个执行器,我们重点关注这个;
  • ReuseExecutor:可重用的执行器;
  • BatchExecutor:批处理的执行器;

类图如下:

在这里插入图片描述

2. Executor和SqlSession的对应关系

一个 SqlSession 对应一个 Executor;每创建一个 SqlSession,都会创建一个对应的 Executor 对象;

这时为啥呢?我们直接看 DefaultSqlSessionFactory.openSession() 创建 SqlSession 对象的方法就知道了;

// ------------------------ DefaultSqlSessionFactory -------------------------
public SqlSession openSession(ExecutorType execType) {return openSessionFromDataSource(execType, null, false);
}// ------------------------ DefaultSqlSessionFactory -------------------------
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Environment environment = configuration.getEnvironment();TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);Transaction tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);// 1. 通过 configuration 创建出 Executor 对象Executor executor = configuration.newExecutor(tx, execType);// 2. 根据 executor 创建出 SqlSession 对象return new DefaultSqlSession(configuration, executor, autoCommit);
}// ----------------------------- Configuration -------------------------------
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Executor executor;// 1. 根据 executorType 的类型创建出不同的 Executor 对象if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}// 如果开启了二级缓存的话,还会包装一层,包装为 CachingExecutorif (cacheEnabled) {executor = new CachingExecutor(executor);}// 2. 如果需要插件增强的话,给 executor 做一层增强,并返回增强后的 executor 对象executor = (Executor) interceptorChain.pluginAll(executor);return executor;
}

四. 一级缓存

1. 一级缓存PerpetualCache

一级缓存是在 BaseExecutor 类中作为成员变量存在的

// ------------------------- BaseExecutor -----------------------------
public abstract class BaseExecutor implements Executor {private static final Log log = LogFactory.getLog(BaseExecutor.class);protected Transaction transaction;protected Executor wrapper;protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;// localCache 就是我们常说的一级缓存// 可以看到它是一个 PerpetualCache 类对象protected PerpetualCache localCache;protected PerpetualCache localOutputParameterCache;protected Configuration configuration;protected int queryStack;private boolean closed;// ...
}

我们看下这个 PerpetualCache 类;

可以看到它内部有一个 HashMap,非常简单,Mybatis 的一级缓存可以看做就是一个简单的 HashMap;

public class PerpetualCache implements Cache {private final String id;private Map<Object, Object> cache = new HashMap<>();public PerpetualCache(String id) {this.id = id;}@Overridepublic void putObject(Object key, Object value) {cache.put(key, value);}@Overridepublic Object getObject(Object key) {return cache.get(key);}@Overridepublic Object removeObject(Object key) {return cache.remove(key);}@Overridepublic void clear() {cache.clear();}
}

2. 一级缓存的命中场景

一级缓存生效的几个条件,其实和 CacheKey 有关:

1、mappedStatementId 必须一样;

2、sql 必须一样;

3、RowBounds.offset 分页 offset 必须一样;

4、RowBounds.limit 分页 limit 必须一样;

5、查询参数 param 必须一样;

6、enviroment 环境参数必须一样,Mybatis 配置文件里的 enviroment,一般都是唯一值;

举例如下:

public static void test1(){StudentDao mapper = sqlSession.getMapper(StudentDao.class);// 1. sql和参数相同Student student = mapper.selectStudent(1001);Student student1 = mapper.selectStudent(1001);System.out.println(student == student1);     //true// 2. 必须是相同的 statementId//  com.bjpowernode.dao.StudentDao.selectById//  com.bjpowernode.dao.StudentDao.selectById3Student student2 = mapper.selectById(1001);Student student3 = mapper.selectById3(1001);System.out.println(student2 == student3);    //false// 3. sqlSession 必须一样// 严格来说应该是 sqlSession 里的 Executor 必须一样// 因为一级缓存 cache 是在 Executor 里的// 调用 openSession() 会创建出新的 DefaultSqlSession 和 Executor 对象Student s4 = mapper.selectById(1001);Student s5 = factory.openSession().getMapper(StudentDao.class).selectById3(1001);System.out.println(s4 == s5);                //false// 4. RowBounds 返回行的范围必须相同// RowBounds 的默认值是:RowBounds.DEFAULTStudent student6 = mapper.selectById(1001);String statementId = "com.bjpowernode.dao.StudentDao.selectById";RowBounds rowBounds = new RowBounds(0, 10);List<Student> stus = sqlSession.selectList(statementId, 1001, rowBounds);System.out.println(student6 == stus.get(0));  //false
}

2.1 CacheKey

我们上面讲的缓存命中场景,其实都是和 CacheKey 有关的,下面我们简单看下这个生成 CacheKey 类对象的地方;

// ------------------------------ BaseExecutor --------------------------------
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameter);// 生成 CacheKey 对象CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}// ------------------------------ BaseExecutor --------------------------------
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {CacheKey cacheKey = new CacheKey();// 1. mappedStatementIdcacheKey.update(ms.getId());// 2. RowBounds.offsetcacheKey.update(rowBounds.getOffset());// 3. RowBounds.limitcacheKey.update(rowBounds.getLimit());// 4. sqlcacheKey.update(boundSql.getSql());// ....// 5. 参数 paramsif (configuration.getEnvironment() != null) {// 6. environmentcacheKey.update(configuration.getEnvironment().getId());}return cacheKey;
}

可以看到 CacheKey 和 6 个变量的值有关,我们举一个例子看看:

在这里插入图片描述

3. 一级缓存的失效场景

一级缓存的失效,大致有以下几种场景:

1、手动清空了一级缓存 cache:sqlSession 的 clearCache()、commit()、rollback() 都会清空一级缓存;

2、执行了 sqlSession.update(),只要执行了 sqlSession.update(),都会清空一级缓存,因为 update() 方法中会做一个清空一级缓存的动作;

3、全局将一级缓存的作用域改为了 STATEMENT,默认是 SESSION 级别的,也就是默认是 SqlSession 级别的;了解即可;

举例如下:

public static void test2(){StudentDao mapper = sqlSession.getMapper(StudentDao.class);// 1. 手动清空一级缓存Student student = mapper.selectById(1001);sqlSession.clearCache();// sqlSession.commit();// sqlSession.rollback();Student student1 = mapper.selectById(1001);System.out.println(student == student1);    // false// 2.执行了 sqlSession.update()// 只要执行过 update 语句,都会清空当前一级缓存Student student2 = mapper.selectById3(1001);mapper.updateStudent(1002,"zq");Student student3 = mapper.selectById3(1001);System.out.println(student2 == student3;    // false
}

4. 一级缓存的调用流程

从上面我们知道一级缓存是在 BaseExecutor 中的,我们直接看 BaseExecutor 的 query();

// ------------------------------ BaseExecutor --------------------------------
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {if (queryStack == 0 && ms.isFlushCacheRequired()) {// 如果当前为第一次调用,且当前 MappedStatement 需要清空缓存,则清空一级缓存// queryStack 是用于嵌套子查询的,后面会讲clearLocalCache();}List<E> list;try {queryStack++;// 1. 先去一级缓存中查,如果缓存中有值直接返回list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list != null) {handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {// 2. 一级缓存中没有值,去查数据库list = queryFromDatabase(ms,parameter,rowBounds,resultHandler,key, boundSql);}} finally {queryStack--;}if (queryStack == 0) {if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {// 如果当前的缓存范围是 STATEMENT, 执行完查询后清空缓存clearLocalCache();}}return list;
}// ------------------------------ BaseExecutor --------------------------------
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List<E> list;// 1. 将 key 和对应的占位符放入一级缓存localCache.putObject(key, EXECUTION_PLACEHOLDER);try {// 2. 调用 doQuery() 获取查询结果list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {// 3. 将 key 对应的占位符移除localCache.removeObject(key);}// 4. 将查询结果放入一级缓存localCache.putObject(key, list);// 5. 返回查询结果return list;
}// ------------------------------ BaseExecutor --------------------------------
public int update(MappedStatement ms, Object parameter) throws SQLException {// 1. 先清空一级缓存// 为什么说执行了 sqlSession.update() 一级缓存会失效,因为会先清空一级缓存clearLocalCache();// 2. 执行 doUpdate() 并返回值return doUpdate(ms, parameter);
}

五. 二级缓存

二级缓存是在 CachingExecutor 中的,二级缓存和一级缓存不一样,二级缓存需要开启才会使用;

我们暂时不去看二级缓存,了解即可;

六. StatementHandler

1. 种类和结构

StatementHandler 的种类和结构大致如下:

我们主要看 PreparedStatementHandler 这个对象:创建 PreparedStatement、设置参数,执行 sql 都是咋 StatementHandler 中执行的;

在这里插入图片描述

2. 分析

我们直接进入到 SimpleExecutor 的 doQuery();

// ------------------------- SimpleExecutor -------------------------------
public <E> List<E> doQuery(MappedStatement ms, Object parameter,RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();// 1. 创建出 StatementHandler 对象StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);// 2. 得到预编译对象 PreparedStatement// 该方法还会进行参数的映射和设置stmt = prepareStatement(handler, ms.getStatementLog());// 3. 通过 statementHandler.query() 执行 sqlreturn handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}
}

下面我们对 doQuery() 中三个步骤做简要分析;

2.1 创建出StatementHandler对象

通过 Configuration 对象创建出 StatementHandler 对象;

// ------------------------- Configuration -------------------------------
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {// 1. 创建出 RoutingStatementHandler// 这里又是装饰器模式// 其实 RoutingStatementHandler 中真正作用的是 PreparedStatementHandlerStatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);// 2. 插件增强statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;
}// ------------------------- RoutingStatementHandler -----------------------------
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {switch (ms.getStatementType()) {case STATEMENT:delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case PREPARED:// 默认创建的就是 PreparedStatementHandler 对象// 这里又是装饰器模式delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case CALLABLE:delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;default:throw new ExecutorException("Unknown statement type");}}

2.2 得到PreparedStatement对象

我们看一下 prepareStatement() 干了啥;

// ------------------------- SimpleExecutor -------------------------------
private Statement prepareStatement(StatementHandler handler, Log statementLog) {Statement stmt;Connection connection = getConnection(statementLog);// 1. 执行 PreparedStatementHandler.prepare() 获得 PreparedStatementHandler 对象stmt = handler.prepare(connection, transaction.getTimeout());// 2. 对参数进行映射处理和设值// 这里后面我们会重点看!!!handler.parameterize(stmt);return stmt;
}// ------------------------- BaseStatementHandler -------------------------------
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {ErrorContext.instance().sql(boundSql.getSql());Statement statement = null;try {// 实例化 statement 对象statement = instantiateStatement(connection);setStatementTimeout(statement, transactionTimeout);setFetchSize(statement);return statement;} catch (Exception e) {closeStatement(statement);throw new ExecutorException("Error preparing statement.  Cause: " + e, e);}
}// ------------------------- PreparedStatementHandler -----------------------------
protected Statement instantiateStatement(Connection connection) throws SQLException {String sql = boundSql.getSql();if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {// ...} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {// 直接执行 connection.prepareStatement(sql)// 预编译 sql 得到 PreparedStatement 对象// 这里就是我们 jdbc 中预编译得到 PreparedStatement 的方式return connection.prepareStatement(sql);} else {// ...}
}

2.3 执行statementHandler.query()

我们简单看下 statementHandler.query() 干了啥;

public <E> List<E> query(Statement statement, ResultHandler resultHandler) {PreparedStatement ps = (PreparedStatement) statement;// 1. 直接执行 ps.execute()ps.execute();// 2. 通过 resultSetHandler 处理结果集return resultSetHandler.handleResultSets(ps);
}

七. Mapper代理类对象

1. UserMapper分析

Spring 为我们生成的 UserMapper 是一个代理类;我们在分析 Mybatis 集成 Spring 后一级缓存失效的场景中已经给 UserMapper 做过一个分析了,我们直接看 UserMapper 的结构;

1、这个 UserMapper 是一个代理类,它的 InvocationHandler 是 MapperProxy 类;

2、MapperProxy 内聚合了一个 SqlSessionTemplate 类,SqlSessionTemplate 实现了 SqlSession 类;

3、MapperProxy 的 invoke() 最终会调用目标类 sqlSessionTemplate 的方法;

4、SqlSessionTemplate 内聚合了一个 sqlSessionProxy,sqlSessionProxy 也是一个代理类,它实现了 SqlSession 类,它的 invocationHandler 是 SqlSessionInterceptor 类;

在这里插入图片描述

类图分析如下,其实代理的目标类是 SqlSession,也就是说干活的类是 SqlSession;

在这里插入图片描述

需要注意两个 InvocationHandler:MapperProxy 和 SqlSessionInterceptor,SqlSessionInterceptor 我们在一级缓存失效的场景中分析过了,这次我们重点看 MapperProxy;

2. MapperProxy

单看 MapperProxy 这个类名,可能认为它是一个代理类对象,其实它是一个 InvocationHandler 对象,我们看下它的 invoke();

// ------------------------------- MapperProxy ---------------------------------
public class MapperProxy<T> implements InvocationHandler {private static final long serialVersionUID = -6424540398559729838L;private final SqlSession sqlSession;private final Class<T> mapperInterface;private final Map<Method, MapperMethod> methodCache;public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {this.sqlSession = sqlSession;this.mapperInterface = mapperInterface;this.methodCache = methodCache;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) {try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else if (isDefaultMethod(method)) {return invokeDefaultMethod(proxy, method, args);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}// 1. 创建或从缓存中得到 MapperMethod 对象final MapperMethod mapperMethod = cachedMapperMethod(method);// 2. 执行 mapperMethod.execute()return mapperMethod.execute(sqlSession, args);}
}

主要是执行 MapperMethod.execute(sqlSession, args);

我们先看下 MapperMethod 的结构,其中 paramNameResolver 用于解析参数对象,后面我们会重点看;

在这里插入图片描述

mapperMethod.execute() 如下:

// ------------------------------- MapperMethod ---------------------------------
public Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) {case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));break;}case UPDATE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.update(command.getName(), param));break;}case DELETE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.delete(command.getName(), param));break;}case SELECT:if (method.returnsVoid() && method.hasResultHandler()) {// ...} else {// 我们直接看这个地方// 1. 处理参数 Object[] args,得到处理后的参数 Object paramObject param = method.convertArgsToSqlCommandParam(args);// 2. 调用 sqlSession.selectxxx() 得到 result// 可以看到参数已经是我们上面得到的 param,不再是 args 了result = sqlSession.selectOne(command.getName(), param);if (method.returnsOptional()&& (result == null || !method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);}}break;case FLUSH:result = sqlSession.flushStatements();break;default:throw new BindingException();}if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {throw new BindingException();}return result;
}// ------------------------------- MethodSignature ---------------------------------
public Object convertArgsToSqlCommandParam(Object[] args) {// 调用 paramNameResolver.getNamedParams(args) 解析得到新的参数对象return paramNameResolver.getNamedParams(args);
}

参数解析是在 ParamNameResolver 中做的,我们重点看下 ParamNameResolver;

2.1 参数解析ParamNameResolver

2.1.1 构造函数

我们先看下它的构造函数;

// ------------------------- ParamNameResolver ---------------------------
public ParamNameResolver(Configuration config, Method method) {final Class<?>[] paramTypes = method.getParameterTypes();final Annotation[][] paramAnnotations = method.getParameterAnnotations();final SortedMap<Integer, String> map = new TreeMap<>();int paramCount = paramAnnotations.length;// 轮询解析方法中的参数for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {if (isSpecialParameter(paramTypes[paramIndex])) {continue;}String name = null;// 1. 如果该参数前有 @Param 注解,name 值为 @Param 注解的 value 值for (Annotation annotation : paramAnnotations[paramIndex]) {if (annotation instanceof Param) {// 方法参数上有 @Param 注解的情况下,hasParamAnnotation 置为 truehasParamAnnotation = true;name = ((Param) annotation).value();break;}}// 2. 如果参数前没有 @Param 注解if (name == null) {// 3. 默认情况下 Mybatis 中 config 的 useActualParamName 默认值为 true// 所以默认情况下 name 值为方法参数名if (config.isUseActualParamName()) {name = getActualParamName(method, paramIndex);}if (name == null) {name = String.valueOf(map.size());}}// map 中值的形式如 {{0, "id"}, {1, "name"}}map.put(paramIndex, name);}// 4. names 也是一个 Map,所以 names 中值的形式如 {{0, "id"}, {1, "name"}}names = Collections.unmodifiableSortedMap(map);
}

names 的值跟有无 @Param 注解有关,我们可以看如下场景中 names 的值;

1、没有 @Param 注解,但是给 Java 加上了 -parameters,names 值为:{{0, “id”}, {1, “username”}}

2、没有 @Param 注解,没有给 Java 加上了 -parameters,names 值为:{{0, “arg0”}, {1, “arg1”}}

User selectByIdAndName(int id, String username);

3、有 @Param 注解,names 值为:{{0, “id”}, {1, “name”}}

User selectByIdAndName(int id, @Param("name") String username);
2.1.2 参数解析

我们再看它的参数解析方法:getNamedParams();

// ------------------------- ParamNameResolver ---------------------------
public Object getNamedParams(Object[] args) {int paramCount = names.size();if (args == null || paramCount == 0) {return null;} else if (!hasParamAnnotation && paramCount == 1) {// 1. 如果方法参数没有 @Param 注解,且只有一个参数// 直接返回该参数,不做任何处理return args[names.firstKey()];} else {// 2. 此时,参数前有 @Param 注解 || 参数不止一个// 创建一个 Map<String, Object> param,该 Map 的类型为 ParamMapfinal Map<String, Object> param = new ParamMap<>();int i = 0;// 3. 遍历我们构造函数中得到的 names, 构造 paramfor (Map.Entry<Integer, String> entry : names.entrySet()) {// 3.1 给 param 添加值// {names.entry.value0, arg[0]}, {names.entry.value1, arg[1]}param.put(entry.getValue(), args[entry.getKey()]);// 3.2 再往 param 中添加 {"param1":args[0]}, {"param2":args[1]}String genericParamName = "param" + String.valueOf(i + 1);if (!names.containsValue(genericParamName)) {param.put(genericParamName, args[entry.getKey()]);}i++;}// 4. 该情况下,返回得到的 param 对象,该 param 是一个 Map 对象return param;}
}

param 的值跟有无 @Param 注解有关,我们可以看如下场景中 param 的值;

我们默认没有给 Java 加上 -parameters 参数,看 param 的值:

1、当参数有 @Param 注解标注;此时 param 的 key 列表为 id、name、param1、param2;

// 此时 param 的 key 列表为 id, name, param1, param2
@Select("select * from student where id=#{id} and name=#{name}")
public Student selectByIdAndName(@Param("id") int id, @Param("name") String name);

2、当参数没有 @Param 注解标注;此时 param 的 key 列表为 arg0、arg1、param1、param2;

// 此时 param 的 key 列表为 arg0, arg1, param1, param2
@Select("select * from student where id=#{param1} and name=#{param2}")
public Student selectByIdAndName(int id, String name);

3、当参数不全有 @Param 注解标注(了解即可,一般我们不会写这么脑瘫的写法);

// 此时 param 的 key 列表为 id, arg1, param1, param2
@Select("select * from student where id=#{id} and name=#{param2}")
public Student selectByIdAndName(@Param("id") int id, String name);

4、当只有一个参数时,且没有 @Param 注解,显然 getNamedParams() 会直接返回该参数;

// 此时参数解析结果就是原参数 Student 对象
// 很显然不能使用 #{stu.id},因为根本没有 stu 这个索引,只能使用 #{id}
@Select("select * from student where id=#{id}")
public Student selectByStudentId2(Student stu);

5、当只有一个参数时,且有 @Param 注解;此时 param 的 key 列表为 stu、param1;

// 此时的参数解析结果是 param 对象:{"stu": student, "param1": student}
// 很显然如果使用 #{id} 来取值的话,Mybatis 不知道该用哪个
// 只有我们明确指出 #{stu.id} 才行,或者也可使用 #{param1.id}
@Select("select * from student where id=#{stu.id}")
public Student selectByStudentId(@Param("stu") Student stu);

此时 param 参数如下:

在这里插入图片描述

至此,参数解析完毕;

八. 参数映射处理

我们在第五大点中 prepareStatement() 创建 PreparedStatement 对象中知道,该方法内还会做参数的映射处理;

// ------------------------- SimpleExecutor -------------------------------
private Statement prepareStatement(StatementHandler handler, Log statementLog) {Statement stmt;Connection connection = getConnection(statementLog);// 1. 执行 PreparedStatementHandler.prepare() 获得 PreparedStatementHandler 对象stmt = handler.prepare(connection, transaction.getTimeout());// 2. 对参数进行映射处理handler.parameterize(stmt);return stmt;
}

下面我们对 PreparedStatementHandler.parameterize() 做一个简单的分析;

// ----------------------- PreparedStatementHandler -----------------------------
public void parameterize(Statement statement) throws SQLException {// 调用 parameterHandler.setParameters() 对参数进行映射parameterHandler.setParameters((PreparedStatement) statement);
}

Mybatis 中只有一个默认的 ParameterHandler,是 DefaultParameterHandler,我们看下这个类的 setParameters();

1. DefaultParameterHandler

DefaultParameterHandler 的 setParameters() 流程不长,我们直接往下看;

// ------------------------ DefaultParameterHandler ---------------------------
public void setParameters(PreparedStatement ps) {// 1. 获取 sql 中所有的 parameterMapping// 其实就是我们 sql 中 #{id} 中对应值List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (parameterMappings != null) {// 2. 遍历处理 parameterMappingfor (int i = 0; i < parameterMappings.size(); i++) {ParameterMapping parameterMapping = parameterMappings.get(i);Object value;// 获取 parameterMapping 的属性 propertyString propertyName = parameterMapping.getProperty();if (boundSql.hasAdditionalParameter(propertyName)) {value = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {// 3. parameterObject 就是我们上文解析得到的参数对象// 如果参数只有一个且没有 @Param 的情况下,value 直接等于 parameterObject// 当然这里是简单参数的情况// 如果 parameterObject 是 JavaBean 类型,还是会走到第 4 步去解析参数value = parameterObject;} else {// 4. 参数不止一个的情况,parameterObject 是一个 map 对象// 此时需要通过辅助的 MetaObject 根据 propertyName 获取参数值MetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}// 5. 获取 parameterMapping 的 TypeHandler// 我们在 sql 中一般不指定 TypeHandler// 此处获取的 typeHandler 一般都为 UnknownTypeHandlerTypeHandler typeHandler = parameterMapping.getTypeHandler();JdbcType jdbcType = parameterMapping.getJdbcType();if (value == null && jdbcType == null) {jdbcType = configuration.getJdbcTypeForNull();}try {// 6. 通过 typeHandler.setParameter() 给 sql 中参数进行设值typeHandler.setParameter(ps, i + 1, value, jdbcType);} catch (TypeException | SQLException e) {throw new TypeException("Could not set parameters for mapping");}}}
}// -------------------------- UnknownTypeHandler ----------------------------
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) {// 1. 根据 parameter、jdbcType 获取一个合适的 TypeHandler// 一般 int 对应 IntegerTypeHandler、String 对应 StringTypeHandlerTypeHandler handler = resolveTypeHandler(parameter, jdbcType);// 2. 再次调用 typeHandler.setParameter()// 我们以 IntegerTypeHandler 为例来看handler.setParameter(ps, i, parameter, jdbcType);
}// -------------------------- IntegerTypeHandler ----------------------------
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType) {// 调用我们熟悉的 jdbc 中的 ps.setInt()// TypeHandlerRegistry 中有所有注册的 TypeHandlerps.setInt(i, parameter);}
}

我们举两个例子来看 DefaultParameterHandler.setParameters();

1.1 参数只有一个且没有@Param

parameterObject 是简单类型,此时 value = parameterObject;parameterMappings 只有一个参数;

在这里插入图片描述

1.2 参数有两个

此时 parameterObject 是一个 Map 对象,parameterMappings 也有两个参数,需要轮询进行处理;

处理逻辑其实就是取 parameterObject 中对应 key 的 value 值;

在这里插入图片描述

至此,参数映射完毕;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/40438.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

化身成羊:关于羊的词群探析

在西方的神话故事中&#xff0c;像主神宙斯&#xff0c;或者基督教义中的上帝&#xff0c;通常都有化身成羊的形象。 那为什么会这样呢&#xff1f; 一、什么是神话(myth)&#xff1f; 神话&#xff0c;正式的用词是 mythology&#xff1a; mythology n.神话&#xff1b;神话…

Echarts中的折线图,多个Y轴集中在左侧(在Vue中使用多个Y轴的折线图)

简述&#xff1a;在 ECharts 中&#xff0c;创建一个带有多个 Y 轴的折线图&#xff0c;并且将这些 Y 轴都集中显示在图表的左侧&#xff0c;可以通过合理配置 yAxis 和 series 的属性来实现。简单记录 一. 函数代码 drawCarNumEcs() {// 初始化echarts图表,并绑定到id为"…

网络安全设备——探针

网络安全设备探针是一种专门用于网络安全领域的工具&#xff0c;它通过对网络流量进行监控和分析&#xff0c;帮助发现和防止网络攻击。以下是对网络安全设备探针的详细解释&#xff1a; 定义与功能 定义&#xff1a;网络安全设备探针是一种设备或软件&#xff0c;它通过捕获…

【docker】运行阶段遇到的问题

目录 1、查询docker 下挂载了哪些工具 2、docker中的简单命令 3、实际场景应用&#xff08;redis&#xff09; 目前工作中仅用到了redis,所以没有太多经验可以交流&#xff0c;暂时仅将我目前遇到的进行发布。还请见谅。 1、查询docker 下挂载了哪些工具 docker ps -a 或者…

Vue组件如何“传话”?这里有个小秘诀!

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来vue篇专栏内容:vue-组件通信 目录 Vue组件通信 &#xff08;1&#xff09; props / $emit 1. 父组件向子组件传…

适合职场小白的待办事项管理方法和工具

刚入职场那会儿&#xff0c;我每天都像只无头苍蝇&#xff0c;忙得团团转却效率低下。待办事项像潮水般涌来&#xff0c;会议、报告、客户跟进……每一项都像是悬在头顶的利剑&#xff0c;让我焦虑不堪。我深知&#xff0c;管理好待办事项是职场生存的必修课&#xff0c;但该如…

内衣洗衣机哪个牌子好用?倾力推荐四大热门产品,质量放心

在当今社会&#xff0c;内衣洗衣机已经成为每个家庭必不可少的家电之一。但由于市场上的内衣洗衣机品牌和型号繁多&#xff0c;对于消费者来说&#xff0c;选择一款实用、性价比高的内衣洗衣机是非常重要的。那么&#xff0c;内衣裤洗衣机哪个品牌最好&#xff1f;接下来我将会…

Python | Leetcode Python题解之第206题反转链表

题目&#xff1a; 题解&#xff1a; # Definition for singly-linked list. # class ListNode: # def __init__(self, val0, nextNone): # self.val val # self.next next class Solution:def reverseList(self, head: Optional[ListNode]) -> Optio…

VulnHub靶场之DarkHole_1

1 信息收集 1.1 主机发现 arp-scan -l 主机IP地址为&#xff1a;192.168.1.17 1.2 端口和服务扫描 nmap -sS -sV -A -T5 -p- 192.168.1.17 开放22&#xff0c;80端口 1.3 目录扫描 dirsearch -u 192.168.1.17 2 渗透 2.1 访问端口 2.2 注册账号 暴力破解不现实&#…

Python爬取国家医保平台公开数据

国家医保服务平台数据爬取python爬虫数据爬取医疗公开数据 定点医疗机构查询定点零售药店查询医保机构查询药品分类与代码查询 等等&#xff0c;数据都能爬 接口地址&#xff1a;/ebus/fuwu/api/nthl/api/CommQuery/queryFixedHospital 签名参数&#xff1a;signData {dat…

java版本ERP管理系统源码 Spring Cloud ERP_ERP系统_erp软件_ERP管理系统

在当今数字化时代&#xff0c;企业对高效、稳定且易于扩展的管理系统的需求日益增长。为了满足这一需求&#xff0c;我们精心打造了一款基于Java技术的ERP&#xff08;Enterprise Resource Planning&#xff09;管理系统。该系统充分利用了Spring Cloud Alibaba、Spring Boot、…

python-计算矩阵边缘元素之和(赛氪OJ)

[题目描述] 输入一个整数矩阵&#xff0c;计算位于矩阵边缘的元素之和。 所谓矩阵边缘的元素&#xff0c;就是第一行和最后一行的元素以及第一列和最后一列的元素。输入&#xff1a; 输入共 m 1 行。 第一行包含两个整数 m, n (1 < m,n < 100) &#xff0c;分别为矩阵的…

VDS虚拟导播切换台软件

VDS 导播软件是一款功能强大的虚拟导播系统软件&#xff0c;具有全媒体接入、播出内容丰富、调音台、快捷切播与导播键盘、云台控制等特点&#xff0c;同时支持向多个平台直播推流。以下是一些常见的 VDS 导播软件特点&#xff1a; 1. 全媒体接入&#xff1a;支持多种设备和网…

【APK】SDKManager运行后闪退

本地JDK已安装&#xff0c;且配置了环境变量&#xff0c;未安装 android studiio 问题描述&#xff1a;右键以管理员身份运行 SDKManager&#xff0c;终端窗口闪退 问题原因&#xff1a;未找到正确的Java路径 解决办法&#xff1a; 1.修改tools目录下的 android.bat 文件&am…

langchain 入门中篇:数据封装,Memory 封装

数据的处理流程可以看一张图来帮助理解 数据来源可以是网络&#xff0c;可以是邮件&#xff0c;可以是本地文件 经过 Document Loaders 加载&#xff0c;再在 Transform 阶段对文档进行 split, filter, translate, extract metadata 等操作&#xff0c;之后在 Embed 阶段进行向…

Keil用ST-LINK下载STM32程序后不自动运行

之后程序可以运行了&#xff0c;但是串口还没有输出&#xff0c;在debug模式下都是ok的。

加权 KNN 算法的原理与详解

加权kNN&#xff0c;k近邻算法的增强改进版本。 加权KNN算法 近邻算法&#xff08;k-Nearest Neighbors, kNN&#xff09;是一种用于分类和回归的非参数方法。它的基本思想是“看邻居”&#xff0c;即通过查找离目标点最近的 K 个数据点&#xff0c;来判断目标点的类别或数值。…

学习aurora64/66b.20240703

简介 The AMD LogiCORE™IP Aurora 64B/66B core是一种可扩展的轻量级高数据速率链路层协议&#xff0c;用于高速串行通信。该协议是开放的&#xff0c;可以使用AMD设备技术实现。 Aurora 64B/66B是一种轻量级的串行通信协议&#xff0c;适用于多千兆位链路 (如下图所示)。它…

【MATLAB源码-第139期】基于matlab的OFDM信号识别与相关参数的估计,高阶累量/小波算法调制识别,循环谱估计,带宽估计,载波数目估计等等。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 在现代无线通信系统中&#xff0c;正交频分复用&#xff08;OFDM&#xff09;因其高效的频谱利用率、强大的抗多径衰落能力以及灵活的带宽分配等优势&#xff0c;成为了一种非常重要的调制技术。然而&#xff0c;随着无线通信…

采沙船智能监测识别摄像机

对于现代河流管理来说&#xff0c;采沙船智能监测识别摄像机正逐渐成为解决非法采砂和保护河流生态环境的重要工具。这类摄像机通过先进的视觉识别和数据分析技术&#xff0c;有效监控和管理河道上的采沙行为&#xff0c;对保护水域资源和改善生态环境具有显著的意义。 采沙船智…