# JDBC

# 什么是JDBC

  • JDBC是Java与数据库交互的统一API,它实际上包括两组API
    • 面向Java应用程序开发人员的API
    • 面向数据库驱动程序开发人员的API

# JDBC的使用步骤

  1. 注册数据库驱动类,并配置数据库URL,用户名,密码等连接信息
  2. 通过DriverManager打开数据库连接
  3. 通过数据库连接,创建Statement对象
  4. 通过Statement对象执行SQL语句,得到ResultSet对象
  5. 通过ResultSet读取数据,并将数据转换成JavaBean对象。
  6. 关闭ResultSet、Statement对象以及数据库连接,释放相关资源

# ORM

# 什么是ORM

  • 在数据库操作时,通过比较通用的方式,完成关系模型到对象模型的转换过程
  • Object Relational Mapping, 对象关系映射框架.
  • 集成了缓存、数据源、数据库连接池等组件对JDBC的过程进行优化.

# 常见的ORM框架

  • Hibernate

    • 特性
      • 实现对象模型与关系模型的映射
      • 屏蔽不同数据库产品中SQL语句的细微差异
      • 提供面向对象的查询语言HQL
    • 缺点
      • 在大数据量、高并发、低延迟场景下,Hibernate的优化难度增加
  • JPA(Java Persistence API)

    • 特性:
      • 仅仅是一个持久化API,并没有提供具体的实现
    • 缺点:
      • JPA的愿景很美好,但是实践中出场率不高
  • Spring JDBC

    • 特性:
      • 直接执行原生SQL语句
      • 提供了多种Template,提供查询参数的绑定功能
      • 提供了多种Callback,提供ResultSet转化为对象列表的功能
      • 凭借高度的灵活性,在java持久化中占有一席之地
    • 缺点:
      • 功能不及Hibernate强大
  • Mybatis

    • 特性:
      • 可配置原生SQL语句
      • 动态SQL拼接
    • 缺点:
      • 没有屏蔽数据库底层方言

# mybatis

  • 一个优秀的持久层框架,对JDBC操作数据库的过程进行封装,使开发者只需要关注sql本身.它是一个半自动的ORM框架。
  • Mybatis的前身是 Apache基金会的开源项目 iBatis,2010年脱离Apache基金会并更名为 mybatis。 2013年11月,MyBatis迁移到GitHub。

# 纯净mybatis架构

mybatis架构

# 纯净Mybatis环境使用示例

public class MybatisTest {
    public static void main(String[] args) throws IOException {
        //使用XML的方式构建 SqlSessionFactory
        String resource = "org/mybatis/example/mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //设置properties有点问题,未被正常解析
//        Configuration configuration= sqlSessionFactory.getConfiguration();
//        Properties properties = new Properties();
//        properties.setProperty("driver","com.mysql.cj.jdbc.Driver");
//        properties.setProperty("url","jdbc:mysql://localhost:3306/zhenhe?useUnicode=true&serverTimezone=CST&characterEncoding=utf-8");
//        properties.setProperty("username","root");
//        properties.setProperty("password","chenkaihai");
//        configuration.setVariables(properties);

        //使用 java编码的方式构建 SqlSessionFactory
//        PooledDataSource dataSource = new PooledDataSource();
//        TransactionFactory transactionFactory = new JdbcTransactionFactory();
//        dataSource.setDriver("com.mysql.cj.jdbc.Driver");
//        dataSource.setUrl("jdbc:mysql://localhost:3306/zhenhe?useUnicode=true&serverTimezone=CST&characterEncoding=utf-8");
//        dataSource.setUsername("root");
//        dataSource.setPassword("chenkaihai");
//
//        Environment environment = new Environment("automannn",transactionFactory,dataSource);
//        Configuration configuration = new Configuration(environment);
//        configuration.addMapper(PersonDao.class);
//        SqlSource sqlSource = new StaticSqlSource(configuration,"select * from person");
//        ResultMapping resultMapping1 = new ResultMapping.Builder(configuration,"pid","pid",String.class).build();
//        ResultMapping resultMapping2 = new ResultMapping.Builder(configuration,"pname","pname",String.class).build();
//        List<ResultMapping> mappingList = new ArrayList<>();
//        mappingList.add(resultMapping1);
//        mappingList.add(resultMapping2);
//        ResultMap resultMap = new ResultMap.Builder(configuration,"com.automannn.practice.mybatis.dao.PersonDao.selectList", PersonEntity.class,mappingList).build();
//        MappedStatement mappedStatement = new MappedStatement.Builder(configuration,"com.automannn.practice.mybatis.dao.PersonDao.selectList",
//                sqlSource, SqlCommandType.SELECT).resultMaps(Arrays.asList(resultMap)).build();
//        configuration.addMappedStatement(mappedStatement);
//
//        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

        //xml定义语句
        SqlSession sqlSession = sqlSessionFactory.openSession();
        PersonDao personDao = sqlSession.getMapper(PersonDao.class);
//        System.out.println(personDao.selectList());

        //注解定义语句
//        System.out.println(personDao.queryAll());

        PersonEntity personEntity = new PersonEntity();
        personEntity.setPname("bbb");

        System.out.println(personDao.selectBySingleParam("abcsd"));
    }
}

# 全局集中配置Configuration

/*xxx: 全局配置项, environment 需要显式配置的属性 */
public class Configuration {

    protected Environment environment;

    protected String databaseId;

    /*xxx: 它记录当前使用的 MapperRegistry对象*/
    protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

    /*xxx: 该属性用于管理 Interceptor 对象,底层使用 ArrayList<Interceptor> 实现*/
    protected final InterceptorChain interceptorChain = new InterceptorChain();

    /*xxx: id与sql语句的映射注册表*/
    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
          .conflictMessageProducer((savedValue, targetValue) ->
              ". please check " + savedValue.getResource() + " and " + targetValue.getResource());

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        /*xxx: 根据参数,选择合适的 Executor 实现*/
        if (ExecutorType.BATCH == executorType) {
          executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
          executor = new ReuseExecutor(this, transaction);
        } else {
          executor = new SimpleExecutor(this, transaction);
        }
        /*xxx: 根据配置,决定是否开启 二级缓存 的功能*/
        if (cacheEnabled) {
          executor = new CachingExecutor(executor);
        }
        /*xxx: 通过 InterceptorChain.pluginAll() 方法,创建 Executord的代理对象*/
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
    }

    //省略其它抽象...

}

public final class Environment {
  private final String id;
  private final TransactionFactory transactionFactory;
  private final DataSource dataSource;
  
  //省略其它抽象...
}

# session工厂构建器

/*xxx: the start of the whole world*/
public class SqlSessionFactoryBuilder {
    /*xxx: mybatis的初始化入口*/
      public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        try {
          /*xxx: 读取配置文件*/
          /*xxx: 通过拆给你就 XMLConfigBuilder对象来解析 mybatis-config.xml*/
          XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
          /*xxx: 解析配置文件得到 Configuration对象,创建 DefaultSqlSessionFactory对象*/
          return build(parser.parse());
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
          ErrorContext.instance().reset();
          try {
            reader.close();
          } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
          }
        }
      }

     public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
      }

    //省略其它抽象...

}

# XML解析构造器---XMLConfigBuilder

/*xxx: 具体建造者,它主要负责解析  mybatis-config.xml 配置文件*/
public class XMLConfigBuilder extends BaseBuilder {
    
    public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
      }
    
    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
    }

    /*xxx: 解析  mybatis-config.xml 配置文件的入口*/
    public Configuration parse() {
        /*xxx: 探测是否已经解析过配置文件*/
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        /*xxx: 在 mybatis-config.xml 配置文件中,查找  <configuration> 节点,并开始解析*/
        /*xxx: 以 confgiration为根节点进行解析 */
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }

    private void parseConfiguration(XNode root) {
        try {
          // issue #117 read properties first
          /*xxx: 解析 <properties> 节点 */
          propertiesElement(root.evalNode("properties"));
          /*xxx: 解析 <settings> 节点*/
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          /*xxx: 设置  vfsImpl 字段*/
          loadCustomVfs(settings);
          loadCustomLogImpl(settings);
          /*xxx: 解析  <typeAliases> 节点*/
          typeAliasesElement(root.evalNode("typeAliases"));
          /*xxx: 解析 <plugins> 节点*/
          pluginElement(root.evalNode("plugins"));
          /*xxx: 解析 <objectFactory>节点*/
          objectFactoryElement(root.evalNode("objectFactory"));
          /*xxx: 解析 <objectWrapperFactory>节点*/
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          /*xxx: 解析 <reflectorFactory>节点*/
          reflectorFactoryElement(root.evalNode("reflectorFactory"));
          /*xxx: 将 settings 值,设置到 Configuration中*/
          settingsElement(settings);
          // read it after objectFactory and objectWrapperFactory issue #631
          /*xxx: 设置 <environments> 节点*/
          environmentsElement(root.evalNode("environments"));
          /*xxx: 解析  <databaseIdProvider> 节点*/
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          /*xxx: 解析 <typeHandlers> 节点*/
          typeHandlerElement(root.evalNode("typeHandlers"));
          /*xxx: 解析  <mappers> 节点*/
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }

    private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
          /*xxx: 处理 <mappers> 的子节点*/
          for (XNode child : parent.getChildren()) {
            /*xxx: <package>子节点*/
            if ("package".equals(child.getName())) {
              String mapperPackage = child.getStringAttribute("name");
              /*xxx:扫描指定的包,并向 MapperRegistry注册 Mapper接口,它根据包名注册*/
              configuration.addMappers(mapperPackage);
            } else {
              /*xxx: 获取  <mapper>节点的  resource,url,class属性,这三个属性互斥*/
              String resource = child.getStringAttribute("resource");
              String url = child.getStringAttribute("url");
              String mapperClass = child.getStringAttribute("class");
              /*xxx: 如果<mapper> 节点指定的是 resource属性*/
              if (resource != null && url == null && mapperClass == null) {
                ErrorContext.instance().resource(resource);
                InputStream inputStream = Resources.getResourceAsStream(resource);
                /*xxx: 则 创建 XMLMapperBuilder对象*/
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                mapperParser.parse();
                /*xxx: 如果 <mapper> 节点 指定的 是 url属性*/
              } else if (resource == null && url != null && mapperClass == null) {
                ErrorContext.instance().resource(url);
                InputStream inputStream = Resources.getUrlAsStream(url);
                /*xxx: 则 也是创建 XMLMapperBuilder对象*/
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                mapperParser.parse();
                /*xxx: 如果 <mapper> 节点 指定的 是 class属性*/
              } else if (resource == null && url == null && mapperClass != null) {
                Class<?> mapperInterface = Resources.classForName(mapperClass);
                /*xxx: 则向 MapperRegistry注册该Mapper接口*/
                configuration.addMapper(mapperInterface);
              } else {
                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
              }
            }
          }
        }
    }
    
    //省略其它抽象...

}

