Loading...
墨滴

SeaOfStar

2021/11/13  阅读:19  主题:默认主题

MyBatis学习---拉勾教育高薪训练营学习总结

MyBatis学习---拉勾教育高薪训练营学习总结

1、普通jdbc操作流程以及问题

1.1 流程

  1. 加载数据库驱动

  2. 创建数据库连接

  3. 创建预执行语句

  • statement:需要拼接sql;数据库需要对语句进行编译处理;不支持批量处理;
  • preparedStatement:预编译的但开销较大,数据库可以直接执行(节省运行时间);支持批量处理;
  1. 执行sql语句
  2. 封装结果,映射为java对象

1.2 问题

  1. 代码重复耦合度高;

  2. 数据库配置信息和sql配置信息硬编码,操作不便;

2、自定义持久层框架思路

2.1 使用端

  1. 提供数据库连接信息配置文件

  2. 提供sql配置文件:crud的配置文件,要包括查询参数、返回值类型、sql语句

2.2 框架端

2.2.1 读取数据库配置文件和sql配置文件到内存

  1. 根据配置文件绝对路径,读取成字节流InputStream到内存中;

  2. 创建Resources工具类将配置文件读取为指定格式到内存中;

2.2.2 通过dom4j解析配置文件

  1. 创建JavaBean封装读取到的配置信息
  • Configuration:数据库连接信息的封装类;
  • MapperedStatement:sql配置信息的封装类

2.2.3 创建相关类进行操作

  1. 创建SqlSessionFactoryBuilder工厂类。利用建造者模式生产SqlSessionFactory; 类方法:build(inputStream);创建者模式,多种组合方式;

  2. 创建SqlSessionFactory接口及其实现类DefaultSqlSession,生产SqlSession对象; 接口方法:openSession();生产SqlSession对象;

  3. 创建SqlSession接口及其实现类DefaultSqlSession对象,执行封装的jdbc操作; 接口方法:selectList();selectOne();

  4. 创建Executor接口及其实现类SimpleExecutor,执行具体jdbc操作; 接口方法:query(Configuration,MappedStatement,Object...params);

3. 核心配置类Configuration

## 部分属性
protected Environment environment;
protected boolean aggressiveLazyLoading; //懒加载配置
protected boolean cacheEnabled; //是否开启二级缓存
protected boolean lazyLoadingEnabled; //懒加载配置
protected Class<?> configurationFactory;
protected final MapperRegistry mapperRegistry; // 二级缓存管理类
protected final InterceptorChain interceptorChain;
protected final TypeHandlerRegistry typeHandlerRegistry; //类型转换器配置
protected final TypeAliasRegistry typeAliasRegistry;
protected final LanguageDriverRegistry languageRegistry;
protected final Map<String, MappedStatement> mappedStatements; //语句对象集合
protected final Map<String, Cache> caches; //二级缓存需要使用
protected final Map<String, ResultMap> resultMaps;
protected final Map<String, ParameterMap> parameterMaps;
protected final Map<String, KeyGenerator> keyGenerators;
protected final Set<String> loadedResources;
protected final Map<String, XNode> sqlFragments;
protected final Collection<XMLStatementBuilder> incompleteStatements; //未完成的xml语句解析
protected final Collection<CacheRefResolver> incompleteCacheRefs;
protected final Collection<ResultMapResolver> incompleteResultMaps;
protected final Collection<MethodResolver> incompleteMethods;
protected final Map<String, String> cacheRefMap; //<cache-ref>标签解析结果
protected ProxyFactory proxyFactory; //mapper代理工程

  • configuration是mybatis的核心配置类,包含了环境配置信息、数据库语句解析配置以及解析结果等;

4. MyBatis工作流程以及源码分析

4.1 Resources getResourcesAsStream(path); -->工具类读取配置文件为字节流、字符流等

4.2 SqlSessionFactoryBuilder类 --> 创建configuration对象,生产DefaultSqlSession对象

