Mybatis分层架构

Mybatis主要分为三层,接口层负责提供数据的增删改查接口、数据处理层则负责具体SQL的执行、框架层则提供Mybatis的底层配置,如数据源、具体sql、事务等配置
image.png

Mybatis的请求处理过程

Mysql执行请求主要是通过SqlSession来进行的,SqlSession作为最顶层的接口,提供增删改查,内部交给Executor去执行(其有几个不同的实现类,分别用来处理不同的查询以及一级和二级缓存)
9033085-ca3974e212f792a0.webp

Mybatis的缓存设计

Mybatis支持一级缓存和二级缓存。
一级缓存默认是会话级别的,也可以通过在Configuration中配置,来设置为statement级别。

  1. <setting name="localCacheScope" value="SESSION"/>

二级缓存则可以跨会话,是NameSpace级别的,二级缓存需要在Configuration开启Cache,并在需要缓存的NameSpace下配置Cache标签

  1. //config.xml中开启二级缓存
  2. <setting name="cacheEnabled" value="true"/>
  3. //xxxMapper.xml中配置cache,默认是本地缓存,可以指定为其他外部缓存
  4. <cache/>

image.png

Mybatis源码知识脑图

Mybatis - 图4

SqlSessionFactory

SqlSessionFactory是Mybatis的SqlSession会话工厂,用来获得一个会话。前面我们已经看到所有的请求都是通过SqlSession进行处理的。当Mybatis与Spring整合时,其作为一个Bean是通过SqlSessionFactoryBean来实例化的,其内部主要通过SqlSessionFactoryBuilder来进行构造,build方法需要一个Configuration实例。其默认实现是DefaultSqlSessionFactory

  1. //SqlSessionFactoryBean
  2. protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
  3. Configuration configuration;
  4. //这里解析config.xml、xxxMapper.xml等配置文件得到Configuration实例
  5. XMLConfigBuilder xmlConfigBuilder = null;
  6. if (this.configLocation != null) {
  7. xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
  8. configuration = xmlConfigBuilder.getConfiguration();
  9. } else {
  10. if (logger.isDebugEnabled()) {
  11. logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
  12. }
  13. configuration = new Configuration();
  14. configuration.setVariables(this.configurationProperties);
  15. }
  16. if (this.objectFactory != null) {
  17. configuration.setObjectFactory(this.objectFactory);
  18. }
  19. if (this.objectWrapperFactory != null) {
  20. configuration.setObjectWrapperFactory(this.objectWrapperFactory);
  21. }
  22. if (hasLength(this.typeAliasesPackage)) {
  23. String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
  24. ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
  25. for (String packageToScan : typeAliasPackageArray) {
  26. configuration.getTypeAliasRegistry().registerAliases(packageToScan,
  27. typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
  28. if (logger.isDebugEnabled()) {
  29. logger.debug("Scanned package: '" + packageToScan + "' for aliases");
  30. }
  31. }
  32. }
  33. if (!isEmpty(this.typeAliases)) {
  34. for (Class<?> typeAlias : this.typeAliases) {
  35. configuration.getTypeAliasRegistry().registerAlias(typeAlias);
  36. if (logger.isDebugEnabled()) {
  37. logger.debug("Registered type alias: '" + typeAlias + "'");
  38. }
  39. }
  40. }
  41. if (!isEmpty(this.plugins)) {
  42. for (Interceptor plugin : this.plugins) {
  43. configuration.addInterceptor(plugin);
  44. if (logger.isDebugEnabled()) {
  45. logger.debug("Registered plugin: '" + plugin + "'");
  46. }
  47. }
  48. }
  49. if (hasLength(this.typeHandlersPackage)) {
  50. String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
  51. ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
  52. for (String packageToScan : typeHandlersPackageArray) {
  53. configuration.getTypeHandlerRegistry().register(packageToScan);
  54. if (logger.isDebugEnabled()) {
  55. logger.debug("Scanned package: '" + packageToScan + "' for type handlers");
  56. }
  57. }
  58. }
  59. if (!isEmpty(this.typeHandlers)) {
  60. for (TypeHandler<?> typeHandler : this.typeHandlers) {
  61. configuration.getTypeHandlerRegistry().register(typeHandler);
  62. if (logger.isDebugEnabled()) {
  63. logger.debug("Registered type handler: '" + typeHandler + "'");
  64. }
  65. }
  66. }
  67. if (xmlConfigBuilder != null) {
  68. try {
  69. xmlConfigBuilder.parse();
  70. if (logger.isDebugEnabled()) {
  71. logger.debug("Parsed configuration file: '" + this.configLocation + "'");
  72. }
  73. } catch (Exception ex) {
  74. throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
  75. } finally {
  76. ErrorContext.instance().reset();
  77. }
  78. }
  79. if (this.transactionFactory == null) {
  80. this.transactionFactory = new SpringManagedTransactionFactory();
  81. }
  82. Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
  83. configuration.setEnvironment(environment);
  84. if (this.databaseIdProvider != null) {
  85. try {
  86. configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
  87. } catch (SQLException e) {
  88. throw new NestedIOException("Failed getting a databaseId", e);
  89. }
  90. }
  91. //解析Bean中配置mapperLocation中指定的xml文件,与Spring集成后一般不会直接写在config.xml中
  92. if (!isEmpty(this.mapperLocations)) {
  93. for (Resource mapperLocation : this.mapperLocations) {
  94. if (mapperLocation == null) {
  95. continue;
  96. }
  97. try {
  98. XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
  99. configuration, mapperLocation.toString(), configuration.getSqlFragments());
  100. xmlMapperBuilder.parse();
  101. } catch (Exception e) {
  102. throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
  103. } finally {
  104. ErrorContext.instance().reset();
  105. }
  106. if (logger.isDebugEnabled()) {
  107. logger.debug("Parsed mapper file: '" + mapperLocation + "'");
  108. }
  109. }
  110. } else {
  111. if (logger.isDebugEnabled()) {
  112. logger.debug("Property 'mapperLocations' was not specified or no matching resources found");
  113. }
  114. }
  115. return this.sqlSessionFactoryBuilder.build(configuration);
  116. }

