在介绍mybatis之前首先,我先要理解它是做是什么?其实mybatis是一个持久化框架,是把Java程序与关系型数据关联起来的一个框架,它把Java对象与表中的数据映射起来,实现Java程序与数据库之间的交互。

mybatis架构简介

mybatis分为三层架构,分别为基础支撑层、核心处理层和接口层,如下图所示:
Mybatis - 图1

基础支撑层

基础支撑层是整个mybatis框架的基础,为整个mybatis框架提供了非常基础的功能,其中每个模块都提供了一个内聚的、单一的能力,mybatis基础支撑层按照这些单一的能力可以划分为上图所示的九个基础模块。

  1. 类型转换模块。在mybatis-config.xml配置文件中通过标签为一个类定义一个别名,这里用到的“别名机制”就是由mybatis基础支撑中的类型转换模块实现的;除了别名机制,类型转换模块还实现了mybatis中JDBC类型和Java类型之间的相互转换,这一功能在绑定实参、映射ResultSet场景中都有所体现:
    1. 在SQL模板绑定用户传入实参的场景中,类型转换模块会将Java类型转换成JDBC类型数据;
    2. 在将ResultSet映射程结过对象的时候,类型转换模块会将JDBC类型数据转换成Java类型数据。
    3. 具体如下图所示:

Mybatis - 图2

  1. 日志模块。日志是我们生产中排查问题、定位Bug、锁定性能瓶颈的主要线索来源。在任何一个成熟系统中都会有级别合理、信息翔实得日志模块,mybatis也不例外。mybatis提供了日志模块来集成Java生态中的第三方日志框架,该模块目前可以集成log4j、Log4j2、slf4j等优秀的日志框架。
  2. 反射工具模板。mybatis的反射工具箱是在Java反射的基础上进行封装,为上层使用方提供更加灵活、方便的API接口,同时缓存Java原生反射相关的元数据,提升反射代码执行的效率,优化反射操作性能。
  3. Binding模块。通过sqlsession获取mapper接口的代理,然后通过这个代理执行关联mapper.xml文件中的数据库操作。通过这种方式,可以将一些错误提前到编译期,以上功能就是通过Binding模块完成。其实binding模块可以让我们无须编写mapper接口的具体实现,利用其自动生成mapper接口的动态代理对象。简单的数据操作,可以直接在mapper接口中使用注解完成。
  4. 数据源模块。
  5. 缓存模块。MyBatis 就提供了一级缓存和二级缓存,具体实现位于基础支撑层的缓存模块中。
  6. 解析器模块。mybatis有两大部分配置文件需要解析,一是mybatis-config,xml配置文件,另一个是Mapper.xml配置文件。这两个文件都是由mybatis的解析器模块进行解析,其中主要是依赖XPath实现XML配置文件以及各类表达式的高效解析。
  7. 事务管理模块。

mybatis中的反射工具类—Reflector

Reflector是Mybatis反射模板的基础。要是用反射模块操作一个Class,都会先将Class封装成一个Reflector对象,在Reflector中缓存Class的元数据信息,可以提高反射效率。

mybatis核心初始化流程

对于mybatis反射操作,Reflector管理类的属性和方法,这些信息都记录在核心字段中,具体如下:

  • type (final Class<?> 类型):该Reflector对象封装Class类型;
  • readablePropertyNames、writablePropertyNames (final String [] 类型):可读、可写属性的名称集合;
  • setMethods、getMethods (final Map 类型):可读、可写属性对应的getter方法和setter方法集合,key是属性的名称,value是一个Invoker对象。Invoker是对Method对象的封装;
  • setTypes、getTypes (final Map> 类型):属性对应的getter方法返回值和setter方法参数值类型,key是属性名称,value是方法的返回值类型或者参数类型;
  • defaultConstructor(Constructor<?> 类型):默认构造方法;
  • caseInsensitivePropertyMap (Map 类型):所有属性名称的集合,记录到这个集合中的属性名称都是大写的。

