MyBatis原理总结

MyBatis原理总结

简介

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

org.mybatis

内容:

  1. 入门
  2. XML配置文件
  3. XML映射文件
  4. 动态SQL
  5. Java API

关键点

  1. Java API作用域

    对象作用作用域安全
    SqlSessionFactoryBuilder创建sqlSessionFactory建议局部方法变量
    SqlSessionFactory建议应用作用域线程安全
    SqlSession建议请求或方法作用域线程不安全
  2. 缓存

    缓存说明原理
    一级缓存,本地默认开启BaseExecutor持有PerpetualCache对象,内部持有一个HashMap
    二级缓存,全局默认不开启CachingExecutor
  3. 延迟加载:动态代理实现

  4. 自定义拦截器

    <plugins>
        <plugin interceptor="com.xl.read.mybatis.source.plugin.QueryInterceptor">
            <property name="value" value="100"/>
        </plugin>
    </plugins>
    
    @Intercepts({@Signature(type = Executor.class, method = "update", args = {
        MappedStatement.class, Object.class})
    })
    public class QueryInterceptor implements Interceptor {
        @Override
        public Object plugin(Object target) {
            return target instanceof Executor? Plugin.wrap(target, this):target;
        }
    
        @Override
        public void setProperties(Properties properties) {
            Interceptor.super.setProperties(properties);
        }
    
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
            Object parameter = invocation.getArgs()[1];
            
            // 前置处理
    	Object ret = invocation.proceed();
            // 后置处理
            return ret;
        }
    }
    

    说明:type指定Executor.class,然后查看方法,找到Executor#update,如下,按照该参数完成类上注解属性args

    int update(MappedStatement ms, Object parameter) throws SQLException;

    setProperties作用:plugin的字标签property配置,可用来存储,然后在拦截前后使用

    intercept作用:前后可进行增强

架构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L7XX1TcJ-1638782445903)(asserts/3298449214-610fab25cf5ad_fix732.jpg)]

模块实现

动态代理

获取代理:MapperProxyFactory创建MapperProxy,自定义Mapper的方法会走到MapperProxy#invoke

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

代理拦截:MapperMethodInvoker#invoke

public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    return mapperMethod.execute(sqlSession, args);
}

execute根据SqlCommandType,委托SqlSession#update执行更新、或SqlSession#query执行查询

public enum SqlCommandType {
  UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
}

Executor

构造:CachingExecutor

委托:BaseExecutor, SimpleExecutor(ReuseExecutor, BatchExecutor)

作用:缓存管理、委托StatementHandler

缓存

二级缓存:CachingExecutor,需要配置开启,持有变量TransactionalCacheManager tcm,然后tcm内部持有变量HashMap<Cache, TransactionalCache>

一级缓存:BaseExecutor,默认开启,持有变量PerpetualCache localCache,然后localCache内部持有HashMap<Object, Object> cache

StatementHandler

构造:RoutingStatementHandler

委托:PreparedStatementHandler(SimpleStatementHandler, CallableStatementHandler)

处理:

  1. 实例化
  2. prepare Statement
    1. prepare:实例化Statement、timeout、fetchSize
    2. parameterize:委托ParameterHandler设置参数
  3. query:执行PreparedStatement#execute,然后委托ResultSetHandler处理响应

ParameterHandler

构造:DefaultParameterHandler

处理:遍历ParameterMapping,区分additionalParameter、null参数、有typeHandler、其他四种场景设置value,获取jdbcType,调用typeHandler.setParameter(ps, i + 1, value, jdbcType);设置参数

typeHandler具体类型,根据TypeHandlerRegistry typeHandlerRegistry的成员变量ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap,由参数类型Type、jdbc类型JdbcType获取,最终调用PreparedStatement#setXxx系列方法设置参数

ResultHandler

构造:DefaultResultSetHandler

处理:?

说明:?

  1. 逻辑分页 RowBounds

  2. 最终会结合ResultMap解析ResultSet

拦截器

目标:Executor, StatementHandler, ParameterHandler, ResultSetHandler

例如:插件会拦截,最终返回Plugin.wrap->Proxy.newProxyInstance创建的代理对象,而Plugin实现了InvocationHandler,会拦截调用处,触发当前方法的invoke

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        Set<Method> methods = signatureMap.get(method.getDeclaringClass());
        if (methods != null && methods.contains(method)) {
            return interceptor.intercept(new Invocation(target, method, args));
        }
        return method.invoke(target, args);
    } catch (Exception e) {
        throw ExceptionUtil.unwrapThrowable(e);
    }
}

流程分析

流程

  1. 读取配置文件,构造SqlSessionFactory
  2. 构造SqlSession
  3. 获取XxxMapper
  4. 调用方法

第一步:读取配置文件,构造SqlSessionFactory

SqlSessionFactoryBuilder#build

方法签名
public SqlSessionFactory build(InputStream inputStream) {
    // 第2个参数:String environment
    // 第3个参数:Properties properties
    return this.build((InputStream)inputStream, (String)null, (Properties)null);
}
处理流程
  1. 构造XMLConfigBuilder

  2. XMLConfigBuilder#parse

    public Configuration parse() {
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }
    

    说明:parseConfiguration会逐个解析配置文件的子标签

  3. 构造SqlSessionFactory

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }
    
parseConfiguration

作用:解析mybatis配置文件的标签

properties

作用:变量

settings

作用:虚拟文件系统、日志、其他设置