Configuration

Configuration也是Mybatis一个非常核心的类,实例这个对象需要解析Mybatis的config.xml文件和所有的Mapper.xml文件,并为每个Statement建立一个缓存,还会为每个Mapper接口绑定一个MapperProxyFactory工厂。Configuration解析配置文件主要通过XmlConfigBuilder、XmlMapperBuilder、XmlStatementBuild来分别解析全局配置文件、Mapper配置文件和具体的Statement配置

SqlSession

SqlSession里面定义了增删改查的方法,其默认实现为DefaultSqlSession,通过SqlSessionFactory进行获取,下面主要看两个问题:
(1)Mapper接口的查询是如何被代理的
通常我们只需要定义查询的Dao接口,但不需要实现,而Mybatis是通过动态代理来实现的,具体可以查看DefaultSqlSession的getMapper方法

  1. //DefaultSqlSession
  2. public <T> T getMapper(Class<T> type) {
  3. return configuration.<T>getMapper(type, this);
  4. }
  5. //Configuration
  6. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  7. return mapperRegistry.getMapper(type, sqlSession);
  8. }
  9. //MapperRegistry
  10. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  11. final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  12. if (mapperProxyFactory == null)
  13. throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  14. try {
  15. return mapperProxyFactory.newInstance(sqlSession);
  16. } catch (Exception e) {
  17. throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  18. }
  19. }
  20. //MapperProxyFactory
  21. //这里使用了jdk的动态代理,为Mapper生成了一个代理类
  22. protected T newInstance(MapperProxy<T> mapperProxy) {
  23. return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  24. }
  25. public T newInstance(SqlSession sqlSession) {
  26. final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
  27. return newInstance(mapperProxy);
  28. }
  29. //MapperProxy
  30. //MapperProxy实现了InvocationHandler接口,来进行代理,一般的invoke方法都会调用被代理接口的实现类的具体方法,
  31. //但是Mybatis里实际上只有Mapper接口,这里并没有实现类,而是直接将请求交给了MapperMethod去处理了
  32. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  33. if (Object.class.equals(method.getDeclaringClass())) {
  34. try {
  35. return method.invoke(this, args);
  36. } catch (Throwable t) {
  37. throw ExceptionUtil.unwrapThrowable(t);
  38. }
  39. }
  40. final MapperMethod mapperMethod = cachedMapperMethod(method);
  41. return mapperMethod.execute(sqlSession, args);
  42. }
  43. private MapperMethod cachedMapperMethod(Method method) {
  44. MapperMethod mapperMethod = methodCache.get(method);
  45. if (mapperMethod == null) {
  46. mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
  47. methodCache.put(method, mapperMethod);
  48. }
  49. return mapperMethod;
  50. }
  51. //MapperMethod, 可以看出最终内部是通过SqlSession对应的curd接口进行数据库的操作的
  52. public Object execute(SqlSession sqlSession, Object[] args) {
  53. Object result;
  54. if (SqlCommandType.INSERT == command.getType()) {
  55. Object param = method.convertArgsToSqlCommandParam(args);
  56. result = rowCountResult(sqlSession.insert(command.getName(), param));
  57. } else if (SqlCommandType.UPDATE == command.getType()) {
  58. Object param = method.convertArgsToSqlCommandParam(args);
  59. result = rowCountResult(sqlSession.update(command.getName(), param));
  60. } else if (SqlCommandType.DELETE == command.getType()) {
  61. Object param = method.convertArgsToSqlCommandParam(args);
  62. result = rowCountResult(sqlSession.delete(command.getName(), param));
  63. } else if (SqlCommandType.SELECT == command.getType()) {
  64. if (method.returnsVoid() && method.hasResultHandler()) {
  65. executeWithResultHandler(sqlSession, args);
  66. result = null;
  67. } else if (method.returnsMany()) {
  68. result = executeForMany(sqlSession, args);
  69. } else if (method.returnsMap()) {
  70. result = executeForMap(sqlSession, args);
  71. } else {
  72. Object param = method.convertArgsToSqlCommandParam(args);
  73. result = sqlSession.selectOne(command.getName(), param);
  74. }
  75. } else {
  76. throw new BindingException("Unknown execution method for: " + command.getName());
  77. }
  78. if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
  79. throw new BindingException("Mapper method '" + command.getName()
  80. + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  81. }
  82. return result;
  83. }