在mybatis中构造一个Reflector对象,传入一个class对象,通过解析class对象,即可填充上述核心字段。整合核心流程如下描述:

  1. 用type字段记录传入的class对象;
  2. 通过反射拿到class类拿到全部构造方法,并进行遍历,过滤得到唯一无参构造方法来初始化defaultConstructor字段。这部分逻辑在addDefaultConstructor()方法中实现;
  3. 读取Class类中的getter方法,填充上面介绍的getMethods集合和getTypes集合,这部分逻辑在addGetMethods()方法中实现;
  4. 读取Class类中setter方法,填充上面介绍的setMethods集合和setTypes集合,这部分逻辑在addSetMethods()方法中实现;
  5. 读取Class中没有getter/setter方法的字段,生成对应的Invoker对象,填充getMethods集合、getTypes集合以及setMethods集合setTypes集合。这部分逻辑在addFields()方法中实现;
  6. 根据前面三步构造方法的getMethods/setMethods集合的keySet,初始化readablePropertyNames、writablePropertyNames集合;
  7. 遍历构造的readablePropertyNames、writablePropertyNames集合,将其中的属性名称全部转化成大写并记录到caseInsensitivePropertyMap集合中。

    1. public Reflector(Class<?> clazz) {
    2. type = clazz; //1
    3. addDefaultConstructor(clazz); //2
    4. addGetMethods(clazz); //3
    5. addSetMethods(clazz); //4
    6. addFields(clazz); //5
    7. //6
    8. readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
    9. writablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
    10. //7
    11. for (String propName : readablePropertyNames) {
    12. caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    13. }
    14. for (String propName : writablePropertyNames) {
    15. caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    16. }
    17. }

    对创建Reflector对象流程中的核心方法解读如下:

  8. addGetMethods()和addSetMethods()方法(以addGetMethods()方法为例)

    1. 获取方法信息。
      1. private void addGetMethods(Class<?> cls) {
      2. Map<String, List<Method>> conflictingGetters = new HashMap<>();
      3. //获取方法信息,通过调用getClassMethods()方法获取当前class类的所有方法的唯一签名,以及每个方法对 //应的Method对象,这里生成的方法是包含返回值的,可以作为该方法全局唯一标识
      4. Method[] methods = getClassMethods(cls);
      5. //
      6. for (Method method : methods) {
      7. if (method.getParameterTypes().length > 0) {
      8. continue;
      9. }
      10. String name = method.getName();
      11. if ((name.startsWith("get") && name.length() > 3)
      12. || (name.startsWith("is") && name.length() > 2)) {
      13. name = PropertyNamer.methodToProperty(name);
      14. addMethodConflict(conflictingGetters, name, method);
      15. }
      16. }
      17. //解决签名冲突,理冲突的核心逻辑其实就是比较 getter 方法的返回值,优先选择返回值为子类的 getter //方法
      18. resolveGetterConflicts(conflictingGetters);
      19. }
      Invoker
      在Reflector对象的初始化过程中,所有属性的getter\setter方法都会被封装成MethodInvoker对象,没有getter\setter的字段也会生成对应的get\setFieldInvoker对象。
      在mybatis中对象反射invoker接口的继承关系如下:
      Mybatis - 图3
      以上对Invoker接口的实现,methodInvoker方法是通过反射方式执行底层封装的method方法(setter\getter方法)完成属性读写效果。get/setFieldInvoker方法是通过反射方式读写底层封装的Field字段,进而实现字段的读写效果。
      ReflectorFactory
      为了提升Reflector的初始化速度,mybatis中提供了ReflectorFactory这个接口工厂类,是对Reflector对象进行缓存,其中最核心的方法是findForClass()方法。
      DefaultReflectorFactory是对ReflectorFactory接口的默认实现。在该默认实现的类中有一个在内存中维护的ConcurrentMap, Reflector> reflectorMap集合,缓存了其创建的所有Reflector对象。
      事务接口
      Transaction接口是Mybatis中对数据库事务的抽象,其中定义了提交事务、回滚事务,以及获取事务底层数据库连接的方法。

      Mapper文件与Java接口的优雅映射之道

      在myabtis中mapper文件与java的接口文件映射功能在binding模块,其中涉及的核心如下:
      Mybatis - 图4
      binding模块核心组件关系图
      MapperRegistry
      MapperRegistry是Mybatis初始过程中构造的一个对象,主要作用是统一维护Mapper接口以及这些Mapper的代理对象工厂。
      MapperRegistry中的核心字段:
  • config(Configuration 类型):指向Mybatis全局唯一的Configuration对象其中维护了解析之后的全部Mybatis配置信息;
  • knowMappers (Map,MapperProxyFactory<?>> 类型):维护了所有解析到的Mapper接口以及MapperProxyFactory对象工厂之间的映射关系。