4.2.1 主要过程
  • SqlSessionFactoryBuilder.build(resourceStream);
    • Configuration config= XmlConfigurationBuilder(inputStream).parse();
      • XmlConfigurationBuilder.pluginElement();解析插件配置
      • XmlConfigurationBuilder.settingsElement(); 解析全局配置
      • XmlConfigurationBuilder.mapperElement(); 解析核心配置文件中关于xxxMapper.xml文件的配置
        • XMLMapperBuilder.parse(); 进行具体解析工作
          • XMLMapperBuilder.configurationElement(); 解析mapper.xml文件中的cache-ref\cache\parameterMap\resultMap\select\update等标签
            • XMLMapperBuilder.cacheRefElement(); ->MapperBuilderAssistant.useCacheRef();
            • XMLMapperBuilder.cacheElement(); ->MapperBuilderAssistant.useNewCache()
            • XMLMapperBuilder.parameterMapElement(); ->MapperBuilderAssistant.addParameterMap();
            • XMLMapperBuilder.resultMapElements();
            • XMLMapperBuilder.buildStatementFromContext();
              • XMLStatementBuilder.parseStatementNode(); 解析select、update等标签;
                • MapperBuilderAssistant.addMappedStatement(); 将生成的mapperStatement对象放入到configuration;如果curretCache未赋值,抛出IncompleteElementException异常,并被捕获处理;MappedStatement.Builder.build()生成MappedStatement对象,并放入到configuration的mappedStatements成员变量中
          • bindMapperForNamespace; 将mapper对应的接口实现类的代理工厂类和namespace绑定;Class boundType=Resources.classForName(namespace);向configuration中的MapperRegistry成员变量中的Map , MapperProxyFactory<?>> knownMappers中添加该boundType;
          • parsePendingResultMaps(); 对未解析完成的操作重新进行;
          • parsePendingCacheRefs();
          • parsePendingStatements();
    • DefaultSqlSessionFactory(config);
  • 其中 XMLMapperBuilder中的MapperBuilderAssistant builderAssistant 成员变量
    • MapperBuilderAssistant.useCacheRef(); 以cache-ref的namespace值为key,从configuration中的caches成员变量中获取value,如果获取到把当前mapperBuilderAssistant对象的成员变量currentCahce值为它;否则抛出IncompleteElementException异常,然后被捕获,执行configuration.addIncompleteCacheRef方法。
    • MapperBuilderAssistant.useNewCache(); 析cache标签,创建一个Cache对象并且放入到configuration中的caches成员变量中;设置当前MapperBuilderAssistant对象的成员变量currentCache为生成的cache

4.3 SqlSessionFactory接口 --> 创建SqlSession对象

4.3.1 接口方法
public interface SqlSessionFactory {
    SqlSession openSession();
    SqlSession openSession(boolean var1);
    SqlSession openSession(Connection var1);
    SqlSession openSession(TransactionIsolationLevel var1);
    SqlSession openSession(ExecutorType var1);
    SqlSession openSession(ExecutorType var1, boolean var2);
    SqlSession openSession(ExecutorType var1, TransactionIsolationLevel var2);
    SqlSession openSession(ExecutorType var1, Connection var2);
    Configuration getConfiguration();
}
4.3.2 接口主要实现类
image-20211113163951438
image-20211113163951438
4.3.3 主要过程和功能
  • 创建executor对象;
  • 创建transactionFactory对象;
  • 创建DefaultSqlSession对象;
public SqlSession openSession() {
    return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)nullfalse);
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  DefaultSqlSession var8;
  try {
    Environment environment = this.configuration.getEnvironment();
    TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    Executor executor = this.configuration.newExecutor(tx, execType);
    var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
  } catch (Exception var12) {
    this.closeTransaction(tx);
    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
  } finally {
    ErrorContext.instance().reset();
  }
  return var8;
}

4.4 **SqlSession**接口