# Mapper解析构造器---XMLMapperBuilder

public class XMLMapperBuilder extends BaseBuilder {

    public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
        this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
            configuration, resource, sqlFragments);
      }
    
    private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
        super(configuration);
        this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
        this.parser = parser;
        this.sqlFragments = sqlFragments;
        this.resource = resource;
    }

    /*xxx: 解析映射文件的入口*/
    public void parse() {
        /*xxx: 判断是否已经加载过  该映射文件 */
        if (!configuration.isResourceLoaded(resource)) {
          /*xxx: 处理 <mapper> 节点*/
          configurationElement(parser.evalNode("/mapper"));
          /*xxx: 将 resource添加到 Configuration.loadedResources集合中保存,它是 HashSet<String> 类型的集合,其中记录了已经加载过的映射文件*/
          configuration.addLoadedResource(resource);
          /*xxx: 注册 Mapper接口*/
          bindMapperForNamespace();
        }
        /*xxx: 由于解析映射配置文件,是 按照从文件头 到 文件尾 的顺序解析的,  但是在解析过程中,可能会引用定义在该节点之后的,还未解析的节点*/
        /*xxx: 这是会 抛出  IncompleteElementException,将相应的节点进行暂存*/
        /*xxx: 根据类型的不同,mybatis提供了三种 类型进行 后置处理*/
    
        /*xxx: 处理 configuratioElement方法中,解析失败的 <resultMap> 节点*/
        parsePendingResultMaps();
        /*xxx: 处理 configurationElement 方法中解析失败的 <cache-ref> 节点*/
        parsePendingCacheRefs();
        /*xxx: 处理  configurationElement 方法中 解析失败的 SQL 语句节点 */
        parsePendingStatements();
    }

    private void configurationElement(XNode context) {
        try {
          /*xxx: 获取 <mapper> 节点的  namespace属性*/
          String namespace = context.getStringAttribute("namespace");
          /*xxx: 如果 namespace 属性为空,则抛出异常*/
          if (namespace == null || namespace.isEmpty()) {
            throw new BuilderException("Mapper's namespace cannot be empty");
          }
          /*xxx: 设置 MapperBuilderAssistant 的 currentNamespace 字段,记录当前命名空间*/
          builderAssistant.setCurrentNamespace(namespace);
    
          /*xxx: 解析 <cache-ref> 节点*/
          cacheRefElement(context.evalNode("cache-ref"));
          /*xxx: 解析 <cache> 节点*/
          cacheElement(context.evalNode("cache"));
          /*xxx: 解析  <parameterMap> 节点,已废弃,不推荐使用*/
          parameterMapElement(context.evalNodes("/mapper/parameterMap"));
          /*xxx: 解析 <resultMap> 节点*/
          resultMapElements(context.evalNodes("/mapper/resultMap"));
          /*xxx: 解析 <sql> 节点*/
          sqlElement(context.evalNodes("/mapper/sql"));
          /*xxx: 解析  <select>,<insert>,<update>, <delete> 等sql节点*/
          buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
        }
    }

    private void buildStatementFromContext(List<XNode> list) {
        if (configuration.getDatabaseId() != null) {
          buildStatementFromContext(list, configuration.getDatabaseId());
        }
        buildStatementFromContext(list, null);
      }
    
    private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        for (XNode context : list) {
          final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
          try {
            statementParser.parseStatementNode();
          } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
          }
        }
    }
    //省略其它抽象...
    
}

# sql语句解析构造器---XMLStatementBuilder

public class XMLStatementBuilder extends BaseBuilder {
    public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context) {
        this(configuration, builderAssistant, context, null);
      }
    
    public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {
        super(configuration);
        this.builderAssistant = builderAssistant;
        this.context = context;
        this.requiredDatabaseId = databaseId;
    }

    public void parseStatementNode() {
        /*xxx: 获取 sql 节点的id ,以及 databaseId属性,若其databaseId属性值,与当前使用的数据库不匹配,则不加载该 sql 节点*/
        /*xxx: 若存在相同id,且 databaseId不为空的sql节点,则不再加载该 sql 节点*/
        String id = context.getStringAttribute("id");
        String databaseId = context.getStringAttribute("databaseId");
    
        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
          return;
        }
    
        /*xxx: 根据 sql 节点的 名称 决定其 SqlCommandType  */
        String nodeName = context.getNode().getNodeName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        /*xxx: 获取sql节点的多种属性值*/
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
        boolean useCache = context.getBooleanAttribute("useCache", isSelect);
        boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
    
        // Include Fragments before parsing
        /*xxx: 在解析 SQL 语句之前,先处理 其中的 <include> 节点*/
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());
    
        String parameterType = context.getStringAttribute("parameterType");
        Class<?> parameterTypeClass = resolveClass(parameterType);
    
        String lang = context.getStringAttribute("lang");
        LanguageDriver langDriver = getLanguageDriver(lang);
    
        // Parse selectKey after includes and remove them.
        /*xxx: 处理 <selectKey> 节点*/
        processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
        // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
        /*xxx: 完成 sql 节点的 解析,该部分是  parseStatementNode 方法的核心*/
        KeyGenerator keyGenerator;
    
        /*xxx: 获取 <selectKey> 节点对应的 SelectKeyGenerator的id*/
        String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
        keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
        if (configuration.hasKeyGenerator(keyStatementId)) {
          keyGenerator = configuration.getKeyGenerator(keyStatementId);
        } else {
          keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
              configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
              ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        }
    
        /*xxx: 创建 sqlSource对象*/
        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
        StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
        Integer fetchSize = context.getIntAttribute("fetchSize");
        Integer timeout = context.getIntAttribute("timeout");
        String parameterMap = context.getStringAttribute("parameterMap");
        String resultType = context.getStringAttribute("resultType");
        Class<?> resultTypeClass = resolveClass(resultType);
        String resultMap = context.getStringAttribute("resultMap");
        String resultSetType = context.getStringAttribute("resultSetType");
        ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
        if (resultSetTypeEnum == null) {
          resultSetTypeEnum = configuration.getDefaultResultSetType();
        }
        /*xxx: 获取 resultSets,keyProperty,keyColumn三个属性*/
        String keyProperty = context.getStringAttribute("keyProperty");
        String keyColumn = context.getStringAttribute("keyColumn");
        String resultSets = context.getStringAttribute("resultSets");
    
        /*xxx: 通过builderAssistant创建MappedStatement对象,并添加到 Configuration.mappedStatements 集合中保存*/
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    }
    //省略其他抽象...
}

# session工厂

public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);

  SqlSession openSession(Connection connection);

  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);

  SqlSession openSession(ExecutorType execType, boolean autoCommit);

  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();

}

# session接口

public interface SqlSession extends Closeable {
    
    /*xxx: 参数表示使用的查询sql语句,返回值 为 查询的结果对象*/
    <T> T selectOne(String statementId);

    /*xxx: 查询结果集有多条,则会封装成结果对象列表返回*/
    <E> List<E> selectList(String statementId);

    int update(String statement, Object parameter);

    /*xxx: 执行 update语句*/
    int update(String statementId);

    /*xxx: 执行 delete语句*/
    int delete(String statementId);

    /*xxx: 清空缓存*/
    void clearCache();

    /*xxx: 获取type对应的 Mapper对象*/
    <T> T getMapper(Class<T> type);

    //省略其他抽象
}

# 执行器---Executor

/*xxx: 核心接口,定义了数据库操作的基本方法*/
/*xxx: 在mybatis起到了承上启下的作用 */
public interface Executor {

    /*xxx: 执行 update,inser,delete三种类型的SQL语句*/
    int update(MappedStatement ms, Object parameter) throws SQLException;    

    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

    /*xxx: 提交事务*/
    void commit(boolean required) throws SQLException;
    
    /*xxx: 回滚事务*/
    void rollback(boolean required) throws SQLException;

    /*xxx: 获取事务对象*/
    Transaction getTransaction();

    //省略其他抽象...
}

# plugin插件原理

/*xxx: 全局配置项, environment 需要显式配置的属性 */
public class Configuration {
    /*xxx: 该属性用于管理 Interceptor 对象,底层使用 ArrayList<Interceptor> 实现*/
    protected final InterceptorChain interceptorChain = new InterceptorChain();

    /*xxx: 该方法供各种渠道调用,完成收集 拦截器的效果 */
    public void addInterceptor(Interceptor interceptor) {
        interceptorChain.addInterceptor(interceptor);
    }