在Mybatis初始化时,会读取全部Mapper.xml配置文件,还会扫描全部Mapper接口中的注解信息,之后会调用MapperRegistry.addMapper()方法填充knowMappers集合。在addMapper()方法填充knowMappers集合之前,MapperRegistry会先保证传入type参数是一个接口且knowMappers集合没有加载过type类型,然后才会创建相应的MapperProxyFactory工厂并记录到knowMappers集合中。

MapperProxyFactory

MapperProxyFactory的核心功能是创建Mapper接口的代理对象,其底层核心原理就是JDK的动态代理。
在MapperRegistry中会依赖MapperProxyFactory的newInstance()方法创建代理对象,底层则是通过JDK动态代理的方式生成代理对象的,如下代码,这里使用的InvocationHandler实现是MapperProxy。

  1. public T newInstance(SqlSession sqlSession) {
  2. final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  3. return newInstance(mapperProxy);
  4. }
  5. @SuppressWarnings("unchecked")
  6. protected T newInstance(MapperProxy<T> mapperProxy) {
  7. return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  8. }

MapperProxy

MapperProxy是生成Mapper接口代理对象的关键,它实现了InvocationHandler接口。
MapperProxy中的核心字段:

  • sqlSession (SqlSession 类型):记录了当前MapperProxy关联的SqlSession对象。在与当前MapperProxy关联的代理对象中,会用该SqlSession访问数据;
  • mapperInterface (Class 类型):Mapper接口类型,也是当前MapperProxy关联的代理对象实现的接口类型;
  • methodCache (Map 类型):用于缓存MapperMethodInvoker对象的集合。methodCache中的key是Mapper接口中的方法,value是该方法对应的MapperMethodInvoker对象;
  • lookupConstructor (Constructor 类型):针对JDK8中的特殊处理,该字段指向了MethodHandler.Lookup的构造方法;
  • privateLookupInMethod (Method 类型):除了JDK8之外的其他JDK版本会使用该字段,该字段指向MethodHandlers.privateLookupIn()方法。

MapperProxy.invoke()方法是代理对象执行的入口,其中会拦截所有非Object方法,针对每个被拦截的方法,都会调用获取对应MapperMethod对象的方法。

MapperMethod

MapperMethod核心:MapperMethod是最终执行SQL语句的地方,同时也记录了Mapper接口中的对应方法。

  1. SqlCommand

MapperMethod的第一个核心字段是command(SqlCommand 类型),其中维护了关联SQL语句的相关信息。在MapperMethod$SqlCommand这个内部类中,通过name字段记录了关联SQL语句的唯一标识,通过type字段(SqlCommandType类型)维护了SQL语句的操作类型,这里SQL语句的操作类型分为INSERT、UPDATE、DELETE、SELECT和FLUSH五种。

  1. //SqlCommand查找Mapper接口中一个方法对应的SQL语句的信息
  2. public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
  3. //获取Mapper接口中对应的方法名称
  4. final String methodName = method.getName();
  5. //获取Mapper接口的类型
  6. final Class<?> declaringClass = method.getDeclaringClass();
  7. //将Mapper接口名称和方法名称拼接起来作为SQL语句唯一标识,到Configuration这个全局配置对象中查找SQL
  8. //语句,MappedStatement对象就是Mapper.xml配置文件中一条SQL语句解析之后得到的对象
  9. MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
  10. configuration);
  11. if (ms == null) {
  12. //针对@Flush注解的处理
  13. if (method.getAnnotation(Flush.class) != null) {
  14. name = null;
  15. type = SqlCommandType.FLUSH;
  16. } else {//没有@Flush注解,会抛出异常
  17. throw new BindingException("Invalid bound statement (not found): "
  18. + mapperInterface.getName() + "." + methodName);
  19. }
  20. } else {
  21. //记录SQL语句唯一标识
  22. name = ms.getId();
  23. //记录SQL语句的操作类型
  24. type = ms.getSqlCommandType();
  25. if (type == SqlCommandType.UNKNOWN) {
  26. throw new BindingException("Unknown execution method for: " + name);
  27. }
  28. }
  29. }