(2)SqlSession是如何执行Sql的
这里以selectOne为例,其内部会调用selectList,然后会交给Executor去执行,而具体使用哪个Executor是依据Sql类型来决定的,如果开启了二级缓存,还会用CachingExecutor对内部实际的Executor进行包装

  1. //DefaultSqlSession
  2. public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  3. try {
  4. //从Configuration中拿到对应的Statement
  5. MappedStatement ms = configuration.getMappedStatement(statement);
  6. //调用executor的query
  7. List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  8. return result;
  9. } catch (Exception e) {
  10. throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
  11. } finally {
  12. ErrorContext.instance().reset();
  13. }
  14. }
  15. //BaseExecutor
  16. public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  17. BoundSql boundSql = ms.getBoundSql(parameter);
  18. CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
  19. return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  20. }
  21. public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  22. ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  23. if (closed) throw new ExecutorException("Executor was closed.");
  24. if (queryStack == 0 && ms.isFlushCacheRequired()) {
  25. clearLocalCache();
  26. }
  27. List<E> list;
  28. try {
  29. queryStack++;
  30. //这里有一个localCache,实际上就是一级缓存,内部是一个HashMap
  31. list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
  32. if (list != null) {
  33. handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
  34. } else {
  35. list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
  36. }
  37. } finally {
  38. queryStack--;
  39. }
  40. if (queryStack == 0) {
  41. for (DeferredLoad deferredLoad : deferredLoads) {
  42. deferredLoad.load();
  43. }
  44. deferredLoads.clear(); // issue #601
  45. if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
  46. clearLocalCache(); // issue #482
  47. }
  48. }
  49. return list;
  50. }
  51. //查数据库
  52. private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  53. List<E> list;
  54. localCache.putObject(key, EXECUTION_PLACEHOLDER);
  55. try {
  56. //这里使用了模板方法设计模式,doQuery的逻辑交给BaseExecutor的子类去实现
  57. list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  58. } finally {
  59. localCache.removeObject(key);
  60. }
  61. localCache.putObject(key, list);
  62. if (ms.getStatementType() == StatementType.CALLABLE) {
  63. localOutputParameterCache.putObject(key, parameter);
  64. }
  65. return list;
  66. }
  67. //SimpleExecutor的实现
  68. public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  69. Statement stmt = null;
  70. try {
  71. Configuration configuration = ms.getConfiguration();
  72. //
  73. StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  74. stmt = prepareStatement(handler, ms.getStatementLog());
  75. //调用StatementHandler的query方法去查询, 底层是基于jdbc的PreparedStatement、statement等来查询数据
  76. return handler.<E>query(stmt, resultHandler);
  77. } finally {
  78. closeStatement(stmt);
  79. }
  80. }
  81. //这里以PreparedStatementHandler的实现,得到数据之后通过ResultSetHandler来处理返回值
  82. public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  83. PreparedStatement ps = (PreparedStatement) statement;
  84. ps.execute();
  85. return resultSetHandler.<E> handleResultSets(ps);
  86. }