    /*xxx: 参数解析器实例化时,追加 拦截器*/
    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
        parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
    }

    /*xxx: 数据库语句实例化时,自动装载 拦截器 */
    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
    }

    /*xxx: 执行器初始化时,自动装载拦截器 */
    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        /*xxx: 根据参数,选择合适的 Executor 实现*/
        if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
            executor = new ReuseExecutor(this, transaction);
        } else {
            executor = new SimpleExecutor(this, transaction);
        }
        /*xxx: 根据配置,决定是否开启 二级缓存 的功能*/
        if (cacheEnabled) {
            executor = new CachingExecutor(executor);
        }
        /*xxx: 通过 InterceptorChain.pluginAll() 方法,创建 Executor的代理对象*/
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
    }

    /*xxx: 结果集处理器初始化时,自动装载拦截器 */
    public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
                                                ResultHandler resultHandler, BoundSql boundSql) {
        ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
        resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
        return resultSetHandler;
    }
}
public class InterceptorChain {
    public Object pluginAll(Object target) {
        /*xxx: 遍历 interceptors 集合*/
        for (Interceptor interceptor : interceptors) {
            /*xxx: 调用 Interceptor.plugin 方法*/
            target = interceptor.plugin(target);
        }
        return target;
    }
}

public interface Interceptor {
    /*xxx: 决定是否触发 intercept方法*/
    /*xxx: 子类需要决定,是否进行代理, 不需要代理的,则返回原对象  */
    default Object plugin(Object target) {
        /*xxx: 该方法本质上是,返回动态代理对象 */
        return Plugin.wrap(target, this);
    }
}

/*xxx: jdk动态代理实现对象 */
public class Plugin implements InvocationHandler {
    //xxx: 省略其他抽象...
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            /*xxx: 获取当前方法所在类  或 接口中,可被 当前 Interceptor 拦截的方法*/
            Set<Method> methods = signatureMap.get(method.getDeclaringClass());
            /*xxx: 如果需要被拦截,则调用 interceptor.intercept 方法 进行拦截处理*/
            if (methods != null && methods.contains(method)) {
                /*xxx: Invocation是ibatis定义的用于辅助 动态代理执行 的 包装对象 */
                return interceptor.intercept(new Invocation(target, method, args));
            }
            /*xxx: 如果不能被拦截,则调用 target对象的相应方法*/
            return method.invoke(target, args);
        } catch (Exception e) {
            throw ExceptionUtil.unwrapThrowable(e);
        }
    }
}
public interface Interceptor {
    /*xxx: 执行拦截逻辑的方法*/
    /*xxx: invocation包装对象带来的元素,决定了具体执行的方法,能达到什么样的效果*/
    Object intercept(Invocation invocation) throws Throwable;
}

/*xxx: mybatis-plus的乐观锁插件 示例*/
/*xxx: @Intercepts是必需的注解 */
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class OptimisticLockerInterceptor implements Interceptor {
    //省略其他抽象...
}

# 纯净mybatis执行关键过程探究

# 动态代理

  • 涉及的关键类: MapperProxyFactory,MapperProxy,MapperMethodInvoker,MapperMethod

  • 本质上,MapperMethod 通过 sqlSession 完成数据库操作.而 sqlSession 又是依赖 executor 完成操作.

  • 一个动态代理案例:

public class DynamicProxyTest {

    public static void main(String[] args) {
        Map<Class, MapperProxyFactory> mapperRegistry = new HashMap<>();
        //通过这个表达方法名称,与映射关系
        Map<String, String> mappedStatements = new HashMap<>();
        mappedStatements.put("com.automannn.practice.mybatis.dynamicProxy.DynamicProxyTest$MyInterface.sayHello","  映射sayHello执行的业务逻辑");
        mapperRegistry.put(MyInterface.class,new MapperProxyFactory(MyInterface.class,mappedStatements));

        //使用
       MyInterface myInterface= (MyInterface) mapperRegistry.get(MyInterface.class).newInstance();
       myInterface.sayHello("automannn");
    }

    public static interface MyInterface {
        String sayHello(String target);
    }

    public static class MapperProxyFactory<T> {
        private final Class<T> mappedInterface;
        private final Map<Method, MethodInvoker> methodCache = new ConcurrentHashMap<>();
        private final Map<String, String> mappedStatements;

        public MapperProxyFactory(Class<T> mappedInterface, Map<String, String> mappedStatements) {
            this.mappedInterface = mappedInterface;
            this.mappedStatements = mappedStatements;
        }

        public T newInstance() {
            Class[] interfaces = new Class[]{this.mappedInterface};
            return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), interfaces, new MapperProxy(methodCache, mappedInterface, mappedStatements));
        }

    }

    public static class MapperProxy implements InvocationHandler {
        private final Map<Method, MethodInvoker> methodCache;

        private final Class mappedInterface;

        private final Map<String, String> mappedStatements;

        public MapperProxy(Map<Method, MethodInvoker> methodCache, Class mappedInterface, Map<String, String> mappedStatements) {
            this.methodCache = methodCache;
            this.mappedInterface = mappedInterface;
            this.mappedStatements = mappedStatements;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return cachedInvoker(method).invoke(proxy,method,args);
        }

        public MethodInvoker cachedInvoker(Method method) {
            MethodInvoker invoker = methodCache.get(method);
            if (invoker != null) {
                return invoker;
            }

            return methodCache.computeIfAbsent(method, method1 -> {
                return new DefaultMethodInvoker(new MappedMethod(method, mappedInterface, mappedStatements,new BusinessExecutor()));
            });
        }
    }

    public static interface MethodInvoker {
        Object invoke(Object proxy, Method method, Object[] args);
    }

    public static class DefaultMethodInvoker implements MethodInvoker {
        private final MappedMethod mappedMethod;

        public DefaultMethodInvoker(MappedMethod mappedMethod) {
            this.mappedMethod = mappedMethod;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) {
            return mappedMethod.execute(args);
        }
    }

    public static class MappedMethod {
        private final Method method;
        private final Class mappedInterface;
        //根据 method方法,获取对应的对象,可以是 文本如sql语句,可以是某个抽象接口的子类,以及其它能够遵循某种规则的对象
        private final Map<String, String> mappedRelation;
        //业务执行器
        private final BusinessExecutor businessExecutor;

        public MappedMethod(Method method, Class mappedInterface, Map<String, String> mappedRelation,BusinessExecutor businessExecutor) {
            this.method = method;
            this.mappedInterface = mappedInterface;
            this.mappedRelation = mappedRelation;
            this.businessExecutor = businessExecutor;
        }

        public Object execute(Object[] args) {
            String methodName="";
            methodName+=method.getDeclaringClass().getName();
            methodName+=".";
            methodName+=method.getName();
            String target = mappedRelation.get(methodName);
            if(!StringUtils.isEmpty(target)){
                target+="参数为:";
                target+= Arrays.asList(args).toString();
                return businessExecutor.executor(target);
            }
            return null;
        }
    }

    public static class BusinessExecutor{

        Object executor(Object target){
            if (target instanceof String){
                System.out.println("业务执行器,执行对象:"+target);
            }
            return "success";
        }
    }
}

# 查询参数解析---ParamNameResolver

  • 参数解析器:
public class ParamNameResolver {

    /*xxx: 该方法接收的参数 是 用户传入的 实参列表,*/
    public Object getNamedParams(Object[] args) {
        final int paramCount = names.size();
        /*xxx: 无参数,返回 null*/
        if (args == null || paramCount == 0) {
          return null;
          /*xxx: 未使用 @Param 注解,且只有 一个参数时*/
        } else if (!hasParamAnnotation && paramCount == 1) {
          Object value = args[names.firstKey()];
          /*xxx: 对实参为 复合参数 的进行包装*/
          return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
        } else {
          /*xxx: 这个map 记录 参数名称 与 实参之间的 对应关系*/
          /*xxx: 如果 向 ParamMap添加已经存在的 key,会报错*/
          /*xxx: 即可以通过两种方式获取参数,param+索引,或者 实际的索引获取*/
          final Map<String, Object> param = new ParamMap<>();
          int i = 0;
          for (Map.Entry<Integer, String> entry : names.entrySet()) {
            /*xxx: 将参数名  与 实参对应关系 记录到  param中*/
            param.put(entry.getValue(), args[entry.getKey()]);
            // add generic param names (param1, param2, ...)
            /*xxx: 为参数创建 param+索引  格式的  默认参数名称,并添加到param集合中 */
            final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
            // ensure not to overwrite parameter named with @Param
            /*xxx: 如果 @Param 指定的参数名称 就是  "param+索引" 格式的,则不需要再添加*/
            if (!names.containsValue(genericParamName)) {
              param.put(genericParamName, args[entry.getKey()]);
            }
            i++;
          }
          return param;
        }
    }

    //省略其它抽象...
}
  • 解析的结果说明:
    • 有@Param参数,返回的结果为 Map结构,map的键为:arg0,arg1,.... 以及 param1,param2 (有@Param的则用名称代替)
    • 无@Param参数:
      • 单参数:
        • 非集合: 直接返回原对象
        • 集合: 返回map,map的键为: arg0, 或 list、 collection
      • 多参数: 返回map,map的键为: arg0,arg1,.... 以及 param1,param2... (二者是对应的)

# 动态语句执行过程---ParameterHandler

  • 抽象结构
public interface ParameterHandler {

  Object getParameterObject();

  /*xxx: 该方法负责调用 PreparedStatement.set*() 方法为SQL 语句 绑定实参*/
  void setParameters(PreparedStatement ps) throws SQLException;
}

public class DefaultParameterHandler implements ParameterHandler {