4.4.1 接口方法
public interface SqlSession extends Closeable {
    <T> selectOne(String var1);
    <T> selectOne(String var1, Object var2);
    <E> List<E> selectList(String var1);
    <E> List<E> selectList(String var1, Object var2);
    <E> List<E> selectList(String var1, Object var2, RowBounds var3);
    <K, V> Map<K, V> selectMap(String var1, String var2);
    <K, V> Map<K, V> selectMap(String var1, Object var2, String var3);
    <K, V> Map<K, V> selectMap(String var1, Object var2, String var3, RowBounds var4);
    <T> Cursor<T> selectCursor(String var1);
    <T> Cursor<T> selectCursor(String var1, Object var2);
    <T> Cursor<T> selectCursor(String var1, Object var2, RowBounds var3);
    void select(String var1, Object var2, ResultHandler var3);
    void select(String var1, ResultHandler var2);
    void select(String var1, Object var2, RowBounds var3, ResultHandler var4);
    int insert(String var1);
    int insert(String var1, Object var2);
    int update(String var1);
    int update(String var1, Object var2);
    int delete(String var1);
    int delete(String var1, Object var2);
    void commit();
    void commit(boolean var1);
    void rollback();
    void rollback(boolean var1);
    List<BatchResult> flushStatements();
    void close();
    void clearCache();
    Configuration getConfiguration();
    <T> getMapper(Class<T> var1);
    Connection getConnection();
}
4.4.2 接口主要实现类
img
img
4.4.3 主要过程和功能
  • 封装了API,具体操作由executor执行。insert、update、delete都是执行executor的update接口,先调用了BaseExecutor、CachingExecutor中的query(),然后调用BaseExecutor子类SimpleExecutor的doQuery();修改同理;
  • 过程

    • 1、根据statement的id,创建了MappedStatement对象;

      private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
          List var6;
          try {
              MappedStatement ms = this.configuration.getMappedStatement(statement);
              var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
          } catch (Exception var10) {
              throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var10, var10);
          } finally {
              ErrorContext.instance().reset();
          }
          return var6;
      }
    • 2、调用executor.query()、executor.update();

4.5 Executo接口r --> 执行jdbc操作,模版模式

4.5.1 接口方法
public interface Executor {
    ResultHandler NO_RESULT_HANDLER = null;
    int update(MappedStatement var1, Object var2) throws SQLException;
    <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException;

    <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;

    <E> Cursor<E> queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException;

    List<BatchResult> flushStatements() throws SQLException;
    void commit(boolean var1) throws SQLException;
    void rollback(boolean var1) throws SQLException;
    CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4);
    boolean isCached(MappedStatement var1, CacheKey var2);
    void clearLocalCache();
    void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class<?> var5);
    Transaction getTransaction();
    void close(boolean var1);
    boolean isClosed();
    void setExecutorWrapper(Executor var1);
}
4.5.2 主要实现类
img
img
4.5.4 主要过程和功能
4.5.4.1 维护缓存;创建StatementHandler、Statement执行数据库操作;
4.5.4.2 过程:未开启二级缓存
  • BaseExecutor

    • BaseExecutor成员变量PerpetualCache localCache 维护了一级缓存;
    • simpleExecutor继承了父类baseExecutor,重写了父类的doQuery()、doUpdate(),这两个方法目的都是处理参数、获取StatementHandler和Statement对象,然后执行handler.query()或handler.update();
  • BaseExecutor.query();

    • 1、根据MappedStatement对象获取BoundSql;根据ms、parameters、rowBounds、boundSql创建CacheKey对象;

      public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
        return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    • 2、继续调用BaseExecutor.query()重载方法:判断是否需要清除一级缓存,是则清除;然后从缓存中获取数据,未获取到数据则继续调用queryFromDatabase(), 进而继续调用其子类doQuery()方法。然后将查询的结果放入到一级缓存中,最后返回数据。

      private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
              this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
              List list;
              try {
                  list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
              } finally {
                  this.localCache.removeObject(key);
              }

              this.localCache.putObject(key, list);
              if (ms.getStatementType() == StatementType.CALLABLE) {
                  this.localOutputParameterCache.putObject(key, parameter);
              }
              return list;
          }
    • 3、this.doQuery() 是抽象方法,实际调用子类SimpleExecutor.doQuery();

      • 3.1 通过configuration.newStatementHandler() 创建StatementHandler对象;
      • 3.2 调用SimpleExecutor.prepareStatement() 创建statement对象、完成参数设置。该方法通过调用StatementHandler.prepare()创建statement对象,然后调用了StatementHandler.parameterize()设置statement的参数;
      • 3.3 通过statementHandler.query()执行查询并返回结果
      public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;

        List var9;
        try {
          Configuration configuration = ms.getConfiguration();
          StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
          stmt = this.prepareStatement(handler, ms.getStatementLog());
          var9 = handler.query(stmt, resultHandler);
        } finally {
          this.closeStatement(stmt);
        }

        return var9;
      }
