文章目录
1 介绍
最近看了一下mybatis的源码,发现了一些有趣而且值得学习的地方
2 源码概述
2.1 几个比较重要的类
SqlSession:
这个类是整个mybatis的核心类,通过该类,我们执行命令,获取映射示例和对应的事务管理器。
基于mybatis原生的jar包时,SqlSession是通过SqlSessionFactory进行创建
对于SqlSessionFactory的创建,其内部是通过创建者模型SqlSessionFactoryBuilder进行创建
对于mybatis-spring而言,是直接通过依赖注入创建对应的SqlSession进行创建
2.2 解析过程
我们可以直接下载mybatis-3的源码,https://github.com/mybatis/mybatis-3
进入到对应的SqlSessionManagerTest类进行源码分析
对于基于xml配置文件的解析,我们只需要通过以下几行代码进行构建:
// 获取对应的mybatis配置信息的文件
final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 通过对应的SqlSessionFactoryBuilder进行创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取当前的映射操作
// AuthorMapper mapper = sqlSessionFactory.getConfiguration().getMapper(AuthorMapper.class, sqlSession);
AuthorMapper mapper = sqlSession.getMapper(AuthorMapper.class);
Author expected = new Author(500, "cbegin", "******", "cbegin@somewhere.com", "Something...", null);
// mapper.insertAuthor(expected); // 另一种使用方法
// 使用sqlSession进行插入操作
int insert = sqlSession.insert("org.apache.ibatis.domain.blog.mappers.AuthorMapper.insertAuthor", expected);
sqlSession.commit();
sqlSession.close();
首先基于SqlSessionFactoryBuilder获取SqlSessionFactory,在通过SqlSessionFactory获取对应的SqlSession。进行sql操作。
在
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
这行代码中,通过SqlSessionFactoryBuilder#build 进行xml文件解析,其内部是通过创建XMLConfigBuilder并持有XPathParser 进行xml文件解析,创建Document和XPath其内部实际上是使用了jdk原生的javax.xml.parse 和 org.w3c.dom 包进行解析,并没有依赖于外部的第三方解析xml的库。
对于xml文件的解析,mybatis实际上是通过将xml文件的配置映射到Configuration类。这一部分的逻辑通过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;
}
/**
* 通用xml解析--> Configuration 类的功能
*/
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
// 校验对应的<settings> 配置是否符合指定Configuration的配置值
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 加载对应的VFS,这个我还没有真的了解:https://blog.csdn.net/qq_17231297/article/details/84567599
loadCustomVfs(settings);
// 记载自定义的日志实现类 logback +slf4j log4j +apace common logg https://blog.csdn.net/weixin_34277853/article/details/88536811
// 适配器模式(牛逼的设计)
loadCustomLogImpl(settings);
// 别名映射
// 内部实际上是Configuration持有了 TypeAliasRegistry 进行管理
typeAliasesElement(root.evalNode("typeAliases"));
// plugins 插件
pluginElement(root.evalNode("plugins"));
// 对象工厂
// MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。
// 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过有参构造方法来实例化。
objectFactoryElement(root.evalNode("objectFactory"));
// ?? 没研究
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 这个relectorFactory可以参看 Properties settings = settingsAsProperties(root.evalNode("settings")); 里面的使用
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 设置settings配置信息
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 创建Configuration#Environment
environmentsElement(root.evalNode("environments"));
// 供应商
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 类型映射
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
具体的解析操作可以查看XMLConfigBuilder类中的parseConfiguration(XNode root) 方法
2.3 工厂模式
在mybatis源码中,我们随处可见的设计模式之一就是工厂模式,比如
事务工厂TransactionFactory
数据源工厂DataSourceFactory
对象创建工厂ObjectFactory
动态代理工厂ProxyFactory
…
mybatis 通过对各个工厂接口的定义,并以Map<String, Class<?>> 的形式存储在Configuration中(参考:TypeAliasRegistry类),并以配置形式的方式进行查找指定工厂实现类,同时也可以自己扩展指定工厂的实现类。
2.4 适配器模式
mybatis源码中,对于适配器模式的牛批使用我们就直接看org.apache.ibatis.logging 源码包中的实现就好了。
mybatis通过定义LogFactory作为获取Log类的工厂类,Log作为其内部的日志记录类,对于市面上常见Slf4j 、 Commons Log 、 Log4j 日志库,mybatis通过适配器进行了各个实现的适配。
对于自定义的logImpl,mybatis的解析操作是通过loadCustomLogImpl(settings)实现
private void loadCustomLogImpl(Properties props) {
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
}
其内部就是通过解析 settings配置项的logImpl 配置值,调用 configuration.setLogImpl(logImpl) 赋值Configuration中的logImpl属性,
并追加到 LogFactory.useCustomLogging(this.logImpl) 中
public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
setImplementation(clazz);
}
useCustomLogging() 方法内部调用 setImplementation(Class) 方法
private static void setImplementation(Class<? extends Log> implClass) {
try {
// 获取对应Log类的构造函数,参数为String.class 类型
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
// 创建一个 Log 对象,打印 debug 日志
Log log = candidate.newInstance(LogFactory.class.getName());
if (log.isDebugEnabled()) {
log.debug("Logging initialized using '" + implClass + "' adapter.");
}
logConstructor = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}
在最后一段代码中,通过logConstructor = candidate; 将当前获取的日志实现类的构造函数赋值给logConstructor,并在getLog方法中使用
public static Log getLog(Class<?> clazz) {
return getLog(clazz.getName());
}
public static Log getLog(String logger) {
try {
return logConstructor.newInstance(logger);
} catch (Throwable t) {
throw new LogException("Error creating logger for logger " + logger + ". Cause: " + t, t);
}
}
其实再LogFactory的静态构造函数中
static {
// tryImplementation 的参数是一个runnable, 这个方法真实个迷
// 这个实际上是按照对应的优先级适配对应的日志信息
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
我们会发现在未自定义配置log实现类的时候,mybatis内部实际上按照slf4j -》apache common log -》log4j2 -》log4j -》jdk common log -》 No Log 的顺序进行实现。
2.5 拦截器
mybatis的源码包org.apache.ibatis.plugin 提供了一套通用的拦截器机制
Interceptor 作为拦截器的接口,用户可以自定义实现自定义的拦截器
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
// NOP
}
}
拦截器中定义了指定的三个方法,intercept(Invocation invocation) 方法接收一个参数Invocation 进行拦截器调用,该方法调用和参数内部实际由Plugin(InvocationHandler) 中的invoke方法进行传递,表示发起一个拦截器调用,如果本地拦截器(处理之后)固有指定的参数返回时,直接return obj即可。如果需要调用下一个拦截器,直接调用其持有参数的invocation.proceed() 方法即可。
InterceptorChain作为拦截器的链路调用管理类
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
其内部实现了加入拦截器,获取拦截器,和通过拦截器链去获取动态代理对象的方法,对于第三个方法,实际上就是源码中的
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
定义方法,该方法内部接收一个对象,方法体中循环遍历加入的拦截器链(从源码也可以看出拦截器的执行顺序与List 有关,基于ArrayList,先加入进去的会先进行调用),并调用拦截器的default Object plugin(Object target)方法,该方法是接口拦截器Interceptor的默认方法,内部实际上使用了Plugin.wrap(target, this);
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
Object o = Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
return o;
}
return target;
}
这段代码简洁清晰,signatureMap 变量实际上是针对自定义的Interceptor类的Intercepts注解进行解析,key为当前拦截器@Intercepts注解定义的@Signature 的type值,value是一个set类型,表示当前拦截器定义的拦截指定类型的指定参数的方法
public @interface Signature {
/**
* Returns the java type.
* 需要拦截的类型
*
* @return the java type
*/
Class<?> type();
/**
* Returns the method name.
* 需要拦截的方法
* @return the method name
*/
String method();
/**
* 需要拦截的方法参数
* Returns java types for method argument.
* @return java types for method argument
*/
Class<?>[] args();
}
接口返回的是jdk的动态代理类,就是用了
Proxy.newProxyInstance(ClassLoader, Class<?>[] interfaces, InvocationHandler)
注意传递的InvocationHandler 就是Plugin类
因此:mybatis提供的这一套拦截器链路调用实际上是针对指定对象的指定参数的方法的拦截操作。
最后进入到InvocationHandler的invoke方法
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);
}
}
首先是会检测当前的method调用的类是否存在于指定拦截器的@Intercepts 配置中,如果存在,有拦截器的intercept方法,否则直接调用原指定对象的值。
完整的mybatis 拦截器的使用见:
@Test
public void testInteceptors() {
Map map = new HashMap();
InterceptorChain interceptorChain = new InterceptorChain();
interceptorChain.addInterceptor(new Interceptor1());
interceptorChain.addInterceptor(new Interceptor2());
interceptorChain.addInterceptor(new Interceptor3());
Map o = (Map)interceptorChain.pluginAll(map);
Object other = o.get("第二个");
System.out.println(other);
}
/**
* 第一个拦截器
*/
@Intercepts({
@Signature(type = Map.class, method = "get", args = {Object.class})})
public static class Interceptor1 implements Interceptor {
@Override
public Object intercept(Invocation invocation) {
String name = invocation.getTarget().getClass().getName();
System.out.println(name);
return "Always";
}
}
/**
* 第二个拦截器
*/
@Intercepts({
@Signature(type = Map.class, method = "get", args = {Object.class})})
public static class Interceptor2 implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
String name = invocation.getTarget().getClass().getName();
System.out.println(name);
return invocation.proceed();
}
}
/**
* 第三个拦截器
*/
@Intercepts({
@Signature(type = Map.class, method = "get", args = {Object.class})})
public static class Interceptor3 implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
String name = invocation.getTarget().getClass().getName();
System.out.println(name);
return invocation.proceed();
}
}
2.5 Reflector
这个类如其名,反射器,它的作用是对普通的POJO类进行反射解析,获取其指定POJO类中的setMethods,getMethods,setTypes, getTypes 信息
官方文档的解释是: 这个类表示一组缓存的类定义信息,允许在属性名称和getter/setter方法之间轻松映射。
实际上,该类严格意义上是能够解析对应的JavaBean对象,关于POJO类和JavaBean的定义,个人的理解是:
POJO类是定义若干个私有的成员变量,并定义了一部分,或者全部成员变量的get/set 方法,POJO类更像是一个纯粹的封装变量,并通过get/set公共方法赋值取值的普通类(Plain Ordinary Java Object / Pure Old Java Object)
JavaBean类则不然,它可以对内部定义的成员变量进行set/get方法定义,甚至也能够通过父类继承,接口实现的方式进行增强。通俗的意义将JavaBean应该具有一个无参的构造函数,并且可序列化
/**
* 当前解析的POJO类的字节码对象
*/
private final Class<?> type;
/**
* POJO类中getXxx isXxx 对应的成员变量名称
*/
private final String[] readablePropertyNames;
/**
* POJO类中setXxx 对应的成员变量名
*/
private final String[] writablePropertyNames;
/**
* 解析setXxx Method
* key -> 成员变量名
* value -> MethodInvoker
*/
private final Map<String, Invoker> setMethods = new HashMap<>();
/**
* 解析getXxx isXxx Method
* key -> 成员变量名
* value -> MethodInvoker
*/
private final Map<String, Invoker> getMethods = new HashMap<>();
/**
* setXxx isXxx
* key -> 成员变量名
* value -> param Type
*/
private final Map<String, Class<?>> setTypes = new HashMap<>();
/**
* getXxx isXxx
* key -> 成员变量名
* value -> param Type
*/
private final Map<String, Class<?>> getTypes = new HashMap<>();
/**
* 无参构造函数
*/
private Constructor<?> defaultConstructor;
/**
* 成员变量:toUpperCase -> normal
*/
private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();
mybatis内部通过工厂类ReflectorFactory 进行获取Reflector 对象,默认的实现DefaultReflectorFactory 增加了缓存功能(将已经解析过的Class 存入工厂常量 reflectorMap 中)。
重点关注一下Reflector 类中的
public Reflector(Class<?> clazz) {
type = clazz;
addDefaultConstructor(clazz);
addGetMethods(clazz);
addSetMethods(clazz);
addFields(clazz);
readablePropertyNames = getMethods.keySet().toArray(new String[0]);
writablePropertyNames = setMethods.keySet().toArray(new String[0]);
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
for (String propName : writablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
}
构造函数,该构造函数含有所有对应的解析功能
对于Reflector 这个类,我们可以重点关注一下以下几个解析策略
1,对于getMethods 结果的解析,Reflector 类实际上是针对了常用getXxx类型和isXxx类型的解析,我们也可以看源码中
addMethodConflict(Map<String, List<Method>> conflictingMethods, String name, Method method)
方法,该方法中的conflictingMethods 的Value值实际上就是处理这个问题的。并且,源码中也给了这种情况下取了一个不错的变量进行加以处理isAmbiguous 是否模棱 的,具体可以看源码
private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
Method winner = null;
String propName = entry.getKey();
boolean isAmbiguous = false;
for (Method candidate : entry.getValue()) {
if (winner == null) {
winner = candidate;
continue;
}
Class<?> winnerType = winner.getReturnType();
Class<?> candidateType = candidate.getReturnType();
if (candidateType.equals(winnerType)) {
if (!boolean.class.equals(candidateType)) {
isAmbiguous = true;
break;
} else if (candidate.getName().startsWith("is")) {
winner = candidate;
}
} else if (candidateType.isAssignableFrom(winnerType)) {
// OK getter type is descendant
} else if (winnerType.isAssignableFrom(candidateType)) {
winner = candidate;
} else {
isAmbiguous = true;
break;
}
}
addGetMethod(propName, winner, isAmbiguous);
}
}
深入到addGetMethod我们可以一探究竟。
2,对于getTypes 结果的解析,mybatis内部实际上是通过断言指定返回结果是否属于Class, ParameterizedType,GenericArrayType 【这几个类可以转出学习学习】等类型进行判断的
private Class<?> typeToClass(Type src) {
Class<?> result = null;
if (src instanceof Class) {
result = (Class<?>) src;
} else if (src instanceof ParameterizedType) {
result = (Class<?>) ((ParameterizedType) src).getRawType();
} else if (src instanceof GenericArrayType) {
Type componentType = ((GenericArrayType) src).getGenericComponentType();
if (componentType instanceof Class) {
result = Array.newInstance((Class<?>) componentType, 0).getClass();
} else {
Class<?> componentClass = typeToClass(componentType);
result = Array.newInstance(componentClass, 0).getClass();
}
}
if (result == null) {
result = Object.class;
}
return result;
}
3,setTypes和setMethods的解析相对简单一点,其内部实际上根据了getTypes和getMethods进行判断哪个setXxx是最优最合适的
3 扩展
对于mybatis源码或者功能的扩展,市面上是包含了大量得衍生物(由此而生成的更亲和开发者的开发工具),下面是一些常用,(不涉及源码解析)
3.1 mybatis-spring
mybatis-spring是mybatis官方针对以spring为依赖注入框架而涉及出来的,它能够帮助你无缝整合到对应的spring中(很明显,现在已经很少使用纯mybatis框架,由SqlSessionFactoryBuilder, SqlSessionFactory, SqlSeesion 得进行纯API得方式使用mybatis了)
官方git地址:https://github.com/mybatis/spring
官方文档:http://mybatis.org/spring/zh/index.html(有中文肯定要看中文得啦)
mybatis-spring值得重点关注的两个地方:SqlSessionFactoryBean和事务
mybatis-spring通过FactoryBean(spring中的一种特殊类)的实现SqlSessionFactoryBean来进行创建SqlSessionFactory,并实现了线程安全的SqlSession->SqlSessionTemplate
并且,mybatis-spring中的事务管理,会纳入到spring中原生的事务管理中,DataSourceTransactionManager
对于基于spring-boot架构的mybatis-spring的使用,官方是提供了通用的starter: mybatis-spring-boot-starter
官方文档:http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
【这个常用,看一下文档就理解,主要是对mybatis-spring的封装】
3.2 Mapper
在去介绍Mapper之前,有必要把其作者的github贴一哈:
https://github.com/abel533
Mapper实际上是我们日常生产环境代码中,用的最多的一个组件之一,他的一个主要的功能是:实现了通用mapper映射的常用方法,比如selectOne,select,updateByPrimaryKey,updateByExamle,… 这些都是我们在对表的基础CURD操作,通过通用Mapper,我们可以大大的简化这些基础CRUD的xml配置和书写。(Mapper也提供了通用和专用的代码生成器)
官方文档:https://github.com/abel533/Mapper/wiki
SpringBoot集成:https://github.com/abel533/Mapper/wiki/1.3-spring-boot
3.3 PageHelper
原生的mybatis的RowBounds,也能够实现分页效果,但是mybatis基于此实际上是内存分页,内部实现原理是会获取所有的数据结果,然后根据传入的RowBounds进行分页操作。
对于分页的支持,我们可以使用PageHelper,他和Mapper一样,都是同一位大佬开发。
官方文档:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md
这个文档已经足够详细,并且我们可以使用它提供的https://github.com/abel533/MyBatis-Spring-Boot 学习mybatis整合Mapper和PageHelper
3.4 OrderHelper
排序组件(这个用的少,实际上PageHelper是提供了排序的功能)