在上面中调用了resolveMappedStatement()方法不仅会尝试根据SQL语句的唯一标识从Configuration全局配置对象中查找关联的MappedStatement对象,还会尝试顺着Mapper接口的继承树进行查找,直至查找成功为止,具体如下:

  1. private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
  2. Class<?> declaringClass, Configuration configuration) {
  3. //将Mapper接口名称和方法名称拼接起来作为SQL语句唯一标识
  4. String statementId = mapperInterface.getName() + "." + methodName;
  5. //检测Configuration中是否包含相应的MappedStatement对象
  6. if (configuration.hasStatement(statementId)) {
  7. return configuration.getMappedStatement(statementId);
  8. } else if (mapperInterface.equals(declaringClass)) {
  9. //如果方法就定义在当前接口中,则证明没有对应的SQL语句,返回null
  10. return null;
  11. }
  12. //如果当前检查的Mapper接口(mapperInterface)中不是定义该方法的接口(declaringClass),
  13. //则会从mapperInterface开始,沿着继承关系向上查找递归每个接口,查找该方法对应的,MappedStatement对象
  14. for (Class<?> superInterface : mapperInterface.getInterfaces()) {
  15. if (declaringClass.isAssignableFrom(superInterface)) {
  16. MappedStatement ms = resolveMappedStatement(superInterface, methodName,
  17. declaringClass, configuration);
  18. if (ms != null) {
  19. return ms;
  20. }
  21. }
  22. }
  23. return null;
  24. }
  1. MethodSignature

    MapperMethod的第二个核心字段是method字段(MethodSignature 类型),其中维护了Mapper接口中方法的信息,首先是Mapper接口方法返回值相关信息:

  • returnsMany、returnsMap、returnsVoid、returnsCursor、returnsOptional(boolean 类型):用于表示方法返回值是否为Collection集合或数组、Map集合、void、cursor、optional类型;
  • returnType (Class<?> 类型):方法返回值的具体类型
  • mapKey (String 类型):如果方法的返回值为Map集合,则通过mapKey字段记录了作为key的列名。mapKey字段的值是通过解析方法上的@MapKey注解得到的;
  • resultHandlerIndex (Integer 类型):记录了Mapper接口方法的参数列表中ResultHandler类型参数的位置;
  • rowBoundsIndex (Integer 类型):记录了Mapper接口方法的参数列表中RowBounds类型参数的位置;
  • paramNameResolver (ParamNameResolver 类型):用来解析方法参数列表的工具类。

在上面的字段中,paramNameResolver字段含义:
在ParamNameResolver中有一个names字段(SortedMap 类型)记录了在参数列表中的位置以及参数名称,其中key是参数在参数列表中的位置索引,value为参数的名称,通过@Param注解指定一个参数名称,如果没有特别指定,则默认使用参数列表中的变量名称作为其名称,这与ParamNameResolver的useActualParamName字段相关。useActualParamName是一个全局变量。若useActualParamName设置为false,ParamNameResolver会使用参数的下标索引作为其名称。另外,names集合会跳过RowBounds类型以及ResultHandler类型的参数,如果下标索引作为参数名称,在names集合中就会出现KV不一致的场景,例子如下图:

Mybatis - 图5
names集合中KV不一致示意图
以上是names集合初始化,以下是从names集合中查询参数名称,该部分逻辑如下:

  1. /**
  2. * <p>
  3. * A single non-special parameter is returned without a name.
  4. * Multiple parameters are named using the naming rule.
  5. * In addition to the default names, this method also adds the generic names (param1, param2,
  6. * ...).
  7. * </p>
  8. */
  9. public Object getNamedParams(Object[] args) {
  10. //获取方法中非特殊类型(RowBounds类型和ResultHandler类型)的参数个数
  11. final int paramCount = names.size();
  12. if (args == null || paramCount == 0) {
  13. //若方法没有非特殊类型参数,返回null
  14. return null;
  15. } else if (!hasParamAnnotation && paramCount == 1) {
  16. //方法参数列表中没有使用@Param注解,且只有一个非特殊类型参数
  17. return args[names.firstKey()];
  18. } else {
  19. //处理存在@Param注解或是存在多个非特殊类型参数的长江
  20. //param集合用于记录了参数名称与实参之间的映射关系,这里的ParamMap继承了HashMap,与HashMap唯一
  21. //的不同:向ParamMap中添加已经存在的key时,会直接抛出异常,而不是覆盖原有的Key
  22. final Map<String, Object> param = new ParamMap<>();
  23. int i = 0;
  24. for (Map.Entry<Integer, String> entry : names.entrySet()) {
  25. //将参数名称与实参的映射保存到param集合中
  26. param.put(entry.getValue(), args[entry.getKey()]);
  27. // add generic param names (param1, param2, ...)
  28. //同时,为参数创建"param+索引"格式的默认参数名称,具体格式为:param1,param2等,将"param+索引"
  29. //的默认参数名称与实参的映射关系也保存到param集合中
  30. final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
  31. // ensure not to overwrite parameter named with @Param
  32. if (!names.containsValue(genericParamName)) {
  33. param.put(genericParamName, args[entry.getKey()]);
  34. }
  35. i++;
  36. }
  37. return param;
  38. }
  39. }

对于MethodSignature构造方法:

  1. public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
  2. //根据TypeParameterResolver工具类解析方法的返回值类型。初始化returnType字段值。
  3. Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
  4. if (resolvedReturnType instanceof Class<?>) {
  5. this.returnType = (Class<?>) resolvedReturnType;
  6. } else if (resolvedReturnType instanceof ParameterizedType) {
  7. this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
  8. } else {
  9. this.returnType = method.getReturnType();
  10. }
  11. this.returnsVoid = void.class.equals(this.returnType);
  12. this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
  13. this.returnsCursor = Cursor.class.equals(this.returnType);
  14. this.returnsOptional = Optional.class.equals(this.returnType);
  15. //如果返回值为Map类型,则从方法的@MapKey注解中获取Map中为key的字段名称
  16. this.mapKey = getMapKey(method);
  17. this.returnsMap = this.mapKey != null;
  18. //解析方法中RowBounds类型参数和ResultHandler类型参数类型的下标索引位置,初始化rowBoundsIndex和
  19. //resultHandlerIndex字段
  20. this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
  21. this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
  22. //创建ParamNameResolver工具对象,在创建ParamNameResolver对象的时候会解析方法的参数列表信息
  23. this.paramNameResolver = new ParamNameResolver(configuration, method);
  24. }
  1. execute()方法

execute()方法是MapperMethod中最核心的方法之一。execute()方法会根据要执行的SQL语句的具体类型执行SqlSession的相应方法完成数据库操作,其核心实现如下:

  1. public Object execute(SqlSession sqlSession, Object[] args) {
  2. Object result;
  3. switch (command.getType()) {
  4. case INSERT: {
  5. Object param = method.convertArgsToSqlCommandParam(args);
  6. result = rowCountResult(sqlSession.insert(command.getName(), param));
  7. break;
  8. }
  9. case UPDATE: {
  10. Object param = method.convertArgsToSqlCommandParam(args);
  11. result = rowCountResult(sqlSession.update(command.getName(), param));
  12. break;
  13. }
  14. case DELETE: {
  15. Object param = method.convertArgsToSqlCommandParam(args);
  16. result = rowCountResult(sqlSession.delete(command.getName(), param));
  17. break;
  18. }
  19. case SELECT:
  20. if (method.returnsVoid() && method.hasResultHandler()) {
  21. executeWithResultHandler(sqlSession, args);
  22. result = null;
  23. } else if (method.returnsMany()) {
  24. result = executeForMany(sqlSession, args);
  25. } else if (method.returnsMap()) {
  26. result = executeForMap(sqlSession, args);
  27. } else if (method.returnsCursor()) {
  28. result = executeForCursor(sqlSession, args);
  29. } else {
  30. Object param = method.convertArgsToSqlCommandParam(args);
  31. result = sqlSession.selectOne(command.getName(), param);
  32. if (method.returnsOptional()
  33. && (result == null || !method.getReturnType().equals(result.getClass()))) {
  34. result = Optional.ofNullable(result);
  35. }
  36. }
  37. break;
  38. case FLUSH:
  39. result = sqlSession.flushStatements();
  40. break;
  41. default:
  42. throw new BindingException("Unknown execution method for: " + command.getName());
  43. }
  44. if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
  45. throw new BindingException("Mapper method '" + command.getName()
  46. + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  47. }
  48. return result;
  49. }