4.5.3 过程:开启二级缓存
  • CachingExecutor

    • CachingExecutor.query 通过TransactionalCacheManager tcm全局变量维护二级缓存,TransactionalCacheManager类有成员变量Map<Cache, TransactionalCache> transactionalCaches;
    • tcm的key为MappedStatement对象的cache变量,同namespace下的mappedStatement对象拥有同一个Cache对象;
    • 先判断二级缓存中是否存在;具体查询、修改操作由其全局变量delegate(先baseExecutor再simpleExecutor)完成;
    • 注意:二级缓存,查询到的结果先放入到HashMap结构的entriesToAddOnCommit全局变量中,只有commit之后,才会将该变量中的数据刷新到真正的缓存对象cache中,然后才可以被查询到;同一个namespace中有增删改操作发生,就会清空二级缓存。
  • CachingExecutor.query(); 1、获取MappedStatement的Cache对象,判断二级缓存TransactionalManager中是否含有Cache为大key、cacheKey为小key的数据;有则返回,否则进行数据库查询; 2、调用BaseExecutor.query();

    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
      BoundSql boundSql = ms.getBoundSql(parameterObject);
      CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
      return this.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();
            if (cache != null) {
                this.flushCacheIfRequired(ms);
                if (ms.isUseCache() && resultHandler == null) {
                    this.ensureNoOutParams(ms, boundSql);
                    List<E> list = (List)this.tcm.getObject(cache, key);
                    if (list == null) {
                        list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                        this.tcm.putObject(cache, key, list);
                    }

                    return list;
                }
            }
        // 调用BaseExecutor.query();然后过程和未开启缓存一样;
            return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        }

4.6 StatementHandler接口

4.6.1 接口方法
public interface StatementHandler {
    Statement prepare(Connection var1, Integer var2) throws SQLException;

    void parameterize(Statement var1) throws SQLException;

    void batch(Statement var1) throws SQLException;

    int update(Statement var1) throws SQLException;

    <E> List<E> query(Statement var1, ResultHandler var2) throws SQLException;

    <E> Cursor<E> queryCursor(Statement var1) throws SQLException;

    BoundSql getBoundSql();

    ParameterHandler getParameterHandler();
}

4.6.2 接口主要实现类
img
img
4.6.3 主要过程和功能
4.6.3.1功能
  • SimpleStatementHandler 管理statement对象并且执行不需要预编译的的sql语句;
  • PreparedStatemenHandler 管理statemetn对象并且向数据库执行需要预编译的sql语句;
  • RoutingStatementHandler:根据MappedStatement的statementType创建simpleStatement、preparementStatement
4.6.3.1 过程
  • 1、BaseStatementHandler.prepare(); 创建statement对象;

    public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
            ErrorContext.instance().sql(this.boundSql.getSql());
            Statement statement = null;

            try {
                statement = this.instantiateStatement(connection);
                this.setStatementTimeout(statement, transactionTimeout);
                this.setFetchSize(statement);
                return statement;
            } catch (SQLException var5) {
                this.closeStatement(statement);
                throw var5;
            } catch (Exception var6) {
                this.closeStatement(statement);
                throw new ExecutorException("Error preparing statement.  Cause: " + var6, var6);
            }
        }
    • BaseStatementHandler.prepare();

      调用instantiateStatement(),由子类重写,进而创建statement对象。

      • SimpleStatementHandler.instantiateStatement(); connection.createStatement()创建statement对象。

      • PreparedStatementHandler.instantiateStatement();

        connection.prepareStatement()创建statement对象

  • 2、设置preparedStatement的参数;

    PreparedStatementHandler.parameterize(); 调用paramenterHandler.setParameters()设置参数;

    public void parameterize(Statement statement) throws SQLException {
      this.parameterHandler.setParameters((PreparedStatement)statement);
    }
  • 3、执行sql并处理查询结果;

    • 执行Statement或者preparedStatement的execute(),然后执行resultSetHandler.handlerResultSets();