    /*xxx: 既可以从动态上下文中,获取实际的参数值  也可以从实际的对象中,获取参数值*/
    public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        /*xxx: 取出 sql 中的 参数映射列表*/
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
          /*xxx: 检测 parameterMapings 集合 是否为空*/
          for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            /*xxx: 过滤掉 存储过程的输出参数*/
            if (parameterMapping.getMode() != ParameterMode.OUT) {
              /*xxx: 记录绑定的实参 */
              Object value;
              /*xxx: 获取参数名称*/
              String propertyName = parameterMapping.getProperty();
              if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
                /*xxx: 获取对应的参数值*/
                value = boundSql.getAdditionalParameter(propertyName);
              } else if (parameterObject == null) {
                /*xxx: 整个实参为空*/
                value = null;
              } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                /*xxx: 实参可以通过 TypeHandler转换成 JdbcType*/
                value = parameterObject;
              } else {
                /*xxx: 获取对象中相应的属性值  或 查找Map对象中的值*/
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                value = metaObject.getValue(propertyName);
              }
              /*xxx: 获取 ParameterMapping 中设置的 TypeHandler 对象*/
              TypeHandler typeHandler = parameterMapping.getTypeHandler();
              JdbcType jdbcType = parameterMapping.getJdbcType();
              if (value == null && jdbcType == null) {
                jdbcType = configuration.getJdbcTypeForNull();
              }
              try {
                /*xxx: 调用 PreparedStatement.set*() 方法*/
                typeHandler.setParameter(ps, i + 1, value, jdbcType);
              } catch (TypeException | SQLException e) {
                throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
              }
            }
          }
        }
      }
    //省略其它抽象...
}

# 查询参数设值过程---typeHandler

  • 抽象结构
/*xxx: 类型转换器 */
/*xxx: 一般情况下, TypeHandler用于完成 单个参数  以及 单个列值 的类型转换*/
public interface TypeHandler<T> {

    /*xxx:负责将数据 由  JdbcType类型 转换为  java 类型*/
    void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

    //省略其它抽象...
}

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
    
    @Override
    /*xxx: 对于非空数据的处理,都交给了子类实现*/
    public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        if (parameter == null) {
          if (jdbcType == null) {
            throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
          }
          try {
            ps.setNull(i, jdbcType.TYPE_CODE);
          } catch (SQLException e) {
            throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
                  + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
                  + "Cause: " + e, e);
          }
        } else {
          try {
            setNonNullParameter(ps, i, parameter, jdbcType);
          } catch (Exception e) {
            throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
                  + "Try setting a different JdbcType for this parameter or a different configuration property. "
                  + "Cause: " + e, e);
          }
        }
    }
    
    public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
    //省略其它抽象...
}

public class StringTypeHandler extends BaseTypeHandler<String> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
          throws SQLException {
        ps.setString(i, parameter);
    }

    //省略其它抽象...
}

# 数据库语句的执行---statementHandler

  • 抽象结构
/*xxx: 核心接口,定义了数据库操作的基本方法*/
/*xxx: 在mybatis起到了承上启下的作用 */
public interface Executor {
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
    
    /*xxx: 执行 update,inser,delete三种类型的SQL语句*/
    int update(MappedStatement ms, Object parameter) throws SQLException;
}

/*xxx: 主要提供了 缓存管理 和  事务管理 的基本功能*/
public abstract class BaseExecutor implements Executor {
    
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        /*xxx: 将参数 封装到 BoundSql 对象*/
        BoundSql boundSql = ms.getBoundSql(parameter);
        /*xxx: 创建 CacheKey 对象*/
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        /*xxx: 调用 重载,进行后续处理*/
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        /*xxx: 检测当前的 Executor 是否已经关闭 */
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
          /*xxx: 非嵌套查询,且 <select> 节点配置的  flushCache 属性为 true ,则会清空一级缓存*/
          /*xxx: flushCache 配置项 是影响 一级缓存 结果对象存活时长的一个方面*/
          clearLocalCache();
        }
        List<E> list;
        try {
          /*xxx: 增加查询层数*/
          queryStack++;
          list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
          if (list != null) {
            /*xxx: 针对存储过程调用的处理, 在一级缓存命中时,获取缓存中保存的 输出类型参数, 并设置到用户传入的实参中*/
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
          } else {
            /*xxx: 其中会调用 doQuery完成数据库查询,并得到映射后的处理对象*/
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
          }
        } finally {
          /*xxx: 当前查询完成,查询层数减少*/
          queryStack--;
        }
        if (queryStack == 0) {
          /*xxx: 延迟加载的相关内容*/
          for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
          }
          // issue #601
          /*xxx: 加载完成后,清空 deferredLoads 集合*/
          deferredLoads.clear();
          if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            /*xxx: 根据localCacheScope 配置 决定是否清空一级缓存*/
            /*xxx: localCacheScope配置 是 影响一级缓存中 结果对象 存活时长的 第二个方面*/
            clearLocalCache();
          }
        }
        return list;
    }

    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        /*xxx: 在缓存中添加占位符*/
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
          /*xxx: 调用 doQuery方法,完成 数据库查询操作,并返回结果对象*/
          list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
          /*xxx: 删除占位符*/
          localCache.removeObject(key);
        }
        /*xxx: 将真正的结果对象添加到一级缓存中*/
        localCache.putObject(key, list);
        /*xxx: 是否为存储过程调用*/
        if (ms.getStatementType() == StatementType.CALLABLE) {
          /*xxx: 缓存输出类型的参数*/
          localOutputParameterCache.putObject(key, parameter);
        }
        return list;
    }

    protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
          throws SQLException;

}

public class SimpleExecutor extends BaseExecutor {
    @Override
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
          /*xxx: 获取配置对象*/
          Configuration configuration = ms.getConfiguration();
          /*xxx: 创建StatementHandler对象,实际返回的是 RoutingStatementHandler对象*/
          StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
          /*xxx: 完成Statement的创建 和初始化*/
          stmt = prepareStatement(handler, ms.getStatementLog());
          /*xxx: 调用 StatementHandler.query 方法,执行sql语句,并通过 ResultSetHandler完成结果集的映射*/
          return handler.query(stmt, resultHandler);
        } finally {
          /*xxx: 关闭 Statement对象*/
          closeStatement(stmt);
        }
    }
    //省略其它抽象...
}

/*xxx: 是MyBatis的核心接口之一,它完成了 MyBatis中最核心的工作,也是 Executor 接口实现的基础*/
public interface StatementHandler {
    /*xxx: 执行 update/insert/delete 语句*/
    int update(Statement statement)
          throws SQLException;
    
    /*xxx: 执行select语句*/
    <E> List<E> query(Statement statement, ResultHandler resultHandler)
          throws SQLException;

    //省略其它抽象...
}

public class SimpleStatementHandler extends BaseStatementHandler {

    @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        /*xxx: 获取sql 语句*/
        String sql = boundSql.getSql();
        /*xxx: 调用 Statement.execute 方法执行sql 语句*/
        statement.execute(sql);
        /*xxx: 映射结果集*/
        return resultSetHandler.handleResultSets(statement);
    }
    
    //省略其它抽象...
}

# 结果处理器---resultHandler

  • 作用: 决定查询结果,以何种方式返回,通过返回值,或者设值的方式等。用于辅助 resultSetHandler
  • 抽象结构:
public interface ResultHandler<T> {
  void handleResult(ResultContext<? extends T> resultContext);
}

//默认情况,通过返回值的方式返回结果
public class DefaultResultHandler implements ResultHandler<Object> {

    private final List<Object> list;    

    @Override
    public void handleResult(ResultContext<?> context) {
        list.add(context.getResultObject());
    }

    //省略其它抽象...
}

# 结果集处理---ResultSetHandler

  • 抽象结构(查询)
/*xxx: 结果集处理器*/
public interface ResultSetHandler {
    /*xxx: 处理结果集,生成相应的结果对象集合*/
    <E> List<E> handleResultSets(Statement stmt) throws SQLException;
    //省略其它抽象...
}

/*xxx: mybatis提供的唯一实现*/
public class DefaultResultSetHandler implements ResultSetHandler {
    @Override
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
    
        /*xxx: 该集合 用于 保存映射结果集 得到的结果对象*/
        final List<Object> multipleResults = new ArrayList<>();
    
        int resultSetCount = 0;
        /*xxx: 获取第一个 ResultSet对象*/
        ResultSetWrapper rsw = getFirstResultSet(stmt);
    
        /*xxx: 获取ResultMap集合*/
        /*xxx: 如果 sql 节点,能够产生多个 ResultSet,则 可以在 SQL节点的 resultMap属性中 配置多个 <resultMap> 节点的id,
        *     它们之间通过 "," 分隔,  可以实现 对多个结果集 的映射*/
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        validateResultMapsCount(rsw, resultMapCount);
        /*xxx: 如果结果集不为空,则 resultMaps集合不能为空,否则报错*/
        while (rsw != null && resultMapCount > resultSetCount) {
          /*xxx: 获取 该结果集对应的  ResultMap对象*/
          ResultMap resultMap = resultMaps.get(resultSetCount);
          /*xxx: 根据 ResultMap 中定义的映射规则,对ResultSet进行映射,并将映射的结果对象,添加到 multipleResults集合中保存*/
          handleResultSet(rsw, resultMap, multipleResults, null);
          /*xxx: 获取下一个结果集*/
          rsw = getNextResultSet(stmt);
          /*xxx: 清空 nestedResultObjects集合*/
          cleanUpAfterHandlingResultSet();
          /*xxx: 递增 resultSetCount*/
          resultSetCount++;
        }
    
        /*xxx: 获取 resultSets是修改呢, 仅对 多结果集的情况适用*/
        String[] resultSets = mappedStatement.getResultSets();
        if (resultSets != null) {
          while (rsw != null && resultSetCount < resultSets.length) {
            /*xxx: 根据 resultSet的名称,获取未处理的 ResultMapping*/
            ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
            if (parentMapping != null) {
              String nestedResultMapId = parentMapping.getNestedResultMapId();
              ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
              /*xxx: 根据 ResultMap 对象 映射结果集*/
              handleResultSet(rsw, resultMap, null, parentMapping);
            }
            /*xxx: 获取下一个结果集*/
            rsw = getNextResultSet(stmt);
            /*xxx: 清空 nestedResultObjects集合*/
            cleanUpAfterHandlingResultSet();
            /*xxx: 递增 resultSetCount*/
            resultSetCount++;
          }
        }
    
