MyBatis学习

一、Mybatis使用
1、新建mybatis配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!-- 导入数据库配置文件的信息--><properties resource="jdbc.properties"></properties><!-- 配置setting属性--><settings><setting name="logImpl" value="STDOUT_LOGGING"/><!-- 开启了一个驼峰命名规则--><setting name="mapUnderscoreToCamelCase" value="true"/></settings><typeAliases><package name="cn.j3code.studyspring.mybatis.helloworld.entity"/></typeAliases><!-- 配置数据库--><environments default="development"><environment id="development"><transactionManager type="JDBC"/><!-- 配置连接池 --><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><!--  mappers中注册我们所有写的dao接口的实现(映射)文件--><mappers><mapper resource="cn/j3code/studyspring/mybatis/helloworld/mapper/UserMapper.xml"/><!-- 如果映射文件有十几百个的话,可以用下面的全局注册<package name="文件所在包路径"></package><package name="cn.liuliang.Dao"></package>--></mappers>
</configuration>

2、新建UserMapper.xml用于测试

<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="cn.j3code.studyspring.mybatis.helloworld.mapper.UserMapper"><select id="findAll" resultType="cn.j3code.studyspring.mybatis.helloworld.entity.User">select * from t_user;</select><resultMap id="user" type="cn.j3code.studyspring.mybatis.helloworld.entity.User"><result property="name" javaType="string" column="name" jdbcType="VARCHAR"/></resultMap><select id="findById" parameterType="int" resultMap="user">select * from t_user where id=#{id}</select><insert id="insert" parameterType="user">insert into t_user(id, name, age) values(#{id},#{name},#{age})</insert></mapper>

3、实际使用样例

package cn.j3code.studyspring.mybatis;import cn.j3code.studyspring.mybatis.helloworld.entity.User;
import cn.j3code.studyspring.mybatis.helloworld.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;import java.io.IOException;@Slf4j
public class MyBatisTest {private SqlSession sqlSession;@BeforeEachpublic void startUp() throws IOException{SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();ClassPathResource resource = new ClassPathResource("myBatisConfig.xml");SqlSessionFactory sqlSessionFactory = builder.build(resource.getInputStream());sqlSession = sqlSessionFactory.openSession();}@Testpublic void testMybatis() {try {// 第一个参数为xml文件的namespace+方法名int rows = sqlSession.insert("cn.j3code.studyspring.mybatis.helloworld.mapper.UserMapper.insert", new User(null, "JERRY", 18));sqlSession.commit();log.info("rows -> {}", rows);}catch (Exception e){e.printStackTrace();sqlSession.rollback();}finally {sqlSession.close();}}@Testpublic void testProcy() throws IOException{try {UserMapper mapper = sqlSession.getMapper(UserMapper.class);int rows = mapper.insert(new User(null, "JERRY", 19));sqlSession.commit();log.info("rows -> {}", rows);}catch (Exception e){e.printStackTrace();sqlSession.rollback();}finally {sqlSession.close();}}}

二、常用类
1、XpathParser
Xpath可以简单的使用路径表达式在XML文档中选取节点元素。该工具核心作用就是解析xml文件,包括我们的配置文件和mapper文件。这项技术也是爬虫核心技术之一。在浏览器中我们可以轻松的获取一个标签的xpath。
在这里插入图片描述
粘贴到的路径如下:
/html/body/div[1]/div[1]/div[2]/div/div[2]/div[3]
含义:根目录下的html下的body下的第一个div下的第一个div下的第二个div下的第一个div下的第二个div下的第三个div。
测试demo

	@Testpublic void testxPathParser() throws Exception{ClassPathResource resource = new ClassPathResource("myBatisConfig.xml");XPathParser xPathParser = new XPathParser(resource.getInputStream());XNode xNode = xPathParser.evalNode("/configuration/properties");log.info("node -> {}", xNode);}

2、Configuration类
a.测试组装Configuration