4.6.3.1 ParameterHandler接口和ResultSetHandler接口
  • ParameterHandler接口:
    • 参数处理器,为preparedStatement动态设置参数。在创建StatementHandler对象的时候创建,configuration.newParameterHandler
    • DefaultParameterHandler.setParameters(); 1、BoundSql对象中有集合属性parameterMappings,其中存储了xml中sql语句中的参数名称,用于通过反射从参数对象中获取指定属性值; 2、通过TypeHandler来处理jdbc类型和Java类型之间的数据映射;ParameterMapping实体有TypeHandler属性;
  • ResultSetHandler接口:
    • 结果映射器:将数据库查询结果映射为实体类。在创建StatementHandler对象的时候创建,configuration.newResultSetHandler

4.7 Statement接口

  • 该接口是java.sql.Statement,由jdk提供;实现类为数据库厂商;
  • ![image-20211113172834245](/Users/yjwu/Library/Application Support/typora-user-images/image-20211113172834245.png)

5. MyBatis基础概念及高级应用

5.1 缓存

聊聊MyBatis缓存机制 - 美团技术团队 (meituan.com)

5.1.1 自带缓存
  • 一二级缓存是底层数据结构都是hashmap,key=namespace:sqlid:params:sql:分页参数

  • 一级缓存

    • 默认开启,SqlSession级别,不同SqlSession之间隔离,当执行commit、rollback操作后就会清空;

    • 一级缓存缓存的是结果对象,同一SqlSession多次查询的结果是同一个对象;

    • 一级缓存可能会发生脏读,一个sqlsession命中缓存,无法读取到另一个sqlsession更新后的数据。

    • PreputalCache实现了mybatis中的cache接口,底层数据结构是HashMap。

    • BaseExecutor的构造方法中,实例化PreputalCache类型的localCache变量。此后,由BaseExecutor完成的query()查询,会先从localCache中获取对象;获取不到,才会调用queryFromDatabase()从数据库中查询,当查询到结果后先放入到localCache中再返回。

  • 二级缓存

    • 作用域是namespace级别,默认关闭,需要全局配置 开启;mapper可以配置 标签设置该namespace下二级缓存的刷新算法、刷新周期、存储对象的个数、缓存是否只读;同时,每个查询语句可以单独配置useCache=false设置二级缓存的关闭,设置flush=true开启刷新缓存(默认开启,一般地,commit操作后都会刷新缓存)。如果使用注解开发,可以在mapper接口上使用@ CacheNamespac注解进行相关配置,该注解还可以配置自定义缓存的实现类,在sql注解上使用@ option(usecache=false)关闭二级缓存,

    • 因为二级缓存缓存的是结果的值,可能保存在内存、磁盘中,所以缓存对象必须实现序列化,这样才能从二级缓存中进行查询后通过反序列化返回。

    • CachingExecutor类使用BaseExecutor类完成实际的jdbc操作,其内部通过TransactionalCacheManager类来进行二级缓存的管理。该tcm变量的key为Cache接口的实现类,实际值为MappedStatement(查询语句)中Cache类型的cache变量;进而等同于Configuration中HashMap类型的caches成员变量中的一个value,而caches变量的key即解析sql配置文件时,从 标签中解析出来的namespace值,默认即为namespace值。所以同一个namespace下的语句,他们再tcm中是同一个key

    • 二级缓存以hashMap存储数据,无法实现分布式缓存;

5.1.2 自定义缓存
  • 使用redis作为缓存
  • @ NameSpace(implemention=PerpetualCache.class)设置二级缓存的实现类;
  • mybatis默认使用PerpetualCache类实现Cache接口操作二级缓存,mybatis提供mybatis-redis包来实现分布式redis缓存;
5.2 mybatis插件机制

拦截器;intercepts注解、signature注解;

6. 问题记录

6.1 sqlSession线程安全问题、为什么要用线程不安全的defaultSqlSession

defaultSqlSession不是线程安全的,所以不能使用单例模式。sqlSessionFactory每次都创建一个新的sqlSession对象。SqlSessionTemplate是线程安全的,TransactionSynchronizationManager通过map缓存,key为SqlSessionFactyory,从中获取;SqlSessionManager是线程安全的,通过threadLocal实现。

6.2 设计模式

  • 创造者模式
  • 模版模式
  • 工厂模式;
  • 装饰者模式:
  • 适配器模式:baseStatement

SeaOfStar

2021/11/13  阅读:19  主题:默认主题

作者介绍

SeaOfStar