Cache接口及核心实现

Cache接口是Mybatis缓存中最顶层的抽象接口,定义了Mybatis缓存最核心、最基础的行为。Cache接口中最核心的方法主要是:putObject()、getObject()和removeObject()三个方法,分别用来写入、查询和删除缓存数据。

Cache接口的实现类如下图所示,其中PerpetualCache担任装饰器模式中的ComponentImpl角色,实现了Cache接口缓存数据的基本能力。
Mybatis - 图6
PerpetualCache中有两个核心字段:一个id(String 类型),记录了缓存对象的唯一标识;另外一个是cache(HashMap 类型),真正实现Cache存储的数据结构,对Cache接口的实现也会直接委托给这个HashMap对象的相关方法。

Cache接口装饰器

除了PerpetualCache之外的其他所有Cache接口实现类,都是装饰器实现,也就是DecoratorImpl的角色。

  1. BlockingCache

BlockingCache是在原有Cache实现之上添加了阻塞线程的特性。对于Key来说,同一时刻,BlockingCache只会让一个业务线程到数据库中去查找,查找到结果后,会添加到BlockingCache中缓存。
作为一个装饰器,BlockingCache还包含一个Cache类型的字段,也就是delegate字段。BlockingCache还包含了一个locks(ConcurrentHashMap 类型)字段和一个timeout(long 类型)字段,其中locks为每个Key分配了一个ReentrantLock,使用加锁操作实现阻塞线程。

  1. FifoCache

Mybatis缓存实际上是JVM堆中的一块内存,需要严格控制Cache的大小,防止Cache占用内存过大影响程序的性能。操作系统有很多缓存淘汰规则,Mybatis也提供了类似的规则来清理缓存。
这就引出了FifoCache装饰器,自然也会包含一个指向Cache的字段(也就是delegate字段),同时还维护了两个与FIFO相关字段:一个keyList队列(LinkedList),主要利用了LinkedList集合有序性,记录缓存条目写入Cache的先后顺序;另一个是当前Cache的大小上限字段(size字段),当Cache大小超过该值时,就会从keyList集合中查找最早的缓存条目并进行清理。
FifoCache的getObject()方法和removeObject()方法实现非常简单,都是直接委托给底层delegate这个被装饰的Cache对象的同名方法。FifoCache的关键字实现在putObject()方法中,在将数据写入被装饰的Cache对象之前,FifoCache会通过cycleKeyList()方法执行FIFO策略清理缓存,然后才会调用delegate.putObject()方法完成数据写入。

  1. LruCache

Mybatis还支持LRU(Least Recently Used,近期最少使用算法)策略来清理缓存。LruCache就是使用LRU策略清理缓存的装饰器实现,如果LruCache发现缓存需要清理,它会清除最近最少使用的缓存条目。
LruCache中除了有一个delegate字段指向被装饰Cache对象之外,还维护了一个LinkedHashMap集合(keyMap 字段),用来记录各个缓存条目最近的使用情况,以及一个eldestKey字段(Object 类型),用来指向最近最少使用的Key。
LinkedHashMap继承了HashMap,底层使用数组存储KV数据,数组中存储的是LinkedHashMap.Entry类型的元素。在LinkedHashMap.Entry中除了存储KV数据之外,还维护了before、after两个字段分别指向当前Entry前后两个Entry节点。在LinkedHashMap中还维护了head、tail两个指针,分别指向了第一个和最后一个Entry节点。LinkedHashMap的原理如下图:
Mybatis - 图7
LinkedHashMap 原理图
在上图(1)中,通过Entry中的before和after指针形成了一个链表,当我们调用get()方法访问key4时,LinkedHashMap除了返回Value4之外还会默默修改Entry链表,将key4移动到链表的尾部,得到上图(2)中的结构。
LruCache中的keyMap覆盖了LinkedHashMap默认的removeEldestEntry()方法实现,当LruCache中缓存条目达到上限的时候,返回true,即删除Entry链表中head指向的Entry。LruCache就是依赖LinkedHashMap上述的这些特点来确定最久未使用的的缓存条目并完成删除的。
LruCache初始化过程中,keyMap对LinkedHashMap.removeEldestEntry()方法的覆盖:

  1. public void setSize(final int size) {
  2. keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
  3. private static final long serialVersionUID = 4267176411845948333L;
  4. //调用LinkedHashMap.put()方法,会调用removeEldestEntry()方法,决定是否删除head指向的Entry数据
  5. @Override
  6. protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
  7. boolean tooBig = size() > size;
  8. if (tooBig) { //已达到缓存上限,更新eldestKey,并返回true,LinkedHashMap会删除该key
  9. eldestKey = eldest.getKey();
  10. }
  11. return tooBig;
  12. }
  13. };
  14. }
  1. SoftCache