	@Testpublic void testConfiguration() throws Exception{Configuration configuration = new Configuration();Properties properties = new Properties();properties.put("driver", "com.mysql.cj.jdbc.Driver");properties.put("url", "jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&useSSL=false&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true");properties.put("username", "root");properties.put("password", "root");configuration.setVariables(properties);// 添加日志实现和驼峰式命名configuration.setLogImpl(Slf4jImpl.class);configuration.setMapUnderscoreToCamelCase(true);// 配置别名configuration.getTypeAliasRegistry().registerAliases("cn.j3code.studyspring.mybatis.helloworld.entity");// 配置环境PooledDataSource pooledDataSource = new PooledDataSource();pooledDataSource.setDriver(configuration.getVariables().getProperty("driver"));pooledDataSource.setUrl(configuration.getVariables().getProperty("url"));pooledDataSource.setUsername(configuration.getVariables().getProperty("username"));pooledDataSource.setPassword(configuration.getVariables().getProperty("password"));JdbcTransactionFactory jdbcTransactionFactory = new JdbcTransactionFactory();Environment environment = new Environment("development", jdbcTransactionFactory, pooledDataSource);configuration.setEnvironment(environment);StaticSqlSource sqlSource = new StaticSqlSource(configuration,"insert into user(name, age) values ('jerry', 1000)");MappedStatement mappedStatement = new MappedStatement.Builder(configuration,"study.insert",sqlSource,SqlCommandType.INSERT).build();// 扫描文件也是将xml中每个标签作为一个statement添加到configuration中configuration.addMappedStatement(mappedStatement);log.info("configuration -> {}", configuration);}

b. 使用XMLConfigBuilder接下生成Configuration

 	@Testpublic void testXMLConfigBuilder() throws Exception{ClassPathResource resource = new ClassPathResource("myBatisConfig.xml");XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(resource.getInputStream());Configuration configuration = xmlConfigBuilder.parse();log.info("configuration -> {}", configuration);}// 进入parse方法private final XPathParser parser;public Configuration parse() {if (this.parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");} else {// 解析标志位 this.parsed = true;// 使用XPathParser获取configuration节点并调用parseConfiguration逐个解析this.parseConfiguration(this.parser.evalNode("/configuration"));return this.configuration;}}// 逐个获取解析节点private void parseConfiguration(XNode root) {try {this.propertiesElement(root.evalNode("properties"));Properties settings = this.settingsAsProperties(root.evalNode("settings"));this.loadCustomVfs(settings);this.typeAliasesElement(root.evalNode("typeAliases"));this.pluginElement(root.evalNode("plugins"));this.objectFactoryElement(root.evalNode("objectFactory"));this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));this.reflectorFactoryElement(root.evalNode("reflectorFactory"));this.settingsElement(settings);this.environmentsElement(root.evalNode("environments"));this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));this.typeHandlerElement(root.evalNode("typeHandlers"));this.mapperElement(root.evalNode("mappers"));} catch (Exception var3) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);}}

3、OGNL表达式

 	@Testpublic void testOGNL() throws OgnlException {User user = new User(1, "tom", 200);Object name = Ognl.getValue("name != null && name == 'hh'", user);log.info("name -> {}", name);}

4、别名注册器
mybatis提供了TypeAliasRegistry作为别名注册器,同时默认注入了大量的基础类型的别名,他是配置类的一个成员变量:

protected final TypeAliasRegistry typeAliasRegistry;this.typeAliasRegistry = new TypeAliasRegistry();// 实现如下
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package org.apache.ibatis.type;import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.ibatis.io.ResolverUtil;
import org.apache.ibatis.io.Resources;public class TypeAliasRegistry {// key:别名, value: classprivate final Map<String, Class<?>> TYPE_ALIASES = new HashMap();public TypeAliasRegistry() {this.registerAlias("string", String.class);this.registerAlias("byte", Byte.class);this.registerAlias("long", Long.class);this.registerAlias("short", Short.class);this.registerAlias("int", Integer.class);this.registerAlias("integer", Integer.class);this.registerAlias("double", Double.class);this.registerAlias("float", Float.class);this.registerAlias("boolean", Boolean.class);this.registerAlias("byte[]", Byte[].class);this.registerAlias("long[]", Long[].class);this.registerAlias("short[]", Short[].class);this.registerAlias("int[]", Integer[].class);this.registerAlias("integer[]", Integer[].class);this.registerAlias("double[]", Double[].class);this.registerAlias("float[]", Float[].class);this.registerAlias("boolean[]", Boolean[].class);this.registerAlias("_byte", Byte.TYPE);this.registerAlias("_long", Long.TYPE);this.registerAlias("_short", Short.TYPE);this.registerAlias("_int", Integer.TYPE);this.registerAlias("_integer", Integer.TYPE);this.registerAlias("_double", Double.TYPE);this.registerAlias("_float", Float.TYPE);this.registerAlias("_boolean", Boolean.TYPE);this.registerAlias("_byte[]", byte[].class);this.registerAlias("_long[]", long[].class);this.registerAlias("_short[]", short[].class);this.registerAlias("_int[]", int[].class);this.registerAlias("_integer[]", int[].class);this.registerAlias("_double[]", double[].class);this.registerAlias("_float[]", float[].class);this.registerAlias("_boolean[]", boolean[].class);this.registerAlias("date", Date.class);this.registerAlias("decimal", BigDecimal.class);this.registerAlias("bigdecimal", BigDecimal.class);this.registerAlias("biginteger", BigInteger.class);this.registerAlias("object", Object.class);this.registerAlias("date[]", Date[].class);this.registerAlias("decimal[]", BigDecimal[].class);this.registerAlias("bigdecimal[]", BigDecimal[].class);this.registerAlias("biginteger[]", BigInteger[].class);this.registerAlias("object[]", Object[].class);this.registerAlias("map", Map.class);this.registerAlias("hashmap", HashMap.class);this.registerAlias("list", List.class);this.registerAlias("arraylist", ArrayList.class);this.registerAlias("collection", Collection.class);this.registerAlias("iterator", Iterator.class);this.registerAlias("ResultSet", ResultSet.class);}public <T> Class<T> resolveAlias(String string) {try {if (string == null) {return null;} else {String key = string.toLowerCase(Locale.ENGLISH);Class value;if (this.TYPE_ALIASES.containsKey(key)) {value = (Class)this.TYPE_ALIASES.get(key);} else {value = Resources.classForName(string);}return value;}} catch (ClassNotFoundException var4) {throw new TypeException("Could not resolve type alias '" + string + "'.  Cause: " + var4, var4);}}public void registerAliases(String packageName) {this.registerAliases(packageName, Object.class);}public void registerAliases(String packageName, Class<?> superType) {ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();resolverUtil.find(new ResolverUtil.IsA(superType), packageName);Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();Iterator var5 = typeSet.iterator();while(var5.hasNext()) {Class<?> type = (Class)var5.next();if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {this.registerAlias(type);}}}public void registerAlias(Class<?> type) {String alias = type.getSimpleName();Alias aliasAnnotation = (Alias)type.getAnnotation(Alias.class);if (aliasAnnotation != null) {alias = aliasAnnotation.value();}this.registerAlias(alias, type);}public void registerAlias(String alias, Class<?> value) {if (alias == null) {throw new TypeException("The parameter alias cannot be null");} else {String key = alias.toLowerCase(Locale.ENGLISH);if (this.TYPE_ALIASES.containsKey(key) && this.TYPE_ALIASES.get(key) != null && !((Class)this.TYPE_ALIASES.get(key)).equals(value)) {throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + ((Class)this.TYPE_ALIASES.get(key)).getName() + "'.");} else {this.TYPE_ALIASES.put(key, value);}}}public void registerAlias(String alias, String value) {try {this.registerAlias(alias, Resources.classForName(value));} catch (ClassNotFoundException var4) {throw new TypeException("Error registering type alias " + alias + " for " + value + ". Cause: " + var4, var4);}}public Map<String, Class<?>> getTypeAliases() {return Collections.unmodifiableMap(this.TYPE_ALIASES);}
}

测试注册别名

	@Testpublic void testTypeAliasRegistry(){TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();// 提供两种方法// 1.注册单个别名typeAliasRegistry.registerAlias("user", User.class);// 2.使用包扫描的方式注册别名typeAliasRegistry.registerAliases("com.study.entity");}// 注册单个别名方法public void registerAlias(String alias, Class<?> value) {if (alias == null) {throw new TypeException("The parameter alias cannot be null");} else {// 先将别名转为小写 String key = alias.toLowerCase(Locale.ENGLISH);// 判断别名是否已经存在if (this.TYPE_ALIASES.containsKey(key) && this.TYPE_ALIASES.get(key) != null && !((Class)this.TYPE_ALIASES.get(key)).equals(value)) {throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + ((Class)this.TYPE_ALIASES.get(key)).getName() + "'.");} else {// 不存在,则放入mapthis.TYPE_ALIASES.put(key, value);}}}// 注册批量别名方法public void registerAliases(String packageName) {this.registerAliases(packageName, Object.class);}public void registerAliases(String packageName, Class<?> superType) {ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();resolverUtil.find(new ResolverUtil.IsA(superType), packageName);// 获取传入包下面的所有类Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();Iterator var5 = typeSet.iterator();while(var5.hasNext()) {Class<?> type = (Class)var5.next();// 不是匿名类 | 不是接口 | 不是内部类if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {this.registerAlias(type);}}}public void registerAlias(Class<?> type) {// 获取类型名称 String alias = type.getSimpleName();// 类上有Alias注解的话获取Alias注解中配置的名称替换之Alias aliasAnnotation = (Alias)type.getAnnotation(Alias.class);if (aliasAnnotation != null) {alias = aliasAnnotation.value();}// 调用单个别名注册方法注册之this.registerAlias(alias, type);}

另外在Configuration初始化时,也会自动注册大量别名

public Configuration() {this.safeResultHandlerEnabled = true;this.multipleResultSetsEnabled = true;this.useColumnLabel = true;this.cacheEnabled = true;this.useActualParamName = true;this.localCacheScope = LocalCacheScope.SESSION;this.jdbcTypeForNull = JdbcType.OTHER;this.lazyLoadTriggerMethods = new HashSet(Arrays.asList("equals", "clone", "hashCode", "toString"));this.defaultExecutorType = ExecutorType.SIMPLE;this.autoMappingBehavior = AutoMappingBehavior.PARTIAL;this.autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;this.variables = new Properties();this.reflectorFactory = new DefaultReflectorFactory();this.objectFactory = new DefaultObjectFactory();this.objectWrapperFactory = new DefaultObjectWrapperFactory();this.lazyLoadingEnabled = false;this.proxyFactory = new JavassistProxyFactory();this.mapperRegistry = new MapperRegistry(this);this.interceptorChain = new InterceptorChain();this.typeHandlerRegistry = new TypeHandlerRegistry();this.typeAliasRegistry = new TypeAliasRegistry();this.languageRegistry = new LanguageDriverRegistry();this.mappedStatements = new StrictMap("Mapped Statements collection");this.caches = new StrictMap("Caches collection");this.resultMaps = new StrictMap("Result Maps collection");this.parameterMaps = new StrictMap("Parameter Maps collection");this.keyGenerators = new StrictMap("Key Generators collection");this.loadedResources = new HashSet();this.sqlFragments = new StrictMap("XML fragments parsed from previous mappers");this.incompleteStatements = new LinkedList();this.incompleteCacheRefs = new LinkedList();this.incompleteResultMaps = new LinkedList();this.incompleteMethods = new LinkedList();this.cacheRefMap = new HashMap();// 注册别名this.typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);this.typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);this.typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);this.typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);this.typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);this.typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);this.typeAliasRegistry.registerAlias("FIFO", FifoCache.class);this.typeAliasRegistry.registerAlias("LRU", LruCache.class);this.typeAliasRegistry.registerAlias("SOFT", SoftCache.class);this.typeAliasRegistry.registerAlias("WEAK", WeakCache.class);this.typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);this.typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);this.typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);this.typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);this.typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);this.typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);this.typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);this.typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);this.typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);this.typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);this.typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);this.typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);this.languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);this.languageRegistry.register(RawLanguageDriver.class);}

5、类型转换器
5.1 TypeHandler
TypeHandler负责对查询结果进行类型转换,我们可以使用ResultSet的原生方法对结果进行类型转换。我们当然可以使用switch-case语法通过判断类型选择调用方法如下:

public Object getResult(JDBCType jdbcType, ResultSet rs, String columnName) throws Exception{Object object = null;switch (jdbcType){case INTEGER:  object = rs.getInt(columnName);case BIGINT:object = rs.getLong(columnName);case VARCHAR:object = rs.getString(columnName);// 省略其他众多情况}return object;}

但这样是面向过程的编码风格,对扩展不利,以后每次新增类型都要修改代码。因此mybatis抽象出一个TypeHandler作为顶层接口,对转换工作做了模型抽离,如下:

public interface TypeHandler<T> {void setParameter(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;T getResult(ResultSet var1, String var2) throws SQLException;T getResult(ResultSet var1, int var2) throws SQLException;T getResult(CallableStatement var1, int var2) throws SQLException;
}

同时实现了大量的实现策略(策略设计模式),核心策略在构造时注册,新增的通过配置文件添加,极大降低了系统耦合性。
在这里插入图片描述
5.2、TypeHandlerRegistry
mybatis提供了一个专门的注册器用来注册TypeHandler。TypeHandlerRegistry也是配置类的一个成员变量:

protected final TypeHandlerRegistry typeHandlerRegistry;

TypeHandlerRegistry的作用如下:

  • 一是为了注册存储类型转换器
  • 二是为了根据javaType或者jdbcType查询合适的TypeHandler

当然,我们可以思考一下javaType–jdbcType以及TypeHandler三者的关系:
事实上,我们一般情况会根据BaseTypeHandler的泛型来确定该处理器的javaType,从而确定使用的是rs的getXXX和setXXX方法,jdbcType本质上没有什么具体的作用,更多的是作为一种标识,用来确定使用一个唯一的handler,在定义TypeHandler时需要指定泛型,也就意味着javaType一定会被指定,那么我们选取TypeHandler时就会有以下两种情况:

  • 1、指定了javaType,也指定了jdbcType,我们可以匹配一个确定的TypeHandler。
  • 2、指定了javaType,未指定jdbcType,我们可以匹配更多TypeHandler,但我们只选一个作为默认

TypeHandlerRegistry源码:

public final class TypeHandlerRegistry {// jdbcType与handler映射,jdbcType为一个枚举类private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap(JdbcType.class);// javaType与map(jdbc-handler map)的映射,一个javaType可能对应多个jdbcTypeprivate final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap();// 兜底方案,当所有typeHandler无法处理时,尝试采用该handler处理private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);// 所有的类型转换器,private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap();// 存储只知道jdbcType不知道javaTypeprivate static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = new HashMap();private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;// 构造方法public TypeHandlerRegistry() {// 传入java类型 this.register((Class)Boolean.class, (TypeHandler)(new BooleanTypeHandler()));this.register((Class)Boolean.TYPE, (TypeHandler)(new BooleanTypeHandler()));this.register((JdbcType)JdbcType.BOOLEAN, (TypeHandler)(new BooleanTypeHandler()));this.register((JdbcType)JdbcType.BIT, (TypeHandler)(new BooleanTypeHandler()));this.register((Class)Byte.class, (TypeHandler)(new ByteTypeHandler()));this.register((Class)Byte.TYPE, (TypeHandler)(new ByteTypeHandler()));this.register((JdbcType)JdbcType.TINYINT, (TypeHandler)(new ByteTypeHandler()));this.register((Class)Short.class, (TypeHandler)(new ShortTypeHandler()));this.register((Class)Short.TYPE, (TypeHandler)(new ShortTypeHandler()));this.register((JdbcType)JdbcType.SMALLINT, (TypeHandler)(new ShortTypeHandler()));this.register((Class)Integer.class, (TypeHandler)(new IntegerTypeHandler()));this.register((Class)Integer.TYPE, (TypeHandler)(new IntegerTypeHandler()));this.register((JdbcType)JdbcType.INTEGER, (TypeHandler)(new IntegerTypeHandler()));this.register((Class)Long.class, (TypeHandler)(new LongTypeHandler()));this.register((Class)Long.TYPE, (TypeHandler)(new LongTypeHandler()));this.register((Class)Float.class, (TypeHandler)(new FloatTypeHandler()));this.register((Class)Float.TYPE, (TypeHandler)(new FloatTypeHandler()));this.register((JdbcType)JdbcType.FLOAT, (TypeHandler)(new FloatTypeHandler()));this.register((Class)Double.class, (TypeHandler)(new DoubleTypeHandler()));this.register((Class)Double.TYPE, (TypeHandler)(new DoubleTypeHandler()));this.register((JdbcType)JdbcType.DOUBLE, (TypeHandler)(new DoubleTypeHandler()));this.register((Class)Reader.class, (TypeHandler)(new ClobReaderTypeHandler()));this.register((Class)String.class, (TypeHandler)(new StringTypeHandler()));this.register((Class)String.class, JdbcType.CHAR, (TypeHandler)(new StringTypeHandler()));this.register((Class)String.class, JdbcType.CLOB, (TypeHandler)(new ClobTypeHandler()));this.register((Class)String.class, JdbcType.VARCHAR, (TypeHandler)(new StringTypeHandler()));this.register((Class)String.class, JdbcType.LONGVARCHAR, (TypeHandler)(new ClobTypeHandler()));this.register((Class)String.class, JdbcType.NVARCHAR, (TypeHandler)(new NStringTypeHandler()));this.register((Class)String.class, JdbcType.NCHAR, (TypeHandler)(new NStringTypeHandler()));this.register((Class)String.class, JdbcType.NCLOB, (TypeHandler)(new NClobTypeHandler()));this.register((JdbcType)JdbcType.CHAR, (TypeHandler)(new StringTypeHandler()));this.register((JdbcType)JdbcType.VARCHAR, (TypeHandler)(new StringTypeHandler()));this.register((JdbcType)JdbcType.CLOB, (TypeHandler)(new ClobTypeHandler()));this.register((JdbcType)JdbcType.LONGVARCHAR, (TypeHandler)(new ClobTypeHandler()));this.register((JdbcType)JdbcType.NVARCHAR, (TypeHandler)(new NStringTypeHandler()));this.register((JdbcType)JdbcType.NCHAR, (TypeHandler)(new NStringTypeHandler()));this.register((JdbcType)JdbcType.NCLOB, (TypeHandler)(new NClobTypeHandler()));this.register((Class)Object.class, JdbcType.ARRAY, (TypeHandler)(new ArrayTypeHandler()));this.register((JdbcType)JdbcType.ARRAY, (TypeHandler)(new ArrayTypeHandler()));this.register((Class)BigInteger.class, (TypeHandler)(new BigIntegerTypeHandler()));this.register((JdbcType)JdbcType.BIGINT, (TypeHandler)(new LongTypeHandler()));this.register((Class)BigDecimal.class, (TypeHandler)(new BigDecimalTypeHandler()));this.register((JdbcType)JdbcType.REAL, (TypeHandler)(new BigDecimalTypeHandler()));this.register((JdbcType)JdbcType.DECIMAL, (TypeHandler)(new BigDecimalTypeHandler()));this.register((JdbcType)JdbcType.NUMERIC, (TypeHandler)(new BigDecimalTypeHandler()));this.register((Class)InputStream.class, (TypeHandler)(new BlobInputStreamTypeHandler()));this.register((Class)Byte[].class, (TypeHandler)(new ByteObjectArrayTypeHandler()));this.register((Class)Byte[].class, JdbcType.BLOB, (TypeHandler)(new BlobByteObjectArrayTypeHandler()));this.register((Class)Byte[].class, JdbcType.LONGVARBINARY, (TypeHandler)(new BlobByteObjectArrayTypeHandler()));this.register((Class)byte[].class, (TypeHandler)(new ByteArrayTypeHandler()));this.register((Class)byte[].class, JdbcType.BLOB, (TypeHandler)(new BlobTypeHandler()));this.register((Class)byte[].class, JdbcType.LONGVARBINARY, (TypeHandler)(new BlobTypeHandler()));this.register((JdbcType)JdbcType.LONGVARBINARY, (TypeHandler)(new BlobTypeHandler()));this.register((JdbcType)JdbcType.BLOB, (TypeHandler)(new BlobTypeHandler()));this.register(Object.class, this.UNKNOWN_TYPE_HANDLER);this.register(Object.class, JdbcType.OTHER, this.UNKNOWN_TYPE_HANDLER);this.register(JdbcType.OTHER, this.UNKNOWN_TYPE_HANDLER);this.register((Class)Date.class, (TypeHandler)(new DateTypeHandler()));this.register((Class)Date.class, JdbcType.DATE, (TypeHandler)(new DateOnlyTypeHandler()));this.register((Class)Date.class, JdbcType.TIME, (TypeHandler)(new TimeOnlyTypeHandler()));this.register((JdbcType)JdbcType.TIMESTAMP, (TypeHandler)(new DateTypeHandler()));this.register((JdbcType)JdbcType.DATE, (TypeHandler)(new DateOnlyTypeHandler()));this.register((JdbcType)JdbcType.TIME, (TypeHandler)(new TimeOnlyTypeHandler()));this.register((Class)java.sql.Date.class, (TypeHandler)(new SqlDateTypeHandler()));this.register((Class)Time.class, (TypeHandler)(new SqlTimeTypeHandler()));this.register((Class)Timestamp.class, (TypeHandler)(new SqlTimestampTypeHandler()));if (Jdk.dateAndTimeApiExists) {Java8TypeHandlersRegistrar.registerDateAndTimeHandlers(this);}this.register((Class)Character.class, (TypeHandler)(new CharacterTypeHandler()));this.register((Class)Character.TYPE, (TypeHandler)(new CharacterTypeHandler()));}// 对于java类型的处理public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {this.register((Type)javaType, (TypeHandler)typeHandler);}// ---->处理private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {// 首先从typeHandler中获取一个注解,这个注解中包含一个数组,这个数组为jdbcTypeMappedJdbcTypes mappedJdbcTypes = (MappedJdbcTypes)typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);if (mappedJdbcTypes != null) {JdbcType[] var4 = mappedJdbcTypes.value();int var5 = var4.length;for(int var6 = 0; var6 < var5; ++var6) {JdbcType handledJdbcType = var4[var6];// 将每一个javaType,jdbcType与handler进行注册this.register(javaType, handledJdbcType, typeHandler);}// 如果没有注解,或者注解里没有内容,则以javaType的形式进行注册if (mappedJdbcTypes.includeNullJdbcType()) {this.register((Type)javaType, (JdbcType)null, (TypeHandler)typeHandler);}} else {this.register((Type)javaType, (JdbcType)null, (TypeHandler)typeHandler);}}// 最终处理private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {if (javaType != null) {// 先根据jdbcType拿到所有mapMap<JdbcType, TypeHandler<?>> map = (Map)this.TYPE_HANDLER_MAP.get(javaType);if (map == null) {map = new HashMap();this.TYPE_HANDLER_MAP.put(javaType, map);}// 添加后重新塞入((Map)map).put(jdbcType, handler);}// 最后将javaType和handler放入all中this.ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);}
}

自定义实现stringTypeHandler,用于理解 MappedJdbcTypes


// 有多个jdbcType与之对应,但具体的实现(handler)对应一个
@MappedJdbcTypes({JdbcType.CHAR,JdbcType.VARCHAR})
public class StringTypeHandler implements TypeHandler<String> {@Overridepublic void setParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException {}// 例子中只重写了该方法@Overridepublic String getResult(ResultSet rs, String columnName) throws SQLException {return rs.getString(columnName);}@Overridepublic String getResult(ResultSet resultSet, int i) throws SQLException {return null;}@Overridepublic String getResult(CallableStatement callableStatement, int i) throws SQLException {return null;}
}

6、反射工具
mybatis封装后的反射工具可以极其简单实现简单属性以及复杂属性的get和set操作,这在获取参数成员变量,封装返回结果等操作时很有用:

	@Testpublic void testMetaObject(){Object user = new User(1, "hhh", 42, new House(120, 1234, 4331));MetaObject metaObject = MetaObject.forObject(user, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());// 1、修改user属性metaObject.setValue("house.area", 150);metaObject.setValue("house.latitude", 9876);System.out.println(user);// 2、修改当前对象的值metaObject.setValue("name", "aaa");System.out.println(user);// 3、取值 area = #{house.area}Object value = metaObject.getValue("house.area");System.out.println(value);value = metaObject.getValue("house");System.out.println(value);value = metaObject.getValue("name");System.out.println(value);}

6.1、MetaObject

public class MetaObject {// 原始对象private final Object originalObject;// 包装后的对象private final ObjectWrapper objectWrapper;// 以下三个工厂我们使用默认就可以了,当然我们可以自由设定// 用来实例化对象的工厂private final ObjectFactory objectFactory;// 用来获取一个包装的对象工厂,给我们扩展使用private final ObjectWrapperFactory objectWrapperFactory;// 可以为每一个类生成Reflector,他提供了反射的基本能力private final ReflectorFactory reflectorFactory;private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {this.originalObject = object;this.objectFactory = objectFactory;this.objectWrapperFactory = objectWrapperFactory;this.reflectorFactory = reflectorFactory;// 1、不包装if (object instanceof ObjectWrapper) {this.objectWrapper = (ObjectWrapper)object;// 2、由工厂进行包装} else if (objectWrapperFactory.hasWrapperFor(object)) {this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);// 3、框架自行包装} else if (object instanceof Map) {this.objectWrapper = new MapWrapper(this, (Map)object);} else if (object instanceof Collection) {this.objectWrapper = new CollectionWrapper(this, (Collection)object);} else {this.objectWrapper = new BeanWrapper(this, object);}}// 对外公开构建方法 public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {return object == null ? SystemMetaObject.NULL_META_OBJECT : new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory);}}

(1)、ObjectFactory
ObjectFactory提供了实例化一个类的能力,可以根据class类型构建对象,默认采用反射的方式:

// DefaultObjectFactory<T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {try {Constructor constructor;if (constructorArgTypes != null && constructorArgs != null) {constructor = type.getDeclaredConstructor((Class[])constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));if (!constructor.isAccessible()) {constructor.setAccessible(true);}return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));} else {constructor = type.getDeclaredConstructor();if (!constructor.isAccessible()) {constructor.setAccessible(true);}return constructor.newInstance();}} catch (Exception var9) {StringBuilder argTypes = new StringBuilder();if (constructorArgTypes != null && !constructorArgTypes.isEmpty()) {Iterator var6 = constructorArgTypes.iterator();while(var6.hasNext()) {Class<?> argType = (Class)var6.next();argTypes.append(argType.getSimpleName());argTypes.append(",");}argTypes.deleteCharAt(argTypes.length() - 1);}StringBuilder argValues = new StringBuilder();if (constructorArgs != null && !constructorArgs.isEmpty()) {Iterator var11 = constructorArgs.iterator();while(var11.hasNext()) {Object argValue = var11.next();argValues.append(String.valueOf(argValue));argValues.append(",");}argValues.deleteCharAt(argValues.length() - 1);}throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + var9, var9);}}

(2)、ObjectWrapperFactory
mybatis提供了ObjectWrapperFactory可以对Object的包装过程实现自定义,其默认实现如下,默认实现不会做任何事情,存在的意义是为了扩展,实现自定义

public class DefaultObjectWrapperFactory implements ObjectWrapperFactory {public DefaultObjectWrapperFactory() {}public boolean hasWrapperFor(Object object) {// 默认返回不能包装return false;}public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) {// 永远不应该调用DefaultObjectWrapperFactory来提供ObjectWrapperthrow new ReflectionException("The DefaultObjectWrapperFactory should never be called to provide an ObjectWrapper.");}
}

(3)、ObjectWrapper
在上面的例子中,操作对象大多是通过ObjectWapper进行的,他的继承结构如下,以BeanWrapper为例,分析下原理
在这里插入图片描述
从源码中得知,BeanWrapper维护了一个object实例本身,一个metaClass(用来进行反射操作)

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());}
}

MetaClass的构建过程如下,一个metaclass实例维护了一个reflectorFactory用来提供reflector,而reflector是真正的反射工具

public class MetaClass {private final ReflectorFactory reflectorFactory;private final Reflector reflector;private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {this.reflectorFactory = reflectorFactory;this.reflector = reflectorFactory.findForClass(type);}public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {return new MetaClass(type, reflectorFactory);}}

(4)、ReflectorFactory
ReflectorFactory维护了我们需要的Reflector,并提供了创建方式,其默认实现是DefaultReflectorFactory,ReflectorFactory也可以进行自定义。

public class DefaultReflectorFactory implements ReflectorFactory {private boolean classCacheEnabled = true;private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap();public DefaultReflectorFactory() {}public boolean isClassCacheEnabled() {return this.classCacheEnabled;}public void setClassCacheEnabled(boolean classCacheEnabled) {this.classCacheEnabled = classCacheEnabled;}public Reflector findForClass(Class<?> type) {if (this.classCacheEnabled) {Reflector cached = (Reflector)this.reflectorMap.get(type);if (cached == null) {cached = new Reflector(type);this.reflectorMap.put(type, cached);}return cached;} else {return new Reflector(type);}}
}

(5)、Reflector
Reflector是一个反射工具,里边缓存了一个类的类型,setter方法,getter方法,默认构造器:

public class Reflector {private final Class<?> type;private final String[] readablePropertyNames;private final String[] writeablePropertyNames;private final Map<String, Invoker> setMethods = new HashMap();private final Map<String, Invoker> getMethods = new HashMap();private final Map<String, Class<?>> setTypes = new HashMap();private final Map<String, Class<?>> getTypes = new HashMap();private Constructor<?> defaultConstructor;private Map<String, String> caseInsensitivePropertyMap = new HashMap();// 构建过程public Reflector(Class<?> clazz) {this.type = clazz;this.addDefaultConstructor(clazz);this.addGetMethods(clazz);this.addSetMethods(clazz);this.addFields(clazz);this.readablePropertyNames = (String[])this.getMethods.keySet().toArray(new String[this.getMethods.keySet().size()]);this.writeablePropertyNames = (String[])this.setMethods.keySet().toArray(new String[this.setMethods.keySet().size()]);String[] var2 = this.readablePropertyNames;int var3 = var2.length;int var4;String propName;for(var4 = 0; var4 < var3; ++var4) {propName = var2[var4];this.caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);}var2 = this.writeablePropertyNames;var3 = var2.length;for(var4 = 0; var4 < var3; ++var4) {propName = var2[var4];this.caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);}}
}

6.2、复杂属性的赋值

metaObject.setValue("house.area", 150);

(1)实现过程

// setter,赋值操作
public void setValue(String name, Object value) {// 属性解析器,用于解析属性PropertyTokenizer prop = new PropertyTokenizer(name);// 如果是非简单属性,如:house.areaif (prop.hasNext()) {// 如果area没有值,需要先实例化areaMetaObject metaValue = this.metaObjectForProperty(prop.getIndexedName());if (metaValue == SystemMetaObject.NULL_META_OBJECT) {if (value == null && prop.getChildren() != null) {// 如果value为null,不要实例化子属性 return;}// 使用objectFactory构建一个user实例// objectFactory的默认实现就是使用反射创建一个实例metaValue = this.objectWrapper.instantiatePropertyValue(name, prop, this.objectFactory);}// metaValue:house,prop.getChildren()--> area,给area赋值metaValue.setValue(prop.getChildren(), value);} else {// 普通属性的赋值this.objectWrapper.set(prop, value);}}

(2)属性分析器(迭代器设计模式)

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package org.apache.ibatis.reflection.property;import java.util.Iterator;public class PropertyTokenizer implements Iterator<PropertyTokenizer> {private String name;private final String indexedName;private String index;private final String children;public PropertyTokenizer(String fullname) {// '.'int delim = fullname.indexOf(46);// 说明这个属性中有,如user.houseif (delim > -1) {// 截取.之前的内容作为name,此处是housethis.name = fullname.substring(0, delim);// .之后的内容作为children,此处是areathis.children = fullname.substring(delim + 1);} else {// 否则就是fullnamethis.name = fullname;this.children = null;}this.indexedName = this.name;// 包含 [ delim = this.name.indexOf(91);if (delim > -1) {this.index = this.name.substring(delim + 1, this.name.length() - 1);this.name = this.name.substring(0, delim);}}// 继续分析子属性public PropertyTokenizer next() {return new PropertyTokenizer(this.children);}
}

(3)构建成员变量实例
house如果没有实例化,但我们试图为其赋值,就必须通过反射进行实例化

metaValue = this.objectWrapper.instantiatePropertyValue(name, prop, this.objectFactory);

以BeanWapper的实现为例:

public MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory) {// 通过属性的名字获取属性类型,Class<?> type = this.getSetterType(prop.getName());try {// 根据类型创建一个实例Object newObject = objectFactory.create(type);// 构建一个metaObjectMetaObject metaValue = MetaObject.forObject(newObject, this.metaObject.getObjectFactory(), this.metaObject.getObjectWrapperFactory(), this.metaObject.getReflectorFactory());// 赋值this.set(prop, newObject);return metaValue;} catch (Exception var7) {throw new ReflectionException("Cannot set value of property '" + name + "' because '" + name + "' is null and cannot be instantiated on instance of " + type.getName() + ". Cause:" + var7.toString(), var7);}}public Class<?> getSetterType(String name) {PropertyTokenizer prop = new PropertyTokenizer(name);if (prop.hasNext()) {MetaObject metaValue = this.metaObject.metaObjectForProperty(prop.getIndexedName());return metaValue == SystemMetaObject.NULL_META_OBJECT ? this.metaClass.getSetterType(name) : metaValue.getSetterType(prop.getChildren());} else {return this.metaClass.getSetterType(name);}}

7、ErrorContext
在mybatis中为了更好的定位异常或错误,特封装ErrorContext类,用于描述错误信息,他比传统异常机制要好很多,输出的内容可以自由定制,他使用了ThreadLocal,可以非常好的将问题定位到某个线程,同时保证了线程安全。
源码如下:

public class ErrorContext {private static final String LINE_SEPARATOR = System.getProperty("line.separator", "\n");private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal();private ErrorContext stored;private String resource;private String activity;private String object;private String message;private String sql;private Throwable cause;public static ErrorContext instance() {ErrorContext context = (ErrorContext)LOCAL.get();if (context == null) {context = new ErrorContext();LOCAL.set(context);}return context;}public ErrorContext store() {this.stored = this;LOCAL.set(new ErrorContext());return (ErrorContext)LOCAL.get();}}

ErrorContext负责封装异常上下文,而ExceptionFactory则负责输出异常上下文:

public class ExceptionFactory {private ExceptionFactory() {}public static RuntimeException wrapException(String message, Exception e) {return new PersistenceException(ErrorContext.instance().message(message).cause(e).toString(), e);}
}

使用示例如下:

@Test
public void testErrorContext(){ExecutorService service = Executors.newFixedThreadPool(10);CountDownLatch countDownLatch = new CountDownLatch(10);for (int i = 0; i < 10; i++) {final int j = i;service.execute(()-> {try {// ErrorContext存储在threadLocal中ErrorContext.instance().activity("在第[" + j + "]流程中。").object(this.getClass().getName()).sql("select xxx fromfd user").resource("user.xml");if(new Random().nextInt(10) > 6){int m = 1/0;}}catch (Exception e){// 该方法从threadLocal中取出ErrorContextthrow ExceptionFactory.wrapException("sql has errors", e);}countDownLatch.countDown();});}
}

三、sql封装
1、MappedStatement
MappedStatement翻译过来是映射语句,其实就是用来封装sql语句的,每个sql标签都会映射成为一个MappedStatement实例,如下是一个sql标签的xml表现形式

<insert id="insert" parameterType="account" flushCache="" keyColumn=""  parameterMap=""useGeneratedKeys="" databaseId="" keyProperty="" lang="" statementType="" timeout="">insert into account(username, money) values(#{username},#{money})
</insert>
属性描述
id在命名空间中唯一标识符,可以用来引用这条语句
parameterType将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为MyBatis可以通过类型处理器(TypeHandler推断出具体传入语句的参数,默认值为未设置(unset)
parameterMap用于引用外部 parameterMap 的属性,目前已被废弃。
resultType期望从这条语句返回结果的类全限定名或别名,注意,如果返回的是集合,那应该设置为集合内元素的类型,而不是集合本身。resultType和resultMap之间只能同时使用一个
resultMap对外部resultMap的命名引用
flushCache将其设置为true后,只要语句被调用都会导致本地缓存和二级缓存被清空,默认值:false
useCache将其设置为true后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对select元素为true。需要前提开启二级缓存
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)
fetchSize这是一个驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。默认值为未设置(unset)(依赖驱动)
statementType可选 STATEMENT,PREPARED,或 CALLABLE。这会让mybatis分别使用Statement,PreparedStatement 或CallableStatement。默认值:PREPARED
resultSetTypeFORWARD_ONLY(结果即指针只能向前),SCROLL_INSENSITIVE(结果集指针可以滚动,可执行上一个、下一个、回到第一个等操作)或 DEFAULT(等价于 unset)中的一个,默认值为 unset(依赖数据库驱动)
databaseId如果配置了数据库厂商标识(databaseIdProvider),MyBatis会加载所有不带databaseId或匹配当前databaseId的语句;如果带和不带的语句都有,则不带的会被忽略
resultOrdered这个设置仅针对嵌套结果 select 语句:如果为true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false
resultSets这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔
public final class MappedStatement {// 源自于哪个mapper文件private String resource;// 配置文件private Configuration configuration;// idprivate String id;// 每次从服务器获取的数量private Integer fetchSize;private Integer timeout;//  STATEMENT,PREPARED,或 CALLABLEprivate StatementType statementType;private ResultSetType resultSetType;// 对sql的包装private SqlSource sqlSource;// 缓存private Cache cache;private ParameterMap parameterMap;private List<ResultMap> resultMaps;// 是否需要刷新缓存private boolean flushCacheRequired;private boolean useCache;private boolean resultOrdered;// UNKNOW,INSERT,UPDATE,DELETE,SELECTprivate SqlCommandType sqlCommandType;// 主键生成器private KeyGenerator keyGenerator;private String[] keyProperties;private String[] keyColumns;// 是否存在嵌套查询的结果集private boolean hasNestedResultMaps;private String databaseId;private Log statementLog;// 语言驱动private LanguageDriver lang;// resultSetprivate String[] resultSets;}

封装MappedStatement样例

@Testpublic void teseMappedStatement() throws Exception{// 构建configurationInputStream inputStream = Resources.getResourceAsStream("myBatisConfig.xml");XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(inputStream);Configuration configuration = xmlConfigBuilder.parse();String sql = "select id,name,age from t_user";SqlSource sqlSource = new StaticSqlSource(configuration, sql);MappedStatement.Builder builder = new MappedStatement.Builder(configuration, "insert", sqlSource, SqlCommandType.SELECT);// 设置结果集List<ResultMapping> resultMappingList = new ArrayList<>();ResultMapping.Builder idBuilder = new ResultMapping.Builder(configuration, "id", "id", new IntegerTypeHandler());ResultMapping.Builder nameBuilder = new ResultMapping.Builder(configuration, "name", "name",String.class);ResultMapping.Builder ageBuilder = new ResultMapping.Builder(configuration, "id", "age", Integer.class);resultMappingList.add(idBuilder.build());resultMappingList.add(nameBuilder.build());resultMappingList.add(ageBuilder.build());ResultMap resultMap = new ResultMap.Builder(configuration, "myResultMap", User.class, resultMappingList).build();builder.resultMaps(Arrays.asList(resultMap));builder.fetchSize(300);builder.databaseId("mysql");MappedStatement mappedStatement = builder.build();log.info("mappedStatement = " + mappedStatement);// 1、去除标签 <if> <where>// 2、替换占位符 #{id} --> select * from user where id = ?BoundSql boundSql = mappedStatement.getBoundSql(new User(1, null, 33));String sql1 = boundSql.getSql();System.out.println(sql1);}

StringJoiner

@Test
public void testStringJoiner(){StringJoiner stringJoiner = new StringJoiner("-");stringJoiner.add("1");stringJoiner.add("2");stringJoiner.add("3");stringJoiner.add("4");stringJoiner.add("5");System.out.println(stringJoiner);
}

mappedStatement.getBoundSql方法解析

public BoundSql getBoundSql(Object parameterObject) {// 有四个实现,常用的是DynamicSqlSource和StaticSqlSourceBoundSql boundSql = this.sqlSource.getBoundSql(parameterObject);List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (parameterMappings == null || parameterMappings.isEmpty()) {boundSql = new BoundSql(this.configuration, boundSql.getSql(), this.parameterMap.getParameterMappings(), parameterObject);}Iterator var4 = boundSql.getParameterMappings().iterator();while(var4.hasNext()) {ParameterMapping pm = (ParameterMapping)var4.next();String rmId = pm.getResultMapId();if (rmId != null) {ResultMap rm = this.configuration.getResultMap(rmId);if (rm != null) {this.hasNestedResultMaps |= rm.hasNestedResultMaps();}}}return boundSql;
}

StaticSqlSource.getBoundSql

public BoundSql getBoundSql(Object parameterObject) {return new BoundSql(this.configuration, this.sql, this.parameterMappings, parameterObject);
}

DynamicSqlSource

public class DynamicSqlSource implements SqlSource {private final Configuration configuration;private final SqlNode rootSqlNode;public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {this.configuration = configuration;this.rootSqlNode = rootSqlNode;}public BoundSql getBoundSql(Object parameterObject) {DynamicContext context = new DynamicContext(this.configuration, parameterObject);this.rootSqlNode.apply(context);SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(this.configuration);Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());BoundSql boundSql = sqlSource.getBoundSql(parameterObject);Iterator var7 = context.getBindings().entrySet().iterator();while(var7.hasNext()) {Map.Entry<String, Object> entry = (Map.Entry)var7.next();boundSql.setAdditionalParameter((String)entry.getKey(), entry.getValue());}return boundSql;}
}

DynamicContext

// 编译解析sql语句所需要的上下文
public class DynamicContext {public static final String PARAMETER_OBJECT_KEY = "_parameter";public static final String DATABASE_ID_KEY = "_databaseId";// 当前ognl上下文private final ContextMap bindings;// 用于append sqlprivate final StringBuilder sqlBuilder = new StringBuilder();private int uniqueNumber = 0;// 初始化ognlstatic {OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());}// 构造方法public DynamicContext(Configuration configuration, Object parameterObject) {if (parameterObject != null && !(parameterObject instanceof Map)) {// 初始化metaObject,方便赋值取值等操作MetaObject metaObject = configuration.newMetaObject(parameterObject);this.bindings = new ContextMap(metaObject);} else {this.bindings = new ContextMap((MetaObject)null);}// 将原始param对象保存在bindings中,this.bindings.put("_parameter", parameterObject);this.bindings.put("_databaseId", configuration.getDatabaseId());}public String getSql() {return this.sqlBuilder.toString().trim();}public void appendSql(String sql) {this.sqlBuilder.append(sql);this.sqlBuilder.append(" ");}// ognl表达式所需要的上下文static class ContextMap extends HashMap<String, Object> {private static final long serialVersionUID = 2977601501966151582L;private MetaObject parameterMetaObject;public ContextMap(MetaObject parameterMetaObject) {this.parameterMetaObject = parameterMetaObject;}public Object get(Object key) {String strKey = (String)key;if (super.containsKey(strKey)) {return super.get(strKey);} else {return this.parameterMetaObject != null ? this.parameterMetaObject.getValue(strKey) : null;}}}}

SqlNode
sqlNode封装了XML中的sql节点,对于多个节点的封装,我们统一使用MixedSqlNode,其继承结构如下:
在这里插入图片描述

public interface SqlNode {boolean apply(DynamicContext var1);
}public class MixedSqlNode implements SqlNode {// 每一个标签的集合 private final List<SqlNode> contents;public MixedSqlNode(List<SqlNode> contents) {this.contents = contents;}public boolean apply(DynamicContext context) {Iterator var2 = this.contents.iterator();// 遍历每一个标签,并传递context进行拼接while(var2.hasNext()) {SqlNode sqlNode = (SqlNode)var2.next();sqlNode.apply(context);}return true;}
}

每一个sqlNode都有不同的职责,其中IfSqlNode使用ognl表达式解析test结果,如果为true,会继续解析if标签中的其他sqlNode,如果为false,直接丢弃。where和set标签主要用于处理前后缀,共同继承自trimSqlNode,trimSqlNode用于除去前后缀,where和set用于定义前后缀的内容。

获取sqlSource(将XNode解析为sqlSource)

<select>select * from t_user<where><if test="id != null">and id = #{id}</if></where>
</select>
@Test
public void testXMLLanguageDriver() throws Exception{// 构建configurationInputStream inputStream = Resources.getResourceAsStream("myBatisConfig.xml");XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(inputStream);Configuration configuration = xmlConfigBuilder.parse();inputStream = Resources.getResourceAsStream("cn/j3code/studyspring/mybatis/helloworld/mapper/sql.xml");XPathParser xPathParser = new XPathParser(inputStream);XNode xNode = xPathParser.evalNode("/select");XMLLanguageDriver xmlLanguageDriver = new XMLLanguageDriver();SqlSource sqlSource = xmlLanguageDriver.createSqlSource(configuration, xNode, User.class);System.out.println(sqlSource);}

最终构造的sqlSource结构
在这里插入图片描述
四、配置类的读取流程

@BeforeEach
public void startUp() throws IOException{SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();ClassPathResource resource = new ClassPathResource("myBatisConfig.xml");// 1、入口SqlSessionFactory sqlSessionFactory = builder.build(resource.getInputStream());sqlSession = sqlSessionFactory.openSession();
}public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {SqlSessionFactory var5;try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);// 2、parser.parse()最终返回Configurationvar5 = this.build(parser.parse());} catch (Exception var14) {throw ExceptionFactory.wrapException("Error building SqlSession.", var14);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException var13) {}}return var5;}public Configuration parse() {if (this.parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");} else {// 3、// 将解析标志设为truethis.parsed = true;// 获取配置xml中configuration节点this.parseConfiguration(this.parser.evalNode("/configuration"));return this.configuration;}
}private void parseConfiguration(XNode root) {// 4、逐个解析每一个标签并赋值try {this.propertiesElement(root.evalNode("properties"));Properties settings = this.settingsAsProperties(root.evalNode("settings"));this.loadCustomVfs(settings);this.typeAliasesElement(root.evalNode("typeAliases"));this.pluginElement(root.evalNode("plugins"));this.objectFactoryElement(root.evalNode("objectFactory"));this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));this.reflectorFactoryElement(root.evalNode("reflectorFactory"));this.settingsElement(settings);this.environmentsElement(root.evalNode("environments"));this.databaseIdProviderElement(root.eva lNode("databaseIdProvider"));this.typeHandlerElement(root.evalNode("typeHandlers"));this.mapperElement(root.evalNode("mappers"));} catch (Exception var3) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);}}

PooledDataSourceFactory

public class PooledDataSourceFactory extends UnpooledDataSourceFactory {public PooledDataSourceFactory() {this.dataSource = new PooledDataSource();}
}

PooledDataSource

public class PooledDataSource implements DataSource {private static final Log log = LogFactory.getLog(PooledDataSource.class);private final PoolState state = new PoolState(this);private final UnpooledDataSource dataSource;// 最大连接活跃数protected int poolMaximumActiveConnections = 10;// 最大空闲连接数protected int poolMaximumIdleConnections = 5;// 连接最大使用时间protected int poolMaximumCheckoutTime = 20000;// 最大等待时间protected int poolTimeToWait = 20000;// 可容忍的最大坏连接数protected int poolMaximumLocalBadConnectionTolerance = 3;// 探活protected String poolPingQuery = "NO PING QUERY SET";protected boolean poolPingEnabled;protected int poolPingConnectionsNotUsedFor;private int expectedConnectionTypeCode;public PooledDataSource() {this.dataSource = new UnpooledDataSource();}public PooledDataSource(UnpooledDataSource dataSource) {this.dataSource = dataSource;}// 获取连接public Connection getConnection() throws SQLException {return this.popConnection(this.dataSource.getUsername(), this.dataSource.getPassword()).getProxyConnection();}private PooledConnection popConnection(String username, String password) throws SQLException {boolean countedWait = false;PooledConnection conn = null;long t = System.currentTimeMillis();int localBadConnectionCount = 0;while(conn == null) {synchronized(this.state) {PoolState var10000;// 空闲的连接不为空,直接从空闲连接中拿一个返回if (!this.state.idleConnections.isEmpty()) {conn = (PooledConnection)this.state.idleConnections.remove(0);if (log.isDebugEnabled()) {log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");}// 当前活跃数<池子允许的最大活跃数 } else if (this.state.activeConnections.size() < this.poolMaximumActiveConnections) {conn = new PooledConnection(this.dataSource.getConnection(), this);if (log.isDebugEnabled()) {log.debug("Created connection " + conn.getRealHashCode() + ".");}// 当前活跃数=池子允许的最大活跃数 } else {// 拿到最开始的连接(最先创建的连接) PooledConnection oldestActiveConnection = (PooledConnection)this.state.activeConnections.get(0);// 判断最老的连接已经使用的时间long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();// 若这个时间>允许使用的最大时间if (longestCheckoutTime > (long)this.poolMaximumCheckoutTime) {// state中相应状态连接数+1++this.state.claimedOverdueConnectionCount;var10000 = this.state;var10000.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;var10000 = this.state;var10000.accumulatedCheckoutTime += longestCheckoutTime;// 从activeConnections中移除该连接this.state.activeConnections.remove(oldestActiveConnection);// 若该连接不是自动提交的if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {try {// 将操作进行回滚 oldestActiveConnection.getRealConnection().rollback();} catch (SQLException var16) {log.debug("Bad connection. Could not roll back");}}// 重新new一个新的连接conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());// 将旧的连接置为不可用oldestActiveConnection.invalidate();if (log.isDebugEnabled()) {log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");}} else {// 第一个连接也没超时,等待try {if (!countedWait) {// 必须去等待数量+1 ++this.state.hadToWaitCount;// 等待标志置为truecountedWait = true;}if (log.isDebugEnabled()) {log.debug("Waiting as long as " + this.poolTimeToWait + " milliseconds for connection.");}long wt = System.currentTimeMillis();// 开始等待this.state.wait((long)this.poolTimeToWait);var10000 = this.state;var10000.accumulatedWaitTime += System.currentTimeMillis() - wt;} catch (InterruptedException var17) {break;}}}// 已经获得连接if (conn != null) {// 测试连接是否可用(ping)if (conn.isValid()) {// 连接是不是自动提交if (!conn.getRealConnection().getAutoCommit()) {// 先回滚conn.getRealConnection().rollback();}// 设置连接类型                        conn.setConnectionTypeCode(this.assembleConnectionTypeCode(this.dataSource.getUrl(), username, password));// 设置连接使用时间conn.setCheckoutTimestamp(System.currentTimeMillis());conn.setLastUsedTimestamp(System.currentTimeMillis());// 修改池状态this.state.activeConnections.add(conn);++this.state.requestCount;var10000 = this.state;var10000.accumulatedRequestTime += System.currentTimeMillis() - t;} else {// 如果连接不可用if (log.isDebugEnabled()) {log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");}// 对坏连接进行统计++this.state.badConnectionCount;++localBadConnectionCount;conn = null;// 如果本次最大坏的连接数>最大的空闲连接数+最大可容忍的坏的连接数if (localBadConnectionCount > this.poolMaximumIdleConnections + this.poolMaximumLocalBadConnectionTolerance) {if (log.isDebugEnabled()) {log.debug("PooledDataSource: Could not get a good connection to the database.");}// 抛异常throw new SQLException("PooledDataSource: Could not get a good connection to the database.");}}}}}if (conn == null) {if (log.isDebugEnabled()) {log.debug("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");}throw new SQLException("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");} else {return conn;}}}

PoolState

// 保存了当前pool的一些状态
public class PoolState {protected PooledDataSource dataSource;// 空闲的连接protected final List<PooledConnection> idleConnections = new ArrayList();// 活跃的连接protected final List<PooledConnection> activeConnections = new ArrayList();// 当前池子被请求的次数protected long requestCount = 0L;// 累计的请求时间protected long accumulatedRequestTime = 0L;// 累计的使用时间(连接在外部被使用的时间)protected long accumulatedCheckoutTime = 0L;// 过期未还的连接数量protected long claimedOverdueConnectionCount = 0L;// 累计的未还的连接的使用时间protected long accumulatedCheckoutTimeOfOverdueConnections = 0L;// 累计的等待时长protected long accumulatedWaitTime = 0L;// 必须去等待的数量protected long hadToWaitCount = 0L;// 坏的连接数量protected long badConnectionCount = 0L;public PoolState(PooledDataSource dataSource) {this.dataSource = dataSource;}
}

归还连接


// org.apache.ibatis.datasource.pooled.PooledConnection#invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String methodName = method.getName();// 若调用的方法为closeif ("close".hashCode() == methodName.hashCode() && "close".equals(methodName)) {this.dataSource.pushConnection(this);return null;} else {try {if (!Object.class.equals(method.getDeclaringClass())) {this.checkConnection();}return method.invoke(this.realConnection, args);} catch (Throwable var6) {throw ExceptionUtil.unwrapThrowable(var6);}}}// 归还连接
protected void pushConnection(PooledConnection conn) throws SQLException {synchronized(this.state) {// 从活跃连接中删除该连接this.state.activeConnections.remove(conn);// 当前连接可用if (conn.isValid()) {PoolState var10000;// 若空闲连接的数量<容器允许的最大的空闲连接数 && if (this.state.idleConnections.size() < this.poolMaximumIdleConnections && conn.getConnectionTypeCode() == this.expectedConnectionTypeCode) {// 做统计var10000 = this.state;var10000.accumulatedCheckoutTime += conn.getCheckoutTime();// 回滚连接if (!conn.getRealConnection().getAutoCommit()) {conn.getRealConnection().rollback();}// 重新包装PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);				// 将连接放到空闲连接this.state.idleConnections.add(newConn);newConn.setCreatedTimestamp(conn.getCreatedTimestamp());newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());// 将原来连接置为不可用conn.invalidate();if (log.isDebugEnabled()) {log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");}// 唤醒state上等待的线程this.state.notifyAll();} else {// 空闲的连接已经满了// 统计var10000 = this.state;var10000.accumulatedCheckoutTime += conn.getCheckoutTime();if (!conn.getRealConnection().getAutoCommit()) {conn.getRealConnection().rollback();}// 将连接断开conn.getRealConnection().close();if (log.isDebugEnabled()) {log.debug("Closed connection " + conn.getRealHashCode() + ".");}// 该连接置为不可用conn.invalidate();}} else {if (log.isDebugEnabled()) {log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");}++this.state.badConnectionCount;}}}

解析Mappers标签

 // 解析mapper入口 this.mapperElement(root.evalNode("mappers"));// 可配置方式1<mapper resource="cn/mybatis/helloworld/mapper/UserMapper.xml"/>// 可配置方式2<package name="文件所在包路径"></package>
private void mapperElement(XNode parent) throws Exception {if (parent != null) {Iterator var2 = parent.getChildren().iterator();while(true) {while(var2.hasNext()) {// 拿到子元素 XNode child = (XNode)var2.next();String resource;// 判断子元素是是否是packageif ("package".equals(child.getName())) {resource = child.getStringAttribute("name");this.configuration.addMappers(resource);} else {resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");XMLMapperBuilder mapperParser;InputStream inputStream;// 配的resourceif (resource != null && url == null && mapperClass == null) {// 解析资源时,在当前线程下封装ErrorContext,若出问题,则告知可能该resource有问题ErrorContext.instance().resource(resource);inputStream = Resources.getResourceAsStream(resource);mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());mapperParser.parse();// 配的url } else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);inputStream = Resources.getUrlAsStream(url);mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());mapperParser.parse();} else {// 配了多个/没配if (resource != null || url != null || mapperClass == null) {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}// 配了classClass<?> mapperInterface = Resources.classForName(mapperClass);this.configuration.addMapper(mapperInterface);}}}return;}}}

mapperParser.parse();

public void parse() {// 若该mapper未被解析,则进行解析if (!this.configuration.isResourceLoaded(this.resource)) {this.configurationElement(this.parser.evalNode("/mapper"));// 将资源即mapper.xml放入configuration(下次需要加载时直接拿)this.configuration.addLoadedResource(this.resource);// 将mapper和命名空间绑定this.bindMapperForNamespace();}this.parsePendingResultMaps();this.parsePendingCacheRefs();this.parsePendingStatements();
}private void configurationElement(XNode context) {// 逐个获取标签进行解析try {String namespace = context.getStringAttribute("namespace");if (namespace != null && !namespace.equals("")) {this.builderAssistant.setCurrentNamespace(namespace);this.cacheRefElement(context.evalNode("cache-ref"));this.cacheElement(context.evalNode("cache"));this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));this.resultMapElements(context.evalNodes("/mapper/resultMap"));this.sqlElement(context.evalNodes("/mapper/sql"));this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));} else {throw new BuilderException("Mapper's namespace cannot be empty");}} catch (Exception var3) {throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3);}
}

阻塞式缓存
特点:调用get方法获取同一个key时,需要进行阻塞等待

public class BlockingCache implements Cache {// 超时时间private long timeout;// 被装饰者private final Cache delegate;// 针对每一个key建立一个ReentrantLockprivate final ConcurrentHashMap<Object, ReentrantLock> locks;public BlockingCache(Cache delegate) {this.delegate = delegate;this.locks = new ConcurrentHashMap();}public Object getObject(Object key) {// 获取锁this.acquireLock(key);// 执行数据库操作Object value = this.delegate.getObject(key);if (value != null) {this.releaseLock(key);}return value;
}private ReentrantLock getLockForKey(Object key) {ReentrantLock lock = new ReentrantLock();// 如果指定的key已存在,则不会put,返回这个key对应的value,// 如果指定的key不存在,则返回nullReentrantLock previous = (ReentrantLock)this.locks.putIfAbsent(key, lock);// 如果previous == null,说明是第一次访问return previous == null ? lock : previous;
}private void acquireLock(Object key) {// 获取绑定在该key上的ReentrantLock Lock lock = this.getLockForKey(key);if (this.timeout > 0L) {try {boolean acquired = lock.tryLock(this.timeout, TimeUnit.MILLISECONDS);if (!acquired) {throw new CacheException("Couldn't get a lock in " + this.timeout + " for the key " + key + " at the cache " + this.delegate.getId());}} catch (InterruptedException var4) {throw new CacheException("Got interrupted while trying to acquire lock for key " + key, var4);}} else {lock.lock();}}}private void releaseLock(Object key) {ReentrantLock lock = (ReentrantLock)this.locks.get(key);if (lock.isHeldByCurrentThread()) {lock.unlock();}}public void putObject(Object key, Object value) {try {this.delegate.putObject(key, value);} finally {this.releaseLock(key);}}

五、理解sqlSession
1、sqlSession = sqlSessionFactory.openSession();
DefaultSqlSessionFactory

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;DefaultSqlSession var8;try {Environment environment = this.configuration.getEnvironment();// 拿到事务工厂TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);// 执行器Executor executor = this.configuration.newExecutor(tx, execType);var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);} catch (Exception var12) {this.closeTransaction(tx);throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);} finally {ErrorContext.instance().reset();}return var8;}

TransactionFactory的实现
在这里插入图片描述
ManagedTransactionFactory:空壳事务管理器,用于继承后自行扩展;
SpringManagedTransactionFactory:spring实现的事务工厂;
JdbcTransactionFactory:mybatis实现的简单事务工厂;

Executor


// 里面包含一些具体的sql操作
public interface Executor {ResultHandler NO_RESULT_HANDLER = null;int update(MappedStatement var1, Object var2) throws SQLException;<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException;<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;<E> Cursor<E> queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException;List<BatchResult> flushStatements() throws SQLException;void commit(boolean var1) throws SQLException;void rollback(boolean var1) throws SQLException;CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4);boolean isCached(MappedStatement var1, CacheKey var2);void clearLocalCache();void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class<?> var5);Transaction getTransaction();void close(boolean var1);boolean isClosed();void setExecutorWrapper(Executor var1);
}

在这里插入图片描述
Mybatis提供了三种sql执行器,分别是SIMPLE(默认)、REUSE、BATCH。CachingExecutor只负责管理缓存

  • SIMPLE(SimpleExecutor),相当于JDBC的stmt.execute(sql) 执行完毕即关闭
  • REUSE(ReuseExecutor),相当于JDBC的stmt.execute(sql) 执行完不关闭,而是将stmt存入 Map<String,Statement> 中缓存,其中key为执行的sql模板;
  • BATCH (BatchExecutor),相当于JDBC语句的 stmt.addBatch(sql),即仅将执行SQL加入到批量计划但不真正执行,所以此时不会执行返回受影响行数,而只有执行stmt.executeBatch()后才会真正执行sql。

Executor executor = this.configuration.newExecutor(tx, execType);public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? this.defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Object 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);}if (this.cacheEnabled) {executor = new CachingExecutor((Executor)executor);}Executor executor = (Executor)this.interceptorChain.pluginAll(executor);return executor;}

Executor执行流程
执行器在执行query方法时,可以分为三个步骤:

  • 解析获取sql
  • 执行sql
  • 处理结果集

1、CachingExecutor.query

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {// 拿到绑定sqlBoundSql boundSql = ms.getBoundSql(parameterObject);// 根据ms,参数,分页信息,boundSql生成一个缓存的keyCacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {// 从ms中拿到cache(mepper.xml中配置的cache)Cache cache = ms.getCache();if (cache != null) {this.flushCacheIfRequired(ms);if (ms.isUseCache() && resultHandler == null) {this.ensureNoOutParams(ms, parameterObject, boundSql);List<E> list = (List)this.tcm.getObject(cache, key);if (list == null) {list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);this.tcm.putObject(cache, key, list);}return list;}}// 未配置二级缓存则不生效return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}// BaseExecutor.query
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());if (this.closed) {throw new ExecutorException("Executor was closed.");} else {// isFlushCacheRequired:是否sql执行完成刷新缓存(sql上的配置)// this.queryStack == 0:避免在嵌套查询的过程中把有必要存在的缓存刷新掉if (this.queryStack == 0 && ms.isFlushCacheRequired()) {this.clearLocalCache();}List list;try {++this.queryStack;// 去一级缓存中拿list = resultHandler == null ? (List)this.localCache.getObject(key) : null;if (list != null) {// 将输出参数进行保存this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {// 到数据库中查list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {--this.queryStack;}if (this.queryStack == 0) {Iterator var8 = this.deferredLoads.iterator();while(var8.hasNext()) {DeferredLoad deferredLoad = (DeferredLoad)var8.next();deferredLoad.load();}this.deferredLoads.clear();// 如果当前缓存配置为STATEMENT,清除缓存   if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {this.clearLocalCache();}}return list;}}// 从数据库里查询数据private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {// 先用占位符占位置,存在嵌套查询时,有些其他语句需要当前数据,拿到占位符,放到deferLoad,最终查询完毕,统一从一级缓存拿到this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);List list;try {list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {this.localCache.removeObject(key);}this.localCache.putObject(key, list);if (ms.getStatementType() == StatementType.CALLABLE) {this.localOutputParameterCache.putObject(key, parameter);}return list;}public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;List var9;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = this.prepareStatement(handler, ms.getStatementLog());var9 = handler.query(stmt, resultHandler);} finally {this.closeStatement(stmt);}return var9;}public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {String sql = this.boundSql.getSql();statement.execute(sql);return this.resultSetHandler.handleResultSets(statement);}public List<Object> handleResultSets(Statement stmt) throws SQLException {ErrorContext.instance().activity("handling results").object(this.mappedStatement.getId());List<Object> multipleResults = new ArrayList();int resultSetCount = 0;ResultSetWrapper rsw = this.getFirstResultSet(stmt);List<ResultMap> resultMaps = this.mappedStatement.getResultMaps();int resultMapCount = resultMaps.size();this.validateResultMapsCount(rsw, resultMapCount);while(rsw != null && resultMapCount > resultSetCount) {ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);rsw = this.getNextResultSet(stmt);this.cleanUpAfterHandlingResultSet();++resultSetCount;}String[] resultSets = this.mappedStatement.getResultSets();if (resultSets != null) {while(rsw != null && resultSetCount < resultSets.length) {ResultMapping parentMapping = (ResultMapping)this.nextResultMaps.get(resultSets[resultSetCount]);if (parentMapping != null) {String nestedResultMapId = parentMapping.getNestedResultMapId();ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId);this.handleResultSet(rsw, resultMap, (List)null, parentMapping);}rsw = this.getNextResultSet(stmt);this.cleanUpAfterHandlingResultSet();++resultSetCount;}}return this.collapseSingleResultList(multipleResults);}private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {try {if (parentMapping != null) {this.handleRowValues(rsw, resultMap, (ResultHandler)null, RowBounds.DEFAULT, parentMapping);} else if (this.resultHandler == null) {DefaultResultHandler defaultResultHandler = new DefaultResultHandler(this.objectFactory);this.handleRowValues(rsw, resultMap, defaultResultHandler, this.rowBounds, (ResultMapping)null);multipleResults.add(defaultResultHandler.getResultList());} else {this.handleRowValues(rsw, resultMap, this.resultHandler, this.rowBounds, (ResultMapping)null);}} finally {this.closeResultSet(rsw.getResultSet());}}public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {if (resultMap.hasNestedResultMaps()) {this.ensureNoRowBounds();this.checkResultHandler();this.handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);} else {this.handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);}}private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {DefaultResultContext<Object> resultContext = new DefaultResultContext();this.skipRows(rsw.getResultSet(), rowBounds);while(this.shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {ResultMap discriminatedResultMap = this.resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, (String)null);Object rowValue = this.getRowValue(rsw, discriminatedResultMap);this.storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());}}private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {ResultLoaderMap lazyLoader = new ResultLoaderMap();Object rowValue = this.createResultObject(rsw, resultMap, lazyLoader, (String)null);if (rowValue != null && !this.hasTypeHandlerForResultObject(rsw, resultMap.getType())) {MetaObject metaObject = this.configuration.newMetaObject(rowValue);boolean foundValues = this.useConstructorMappings;if (this.shouldApplyAutomaticMappings(resultMap, false)) {foundValues = this.applyAutomaticMappings(rsw, resultMap, metaObject, (String)null) || foundValues;}foundValues = this.applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, (String)null) || foundValues;foundValues = lazyLoader.size() > 0 || foundValues;rowValue = !foundValues && !this.configuration.isReturnInstanceForEmptyRow() ? null : rowValue;}return rowValue;}

query执行逻辑总结

  • query方法会包装成一个CachingExecutor(需要配置中开启二级缓存才生效)
  • 根据ms,查询参数,分页信息,boundsql构造一个用于缓存的key(二级缓存)
  • 当二级缓存未配置开启或未拿到缓存数据时,走BaseExecutor.query
  • baseExecutor中提供了查询模板(针对一级缓存:deferredLoad和嵌套查询:queryStack)。先在以及缓存中拿,未拿到走查询

六、懒加载
在mybatis中增加如下配置可实现懒加载

		<setting name="lazyLoadingEnabled" value="true"/><setting name="aggressiveLazyLoading" value="true"/><setting name="lazyLoadTriggerMethods" value=""/>
  • lazyLoadingEnabled:懒加载的全局开关。如果设置为false,则相关的sql会立即执行,默认为false
  • aggressiveLazyLoading:急切的执行懒加载,设置为true时,只要触发了懒加载就会将该层及其级联的查询全部执行,默认为true。设置为false时,每个属性都按需加载,这个配置适用于多层级联的懒加载
  • lazyLoadTriggerMethods:触发一次性加载的方法
  // DefaultResultSetHandler.getNestedQueryMappingValueResultLoader resultLoader = new ResultLoader(this.configuration, this.executor, nestedQuery,     nestedQueryParameterObject, targetType, key, nestedBoundSql);if (propertyMapping.isLazy()) {// 开启懒加载时,会将resultLoader放入loaderMap中,需要时再调用resultLoader.loadResult进行加载lazyLoader.addLoader(property, metaResultObject, resultLoader);value = DEFERED;} else {// 否则直接进行加载 value = resultLoader.loadResult();}public void addLoader(String property, MetaObject metaResultObject, ResultLoader resultLoader) {String upperFirst = getUppercaseFirstProperty(property);if (!upperFirst.equalsIgnoreCase(property) && this.loaderMap.containsKey(upperFirst)) {throw new ExecutorException("Nested lazy loaded result property '" + property + "' for query id '" + resultLoader.mappedStatement.getId() + " already exists in the result map. The leftmost property of all lazy loaded properties must be unique within a result map.");} else {this.loaderMap.put(upperFirst, new LoadPair(property, metaResultObject, resultLoader));}}

加载时机:在使用返回值或使用返回值中和级联字段相关的get,set方法时进行加载。因此,具体获取结果的对象一定为代理对象,详见代码如下:

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {ResultLoaderMap lazyLoader = new ResultLoaderMap();// 此时获取的rowValue为代理对象Object rowValue = this.createResultObject(rsw, resultMap, lazyLoader, (String)null);if (rowValue != null && !this.hasTypeHandlerForResultObject(rsw, resultMap.getType())) {MetaObject metaObject = this.configuration.newMetaObject(rowValue);boolean foundValues = this.useConstructorMappings;if (this.shouldApplyAutomaticMappings(resultMap, false)) {foundValues = this.applyAutomaticMappings(rsw, resultMap, metaObject, (String)null) || foundValues;}foundValues = this.applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, (String)null) || foundValues;foundValues = lazyLoader.size() > 0 || foundValues;rowValue = !foundValues && !this.configuration.isReturnInstanceForEmptyRow() ? null : rowValue;}return rowValue;}private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {this.useConstructorMappings = false;List<Class<?>> constructorArgTypes = new ArrayList();List<Object> constructorArgs = new ArrayList();// 创建结果普通实例Object resultObject = this.createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);if (resultObject != null && !this.hasTypeHandlerForResultObject(rsw, resultMap.getType())) {List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();Iterator var9 = propertyMappings.iterator();while(var9.hasNext()) {ResultMapping propertyMapping = (ResultMapping)var9.next();if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {// 创建代理结果resultObject = this.configuration.getProxyFactory().createProxy(resultObject, lazyLoader, this.configuration, this.objectFactory, constructorArgTypes, constructorArgs);break;}}}this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty();return resultObject;}public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {return JavassistProxyFactory.EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);}// EnhancedResultObjectProxyImpl
public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {String methodName = method.getName();try {synchronized(this.lazyLoader) {if ("writeReplace".equals(methodName)) {Object original;if (this.constructorArgTypes.isEmpty()) {original = this.objectFactory.create(this.type);} else {original = this.objectFactory.create(this.type, this.constructorArgTypes, this.constructorArgs);}PropertyCopier.copyBeanProperties(this.type, enhanced, original);if (this.lazyLoader.size() > 0) {return new JavassistSerialStateHolder(original, this.lazyLoader.getProperties(), this.objectFactory, this.constructorArgTypes, this.constructorArgs);}return original;}if (this.lazyLoader.size() > 0 && !"finalize".equals(methodName)) {if (!this.aggressive && !this.lazyLoadTriggerMethods.contains(methodName)) {String property;// 调用set方法,懒加载会被取消if (PropertyNamer.isSetter(methodName)) {property = PropertyNamer.methodToProperty(methodName);this.lazyLoader.remove(property);} else if (PropertyNamer.isGetter(methodName)) {// 如果为get方法,拿到Propertyproperty = PropertyNamer.methodToProperty(methodName);// 判断lazyLoader中是否有该property,有则加载if (this.lazyLoader.hasLoader(property)) {this.lazyLoader.load(property);}}} else {// 触发一次性加载 this.lazyLoader.loadAll();}}}return methodProxy.invoke(enhanced, args);} catch (Throwable var10) {throw ExceptionUtil.unwrapThrowable(var10);}}

七、动态代理
mybatis提供了非常强大的动态代理能力,我们可以使用getMapper方法获取一个代理对象,更加方便我们执行增删改查操作

 public void testProcy() throws IOException{try {// mapper即为代理对象UserMapper mapper = sqlSession.getMapper(UserMapper.class);int rows = mapper.insert(new User(null, "JERRY", 19));sqlSession.commit();log.info("rows -> {}", rows);}catch (Exception e){e.printStackTrace();sqlSession.rollback();}finally {sqlSession.close();}}public <T> T getMapper(Class<T> type) {return this.configuration.getMapper(type, this);
}public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return this.mapperRegistry.getMapper(type, sqlSession);
}public <T> T getMapper(Class<T> type, SqlSession sqlSession) {MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");} else {try {return mapperProxyFactory.newInstance(sqlSession);} catch (Exception var5) {throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);}}
}public T newInstance(SqlSession sqlSession) {MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);return this.newInstance(mapperProxy);
}protected T newInstance(MapperProxy<T> mapperProxy) {return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}// mapperProxy
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {// 如果为Object的方法直接调用if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);}if (this.isDefaultMethod(method)) {return this.invokeDefaultMethod(proxy, method, args);}} catch (Throwable var5) {throw ExceptionUtil.unwrapThrowable(var5);}MapperMethod mapperMethod = this.cachedMapperMethod(method);return mapperMethod.execute(this.sqlSession, args);}// mapperMethod.execute
public Object execute(SqlSession sqlSession, Object[] args) {Object param;Object result;switch (this.command.getType()) {case INSERT:param = this.method.convertArgsToSqlCommandParam(args);// 调用sqlSession的相应方法,本质是调用executor的相应方法result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));break;case UPDATE:param = this.method.convertArgsToSqlCommandParam(args);result = this.rowCountResult(sqlSession.update(this.command.getName(), param));break;case DELETE:param = this.method.convertArgsToSqlCommandParam(args);result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));break;case SELECT:if (this.method.returnsVoid() && this.method.hasResultHandler()) {this.executeWithResultHandler(sqlSession, args);result = null;} else if (this.method.returnsMany()) {result = this.executeForMany(sqlSession, args);} else if (this.method.returnsMap()) {result = this.executeForMap(sqlSession, args);} else if (this.method.returnsCursor()) {result = this.executeForCursor(sqlSession, args);} else {param = this.method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(this.command.getName(), param);}break;case FLUSH:result = sqlSession.flushStatements();break;default:throw new BindingException("Unknown execution method for: " + this.command.getName());}

测试代理demo

@Test
public void testCreateProxy() throws IOException{MapperProxy<UserMapper> mapperProxy = new MapperProxy<>(sqlSession, UserMapper.class, new HashMap<>());UserMapper mapper = (UserMapper)Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{UserMapper.class}, mapperProxy);int rows = mapper.insert(new User(null, "HAHAHHA", 20));sqlSession.commit();
}

八、插件
Mybatis允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,Mybatis允许使用插件来拦截的方法调用包括以下内容:

  • Executor --> (update,query,flushStatements,commit,rollback,getTransaction,close,isClosed)
  • ParameterHandler --> (getParameterObject,setParameters)
  • ResultSetHandler --> (handleResultSets,handleOutputParameters)
  • StatementHandler --> (prepare,parameterize,batch,update,query)

这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看Mybatis发行包中的源代码。如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。因为在试图修改或重写已有方法的行为时,很可能会破坏MyBatis的核心模块。这些都是更底层的类和方法,所以使用插件时要特别当心。
使用Mybatis插件可以极大提升Mybatis的可扩展性,如实现自动分页,分库分表,数据脱敏,sql信息统计,数据加解密等功能。
通过Mybatis提供的强大机制,使用插件是非常简单的,只需实现Interceptor接口,并指定想要拦截的方法签名即可。其中@Signature注解包含三个元素:type,method,args。其中,type指明要拦截的类,method指明方法名,args指明方法的参数列表。通过指定着三个元素,我们就能完全确定一个要拦截的方法。

package cn.j3code.studyspring.mybatis.helloworld.interceptors;import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;import java.sql.Statement;
import java.util.Properties;@Slf4j
@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class}),})
public class SqlCostTimeInterceptor implements Interceptor {private Properties properties;@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 1、拿到sql语句StatementHandler statementHandler = (StatementHandler) invocation.getTarget();BoundSql boundSql = statementHandler.getBoundSql();String sql = boundSql.getSql();Long start = System.currentTimeMillis();Object result = null;try{result = invocation.proceed();}finally {if(log.isDebugEnabled()){log.info("name --> {}", properties.get("name"));log.debug("sql语句[{}],执行耗时{}毫秒", sql, System.currentTimeMillis() - start);}}return result;}@Overridepublic Object plugin(Object target) {//如果没有特殊定制,直接使用Plugin这个工具类返回一个代理对象即可return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {this.properties = properties;}
}
 <plugins><plugin interceptor="cn.j3code.studyspring.mybatis.helloworld.interceptors.SqlCostTimeInterceptor"><property name="name" value="hello plugin"/></plugin></plugins>

插件源码流程:
Mybatis的插件很巧妙的使用代理的方式实现了责任链设计模式,插件被应用是统一调用了interceptorChain.pluginAll()方法。
在创建parameterHandler,ResultSetHandler,StatementHandler以及Executor的时候,都调用了pluginAll方法

public class InterceptorChain {private final List<Interceptor> interceptors = new ArrayList();public Object pluginAll(Object target) {Interceptor interceptor;for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {interceptor = (Interceptor)var2.next();}return target;}}

每一个拦截器都是Interceptor的具体实现,调用wrap方法会对目标对象进行包装,如下:

Plugin.wrap(target, this);
public class Plugin implements InvocationHandler {// 方法通过wrap进行动态代理,返回代理对象public static Object wrap(Object target, Interceptor interceptor) {Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);Class<?> type = target.getClass();Class<?>[] interfaces = getAllInterfaces(type, signatureMap);return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;}// 本质在方法调用时会执行invokepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {// 获取拦截的方法Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());// 如果是被拦截的方法,本质会执行插件的intercept方法return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);} catch (Exception var5) {throw ExceptionUtil.unwrapThrowable(var5);}}
}

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

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

相关文章

微信小程序编辑器代码格式缩进设置

第一步点击这个编辑器设置&#xff1a; 然后设置tab为空格&#xff0c;并且设置占几个空格&#xff0c;这里是4个空格。 这样就好了&#xff0c;文件保存就会自动设置好缩进格式了。

QT串口助手:识别串口号,发送,接收,十六进制

1 摘要 本文主要讲述如何使用QT从零开始实现一个串口助手的基本功能&#xff0c;功能如标题所示&#xff0c;文末附有源码供大家参考。文中若有纰漏&#xff0c;烦请读者斧正。 2 环境 QT 5.14.1Window 11 3 功能 串口打开/关闭 启动软件时识别串口号打开按键随串口打开状…

机器学习之 Jupyter Notebook 使用

&#x1f388; 作者&#xff1a;Linux猿 &#x1f388; 简介&#xff1a;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;Linux、C/C、云计算、物联网、面试、刷题、算法尽管咨询我&#xff0c;关注我&#xff0c;有问题私聊&#xff01; &…

CocosCreator3.8研究笔记(四)CocosCreator 脚本说明及使用(上)

在Cocos Creator中&#xff0c;脚本代码文件分为模块和插件两种方式&#xff1a; 模块一般就是项目的脚本&#xff0c;包含项目中创建的代码、引擎模块、第三方模块。 插件脚本&#xff0c;是指从 Cocos Creator 属性检查器中导入的插件&#xff0c;一般是引入第三方引入库文件…

vulnhub Seattle-0.0.3

环境&#xff1a;vuluhub Seattle-0.0.3 1.catelogue处任意文件下载(目录穿越) http://192.168.85.139/download.php?item../../../../../../etc/passwd 有个admin目录&#xff0c;可以下载里面的文件进行读取 2.cltohes详情页面处(参数prod)存在sql报错注入 http://192.16…

【C++刷题】动态规划

文章目录 前言一、斐波那契系列1.第 N 个泰波那契数2.三步问题3.使用最小花费爬楼梯4.解码方法5.不同路径6.下降路径最小和7.地下城游戏 二、多种状态系列1.按摩师2.打家劫舍II3.删除并获得点数4.粉刷房子5.买卖股票的最佳时机6.买卖股票的最佳时机III 三、子数组和子串系列1.最…

【iOS】Masonry的基本使用

文章目录 前言一、使用Masonry的原因二、约束的常识三、Masonry的简单使用四、Masonry的用例总结 前言 暑假安装了cocoapods&#xff0c;简单使用其调用了SVGKit&#xff0c;但是没有学习Masonry&#xff0c;特此总结博客记录Masonry的学习 一、使用Masonry的原因 Masonry是一…

最新ChatGPT程序源码+AI系统+详细图文部署教程/支持GPT4.0/支持Midjourney绘画/Prompt知识库

一、AI系统 如何搭建部署人工智能源码、AI创作系统、ChatGPT系统呢&#xff1f;小编这里写一个详细图文教程吧&#xff01;SparkAi使用Nestjs和Vue3框架技术&#xff0c;持续集成AI能力到AIGC系统&#xff01; 1.1 程序核心功能 程序已支持ChatGPT3.5/GPT-4提问、AI绘画、Mi…

详解 SpringMVC 的 @RequestMapping 注解

文章目录 1、RequestMapping注解的功能2、RequestMapping注解的位置3、RequestMapping注解的value属性4、RequestMapping注解的method属性5、RequestMapping注解的params属性&#xff08;了解&#xff09;6、RequestMapping注解的headers属性&#xff08;了解&#xff09;7、Sp…

6. vue-element-admin 二次开发避坑指南

vue-element-admin 二次开发避坑指南 1.1 前言1.1.1 切换标签时未保存页面的操作内容1.1.2 markdown 样式乱码1.1.3 修改默认尺寸1.1.4 当后端服务器宕机情况下页面加载层一直转圈无法停止&#xff0c;只能关闭页面1.1.5 隐藏齿轮 1.1 前言 上一篇博文&#xff0c;我们分享了vu…

从零构建深度学习推理框架-11 Resnet

op和layer结构 在runtime_ir.cpp中&#xff0c;我们上一节只构建了input和output&#xff0c;对于中间layer的具体实现一直没有完成&#xff1a; for (const auto& kOperator : this->operators_) {if (kOperator->type "pnnx.Input") {this->input_o…

ssm+vue“魅力”繁峙宣传网站源码和论文

ssmvue“魅力”繁峙宣传网站源码和论文102 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm 摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身…

C语言:截断+整型提升+算数转换练习

详情关于整型提升、算数转换与截断见文章&#xff1a; 《C语言&#xff1a;整型提升》 《C语言&#xff1a;算数转换》 一、代码一 int main() { char a -1; signed char b -1; unsigned char c -1; printf("%d %d %d", a, b, c); return 0; } 求…

使用安全复制命令scp在Windows系统和Linux系统之间相互传输文件

现在已经有很多远程控制服务器的第三方软件平台&#xff0c;比如FinalShell&#xff0c;MobaXterm等&#xff0c;半可视化界面&#xff0c;使用起来非常方便和友好&#xff0c;两个系统之间传输文件直接拖就行&#xff0c;当然也可以使用命令方式在两个系统之间相互传递。 目录…

C++面试题(丝)-计算机网络部分(1)

目录 1计算机网络 53 简述epoll和select的区别&#xff0c;epoll为什么高效&#xff1f; 54 说说多路IO复用技术有哪些&#xff0c;区别是什么&#xff1f; 55 简述socket中select&#xff0c;epoll的使用场景和区别&#xff0c;epoll水平触发与边缘触发的区别&#xff1f;…

使用 ElasticSearch 作为知识库,存储向量及相似性搜索

一、ElasticSearch 向量存储及相似性搜索 在当今大数据时代&#xff0c;快速有效地搜索和分析海量数据成为了许多企业和组织的重要需求。Elasticsearch 作为一款功能强大的分布式搜索和分析引擎&#xff0c;为我们提供了一种优秀的解决方案。除了传统的文本搜索&#xff0c;El…

【两周学会FPGA】从0到1学习紫光同创FPGA开发|盘古PGL22G开发板学习之数码管动态显示(五)

本原创教程由深圳市小眼睛科技有限公司创作&#xff0c;版权归本公司所有&#xff0c;如需转载&#xff0c;需授权并注明出处 适用于板卡型号&#xff1a; 紫光同创PGL22G开发平台&#xff08;盘古22K&#xff09; 一&#xff1a;盘古22K开发板&#xff08;紫光同创PGL22G开发…

http和https的区别?

什么是 HTTP&#xff1f; HTTP是一种互联网数据传输协议&#xff0c;用于在网络服务器和客户端之间进行数据传输。作为万维网的基础&#xff0c;HTTP协议允许网络浏览器向网络服务器发送请求&#xff0c;服务器则会返回响应。HTTP协议基于文本&#xff0c;因此传输的数据是人类…

41.岛屿数量(第四期模拟笔试)(BFS练习题)

题目&#xff1a; 给定一个 m 行 n 列的二维地图&#xff0c;初始化每个单元格都是海洋&#xff0c;二维地图外也全是海洋。 操作 addLand 会将单元格&#xff08;col, row&#xff09;变为陆地。 定义一系列相连的被海洋包围的陆地为岛屿&#xff0c; 横向相邻或者纵向相连的…

微信小程序请求接口返回的二维码(图片),本地工具和真机测试都能显示,上线之后不显示问题

请求后端接口返回的图片&#xff1a; 页面展示&#xff1a; 代码实现&#xff1a; :show-menu-by-longpress"true" 是长按保存图片 base64Code 是转为base64的地址 <image class"code" :src"base64Code" alt"" :show-menu-by-long…