Mybatis学习总结
在mybatis的源码中可以学习到很多知识:设计模式,mybatis整体架构,每个部分的重点解析,下面我将一一总结。
整体架构:
一一解释这些组件的作用:
配置文件:
首先要想与数据库交互必须要有数据库的基本配置信息,以及要执行的sql信息。所以在mybatis中则是将这些信息统一存放在xml文件中去
xmlConfiguration和xmlMappStatement类:
负责进行解析配置文件,configuration解析数据库的核心配置信息,mappStatement负责解析sql配置信息。
configuration和mappstatement类:
负责将解析出来的配置文件存储到对应的类中,configuration负责存储核心配置信息,mappStatement类负责存储sql配置信息
sqlSessionFactoryBuilder类:
负责构造sqlSession工厂,因为构造sqlSessionFactory的过程中需要很多复杂的操作,因此单独使用构造类将这些复杂操作进行封装不对外暴露,使得使用方能够专注于sqlSession工厂本身
sqlSessionFactory类:
负责生产sqlSession的工厂类,会根据不同的线程动态生成不同的sqlSession,同样的这个工厂类的目的就是为了封装生成sqlSession的过程,他与构造器的区别在于构造器一般是生成对象时候需要执行复杂操作,而工厂模式则是专注于组装不同的实例化对象
SqlSession类:
当一个线程与数据库交互的时候,都会通过mybatis生成对应的sqlSession通过这个sqlSession类对象来实现与数据库的交互过程
Executor类:
sqlSession的内部则是通过executor来间接与JDBC进行交互
StatementHandler类:
在executor内部当中则是含有很多statementhandler,这些handler则主要是对结果参数的封装和直接调用jdbc,StatementHandler
是 方法作用域 的对象。通常,每次执行一个数据库操作(尤其是使用 SimpleExecutor
时),都会创建一个新的 StatementHandler
实例(及其内部的 ParameterHandler
, ResultSetHandler
)
设计模式:
在mybatis中使用了多种的设计模式--工厂模式,构建者模式,模板方法,装饰器模式,代理模式等
下面我们来一一讲述这些设计模式使用的地方
构建者模式:
public class SqlSessionFactoryBuilder {// 多个重载的 build 方法,接收不同的配置源public SqlSessionFactory build(InputStream inputStream) {return build(inputStream, null, null);}public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {// 创建 XML 配置解析器XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);// 解析配置并构建 Configuration 对象return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}public SqlSessionFactory build(Configuration config) {// 最终使用 Configuration 对象创建 SqlSessionFactoryreturn new DefaultSqlSessionFactory(config);}
}
在SqlSessionFactoryBuilder 中就使用了构建者模式,通过传递进来配置文件的输入流,构建者将会通过builder这个方法将复杂的构建过程进行封装从而返回构建完成的工厂类。因此在实际业务中对于哪些复杂的类进行创建的时候可以采用构建者模式进行
工厂模式:
public interface SqlSessionFactory {SqlSession openSession();SqlSession openSession(boolean autoCommit);SqlSession openSession(Connection connection);// 其他重载方法...
}public class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}@Overridepublic 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);// 创建执行器(Executor)final Executor executor = configuration.newExecutor(tx, execType);// 创建并返回 SqlSessionreturn 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();}}
}
工厂模式的目的也是构建对象,但往往是那种复杂度低的对象,也就是说将现有的一些属性直接传递给新对象即可构建完成。同时工厂模式则是专注于构建不同的对象传递出去,而构建者模式则是专注于构建复杂对象,专注点不同
模板方法:
public abstract class BaseExecutor implements Executor {protected Transaction transaction;protected Executor wrapper;// 其他属性...@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameter);CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);return query(ms, parameter, rowBounds, resultHandler, key, boundSql);}@Overridepublic <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 (closed) {throw new ExecutorException("Executor was closed.");}// 先清局部缓存(如果需要)if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();}List<E> list;try {queryStack++;// 先从局部缓存中查询list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list != null) {handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {// 缓存未命中,从数据库查询list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {queryStack--;}// 其他处理...return list;}// 模板方法:从数据库查询的具体实现由子类完成protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;// 其他模板方法protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;// ...
}
在mybatis中则是BaseExecutor类中采用了模板方法的实际模式,通过一个抽象类将哪些抽象方法留给子类去实现,而抽象类的普通方法则涵盖着抽象方法,也就是说抽象类控制着整个方法的执行流程,而哪些子类则是去实现哪些精细化的方法。抽象类控制着大体,子类实现细节。
装饰器模式:
public class CachingExecutor implements Executor {private final Executor delegate; // 被装饰的执行器private final TransactionalCacheManager tcm = new TransactionalCacheManager();public CachingExecutor(Executor delegate) {this.delegate = delegate;delegate.setExecutorWrapper(this);}@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameterObject);// 创建缓存键CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);// 执行查询(带缓存逻辑)return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)throws SQLException {// 获取二级缓存Cache cache = ms.getCache();if (cache != null) {// 处理缓存刷新flushCacheIfRequired(ms);if (ms.isUseCache() && resultHandler == null) {// 确保存储过程结果不被缓存ensureNoOutParams(ms, boundSql);// 从缓存中获取结果@SuppressWarnings("unchecked")List<E> list = (List<E>) tcm.getObject(cache, key);if (list == null) {// 缓存未命中,委托给被装饰的执行器执行查询list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);// 将查询结果放入缓存tcm.putObject(cache, key, list); }return list;}}// 如果没有缓存,直接委托给被装饰的执行器return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}// 其他方法(委托给被装饰对象)...@Overridepublic int update(MappedStatement ms, Object parameterObject) throws SQLException {// 可能刷新缓存flushCacheIfRequired(ms);// 委托给被装饰的执行器执行更新return delegate.update(ms, parameterObject);}// 其他委托方法...
}
对于CachingExecutor 则采用了装饰器模式的方式进行设计的,可以看到他的内部属性含有一个Executor类的属性,同时本身自己也属于Executor的子类。
这个CacheExecutor使用的地方在二级缓存中,当检测到配置文件使用了cache,那么生成Executor的时候就会生成CacheExecutor这个装饰类,然后这个装饰类内部又会蕴含着原来普通的Executor类,当调用方法的时候虽然调用的CacheExecutor类但往往内核调用的则是普通Executor,CacheExecutor仅仅只是添加了一些方法。
代理模式
public class MapperProxyFactory<T> {private final Class<T> mapperInterface;private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();public MapperProxyFactory(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}@SuppressWarnings("unchecked")protected T newInstance(MapperProxy<T> mapperProxy) {// 使用 JDK 动态代理生成代理对象return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),new Class[] { mapperInterface },mapperProxy);}public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}
}public class MapperProxy<T> implements InvocationHandler, Serializable {private static final long serialVersionUID = -642454039855972983L;private final SqlSession sqlSession;private final Class<T> mapperInterface;private final Map<Method, MapperMethodInvoker> methodCache;public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {this.sqlSession = sqlSession;this.mapperInterface = mapperInterface;this.methodCache = methodCache;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {// 处理 Object 类的方法(如 toString、hashCode)if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else {// 处理 Mapper 接口的方法return cachedInvoker(method).invoke(proxy, method, args, sqlSession);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}}private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {// 从缓存中获取方法调用器try {return methodCache.computeIfAbsent(method, m -> {if (m.isDefault()) {// 处理默认方法try {if (privateLookupInMethod == null) {return new DefaultMethodInvoker(getMethodHandleJava8(method));} else {return new DefaultMethodInvoker(getMethodHandleJava9(method));}} catch (IllegalAccessException | InstantiationException | InvocationTargetException| NoSuchMethodException e) {throw new RuntimeException(e);}} else {// 处理普通方法return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));}});} catch (RuntimeException re) {Throwable cause = re.getCause();throw cause == null ? re : cause;}}// 其他辅助方法...
}
mybatis中在mapper子类的实现类中使用了动态代理的方式,我们采用在使用mybatis的时候往往mapper接口都是没有实现类的,mybatis则是结合spring框架采用动态代理的方式来实现的。本身我们并没有书写mapper的接口实现类而是通过spring容器在调用的时候通过动态代理的方式生成的实现类,并且根据调用的接口名称等信息定位到statementId和参数的信息以及实际值来进行对应的sqlSession调用。也就是说通过动态代理的方式来讲调用信息引入到真正的mybatis框架内部,动态代理是外部与mybatis进行交互的一个入口