作者:小傅哥 博客:https://bugstack.cn
❝沉淀、分享、成长,让自己和他人都能有所收获!😜 ❞
总有不少研发伙伴问小傅哥:“为什么学设计模式、看框架源码、补技术知识,就一个普通的业务项目,会造飞机不也是天天写CRUD吗?”
你说的没错,但你天天写CRUD,你觉得 烦不? 慌不? 是不是既担心自己没有得到技术成长,也害怕将来没法用这些都是CRUD的项目去参加;述职、晋升、答辩,甚至可能要被迫面试时,自己手里一点干货也没有的情况。
所以你/我作为一个小镇卷码家,当然要扩充自己的知识储备,否则架构,架构思维不懂
、设计,设计模式不会
、源码、源码学习不深
,最后就用一堆CRUD写简历吗?
在 Mybatis 两万多行的框架源码实现中,使用了大量的设计模式来解耦工程架构中面对复杂场景的设计,这些是设计模式的巧妙使用才是整个框架的精华,这也是小傅哥喜欢卷源码的重要原因。经过小傅哥的整理有如下10种设计模式的使用,如图所示
Mybatis 框架源码10种设计模式
讲道理,如果只是把这10种设计模式背下来,等着下次面试的时候拿出来说一说,虽然能有点帮助,不过这种学习方式就真的算是把路走窄了。就像你每说一个设计模式,能联想到这个设计模式在Mybatis的框架中,体现到哪个流程中的源码实现上了吗?这个源码实现的思路能不能用到你的业务流程开发里?别总说你的流程简单,用不上设计模式!难度因为有钱、富二代,就不考试吗?🤔
好啦,不扯淡了,接下来小傅哥就以《手写Mybatis:渐进式源码实践》的学习,给大家列举出这10种设计模式,在Mybatis框架中都体现在哪里了!
关注公众号【bugstack虫洞栈】回复【Mybatis】
源码详见:cn.bugstack.mybatis.session.SqlSessionFactory
public interface SqlSessionFactory {
SqlSession openSession();
}
源码详见:cn.bugstack.mybatis.session.defaults.DefaultSqlSessionFactory
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
TransactionFactory transactionFactory = environment.getTransactionFactory();
tx = transactionFactory.newTransaction(configuration.getEnvironment().getDataSource(), TransactionIsolationLevel.READ_COMMITTED, false);
// 创建执行器
final Executor executor = configuration.newExecutor(tx);
// 创建DefaultSqlSession
return new DefaultSqlSession(configuration, executor);
} catch (Exception e) {
try {
assert tx != null;
tx.close();
} catch (SQLException ignore) {
}
throw new RuntimeException("Error opening session. Cause: " + e);
}
}
}
Mybatis 工厂模式
SqlSessionFactory
是获取会话的工厂,每次我们使用 Mybatis 操作数据库的时候,都会开启一个新的会话。在会话工厂的实现中负责获取数据源环境配置信息、构建事务工厂、创建操作SQL的执行器,并最终返回会话实现类。SqlSessionFactory
、ObjectFactory
、MapperProxyFactory
、DataSourceFactory
源码详见:cn.bugstack.mybatis.session.Configuration
public class Configuration {
// 缓存机制,默认不配置的情况是 SESSION
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
// 映射注册机
protected MapperRegistry mapperRegistry = new MapperRegistry(this);
// 映射的语句,存在Map里
protected final Map<String, MappedStatement> mappedStatements = new HashMap<>();
// 缓存,存在Map里
protected final Map<String, Cache> caches = new HashMap<>();
// 结果映射,存在Map里
protected final Map<String, ResultMap> resultMaps = new HashMap<>();
protected final Map<String, KeyGenerator> keyGenerators = new HashMap<>();
// 插件拦截器链
protected final InterceptorChain interceptorChain = new InterceptorChain();
// 类型别名注册机
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
// 类型处理器注册机
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
// 对象工厂和对象包装器工厂
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
protected final Set<String> loadedResources = new HashSet<>();
//...
}
Mybatis 单例模式
ErrorContext
、LogFactory
、Configuration
源码详见:cn.bugstack.mybatis.mapping.ResultMap#Builder
public class ResultMap {
private String id;
private Class<?> type;
private List<ResultMapping> resultMappings;
private Set<String> mappedColumns;
private ResultMap() {
}
public static class Builder {
private ResultMap resultMap = new ResultMap();
public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings) {
resultMap.id = id;
resultMap.type = type;
resultMap.resultMappings = resultMappings;
}
public ResultMap build() {
resultMap.mappedColumns = new HashSet<>();
// step-13 新增加,添加 mappedColumns 字段
for (ResultMapping resultMapping : resultMap.resultMappings) {
final String column = resultMapping.getColumn();
if (column != null) {
resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
}
}
return resultMap;
}
}
// ... get
}
Mybatis 建造者模式
SqlSessionFactoryBuilder
、XMLConfigBuilder
、XMLMapperBuilder
、XMLStatementBuilder
、CacheBuilder
源码详见:cn.bugstack.mybatis.logging.Log
public interface Log {
boolean isDebugEnabled();
boolean isTraceEnabled();
void error(String s, Throwable e);
void error(String s);
void debug(String s);
void trace(String s);
void warn(String s);
}
源码详见:cn.bugstack.mybatis.logging.slf4j.Slf4jImpl
public class Slf4jImpl implements Log {
private Log log;
public Slf4jImpl(String clazz) {
Logger logger = LoggerFactory.getLogger(clazz);
if (logger instanceof LocationAwareLogger) {
try {
// check for slf4j >= 1.6 method signature
logger.getClass().getMethod("log", Marker.class, String.class, int.class, String.class, Object[].class, Throwable.class);
log = new Slf4jLocationAwareLoggerImpl((LocationAwareLogger) logger);
return;
} catch (SecurityException e) {
// fail-back to Slf4jLoggerImpl
} catch (NoSuchMethodException e) {
// fail-back to Slf4jLoggerImpl
}
}
// Logger is not LocationAwareLogger or slf4j version < 1.6
log = new Slf4jLoggerImpl(logger);
}
@Override
public boolean isDebugEnabled() {
return log.isDebugEnabled();
}
@Override
public boolean isTraceEnabled() {
return log.isTraceEnabled();
}
@Override
public void error(String s, Throwable e) {
log.error(s, e);
}
@Override
public void error(String s) {
log.error(s);
}
@Override
public void debug(String s) {
log.debug(s);
}
@Override
public void trace(String s) {
log.trace(s);
}
@Override
public void warn(String s) {
log.warn(s);
}
}
Mybatis 适配器模式
源码详见:cn.bugstack.mybatis.binding.MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}
// ...
}
Mybatis 代理模式
DriverProxy
、Plugin
、Invoker
、MapperProxy
源码详见:cn.bugstack.mybatis.scripting.xmltags.SqlNode
public interface SqlNode {
boolean apply(DynamicContext context);
}
源码详见:cn.bugstack.mybatis.scripting.xmltags.IfSqlNode
public class IfSqlNode implements SqlNode{
private ExpressionEvaluator evaluator;
private String test;
private SqlNode contents;
public IfSqlNode(SqlNode contents, String test) {
this.test = test;
this.contents = contents;
this.evaluator = new ExpressionEvaluator();
}
@Override
public boolean apply(DynamicContext context) {
// 如果满足条件,则apply,并返回true
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
}
源码详见:cn.bugstack.mybatis.scripting.xmltags.XMLScriptBuilder
public class XMLScriptBuilder extends BaseBuilder {
private void initNodeHandlerMap() {
// 9种,实现其中2种 trim/where/set/foreach/if/choose/when/otherwise/bind
nodeHandlerMap.put("trim", new TrimHandler());
nodeHandlerMap.put("if", new IfHandler());
}
List<SqlNode> parseDynamicTags(Element element) {
List<SqlNode> contents = new ArrayList<>();
List<Node> children = element.content();
for (Node child : children) {
if (child.getNodeType() == Node.TEXT_NODE || child.getNodeType() == Node.CDATA_SECTION_NODE) {
} else if (child.getNodeType() == Node.ELEMENT_NODE) {
String nodeName = child.getName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new RuntimeException("Unknown element " + nodeName + " in SQL statement.");
}
handler.handleNode(element.element(child.getName()), contents);
isDynamic = true;
}
}
return contents;
}
// ...
}
配置详见:resources/mapper/Activity_Mapper.xml
<select id="queryActivityById" parameterType="cn.bugstack.mybatis.test.po.Activity" resultMap="activityMap" flushCache="false" useCache="true">
SELECT activity_id, activity_name, activity_desc, create_time, update_time
FROM activity
<trim prefix="where" prefixOverrides="AND | OR" suffixOverrides="and">
<if test="null != activityId">
activity_id = #{activityId}
</if>
</trim>
</select>
Mybatis 组合模式
源码详见:cn.bugstack.mybatis.session.Configuration
public Executor newExecutor(Transaction transaction) {
Executor executor = new SimpleExecutor(this, transaction);
// 配置开启缓存,创建 CachingExecutor(默认就是有缓存)装饰者模式
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
return executor;
}
Mybatis 装饰器模式
源码详见:cn.bugstack.mybatis.executor.BaseExecutor
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
if (closed) {
throw new RuntimeException("Executor was closed.");
}
// 清理局部缓存,查询堆栈为0则清理。queryStack 避免递归调用清理
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 根据cacheKey从localCache中查询数据
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list == null) {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}
源码详见:cn.bugstack.mybatis.executor.SimpleExecutor
protected int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 新建一个 StatementHandler
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// 准备语句
stmt = prepareStatement(handler);
// StatementHandler.update
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
Mybatis 模板模式
BaseExecutor
、SimpleExecutor
、BaseTypeHandler
源码详见:cn.bugstack.mybatis.type.TypeHandler
public interface TypeHandler<T> {
/**
* 设置参数
*/
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* 获取结果
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
/**
* 取得结果
*/
T getResult(ResultSet rs, int columnIndex) throws SQLException;
}
源码详见:cn.bugstack.mybatis.type.LongTypeHandler
public class LongTypeHandler extends BaseTypeHandler<Long> {
@Override
protected void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType) throws SQLException {
ps.setLong(i, parameter);
}
@Override
protected Long getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getLong(columnName);
}
@Override
public Long getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getLong(columnIndex);
}
}
Mybatis 策略模式
PooledDataSource\UnpooledDataSource
、BatchExecutor\ResuseExecutor\SimpleExector\CachingExecutor
、LongTypeHandler\StringTypeHandler\DateTypeHandler
源码详见:cn.bugstack.mybatis.reflection.property.PropertyTokenizer
public class PropertyTokenizer implements Iterable<PropertyTokenizer>, Iterator<PropertyTokenizer> {
public PropertyTokenizer(String fullname) {
// 班级[0].学生.成绩
// 找这个点 .
int delim = fullname.indexOf('.');
if (delim > -1) {
name = fullname.substring(0, delim);
children = fullname.substring(delim + 1);
} else {
// 找不到.的话,取全部部分
name = fullname;
children = null;
}
indexedName = name;
// 把中括号里的数字给解析出来
delim = name.indexOf('[');
if (delim > -1) {
index = name.substring(delim + 1, name.length() - 1);
name = name.substring(0, delim);
}
}
// ...
}
Mybatis 迭代器模式
PropertyTokenizer
一份源码的成体系拆解渐进式学习,可能需要1~2个月的时间,相比于爽文和疲于应试要花费更多的经历。但你总会在一个大块时间学习完后,会在自己的头脑中构建出一套完整体系关于此类知识的技术架构,无论从哪里入口你都能清楚各个分支流程的走向,这也是你成为技术专家路上的深度学习。
如果你也想有这样酣畅淋漓的学习,千万别错过傅哥为你编写的资料《手写Mybatis:渐进式源码实践》目录如图所示,共计20章
《手写Mybatis:渐进式源码实践》
- END -