        return collapseSingleResultList(multipleResults);
    }

    /*xxx: 处理单结果集*/
    private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
        try {
          if (parentMapping != null) {
            /*xxx: 处理多结果集中的嵌套映射*/
            handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
          } else {
            if (resultHandler == null) {
              /*xxx: 如果用户未指定处理映射结果对象的 ResultHandler对象,则使用 DefaultResultHandler作为默认的ResultHandler对象*/
              DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
              /*xxx: 对 ResultSet进行映射,并将映射得到的结果对象 添加到 DefaultResultHandler对象中暂存*/
              handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
              /*xxx: 将 DefulatResultHanlder中保存到结果对象,添加到 multipleResults 集合中*/
              multipleResults.add(defaultResultHandler.getResultList());
            } else {
              /*xxx: 使用 用户指定的 ResultHandler 对象 处理结果对象*/
              handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
            }
          }
        } finally {
          // issue #228 (close resultsets)
          /*xxx: 调用 close() 方法关闭结果集*/
          closeResultSet(rsw.getResultSet());
        }
    }

    /*xxx: 映射结果集的核心代码*/
    public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
        /*xxx: 针对存在嵌套 ResultMap的情况*/
        if (resultMap.hasNestedResultMaps()) {
          ensureNoRowBounds();
          checkResultHandler();
          handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
        } else {
          /*xxx: 针对不含嵌套映射的简单映射处理*/
          handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
        }
    }

    private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
          throws SQLException {
        DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
        ResultSet resultSet = rsw.getResultSet();
        /*xxx: 调用该方法,根据 RowBounds中的  offset 值 定位到 指定的记录行*/
        skipRows(resultSet, rowBounds);
        /*xxx: 检测是否还有需要映射记录*/
        while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
          /*xxx: 确定映射使用的ResultMap对象*/
          ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
          /*xxx: 对ResultSet的一行记录进行映射*/
          Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
          /*xxx: 调用 storeObject 方法  保存映射得到的 结果对象*/
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
        }
    }

    private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
        /*xxx: 与延迟加载有关*/
        final ResultLoaderMap lazyLoader = new ResultLoaderMap();
        /*xxx: 创建 映射后的结果对象*/
        Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
        if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
          final MetaObject metaObject = configuration.newMetaObject(rowValue);
          boolean foundValues = this.useConstructorMappings;
          /*xxx: 检测是否 开启了 自动映射功能*/
          if (shouldApplyAutomaticMappings(resultMap, false)) {
            /*xxx: 自动映射  ResultMap 中 未 名前映射的列*/
            foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
          }
          /*xxx: 映射 ResultMap中明确映射列*/
          /*xxx: 自此,完成对 resultSet某一行的解析*/
          foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
          foundValues = lazyLoader.size() > 0 || foundValues;
          rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
        }
        return rowValue;
    }

    private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
          throws SQLException {
        /*xxx: 获取 该ResultMap 中,明确需要 进行映射的列名集合*/
        final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
        boolean foundValues = false;
        /*xxx: 获取 ResultMap.propertyResultMappings 集合*/
        final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
        for (ResultMapping propertyMapping : propertyMappings) {
          /*xxx: 处理列前缀*/
          String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
          if (propertyMapping.getNestedResultMapId() != null) {
            // the user added a column attribute to a nested result map, ignore it
            /*xxx: 该属性需要使用 一个嵌套 ResultMap进行映射,忽略columns属性*/
            column = null;
          }
          /*xxx: 三种情况*/
          /*xxx: column 是  {prop1=col1,prop2=col2},一般与嵌套查询配合使用*/
          /*xxx: 基本类型的属性映射*/
          /*xxx: 多结果集的场景处理*/
          if (propertyMapping.isCompositeResult()
              || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
              || propertyMapping.getResultSet() != null) {
            /*xxx: 通过 getPropertyMappingValue 方法完成映射,并得到属性值*/
            Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
            // issue #541 make property optional
            /*xxx: 获取属性名称*/
            final String property = propertyMapping.getProperty();
            if (property == null) {
              continue;
              /*xxx:占位符对象,用于延迟加载*/
            } else if (value == DEFERRED) {
              foundValues = true;
              continue;
            }
            if (value != null) {
              foundValues = true;
            }
            if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
              // gcode issue #377, call setter on nulls (value is not 'found')
              /*xxx: 设置属性值*/
              metaObject.setValue(property, value);
            }
          }
        }
        return foundValues;
    }

    //省略其它抽象...
}
  • 抽象结构(更新)
public interface Executor {

  /*xxx: 执行 update,inser,delete三种类型的SQL语句*/
  int update(MappedStatement ms, Object parameter) throws SQLException;

}

/*xxx: 主要提供了 缓存管理 和  事务管理 的基本功能*/
public abstract class BaseExecutor implements Executor {
    @Override
    public int update(MappedStatement ms, Object parameter) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
        /*xxx: 判断当前 Executor 是否已经关闭 */
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        /*xxx: 该方法  会 调用  localCache ,localOutputParameterCache两个缓存的 clear 方法,完成清理工作*/
        /*xxx: 这是 影响一级缓存中,数据存活时长的 第三个方面*/
        clearLocalCache();
        /*xxx: 调用 doUpdate 方法 执行sql语句*/
        return doUpdate(ms, parameter);
    }

    protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

    //省略其它抽象...
}

public class SimpleExecutor extends BaseExecutor {
    @Override
    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;
        try {
          Configuration configuration = ms.getConfiguration();
          StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
          stmt = prepareStatement(handler, ms.getStatementLog());
          return handler.update(stmt);
        } finally {
          closeStatement(stmt);
        }
    }

    //省略其它抽象...
}

/*xxx: 是MyBatis的核心接口之一,它完成了 MyBatis中最核心的工作,也是 Executor 接口实现的基础*/
public interface StatementHandler {
    
    /*xxx: 执行 update/insert/delete 语句*/
    int update(Statement statement)
          throws SQLException;
}

public class SimpleStatementHandler extends BaseStatementHandler {
    @Override
    public int update(Statement statement) throws SQLException {
        /*xxx: 获取 sql 语句*/
        String sql = boundSql.getSql();
        /*xxx: 获取用户传入的实参*/
        Object parameterObject = boundSql.getParameterObject();
        /*xxx: 获取配置的 KeyGenerator 对象*/
        KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
        int rows;
        if (keyGenerator instanceof Jdbc3KeyGenerator) {
          /*xxx: 执行sql语句*/
          statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
          /*xxx: 获取受影响的行数*/
          rows = statement.getUpdateCount();
          /*xxx: 将数据库生产的主键,添加到 parameterObject中*/
          keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
        } else if (keyGenerator instanceof SelectKeyGenerator) {
          statement.execute(sql);
          rows = statement.getUpdateCount();
          /*xxx: 执行 <select>节点中 配置的 SQL 语句 获取数据库生成的主键,并添加到 parameterObject中*/
          keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
        } else {
          statement.execute(sql);
          rows = statement.getUpdateCount();
        }
        return rows;
    }

    //省略其它抽象...
}

# spring环境下mybatis架构

# spring环境下mybatis使用流程

class Config{
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(){
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        Resource resource = new ClassPathResource("org/mybatis/example/PersonMapper.xml");
        sqlSessionFactoryBean.setMapperLocations(resource);
        
        DataSource dataSource;//xxx: 省略构造流程...
        //xxx: 省略其他抽象...
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }
}
//xxx: 注意,此步不配置亦可。  不配置时,无法通过 类结构 的方式进行查询
class Config{
    @Bean
    /*xxx: spring通过工厂类实现的自动注入: MapperFactoryBean,最终依然是通过 sqlSession获取的对象*/
    public MapperScannerConfigurer mybatisScanner(){
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setBasePackage("com.automannn.practice.mybatis.dao");
        return mapperScannerConfigurer;
    }
}
class Config{
    @Bean
    /*xxx: 获取模板操作类,可直接进行 datasource 的crud操作 */
    public SqlSessionTemplate sqlSessionTestBySql(SqlSessionFactoryBean factoryBean){
        try {
            return new SqlSessionTemplate(factoryBean.getObject());
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
class Config{
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringMybatisTest.class);

        //xxx: 第一种,直接通过sql查询
        SqlSession sqlSession= (SqlSession) applicationContext.getBean("sqlSessionTestBySql");
        List list =  sqlSession.selectList("com.automannn.practice.mybatis.dao.PersonDao.selectList");
        System.out.println(list);

        System.out.println("========通过接口查询============");
        //xxx: 第二种,获取接口查询
        System.out.println(applicationContext.getBean(PersonDao.class));
        PersonDao personDao= sqlSession.getMapper(PersonDao.class);
        System.out.println(personDao.selectList());
    }
}

# spring环境下mybatis原理

构建sqlSessionFactory,有三种策略,要么使用现成的配置, 要么 从mybatis-config的配置文件进行解析,要么 实例化一个空的配置

  • 配置原理
/*xxx: 构建sqlSessionFactory,有三种策略,要么使用现成的配置,  要么 从mybatis-config的配置文件进行解析,要么 实例化一个空的配置 */
public class SqlSessionFactoryBean
    implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

    private SqlSessionFactory sqlSessionFactory;
    
    @Override
    public SqlSessionFactory getObject() throws Exception {
        if (this.sqlSessionFactory == null) {
            afterPropertiesSet();
        }

        return this.sqlSessionFactory;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.sqlSessionFactory = buildSqlSessionFactory();
    }

    /*xxx: 构建sqlSessionFactory*/
    protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
        //xxx: 省略其他抽象...
        /*xxx: 将 配置文件进行解析,并保存起来*/
        return this.sqlSessionFactoryBuilder.build(targetConfiguration);
    }
    
}
  • 使用原理
