Skip to content
On this page

Mybatis 第一讲

Mapper的生成

org.mybatis.spring.annotation.MapperScan 在我们的开发过程中,与数据库交互我们第一步大多数都是从这个注解开始,这个注解表示需要扫描一些类加载到Spring 的bean 容器里进行管理。

java
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  /**
   * {@inheritDoc}
   * 
   * @deprecated Since 2.0.2, this method not used never.
   */
  @Override
  @Deprecated
  public void setResourceLoader(ResourceLoader resourceLoader) {
    // NOP
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
  }
	// 注册扫描配置类
  void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
      BeanDefinitionRegistry registry, String beanName) {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

在做依赖的时候需要注入Mapper ,需要一个SqlSessionFactory ,但是一开始的Bean容器是不存在SqlSessionFactory的,这个就需要去构建他,SqlSessionFactory 实现了InitializingBean 接口会自动触发 afterPropertiesSet(), 在里面他会开始向Bean 容器正式注册Mapper,并且在mybatis 的Configuration 也注册Bean 和 Method

java
try {
	XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                            targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
	xmlMapperBuilder.parse();
} catch (Exception e) {
	throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
	ErrorContext.instance().reset();
}
1
2
3
4
5
6
7
8
9

调用到后面会调用到一个addMapper()

java
mybatisMapperRegistry.addMapper(type);

knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
1
2
3

会存放进已知mapper ,这个MybatisMapperProxyFactory

java
public class MybatisMapperProxyFactory<T> {
    
    @Getter
    private final Class<T> mapperInterface;
    @Getter
    private final Map<Method, MybatisMapperProxy.MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
    
    public MybatisMapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    @SuppressWarnings("unchecked")
    protected T newInstance(MybatisMapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        final MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

他其实是JDK动态代理 注入到knownMappers, 以后获取Mapper 都是从这个knownMappers 里面获取

java
    @Override
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        // TODO 这里换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory
        final MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry.");
        }
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

方法的调用

list()方法为例,在调用Mapper 的方法,我们知道因为JDK动态代理其实是在调用MapperProxy 的invoke()方法。

java
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // 是否是Object 的原生方法,是的话就掉原生的方法
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else {
                // 不是那就去缓存先找一下方法,然后在调用
                return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }

	private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
        try {
            // 去找一下试试,没找到就添加进缓存
            return CollectionUtils.computeIfAbsent(methodCache, 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 MybatisMapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
                }
            });
        } catch (RuntimeException re) {
            Throwable cause = re.getCause();
            throw cause == null ? re : cause;
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

通过返回的PlainMethodInvokerinvoke()方法触发缓存Method 的调用。MybatisMapperMethod 这个类主要是对方法的元数据的补充,和实际执行方法的封装execute(SqlSession sqlSession, Object[] args)

java
 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
        // 准备执行
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
1
2
3
4
5
6
7
8
9
10
11

我们知道Mybtais是有缓存的,就在这个query 的方法处理里面

java

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        // 拿到缓存的Key
        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

	public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
        throws SQLException {
        Cache cache = ms.getCache();
        Optional<IPage> pageOptional = ParameterUtils.findPage(parameterObject);
        // 缓存处理 没有就不进
        if (cache != null) {
            flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                ensureNoOutParams(ms, boundSql);
                Object result = tcm.getObject(cache, key);
                if (result == null) {
                    if (pageOptional.isPresent()) {
                        IPage page = pageOptional.get();
                        CacheKey countCacheKey = null;
                        if (page.isSearchCount()) {
                            // 这里的执行sql为原select语句,标准一点的是需要将此转换为count语句当做缓存key的,留做当优化把.
                            countCacheKey = getCountCacheKey(ms, boundSql, parameterObject, RowBounds.DEFAULT);
                            // 复用count结果缓存,减少count查询.
                            Number count = (Number) tcm.getObject(cache, countCacheKey);
                            if (count != null) {
                                page.hitCount(true);
                                page.setTotal(count.longValue());
                            }
                        }
                        // 切勿将这提取至上方,如果先查的话,需要提前将boundSql拷贝一份
                        result = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                        List<E> records = (List<E>) result;
                        page.setRecords(records);
                        tcm.putObject(cache, key, records);
                        if (countCacheKey != null && !page.isHitCount()) {
                            tcm.putObject(cache, countCacheKey, page.getTotal());
                        }
                        return new PageList(records, page.getTotal());
                    } else {
                        result = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                        // issue #578 and #116
                        tcm.putObject(cache, key, result);
                        return (List<E>) result;
                    }
                } else {
                    if (pageOptional.isPresent()) {
                        IPage page = pageOptional.get();
                        if (page.isSearchCount()) {
                            CacheKey cacheKey = getCountCacheKey(ms, boundSql, parameterObject, RowBounds.DEFAULT);
                            Number count = (Number) tcm.getObject(cache, cacheKey);
                            if (count != null) {
                                page.hitCount(true);
                                return new PageList((List) result, count.longValue());
                            } else {
                                // 某些特殊情况,比如先不查count,缓存了list数据或者count缓存数据被淘汰(这几率比较小),就再查一次算了。
                                result = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                                List<E> records = (List<E>) result;
                                tcm.putObject(cache, cacheKey, page.getTotal());
                                return records;
                            }
                        }
                        return new PageList((List) result, 0L);
                    } else {
                        return (List<E>) result;
                    }
                }
            }
        }
        // 没有缓存走这里
        return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

之后交给BaseExecutor#queryFromDatabase 去执行查询。

java
    @Override
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            // 插件的处理
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            stmt = prepareStatement(handler, ms.getStatementLog(), false);
             // 执行SQL
            return stmt == null ? Collections.emptyList() : handler.query(stmt, resultHandler);
        } finally {
            closeStatement(stmt);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

参数的处理

java
public class DynamicSqlSource implements SqlSource {
  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
java
public class SqlSourceBuilder extends BaseBuilder {

  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    String sql;
    if (configuration.isShrinkWhitespacesInSql()) {
      sql = parser.parse(removeExtraWhitespaces(originalSql));
    } else {
      sql = parser.parse(originalSql);
    }
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

返回值的处理

插件机制

Released under the MIT License.