核心处理层

核心处理层是Mybatis的核心,主要是mybatis的初始化以及执行一条SQL语句的全流程。

  1. 配置解析。mybatis有三处可以添加配置信息的地方,分别是:mybatis-config.xml配置文件、mapper.xml配置文件以及mapper接口中的注解信息。在mybatis初始化过程中会加载这些配置信息,并将解析之后得到的配置对象保存到Configuration对象中。
  2. SQL解析与scripting模块。mybatis提供了动态SQL功能,通过其提供的标签,根据实际运行条件动态生成执行SQL语句。mybatis提供的动态标签有: 等。mybatis中的scripting模块就是负责动态生成SQL的核心模块。他会根据运行时用户传入的实参,解析动态sql中的标签,并形成SQL模板,然后处理SQL模板中的占位符,用运行时的实参填充占位符,得到数据库真正可执行的SQL语句。
  3. SQL执行。在mybatis中执行一条SQL语句,涉及到的核心组件有:Executor、StatementHandler、ParameterHandler和ResultSetHandler。其中Executor会调用事务管理模块实现事务的相关控制,同时会通过缓存模块管理一级缓存和二级缓存。SQL语句真正执行将会由StatementHandler实现。StatementHandler会先依赖ParameterHandler进行SQL模板的实参绑定,然后由java.sql.Statement对象将SQL语句以及绑定好的实参传到数据库执行,从数据库中拿到ResultSet,最后,由ResultSetHandler将ResultSet映射成Java对象返回给调用方,这是SQL执行模板的核心。以下是SQL语句的核a心过程:

Mybatis - 图8

  1. 插件。

接口层

接口层是mybatis暴露给调用的接口集合,这些接口都是使用mybatis时常用的接口,例如:sqlSession接口、sqlSessionFactory接口等。其中最核心的是sqlSession接口(该接口可以获取mapper代理、执行SQL语句、控制事务开关等)。


JDBC

JDBC(Java DataBase Connectivity)是Java程序与关系型数据库交互的统一API。实际上,JDBC由两部分API构成:第一部分是面向开发者的Java API,它是一个统一的、标准的Java API,独立于各个数据库产品的接口规范;第二部分是面向对象数据库驱动程序开发者的API,它是由各个数据厂家提供的数据库驱动,是第一部分规范的底层实现,用于连接具体的数据库产品。

JDBC操作的核心步骤

  1. 注册数据库驱动类,指定数据库链接地址,其中包括DB的用户名、密码及其他连接信息;
  2. 调用DriverManager.getConnection()方法创建Connection连接到数据库;
  3. 调用Connection的createStatement()或prepareStatement()方法,创建Statement对象,此时会指定SQL(或是SQL语句模板+SQL参数);
  4. 通过Statement对象执行SQL语句,得到ResultSet对象,也就是查询结果集;
  5. 遍历ResultSet,从结果集中读取数据,并将每一行数据库记录转换成一个JavaBean对象;
  6. 关闭ResultSet结果集、Statement对象及数据库Connection,从而释放这些对象占用的底层资源。
  7. mybatis mapper内的方法能重载么

不能,因mybatis使用 package+mapper+method 作为key,去xml文件中匹配sql,若重载方法时会产生矛盾。