/*xxx: bean工厂后置处理器*/
public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {

    /*xxx: 指定要自动扫描接口的基础包,实现接口*/
    private String basePackage;

    @Override
    /*xxx: 工厂后置处理*/
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        /*xxx: 扫描器 */
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

        /*xxx: 根据配置项,过滤不需要扫描的接口*/
        scanner.registerFilters();
        
        //xxx: 省略其他抽象...
        scanner.scan(
                StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
    }
}
/*xxx: 扫描器 */
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {

    private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
    
    @Override
    /*xxx: 扫描 */
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

        /*xxx: 对扫描的bean进行处理*/
        processBeanDefinitions(beanDefinitions);

        return beanDefinitions;
    }

    /*xxx: 处理扫描到的 bean定义信息*/
    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        //xxx: 省略其他抽象...
        
        for (BeanDefinitionHolder holder : beanDefinitions) {
            //xxx: 设置mybatis的代理对象实现
            definition.setBeanClass(this.mapperFactoryBeanClass);

            if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
                //xxx: 这是直接通过 类结构 获取实际类的 关键...
                definition.getPropertyValues().add("sqlSessionFactory",
                        new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
                explicitFactoryUsed = true;
            } else if (this.sqlSessionFactory != null) {
                definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
                explicitFactoryUsed = true;
            }
        }
    }
    
    

    @Override
    /*xxx: 当扫描到的为接口,才进行处理 */
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
    }

    /*xxx: 配置扫描正确的接口,能够搜寻某个特定接口的子类接口,也可以搜寻特定的注解类  */
    public void registerFilters() {
        //xxx: 省略其它抽象...
        if (acceptAllInterfaces) {
            /*xxx: 默认情况下,扫描所有的类*/
            addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
        }
    }
}

# springBoot环境下mybatis架构

# springBoot环境下mybatis的使用流程

//xxx: 由于当前环境,存在mybatis-plus依赖,个别bean存在不兼容情况,暂时排除
@SpringBootApplication(exclude = {MybatisPlusAutoConfiguration.class})
@MapperScan("com.automannn.practice.mybatis.dao")
public class SpringBootMybatisTest {

    public static void main(String[] args) {
      ConfigurableApplicationContext applicationContext= SpringApplication.run(SpringBootMybatisTest.class);

     PersonDao personDao= applicationContext.getBean(PersonDao.class);
     personDao.selectList();
    }
}
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/zhenhe?characterEncoding=UTF-8&useUnicode=true&useSSL=false&serverTimezone=GMT%2b8
    username: root
    password: chenkaihai
mybatis:
  configuration:
    mapUnderscoreToCamelCase: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapperLocations:
    - classpath*:org/mybatis/example/mapper/*.xml

# springBoot环境下mybatis的原理

  • 值配置原理
@EnableConfigurationProperties(MybatisProperties.class)
public class MybatisAutoConfiguration implements InitializingBean {
}
@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {

    public static final String MYBATIS_PREFIX = "mybatis";
    
    //xxx: xml映射文件配置
    private String[] mapperLocations;

    @NestedConfigurationProperty
    /*xxx: 这个属性,可以对 spring的原生特性,进行配置 */
    private Configuration configuration;
    
    //xxx: 省略其它配置
}
  • 配置原理
public class MybatisAutoConfiguration implements InitializingBean {
    
    //xxx: 很多的特性,可以直接通过注入bean的方式进行改写...
    public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,
                                    ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider,
                                    ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,
                                    ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
        this.properties = properties;
        this.interceptors = interceptorsProvider.getIfAvailable();
        this.typeHandlers = typeHandlersProvider.getIfAvailable();
        this.languageDrivers = languageDriversProvider.getIfAvailable();
        this.resourceLoader = resourceLoader;
        this.databaseIdProvider = databaseIdProvider.getIfAvailable();
        this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
    }

    @Bean
    @ConditionalOnMissingBean
    /*xxx: 自动配置 sqlSessionFactory类,依赖数据源 */
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        
        //xxx: 省略其他抽象...
        
        /*xxx: 从工厂类中,获取对象 */
        return factory.getObject();
    }
}
  • 使用原理
public class MybatisAutoConfiguration implements InitializingBean {
    
    @org.springframework.context.annotation.Configuration
    @Import(AutoConfiguredMapperScannerRegistrar.class)
    //xxx: springBoot的mybatis自动装载,是有条件的, 当自身重写过配置,则默认配置不生效
    @ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
    public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {

        @Override
        public void afterPropertiesSet() {
            logger.debug(
                    "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
        }
    }
}
/*xxx: 默认扫描springBoot覆盖的包下的接口,如果需要扫描更多的mapper接口,可以使用 @MapperScan注解,这将使扫描到的mapper接口开箱即用*/
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        List<String> packages = AutoConfigurationPackages.get(this.beanFactory);

        //xxx: 使用 自动配置的包路径 作为 basePackages,也就是  @AutoConfiguration -> @SpringBoot所在的包 
        List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
        if (logger.isDebugEnabled()) {
            packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
        }

        //xxx: 省略其他抽象...
        
        /*xxx: 扫描Mapper接口*/
        builder.addPropertyValue("annotationClass", Mapper.class);

        //xxx: 扫描路径
        builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));

        /*xxx: 注册工厂后置处理器 */
        registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
    }
}

# spring环境与springBoot环境注册代理接口的注意事项

  1. 如果springBoot环境,(需要结合 @Configuration使之生效)使用了@MapperScan 注解,则使用的 spring环境的扫描配置,只会扫描指定包,及其下的接口
  2. 如果没有指定 @MapperScan注解,并且没有自定义MapperScannerConfigurer, 则会使用springBoot环境的扫描器,其扫描规则是 springBoot配置生效的所有包路径下的,带有@Mapper的接口

# 纯净mybatis-plus架构

mybatis-plus并无对mybatis的基础变动,是对其用法的扩展,装饰,增强。因此官方只提供了与spring环境结合的案例 理论上,通过配置 configuration.xml也可实现纯净模式

# spring环境下mybatis-plus架构

# spring环境下mybatis-plus使用示例

//xxx: mybatis-plus需要在 原有接口的基础上,继承接口类
public interface PersonDao extends BaseMapper<PersonEntity> {
    
}
class Config{
    @Bean
    public MybatisSqlSessionFactoryBean sqlSessionFactoryBean(){
        
        //xxx: 与 mybatis不同的是  改写了 factory: com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean
        //xxx: 此外,它与 mybatis 的factoryBean并不存在父子关系   
        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        
        //xxx: 省略其它抽象...
        PooledDataSource dataSource ;
        sqlSessionFactoryBean.setDataSource(dataSource);

        sqlSessionFactoryBean.setPlugins(new PaginationInterceptor());

        Resource resource = new ClassPathResource("org/mybatis/example/mapper/PersonMapper.xml");
        sqlSessionFactoryBean.setMapperLocations(resource);
        
        return sqlSessionFactoryBean;
    }
}
//xxx: 此处跟 mybatis 环境一致 
class Config{
    @Bean
    /*xxx: spring通过工厂类实现的自动注入: MapperFactoryBean,最终依然是通过 sqlSession获取的对象*/
    public MapperScannerConfigurer mybatisScanner(){
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setBasePackage("com.automannn.practice.mybatis.dao");
        return mapperScannerConfigurer;
    }

    @Bean
    public SqlSessionTemplate sqlSessionTestBySql(SqlSessionFactory sqlSessionFactory){
        try {
            return new SqlSessionTemplate(sqlSessionFactory);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
//xxx: 此处跟 mybatis环境一致 
class  Main{
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MybatisPlusSpringTest.class);
        
        //第一种,直接通过sql查询
        SqlSession sqlSession= (SqlSession) applicationContext.getBean("sqlSessionTestBySql");
        List list =  sqlSession.selectList("com.automannn.practice.mybatis.dao.PersonDao.selectList");
        System.out.println(list);

        System.out.println("========通过接口查询============");
        //第二种,获取接口查询
        System.out.println(applicationContext.getBean(PersonDao.class));
        PersonDao personDao= sqlSession.getMapper(PersonDao.class);
        System.out.println(personDao.selectList());
    }
}

# spring环境下mybatis-plus原理

/*
* xxx: 拷贝类
*  xxx:  修改方法 buildSqlSessionFactory() 加载自定义: MybatisXmlConfigBuilder 
*   xxx: 移除 sqlSessionFactoryBuilder, 强制使用 MybatisSqlSessionFactoryBuilder
*    xxx: 移除 environment属性, 强制使用 MybatisSqlSessionFactoryBean.class.getSimpleName
* */
public class MybatisSqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    /*xxx: 自定义枚举包 */
    @Setter
    private String typeEnumsPackage;

    /*xxx: 自定义全局配置*/
    @Setter
    private GlobalConfig globalConfig;

    protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
        //xxx: 省略其他抽象...
        
        /*xxx: 无配置启动时所必须的*/
        this.globalConfig = Optional.ofNullable(this.globalConfig).orElseGet(GlobalConfigUtils::defaults);
        this.globalConfig.setDbConfig(Optional.ofNullable(this.globalConfig.getDbConfig()).orElseGet(GlobalConfig.DbConfig::new));
        
        //xxx: 省略其他抽象... 

        final SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(targetConfiguration);

        //xxx: 工具类,可在外部快捷获取 sqlSession工厂实例 
        SqlHelper.FACTORY = sqlSessionFactory;
        
        //xxx: 打印骚东西 Banner
        if (globalConfig.isBanner()) {
            System.out.println(" _ _   |_  _ _|_. ___ _ |    _ ");
            System.out.println("| | |\\/|_)(_| | |_\\  |_)||_|_\\ ");
            System.out.println("     /               |         ");
            System.out.println("                        " + MybatisPlusVersion.getVersion() + " ");
        }