loadCustomVfs(settings);
loadCustomLogImpl(settings);
private void settingsElement(Properties props) {
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
    configuration.setShrinkWhitespacesInSql(booleanValueOf(props.getProperty("shrinkWhitespacesInSql"), false));
    configuration.setDefaultSqlProviderType(resolveClass(props.getProperty("defaultSqlProviderType")));
}
typeAliases

说明:保存到Configuration#typeAliasRegistry成员变量中,该变量类型为TypeAliasRegistry,内部持有一个HashMap,用来映射key到Class

public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
        throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748
    String key = alias.toLowerCase(Locale.ENGLISH);
    if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
        throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
    }
    typeAliases.put(key, value);
}
plugins

说明:拦截器

  1. 使用:定义拦截器并配置使用,见上面[自定义拦截器]

  2. 解析

    private void pluginElement(XNode parent) throws Exception {
        if (parent != null) {
            for (XNode child : parent.getChildren()) {
                String interceptor = child.getStringAttribute("interceptor");
                Properties properties = child.getChildrenAsProperties();
                Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
                interceptorInstance.setProperties(properties);
                configuration.addInterceptor(interceptorInstance);
            }
        }
    }
    
    public void addInterceptor(Interceptor interceptor) {
        // InterceptorChain interceptorChain = new InterceptorChain()
        interceptorChain.addInterceptor(interceptor);
    }
    

    其中,拦截器链持有List<Interceptor>,然后在生成被拦截对象时,调用Interceptor#plugin生成代理对象,调用被拦截对象的方法时实际调用被代理对象

    例如:被拦截对象Executor,拦截器XxxInterceptor,生成Executor后会基于拦截器链创建代理,返回代理对象,调用方法时先执行Interceptor#intercept

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-clClFbbQ-1638782445904)(asserts/image-20211203221611069.png)]

environment

作用:解析environments标签下面的environment,只处理environment.id等于environments.default的environment标签,包含transactionManager和dataSource两个子标签的处理

  1. 事务管理器:有两种类型事务管理器JDBC|MANAGED,JDBC依赖数据源的Connection管理事务,而MANAGED将事务交给容器管理。

    private TransactionFactory transactionManagerElement(XNode context) throws Exception {
        if (context != null) {
            String type = context.getStringAttribute("type");
            Properties props = context.getChildrenAsProperties();
            TransactionFactory factory = (TransactionFactory) resolveClass(type).getDeclaredConstructor().newInstance();
            factory.setProperties(props);
            return factory;
        }
        throw new BuilderException("Environment declaration requires a TransactionFactory.");
    }
    
  2. 数据源:有三种类型数据源UNPOOLED|POOLED|JNDI,非连接池每次打开关闭,连接池使用池管理,JNDI则是在EJB或应用服务器这类容器中使用,集中或外部配置数据源

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LqqpPmTO-1638782445905)(asserts/image-20211204140223864.png)]

typeHandlers

作用:注册类型处理器,用于实现javaType和jdbcType的互转处理

使用:

  1. 定义一个TypeHandlers
  2. 注册到mybatis
  3. 使用

注册流程:标签解析阶段,最终存储为ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap

其他标签
objectFactory
objectWrapperFactory
reflectorFactory
databaseIdProvider
mappers

第二步:构造SqlSession

DefaultSqlSessionFactory#openSession()

方法签名
// ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        final Environment environment = configuration.getEnvironment();
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        final Executor executor = configuration.newExecutor(tx, execType);
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}
处理流程
  1. 从Environment中,获取TransactionFactory、DataSource

    Q:何时实例化?

    A:解析environments——>transactionManager标签时,反射获取实例

  2. 构造Transaction

    public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
        return new JdbcTransaction(ds, level, autoCommit);
    }
    
  3. 构造Executor

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor 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);
        }
        // 二级缓存开关,默认为true
        if (cacheEnabled) {
            executor = new CachingExecutor(executor);
        }
        // 这就是上面插件处理,在创建Executor后,经过Interceptor#plugin生成代理对象
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
    }
    
  4. 构造DefaultSqlSession

第三步:获取XxxMapper

DefaultSqlSession#getMapper

方法签名
public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
}

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}
处理流程
  1. 从MapperRegistry#knownMappers获取MapperProxyFactory

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }
    
  2. 创建MapperProxy

    public T newInstance(SqlSession sqlSession) {
        // MapperProxy实现了InvocationHandler
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }
    
  3. 创建XxxMapper的代理实例

    protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }
    

说明:从Configuration.mapperRegistry获取MapperProxyFactory,然后调用newInstance创建代理实例

说明:上述的MapperProxy,实现Invocationhandler,有个方法invoke,在XxxMapper方法调用时,会进入MapperProxy#invoke

第四步:update

  1. XxxMapper#insert

  2. MapperProxy#invoke

  3. MapperMethodInvoker#invoke

  4. MapperMethod#execute

  5. SqlSession#insert

  6. SqlSession#update

    1. CachingExecutor#update:代理(插件+Executor)
    2. BaseExecutor#update
    3. BaseExecutor#doUpdate
    4. RoutingStatementHandler#update
      1. PreparedStatementHandler#update

第四步:query

  1. XxxMapper#selectList
  2. MapperProxy#invoke
  3. MapperMethodInvoker#invoke
  4. MapperMethod#execute
  5. SqlSession#selectList
    1. CachingExecutor#query
    2. BaseExecutor#query
    3. SimpleExecutor#doQuery
    4. RoutingStatementHandler#query
      1. PreparedStatementHandler#query
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值