Executor的实现类如下:
image.png
StatementHandler的实现类:
StatementHandler.png
Cache的实现类:里面的很多类都采用了装饰器模式,来装饰底层的cache

装饰链路:SynchronizedCache -> LoggingCache -> SerializedCache -> LruCache -> PerpetualCache。

Cache.png
其中LRUCache的实现是基于LinkedHashMap来实现的, 主要是重写其removeEldestEntry方法。如果是自己实现,可以基于HashMap + LinkedList来实现

  1. public class LruCache implements Cache {
  2. private final Cache delegate;
  3. private Map<Object, Object> keyMap;
  4. private Object eldestKey;
  5. public LruCache(Cache delegate) {
  6. this.delegate = delegate;
  7. setSize(1024);
  8. }
  9. @Override
  10. public String getId() {
  11. return delegate.getId();
  12. }
  13. @Override
  14. public int getSize() {
  15. return delegate.getSize();
  16. }
  17. public void setSize(final int size) {
  18. keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
  19. private static final long serialVersionUID = 4267176411845948333L;
  20. protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
  21. boolean tooBig = size() > size;
  22. if (tooBig) {
  23. eldestKey = eldest.getKey();
  24. }
  25. return tooBig;
  26. }
  27. };
  28. }
  29. @Override
  30. public void putObject(Object key, Object value) {
  31. delegate.putObject(key, value);
  32. cycleKeyList(key);
  33. }
  34. @Override
  35. public Object getObject(Object key) {
  36. keyMap.get(key); //touch
  37. return delegate.getObject(key);
  38. }
  39. @Override
  40. public Object removeObject(Object key) {
  41. return delegate.removeObject(key);
  42. }
  43. @Override
  44. public void clear() {
  45. delegate.clear();
  46. keyMap.clear();
  47. }
  48. public ReadWriteLock getReadWriteLock() {
  49. return null;
  50. }
  51. private void cycleKeyList(Object key) {
  52. keyMap.put(key, key);
  53. if (eldestKey != null) {
  54. delegate.removeObject(eldestKey);
  55. eldestKey = null;
  56. }
  57. }

写在最后

Mybatis虽然解决Java对象和数据库之间的ORM映射,为开发省去了手写jdbc和事务等重复的逻辑。但目前的数据库架构早已告别了单机时代,而集群时代又将面临很多Mybatis所解决不了的问题,如更多的数据、更高的并发等,因而就衍生了支持读写分离分库分表的更上层的数据访问层的中间件