        return sqlSessionFactory;
    }
}

# MybatisConfiguration的改动

public class MybatisConfiguration extends Configuration {
    /*xxx: mapper注册器 */
    protected final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);

    @Setter
    @Getter
    /*xxx: 自定义配置 */
    private GlobalConfig globalConfig = GlobalConfigUtils.defaults();

    @Override
    /*xxx: 使用自定义的 mapperRegistry*/
    public MapperRegistry getMapperRegistry() {
        return mybatisMapperRegistry;
    }
}
public class GlobalConfig implements Serializable {
    /**
     * 是否开启 LOGO
     */
    private boolean banner = true;

    /**
     * 数据库相关配置
     */
    private DbConfig dbConfig;

    /**
     * SQL注入器
     */
    private ISqlInjector sqlInjector = new DefaultSqlInjector();
    
    /**
     * Mapper父类
     */
    private Class<?> superMapperClass = Mapper.class;

    @Data
    public static class DbConfig {
        /**
         * 表名前缀
         */
        private String tablePrefix;

        /**
         * 逻辑删除全局字段 (默认无 设置会自动扫描实体字段)
         */
        private String logicDeleteField;
        
        //xxx: 省略其他抽象...
    }
}
public class MybatisMapperRegistry extends MapperRegistry {
    @Override
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (hasMapper(type)) {
                // xxx: 如果之前注入 直接返回
                return;
                // xxx: 这里就不抛异常了
//                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
                //xxx: 省略其他抽象...

                /*xxx: 每次调用该接口,都会为其添加附加操作 */
                MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
                parser.parse();
                //xxx: 省略其他抽象... 
            }
        }
    }
}

# MybatisXmlConfigBuilder的功能

  • 主要为了与 MybatisConfiguration适配,核心的解析逻辑由mybatis完成

# MybatisSqlSessionFactoryBuilder的功能

  • 主要功能也是与 MybatisConfiguration适配

# MybatisPlus实现自动添加CRUD流程

public class MybatisMapperRegistry extends MapperRegistry {
    @Override
    public <T> void addMapper(Class<T> type) {
        //xxx: 标志该接口已经加载过
        knownMappers.put(type, new MybatisMapperProxyFactory<>(type));

        /*xxx: 每次调用该接口,都会为其添加附加操作,mybatis原生会处理 @Select,@Update等自定义语句 */
        MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
        parser.parse();
        //xxx: 省略其他抽象...
    }
}
public class MybatisMapperAnnotationBuilder extends MapperAnnotationBuilder {
    //xxx: 省略其他抽象...
    @Override
    public void parse() {
        if (!configuration.isResourceLoaded(resource)) {
            //xxx: 常规解析,省略...

            // xxx:  注入 CURD 动态 SQL , 放在最后, because 可能会有人会用注解重写sql
            // xxx: 当前接口为 超类的子接口,才进行添加动态sql
            if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
                GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
            }
        }
    }
}
/*xxx: sql自动注入器*/
public interface ISqlInjector {
    /*xxx: 注入sql */
    void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass);
}

public abstract class AbstractSqlInjector implements ISqlInjector {
    
    /*xxx: 获取泛型模型 */
    protected Class<?> extractModelClass(Class<?> mapperClass) {
        Type[] types = mapperClass.getGenericInterfaces();
        ParameterizedType target = null;
        for (Type type : types) {
            if (type instanceof ParameterizedType) {
                Type[] typeArray = ((ParameterizedType) type).getActualTypeArguments();
                if (ArrayUtils.isNotEmpty(typeArray)) {
                    for (Type t : typeArray) {
                        if (t instanceof TypeVariable || t instanceof WildcardType) {
                            break;
                        } else {
                            target = (ParameterizedType) type;
                            break;
                        }
                    }
                }
                break;
            }
        }
        return target == null ? null : (Class<?>) target.getActualTypeArguments()[0];
    }

    @Override
    public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
        //xxx: entity实体类 
        Class<?> modelClass = extractModelClass(mapperClass);

        //xxx: mapper的方法列表, 包含超类的 crud方法
        List<AbstractMethod> methodList = this.getMethodList(mapperClass);

        TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
        //xxx:  循环注入自定义方法
        methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
    }

    /*xxx: 获取注入的方法*/
    public abstract List<AbstractMethod> getMethodList(Class<?> mapperClass);
}

/*xxx: 默认的sql注入器 */
public class DefaultSqlInjector extends AbstractSqlInjector {
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        return Stream.of(
                new Insert(),
                new Delete(),
                new DeleteByMap(),
                new DeleteById(),
                new DeleteBatchByIds(),
                new Update(),
                new UpdateById(),
                new SelectById(),
                new SelectBatchByIds(),
                new SelectByMap(),
                new SelectOne(),
                new SelectCount(),
                new SelectMaps(),
                new SelectMapsPage(),
                new SelectObjs(),
                new SelectList(),
                new SelectPage()
        ).collect(toList());
    }
}
//xxx: 需要注入的方法抽象
public abstract class AbstractMethod implements Constants {
    /*xxx: 核心注入方法 */
    public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        this.configuration = builderAssistant.getConfiguration();
        this.builderAssistant = builderAssistant;
        this.languageDriver = configuration.getDefaultScriptingLanguageInstance();
        /* 注入自定义方法 */
        injectMappedStatement(mapperClass, modelClass, tableInfo);
    }

    public abstract MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo);
}

/*xxx: 根据条件删除 */
public class Delete extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        String sql;
        SqlMethod sqlMethod = SqlMethod.LOGIC_DELETE;
        if (tableInfo.isLogicDelete()) {
            sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), sqlLogicSet(tableInfo),
                    sqlWhereEntityWrapper(true, tableInfo),
                    sqlComment());
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
            return addUpdateMappedStatement(mapperClass, modelClass, getMethod(sqlMethod), sqlSource);
        }else {
            sqlMethod = SqlMethod.DELETE;
            sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(),
                    sqlWhereEntityWrapper(true, tableInfo),
                    sqlComment());
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
            return this.addDeleteMappedStatement(mapperClass, getMethod(sqlMethod), sqlSource);
        }
    }
}

//xxx: sql语句模式 
public enum SqlMethod {
    INSERT_ONE("insert", "插入一条数据(选择字段插入)", "<script>\nINSERT INTO %s %s VALUES %s\n</script>"),

    UPDATE_BY_ID("updateById", "根据ID 选择修改数据", "<script>\nUPDATE %s %s WHERE %s=#{%s} %s\n</script>"),
    
    //xxx: 省略其他抽象...

    SELECT_PAGE("selectPage", "查询满足条件所有数据(并翻页)", "<script>\nSELECT %s FROM %s %s %s\n</script>");

    private final String method;
    private final String desc;
    private final String sql;

    SqlMethod(String method, String desc, String sql) {
        this.method = method;
        this.desc = desc;
        this.sql = sql;
    }
}
//xxx: 需要注入的方法抽象
public abstract class AbstractMethod implements Constants {
    /*xxx: 添加MappedStatement 到 Mybatis 容器 */
    protected MappedStatement addMappedStatement(Class<?> mapperClass, String id, SqlSource sqlSource,
                                                 SqlCommandType sqlCommandType, Class<?> parameterType,
                                                 String resultMap, Class<?> resultType, KeyGenerator keyGenerator,
                                                 String keyProperty, String keyColumn) {
        //xxx: 如果进行了重写,则不再注入 
        String statementName = mapperClass.getName() + DOT + id;
        if (hasMappedStatement(statementName)) {
            logger.warn(LEFT_SQ_BRACKET + statementName + "] Has been loaded by XML or SqlProvider or Mybatis's Annotation, so ignoring this injection for [" + getClass() + RIGHT_SQ_BRACKET);
            return null;
        }

        return builderAssistant.addMappedStatement(id, sqlSource, StatementType.PREPARED, sqlCommandType,
                null, null, null, parameterType, resultMap, resultType,
                null, !isSelect, isSelect, false, keyGenerator, keyProperty, keyColumn,
                configuration.getDatabaseId(), languageDriver, null);
    }
}
//xxx: 数据库表反射信息 
public class TableInfo implements Constants {
    /**
     * 表名称
     */
    private String tableName;
    
    /**
     * 表映射结果集
     */
    private String resultMap;

    /**
     * 主键是否有存在字段名与属性名关联
     * <p>true: 表示要进行 as</p>
     */
    private boolean keyRelated;

    /**
     * 表字段信息列表
     */
    private List<TableFieldInfo> fieldList;
    
    //xxx: 省略其他抽象...
}

/*xxx: 字段名信息*/
public class TableFieldInfo implements Constants {
    /**
     * 字段名
     */
    private final String column;
    
    /**
     * 属性名
     */
    private final String property;

    /**
     * 是否进行 select 查询
     * <p>大字段可设置为 false 不加入 select 查询范围</p>
     */
    private boolean select = true;
    
    //xxx: 省略其他抽象...
}

# 关键点总结

  1. 为什么在不编写mapper映射文件的情况下,不报错?
    • mybatis原生环境下,就算没有写Mapper也不会报错,以前的固有认知存在错误。
    • 没有编写 mapper.xml文件时,通过 MapperFactoryBean,DaoSupport.checkDaoConfig(由springDao提供)
/*xxx: 动态生产实现类*/
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    
    //xxx: 省略其他抽象...
    @Override
    protected void checkDaoConfig() {
        if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
            //xxx: 该方法,会触发 mybatis-plus的动态crud注入过程
            configuration.addMapper(this.mapperInterface);
        }
    }
}
  1. 如何实现自带crud方法的?
    • 重写了 mapperRegistry,每次加入Mapper时,检测是否实现了超类
    • 对于继承了BaseMapper超类的接口,进行注入
    • 注入的方法,由代码实现构造,关键类有: TableInfoAbstractMethod
  2. 条件构造器以编程风格构造参数的架构
@FunctionalInterface
public interface ISqlSegment extends Serializable {

    /**
     * SQL 片段
     */
    String getSqlSegment();
}

public abstract class Wrapper<T> implements ISqlSegment {
    @Override
    public String getSqlSegment() {
        String sqlSegment = expression.getSqlSegment();
        if (StringUtils.isNotBlank(sqlSegment)) {
            return sqlSegment + lastSql.getStringValue();
        }
        if (StringUtils.isNotBlank(lastSql.getStringValue())) {
            return lastSql.getStringValue();
        }
        return null;
    }
    
    //xxx: 省略其他抽象, 流程大致跟 xml动态语句解析差不多
}
  1. mybatis-plus对于接口重复映射要求更低
    • mybatis如果某个接口已经映射过,再次扫描到,将会报错
    • mybatis-plus将不会报错,而是直接跳过,也就是说有多个接口进行绑定时,以第一个为准

# springBoot环境下mybatis-plus架构

# springBoot环境下mybatis-plus使用流程

@ConfigurationProperties(prefix ="mybatis-plus")
public class MybatisPlusProperties {
    //xxx: mybatis-plus的默认值位置,
    private String[] mapperLocations = new String[]{"classpath*:/mapper/**/*.xml"};

    @NestedConfigurationProperty
    //xxx: 注意这里面有一个较为重要的配置为 : globalConfig
    private MybatisConfiguration configuration;

    @NestedConfigurationProperty
    //xxx: 也可直接进行配置
    private GlobalConfig globalConfig = GlobalConfigUtils.defaults();
    
    //xxx: 省略其他抽象...
}
mybatis-plus:
  configuration:
    mapUnderscoreToCamelCase: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapperLocations:
    - classpath*:org/mybatis/example/mapper/*.xml
//xxx: 由于当前环境,存在mybatis-springBoot依赖,个别bean存在不兼容情况,暂时排除
@SpringBootApplication(exclude = {MybatisAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
@MapperScan("com.automannn.practice.mybatis.dao")
public class SpringBootMybatisPlusTest {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext= SpringApplication.run(SpringBootMybatisPlusTest.class);

        PersonDao personDao= applicationContext.getBean(PersonDao.class);
        personDao.selectList();

        PersonPlusTestDao personPlusTestDao = applicationContext.getBean(PersonPlusTestDao.class);
        personPlusTestDao.selectList(Wrappers.emptyWrapper());
    }
}

# springBoot环境下mybatis-plus注意事项

  • 配置的前缀与 mybatis不同,其余配置与 mybatis的配置兼容,此外,还有新增的配置
  • mybatis-plus最好不要与mybatis-springBoot混用,可能造成bean装载异常,增加不必要的工作量
  • mybatis-plus的springBoot依赖模块为 mybatis-plus-boot-starter

# 分页插件的原理

# mybatis-plus分页插件

  • 通过statement.prepare方法,改写sql,主要分为两步
    • 查询数据总数,如果小于等于0,则不进行后续操作
    • 为原有的查询语句,增加 limit ?,? 参数
  • 入参的size<0时,该次查询将不会使用分页
  • 如果返回类型是 IPage 则入参的 IPage 不能为null,因为 返回的IPage == 入参的IPage; 如果想临时不分页,可以在初始化IPage时size参数传 <0 的值;
  • 如果返回类型是 List 则入参的 IPage 可以为 null(为 null 则不分页),但需要你手动 入参的IPage.setRecords(返回的 List);
  • 如果 xml 需要从 page 里取值,需要 page.属性 获取
  • 分页插件要生效,需要手动进行装载

# pageHelper分页工具

  • PageHelper提供了静态方法用于查询,其本质是提供分页参数,该参数存储在线程上下文中
  • 真正完成,需要靠插件完成,再pageHelper中为 com.github.pagehelper.PageInterceptor
  • 它的流程跟 mybatis-plus大致上差不多: 查询总数->设置总数(其它的参数一并计算好)->进行分页查询->设置数据
  • 由于静态工具类的原因,同一个线程的startPage之后,需要紧接着使用query查询生效,不然容易dna错乱
@ConfigurationProperties(prefix = PageHelperProperties.PAGEHELPER_PREFIX)
public class PageHelperProperties {
    public static final String PAGEHELPER_PREFIX = "pagehelper";

    //xxx: 可以自定义参数
    private Properties properties = new Properties();

    public String getPageSizeZero() {
        return properties.getProperty("pageSizeZero");
    }
    
    //xxx: 省略其它抽象...
}

# 多数据源

# 多数据源方案

  • 分包方式,不同的数据源配置不同的MapperScan和mapper文件(每个分包需要配置相应的事务管理器)
    • 该方案,可通过 Transactional API实现事务
  • 切面方式,spring官方提议的方案(org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource)
    • 该方案无法实现全局分布式事务(可通过TransactionManager对多数据源抽象对象进行管理,具体的回滚表现,尚未测试)
  • 开源框架方式,如 dynamic-datasource-spring-boot-starter,其强依赖mybatis-plus框架
    • 可以实现事务隔离和注解,受限于具体的框架,如mybatisplus;

# spring提议的多数据源实现方案架构

/*xxx: 动态数据源 抽象实现类*/
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
    @Nullable
    /*xxx: 多数据源缓存*/
    private Map<Object, DataSource> resolvedDataSources;
    
    @Override
    /*xxx: 获取连接对象时,动态决定通过哪个数据源获取 */
    public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
    }

    /*xxx: 获取数据源*/
    protected DataSource determineTargetDataSource() {
        Object lookupKey = determineCurrentLookupKey();
        DataSource dataSource = this.resolvedDataSources.get(lookupKey);

        return dataSource;
    }

    @Override
    public void afterPropertiesSet() {
        //xxx: targetSources是必需属性
        
        this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());

        this.targetDataSources.forEach((key, value) -> {
            Object lookupKey = resolveSpecifiedLookupKey(key);
            DataSource dataSource = resolveSpecifiedDataSource(value);
            this.resolvedDataSources.put(lookupKey, dataSource);
        });
    }
}

# mybatis疑难

# 同样的代码,报参数找不到(都没加@Param)

  • 编译机制导致的

  • 默认情况下,maven编译接口时会替换参数为arg0,arg1...(类不会替换参数)

  • spring-boot-starter-parent配置了所有参数均需要保留,注意spring-boot-parent与spring-boot-starter-parent的区别

  • 也可以手动设置,直接在顶级pom设置即可

    <plugin>
             <!--3.6.1及之前版本设置-->          <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.6.1</version>
                    <configuration>
                        <compilerArgs>-parameters</compilerArgs>
                    </configuration>
    </plugin>
    
    <plugin>
       <!--3.6.2及以后版本设置-->             <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
                    <configuration>
                        <parameters>true</parameters>
                    </configuration>
     </plugin>
    
    

# mybatis疑难

# 同样的代码,报参数找不到(都没加@Param)

  • 编译机制导致的

  • 默认情况下,maven编译接口时会替换参数为arg0,arg1...(类不会替换参数)

  • spring-boot-starter-parent配置了所有参数均需要保留,注意spring-boot-parent与spring-boot-starter-parent的区别

  • 也可以手动设置,直接在顶级pom设置即可

    <plugin>
             <!--3.6.1及之前版本设置-->          <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.6.1</version>
                    <configuration>
                        <compilerArgs>-parameters</compilerArgs>
                    </configuration>
    </plugin>
    
    <plugin>
       <!--3.6.2及以后版本设置-->             <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
                    <configuration>
                        <parameters>true</parameters>
                    </configuration>
     </plugin>
    
    

# mybatis疑难

# 同样的代码,报参数找不到(都没加@Param)

  • 编译机制导致的
  • 默认情况下,maven编译接口时会替换参数为arg0,arg1...(类不会替换参数)
  • spring-boot-starter-parent配置了所有参数均需要保留,注意spring-boot-parent与spring-boot-starter-parent的区别
  • 也可以手动设置,直接在顶级pom设置即可,如下:
<plugin>
         <!--3.6.1及之前版本设置-->          <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.1</version>
                <configuration>
                    <compilerArgs>-parameters</compilerArgs>
                </configuration>
</plugin>

<plugin>
   <!--3.6.2及以后版本设置-->             <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <parameters>true</parameters>
                </configuration>
 </plugin>

# if判断语句语句不正常

  • 使用了 <if test="param=='1' "/>尽管值能够对上,也进不去判断语句中
  • 正常情况下用字符串放在单引号里面没有问题,但如果是用==来做判断单个字符时,单引号必须改为双引号
  • 这是因为**该常量会被转化为字符char**

# mybatisPlus查询器Wrapper

# 普通查询器

  • 常见的如:QueryWrapper,UpdateWrapper
  • 参数只能以数据库列名的方式进行指定

# lamda查询器

  • 常见的如: LambdaQueryWrapper,LambdaUpdateWrapper
  • 参数可以通过Lambda表达式获取,但是需要注意默认情况下属性通过驼峰形式进行映射,除非关闭了驼峰属性,或者使用了@TableField注解
  • lambdaQuery与lambdaUpdate必须指明泛型,可以通过工具参数指明如Wrappers.lambdaQuery(new GeneratorCodeWidgetGrowPO()),也可以手动实例化指明泛型,如LambdaUpdateWrapper<GeneratorCodeWidgetGrowPO> wrapper= new LambdaUpdateWrapper<>()

# 链式查询器

  • 常见的如: QueryChainWrapper,UpdateChainWrapper,LambdaQueryWrapper,LambdaUpdateChainWrapper
  • 顾名思义,支持链式操作
  • 链式查询器不支持作为查询条件,或者更新条件的参数使用,否则可能导致MybatisPlusException:can not use this method for “getSqlSet”异常
  • 换言之,它的应用场景为直接调用代码查询的场景