• MyBatis详解(一)


    MyBatis简单介绍

    【1】MyBatis是一个持久层的ORM框架【Object Relational Mapping,对象关系映射】,使用简单,学习成本较低。可以执行自己手写的SQL语句,比较灵活。但是MyBatis的自动化程度不高,移植性也不高,有时从一个数据库迁移到另外一个数据库的时候需要自己修改配置,所以称只为半自动ORM框架。

    传统JDBC介绍

    【1】简单使用

    @Test
    public  void test() throws SQLException {
        Connection conn=null;
        PreparedStatement pstmt=null;
        try {
            // 1.加载驱动,其实这一步可以不加因为DriverManager里面会有自动加载驱动的一步
            Class.forName("com.mysql.jdbc.Driver");
    
            // 2.创建连接
            conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis_example", "root", "123456");
    
            //开启事务
            conn.setAutoCommit(false);
    
            // SQL语句
            String sql="select id,user_name,create_time from t_user where id=? ";
    
            // 获得sql执行者
            pstmt=conn.prepareStatement(sql);
            pstmt.setInt(1,1);
    
            // 执行查询
            //ResultSet rs= pstmt.executeQuery();
            pstmt.execute();
            ResultSet rs= pstmt.getResultSet();
    
            rs.next();
            User user =new User();
            user.setId(rs.getLong("id"));
            user.setUserName(rs.getString("user_name"));
            user.setCreateTime(rs.getDate("create_time"));
            System.out.println(user.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
        finally{
            // 关闭资源
            try {
                if(conn!=null){
                    conn.close();
                }
                if(pstmt!=null){
                    pstmt.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    【1.1】DriverManager如何自动加载驱动【利用静态代码块调用初始化方法,至于为什么要使用SPI机制,主要是为了实现解耦和可插拔,因为驱动有多种】

      注:驱动展示【位于mysql-connector-java-5.1.22.jar/META-INF/services/java.sql.Driver  内容为:com.mysql.jdbc.Driver】

    static {
        // 启动类加载器加载DriverManager类,触发静态方法执行
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
    
    private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
    
        // 加载java.sql.Driver驱动的实现
        AccessController.doPrivileged(new PrivilegedAction() {
            public Void run() {
                // 1、创建一个 ServiceLoader对象,【这里就将上下文类加载器设置到ServiceLoader对象的变量上了】
                ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
                // 2、创建一个迭代器对象
                Iterator driversIterator = loadedDrivers.iterator();
    
                try{
                    // 3、这里调用driversIterator.hasNext()的时候,触发将 META-INF/services 下的配置文件中的数据读取进来,方便下面的next方法使用
                    while(driversIterator.hasNext()) {
                        // 4、【关键】:触发上面创建的迭代器对象的方法调用。这里才是具体加载的实现逻辑,非常不好找
                        driversIterator.next();
                    }
                } catch(Throwable t) {}
                return null;
            }
        });
    
        //判断有没有加载到
        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {...}
        }
    }
    
    //ServiceLoader类#hasNext方法
    public boolean hasNext() {
        if (acc == null) {
            return hasNextService();
        } else {
            PrivilegedAction action = new PrivilegedAction() {
                public Boolean run() { return hasNextService(); }
            };
            return AccessController.doPrivileged(action, acc);
        }
    }
    
    private boolean hasNextService() {
        if (nextName != null) {
            return true;
        }
        if (configs == null) {
            try {
                // 1、拼凑要读取的文件的全名
                // final String PREFIX = "META-INF/services/";
                String fullName = PREFIX + service.getName();
    
                // 2、根据 fullName 去到META-INF/services/目录下寻找配置文件
                // 如果类加载器为空,则使用系统类加载器,如果不为空则使用指定的类加载器
                if (loader == null)
                    configs = ClassLoader.getSystemResources(fullName);
                else
                    configs = loader.getResources(fullName);
            } catch (IOException x) {
                fail(service, "Error locating configuration files", x);
            }
        }
        while ((pending == null) || !pending.hasNext()) {
            if (!configs.hasMoreElements()) {
                return false;
            }
    
            // 3、使用parse方法解析配置文件中的每一行数据
            pending = parse(service, configs.nextElement());
        }
        nextName = pending.next();
        return true;
    }

    【2】当然正常情况一般是封装成类来使用的,如

    //数据访问基类
    public class BaseDao {
        // 驱动
        private static String DRIVER = null;
        // 链接字符串
        private static String URL = null;
        // 用户名
        private static String USERNAME = null;
        // 密码
        private static String PASSWORD = null;
    
        //初始化
        static {  init();  }
    
        // 初始化
        private static void init() {
            try {
                // 使用Properties对象读取资源文件属性
                Properties pro = new Properties();
                // 获得资源文件输入流
                InputStream inStream = BaseDao.class.getClassLoader().getResourceAsStream("db.properties");
                // 加载输入流
                pro.load(inStream);
                DRIVER = pro.getProperty("mysql.driverClass");
                URL = pro.getProperty("mysql.jdbcUrl");
                USERNAME = pro.getProperty("mysql.user");
                PASSWORD = pro.getProperty("mysql.password");
    
                Class.forName(DRIVER);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        //获取数据库连接对象
        protected Connection getConnection() {
            Connection conn = null;
            try {
                conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return conn;
        }
    
        /**
         * 关闭所有链接
         * @param conn
         * @param stmt
         * @param rs
         */
        protected void CloseAll(Connection conn, Statement stmt, ResultSet rs) {
            try {
                if (conn != null) {
                    conn.close();
                }
                if (stmt != null) {
                    stmt.close();
                }
                if (rs != null) {
                    rs.close();
                }
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    
        /**
         * 执行 增、删、 改的公共方法
         * @param sql     SQL语句
         * @param prams   参数
         * @return           受影响的行数
         */
        protected int executeUpdate(String sql, Object... prams) {
            // 获得数据库链接对象
            Connection conn = getConnection();
            // 声明SQL执行者
            PreparedStatement pstmt = null;
            try {
                // 获得SQL执行者
                pstmt = conn.prepareStatement(sql);
    
                // 循环加载参数
                for (int i = 0; i < prams.length; i++) {
                    pstmt.setObject(i + 1, prams[i]);
                }
                // 执行executeUpdate 返回受影响行数
                return pstmt.executeUpdate();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                // 关闭所有需要关闭的对象
                CloseAll(conn, pstmt, null);
            }
    
            return 0;
        }
    
        /**
         * 执行查询 返回单个值
         * @param sql         SQL语句
         * @param prams     参数
         * @return             OBJECT
         */
        protected Object executeScaler(String sql, Object... prams) {
            // 获得数据库链接对象
            Connection conn = getConnection();
            // 声明SQL执行者
            PreparedStatement pstmt = null;
            // 声明查询结果集
            ResultSet rs = null;
            // 接收单个值
            Object value = null;
            try {
                // 获得SQL执行者
                pstmt = conn.prepareStatement(sql);
    
                // 循环加载参数
                for (int i = 0; i < prams.length; i++) {
                    pstmt.setObject(i + 1, prams[i]);
                }
                // 执行executeUpdate 返回受影响行数
                rs = pstmt.executeQuery();
    
                if (rs.next()) {
                    value = rs.getObject(1);
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                CloseAll(conn, pstmt, rs);
            }
            return value;
        }
    
        /**
         * 执行查询返回list
         * 
         * @param sql         SQL语句
         * @param clazz     类的类型
         * @return List
         */
        public  List executeList(String sql, Class clazz, Object... prams) {
            // 数据集合
            List list = new ArrayList();
            // 获得数据库连接
            Connection conn = getConnection();
            // 声明SQL执行者
            PreparedStatement pstmt = null;
            // 声明查询结果集
            ResultSet rs = null;
            try {
    
                // 3. 通过链接创建一个SQL执行者
                pstmt = conn.prepareStatement(sql);
    
                // 循环加载参数
                for (int i = 0; i < prams.length; i++) {
                    //内部会通过instance of判断这个参数到底是哪个类型的具体对象
                    pstmt.setObject(i + 1, prams[i]);
                }
    
                // 4 执行查询SQL 返回查询结果
                rs = pstmt.executeQuery();
    
                // 获得结果集的列信息对象
                ResultSetMetaData rsmd = rs.getMetaData();
    
                while (rs.next()) {
                    // 通过类反射实例化
                    T obj = clazz.newInstance();
    
                    // 循环所有的列
                    for (int i = 1; i <= rsmd.getColumnCount(); i++) {
                        /* 通过属性名称使用反射给泛型实例赋值 Begin */
    
                        // 获得每一列的列名
                        String cloName = rsmd.getColumnName(i);
                        // 根据列名反射到类的字段
                        Field filed = clazz.getDeclaredField(cloName);
                        // 设置私有属性的访问权限
                        filed.setAccessible(true);
    
                        // 给泛型实例的某一个属性赋值
                        filed.set(obj, rs.getObject(cloName));
                        /* 通过属性名称使用反射给泛型实例赋值 End */
                    }
                    // 将泛型实例添加到 泛型集合中
                    list.add(obj);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return list;
        }
    
        /**
         * 执行查询返回JavaBean
         * 
         * @param sql         SQL语句
         * @param clazz     类的类型
         * @return JavaBean
         */
        public  T executeJavaBean(String sql, Class clazz, Object... prams) {
            // 声明数据对象
            T obj=null;
            // 获得数据库连接
            Connection conn = getConnection();
            // 声明SQL执行者
            PreparedStatement pstmt = null;
            // 声明查询结果集
            ResultSet rs = null;
            try {
    
                // 3. 通过链接创建一个SQL执行者
                pstmt = conn.prepareStatement(sql);
    
                // 循环加载参数
                for (int i = 0; i < prams.length; i++) {
                    pstmt.setObject(i + 1, prams[i]);
                }
    
                // 4 执行查询SQL 返回查询结果
                rs = pstmt.executeQuery();
    
                // 获得结果集的列信息对象
                ResultSetMetaData rsmd = rs.getMetaData();
    
                if (rs.next()) {
                    // 通过类反射实例化
                    obj = clazz.newInstance();
    
                    // 循环所有的列
                    for (int i = 1; i <= rsmd.getColumnCount(); i++) {
                        /* 通过属性名称使用反射给泛型实例赋值 Begin */
    
                        // 获得每一列的列名
                        String cloName = rsmd.getColumnName(i);
                        // 根据列名反射到类的字段
                        Field filed = clazz.getDeclaredField(cloName);
                        // 设置私有属性的访问权限
                        filed.setAccessible(true);
    
                        // 给泛型实例的某一个属性赋值
                        filed.set(obj, rs.getObject(cloName));
                        /* 通过属性名称使用反射给泛型实例赋值 End */
                    } 
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return obj;
        }
    }

    【3】总结JDBC的四大核心对象

    1)DriverManager(驱动管理对象):获取数据库连接;
    2)Connection(数据库连接对象):获取执行sql对象;管理事务;
    3)Statement(执行sql对象):executeUpdate执行DML语句(增删改)DDL语句;executeQuery 执行DQL语句;
    4)ResultSet(结果集对象)

    【4】传统JDBC的问题

    1)数据库连接创建,释放频繁造成西戎资源的浪费,从而影响系统性能,使用数据库连接池可以解决问题。
    2)sql语句在代码中硬编码,造成代码的不已维护,实际应用中sql的变化可能较大,sql代码和java代码没有分离开来维护不方便。
    3)使用preparedStatement向有占位符传递参数存在硬编码问题因为sql中的where子句的条件不确定,同样是修改不方便。
    4)对结果集中解析存在硬编码问题,sql的变化导致解析代码的变化,系统维护不方便。

    【5】针对问题的优化

    1、数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。
    优化部分,如mybatis:在SqlMapConfig.xml中配置数据连接池,使用连接池管理数据库链接。
    
    2、Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
    优化部分,如mybatis:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
    
    3、向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
    优化部分,如mybatis:自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。
    
    4、对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
    优化部分,如mybatis:自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。

    Mybaits整体体系图

    【1】图示

              

    【2】分析

    把Mybatis的功能架构分为三层:
    1)基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
    对应示例中的部分为:  SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
    
    2)API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
    对应示例中的部分为:  SqlSession session = sqlMapper.openSession();
    
    3)数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
    对应示例中的部分为:  
    User user = (User)session.selectOne("com.mapper.UserMapper.selectById", 1);
    或者
    UserMapper mapper = session.getMapper(UserMapper.class);
    User user = mapper.selectById(1L);

    【3】简单示例

    public static void main(String[] args) {
        String resource = "mybatis-config.xml";
        Reader reader;
        try {
            //将XML配置文件构建为Configuration配置类
            reader = Resources.getResourceAsReader(resource);
            // 通过加载配置文件流构建一个SqlSessionFactory  DefaultSqlSessionFactory
            SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
            // 数据源 执行器  DefaultSqlSession
            SqlSession session = sqlMapper.openSession();
            try {
                // 执行查询 底层执行jdbc
                //User user = (User)session.selectOne("com.mapper.UserMapper.selectById", 1);
    
                UserMapper mapper = session.getMapper(UserMapper.class);
                User user = mapper.selectById(1L);
                
                session.commit();
                System.out.println(user.getUserName());
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                session.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    Mybaits插件的分析

    【1】插件主要作用于四⼤组件对象

    1)执⾏器 Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed 等⽅法)
    2)参数处理器 ParameterHandler (getParameterObject, setParameters 等⽅法)
    3)结果集处理器 ResultSetHandler (handleResultSets, handleOutputParameters 等⽅法)
    4)SQL语法构建处理器 StatementHandler (prepare, parameterize, batch, update, query 等⽅法)    

    源码分析部分

    MyBatis解析全局配置文件的源码分析

    【1】分析怎么通过加载配置文件流构建一个SqlSessionFactory

    public SqlSessionFactory build(Reader reader) {
        return build(reader, null, null);
     }
    
    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        try {
          XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
          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.
          }
        }
    }
    
    // 到这里配置文件已经解析成了Configuration
    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

    【1.1】分析XMLConfigBuilder类怎么将xml的资源文件流解析成Configuration

    【1.1.1】新建XMLConfigBuilder过程【所以说XMLConfigBuilder并不负责解析,解析的是它里面的XPathParser类,】

    /**
    * 创建一个用于解析xml配置的构建器对象
    * @param inputStream 传入进来的xml的配置
    * @param environment 我们的环境变量
    * @param props:用于保存我们从xml中解析出来的属性
    */
    public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        /**
         * 该方法做了二个事情
         * 第一件事情:创建XPathParser 解析器对象,在这里会把我们的
         * 把我们的mybatis-config.xml解析出一个Document对象
         * 第二节事情:调用重写的构造函数来构建我XMLConfigBuilder
         */
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
    }
    
    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        //调用父类的BaseBuilder的构造方法:给configuration赋值,typeAliasRegistry别名注册器赋值,TypeHandlerRegistry赋值
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
    
        //把props绑定到configuration的props属性上
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
    }
    
    //又因为class XMLConfigBuilder extends BaseBuilder,所以看一下他的父类
    public abstract class BaseBuilder {
        // mybatis的全局配置文件
        protected final Configuration configuration;
        // 用于保存我们的Entity的别名
        protected final TypeAliasRegistry typeAliasRegistry;
        // 用户保存我们java类型和jdbc数据库类型的
        protected final TypeHandlerRegistry typeHandlerRegistry;
    
        public BaseBuilder(Configuration configuration) {
            this.configuration = configuration;
            this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
            this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
        }
        ....
    }

    【1.1.2】parser.parse()解析过程【本质上就是从根结点开始解析

    public Configuration parse() {
        //若已经解析过了 就抛出异常
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        //设置解析标志位,保证只解析一次
        parsed = true;
        /**
         * 解析我们的mybatis-config.xml的节点
         *  
         */
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }
    
    //方法实现说明:解析我们mybatis-config.xml的 configuration节点
    private void parseConfiguration(XNode root) {
        try {
          // 解析 properties节点
          // 如:
          propertiesElement(root.evalNode("properties"));
          
          // 解析我们的mybatis-config.xml中的settings节点
          // 如:
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          
          // 基本没有用过该属性
          // VFS含义是虚拟文件系统;主要是通过程序能够方便读取本地文件系统、FTP文件系统等系统中的文件资源。
          // Mybatis中提供了VFS这个配置,主要是通过该配置可以加载自定义的虚拟文件系统应用程序
          loadCustomVfs(settings);
          
          // 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
          // SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
          // 解析到org.apache.ibatis.session.Configuration#logImpl
          loadCustomLogImpl(settings);
    
          // 解析别名typeAliases节点
          typeAliasesElement(root.evalNode("typeAliases"));
    
          // 解析插件节点(比如分页插件),解析到 interceptorChain.interceptors
          pluginElement(root.evalNode("plugins"));
    
          objectFactoryElement(root.evalNode("objectFactory"));
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          reflectorFactoryElement(root.evalNode("reflectorFactory"));
          // 设置settings 到 configuration 里面
          settingsElement(settings);
    
          // 解析mybatis环境,解析到:org.apache.ibatis.session.Configuration#environment
          // 在集成spring情况下由 spring-mybatis提供数据源 和事务工厂
          environmentsElement(root.evalNode("environments"));
          
          // 解析数据库厂商
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          
          // 解析类型处理器节点
          typeHandlerElement(root.evalNode("typeHandlers"));
    
          // 解析mapper节点(这个是最重要的)
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException(...);
        }
    }

    【1.1.2.1】展示xml配置文件与Mapper文件

    
    
    
        
        
    
        
        
            
            
            
            
    
    
    
    
        
    
        
    
    
    
    
    
    
    
        
       
    
        
        
            
        
    
        
            
               
                
                
                    
                    
                    
                    
                
            
        
    
        
            
            
            
    
            
        
    

    【1.1.2.2】部分内容分析

    【1.1.2.2.1】解析 properties节点

    // 本质上这个分离方式便是为了解耦
    // 大体上看来加载properties有两种,一种是本地文件,一种是远程文件
    // 而且加载出来的数据其实是会放置于configuration属性里面的,也就是XMLConfigBuilder类里面
    private void propertiesElement(XNode context) throws Exception {
        if (context != null) {
          Properties defaults = context.getChildrenAsProperties();
          String resource = context.getStringAttribute("resource");
          String url = context.getStringAttribute("url");
          if (resource != null && url != null) {
            throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
          }
          if (resource != null) {
            defaults.putAll(Resources.getResourceAsProperties(resource));
          } else if (url != null) {
            defaults.putAll(Resources.getUrlAsProperties(url));
          }
          Properties vars = configuration.getVariables();
          if (vars != null) {
            defaults.putAll(vars);
          }
          parser.setVariables(defaults);
          configuration.setVariables(defaults);
        }
    }
    
    //下面便展示一下db.properties的常用内容
    mysql.driverClass=com.mysql.jdbc.Driver
    mysql.jdbcUrl=jdbc:mysql://localhost:3306/mybatis_example?characterEncoding=utf8
    mysql.user= root
    mysql.password= 123456

    【1.1.2.2.2】解析我们的mybatis-config.xml中的settings节点

    private Properties settingsAsProperties(XNode context) {
        if (context == null) {
          return new Properties();
        }
        //分析配置的节点
        Properties props = context.getChildrenAsProperties();
    
        // 其实就是去configuration类里面拿到所有setter方法,看看有没有当前的配置项
        MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
        for (Object key : props.keySet()) {
          if (!metaConfig.hasSetter(String.valueOf(key))) {
            throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
          }
        }
        return props;
    }

    【1.1.2.2.3】解析别名typeAliases节点

    // 别名设置,别名设置最常用的就是对于entity【也就是实体类对象】
    // 常常作用域Mapper的resultMap属性,也就是查询结果的映射,映射到对于的实体类上
    // 由下面分析可以看出来是具备单个或者对包类路径的扫描
    private void typeAliasesElement(XNode parent) {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
              String typeAliasPackage = child.getStringAttribute("name");
              configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            } else {
              String alias = child.getStringAttribute("alias");
              String type = child.getStringAttribute("type");
              try {
                Class clazz = Resources.classForName(type);
                if (alias == null) {
                  typeAliasRegistry.registerAlias(clazz);
                } else {
                  typeAliasRegistry.registerAlias(alias, clazz);
                }
              } catch (ClassNotFoundException e) {
                throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
              }
            }
          }
        }
    }

    【1.1.2.2.4】解析插件节点(比较重要)

    // 对于插件的解析,很明显的有用到装饰器模式的概念,其实都是封装成了interceptor
    // 然后统一存放于configuration里面
    private void pluginElement(XNode parent) throws Exception {
        if (parent != null) {
            for (XNode child : parent.getChildren()) {
                String interceptor = child.getStringAttribute("interceptor");
                Properties properties = child.getChildrenAsProperties();
                Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
                interceptorInstance.setProperties(properties);
                configuration.addInterceptor(interceptorInstance);
            }
        }
    }
    
    //实际上是封装成了这个类,因为Interceptor是接口,是不具备实例化能力的
    @Intercepts({})
    public class ExamplePlugin implements Interceptor {
      private Properties properties;
    
      @Override
      public Object intercept(Invocation invocation) throws Throwable {
        return invocation.proceed();
      }
    
      @Override
      public Object plugin(Object target) {
        return Plugin.wrap(target, this);
      }
    
      @Override
      public void setProperties(Properties properties) {
        this.properties = properties;
      }
    
      public Properties getProperties() {
        return properties;
      }
    
    }

    【1.1.2.2.5】解析mybatis环境

    //这里面主要的就是事务工厂和数据源的设置
    private void environmentsElement(XNode context) throws Exception {
        if (context != null) {
            if (environment == null) {
                environment = context.getStringAttribute("default");
            }
            for (XNode child : context.getChildren()) {
                String id = child.getStringAttribute("id");
                if (isSpecifiedEnvironment(id)) {
                  TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                  DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                  DataSource dataSource = dsFactory.getDataSource();
                  Environment.Builder environmentBuilder = new Environment.Builder(id)
                      .transactionFactory(txFactory)
                      .dataSource(dataSource);
                  configuration.setEnvironment(environmentBuilder.build());
                }
            }
        }
    }
    
    //展示一下一般可以在xml里面怎么配置
    
        
            
            
                
                
                
                
            
        
    
        
            
            
                
                
                
                
            
        
    
    
    //其实里面的JDBC和POOLED是怎么来的呢,都是源于别名那部分的,在Configuration类构造函数里面,他就会为别名里面注册一部分常用数据
    public Configuration() {
        typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
        typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
    
        typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
        typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
        typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
    
        typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
        typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
        typeAliasRegistry.registerAlias("LRU", LruCache.class);
        typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
        typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
    
        typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
    
        typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
        typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
    
        typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
        typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
        typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
        typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
        typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
        typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
        typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
    
        typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
        typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
    
        languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
        languageRegistry.register(RawLanguageDriver.class);
    }

    【1.1.2.2.6】类型处理器解析

    private void typeHandlerElement(XNode parent) {
        if (parent != null) {
            for (XNode child : parent.getChildren()) {
                // 根据包类路径注册
                if ("package".equals(child.getName())) {
                    String typeHandlerPackage = child.getStringAttribute("name");
                    typeHandlerRegistry.register(typeHandlerPackage);
                } else {
                    //根据单个进行注册,属性有 handler(处理器) ,jdbcType(对应数据库类型) ,javaType(对应java类型)
                    String javaTypeName = child.getStringAttribute("javaType");
                    String jdbcTypeName = child.getStringAttribute("jdbcType");
                    String handlerTypeName = child.getStringAttribute("handler");
                    Class javaTypeClass = resolveClass(javaTypeName);
                    JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
                    Class typeHandlerClass = resolveClass(handlerTypeName);
                    if (javaTypeClass != null) {
                    if (jdbcType == null) {
                          typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
                    } else {
                          typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
                    }
                    } else {
                        typeHandlerRegistry.register(typeHandlerClass);
                    }
                }
            }
        }
    }

    【1.1.2.2.7】最重要的mapper解析,明显是有四种类型

    private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            // 获取我们mappers节点下的一个一个的mapper节点
            for (XNode child : parent.getChildren()) {
                // 判断我们mapper是不是通过批量注册的,如
                if ("package".equals(child.getName())) {
                      String mapperPackage = child.getStringAttribute("name");
                      configuration.addMappers(mapperPackage);
                } else {
                      // 判断从classpath下读取我们的mapper,如
                      String resource = child.getStringAttribute("resource");
    
                      // 判断是不是从我们的网络资源读取(或者本地磁盘得),如
                      String url = child.getStringAttribute("url");
    
                      // 解析这种类型(要求接口和xml在同一个包下),如
                      String mapperClass = child.getStringAttribute("class");
    
                     // 拿这种进行分析
                      if (resource != null && url == null && mapperClass == null) {
                        ErrorContext.instance().resource(resource);
    
                        // 把我们的文件读取出一个流
                        InputStream inputStream = Resources.getResourceAsStream(resource);
                        // 创建读取XmlMapper构建器对象,用于来解析我们的mapper.xml文件
                        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
    
                        // 真正的解析我们的mapper.xml配置文件(说白了就是来解析我们的sql)
                        mapperParser.parse();
    
                      } else if (resource == null && url != null && mapperClass == null) {
    
                          // 这种从网络资源读取,与上面的大体一致就不解释了
                        ErrorContext.instance().resource(url);
                        InputStream inputStream = Resources.getUrlAsStream(url);
                        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                        mapperParser.parse();
    
                      } else if (resource == null && url == null && mapperClass != null) {
    
                        Class mapperInterface = Resources.classForName(mapperClass);
                        configuration.addMapper(mapperInterface);
                        
                      } else {
                          // 都不是则抛出异常
                        throw new BuilderException(...);
                      }
                }
            }
        }
    }

    【1.1.2.2.7.1】分析addMapper方法最后是怎么解析的

    // MapperRegistry类的两个属性值
    // private final Configuration config;
    // private final Map, MapperProxyFactory> knownMappers = new HashMap<>();
    
    //Configuration类#addMapper方法
    // 传入包路径的方法
    public void addMappers(String packageName) {
        mapperRegistry.addMappers(packageName);
    }
    
    public void addMappers(String packageName) {
        addMappers(packageName, Object.class);
    }
    
    //说白了也就是从包路径下面拿到所有的类,然后调用传入类的方法
    public void addMappers(String packageName, Class superType) {
        ResolverUtil> resolverUtil = new ResolverUtil<>();
        resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
        Set>> mapperSet = resolverUtil.getClasses();
        for (Class mapperClass : mapperSet) {
            // 调用的也是核心方法1
              addMapper(mapperClass);
        }
    }
    
    // 传入类的方法
    public  void addMapper(Class type) {
        mapperRegistry.addMapper(type);
    }
    
    // 核心方法1
    // 方法实现说明:把我们的Mapper class保存到我们的knownMappers map 中
    public  void addMapper(Class type) {
        // 判断我们传入进来的type类型是不是接口
        if (type.isInterface()) {
          // 判断我们的缓存中有没有该类型
            if (hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
              }
              boolean loadCompleted = false;
              try {
                // 创建一个MapperProxyFactory 把我们的Mapper接口保存到工厂类中
                knownMappers.put(type, new MapperProxyFactory<>(type));
                // mapper注解构造器
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
    
                // 进行解析
                parser.parse();
                loadCompleted = true;
              } finally {
                if (!loadCompleted) {
                  knownMappers.remove(type);
                }
              }
        }
    }

    【1.1.2.2.7.2】分析parse()方法怎么解析,其实有两种方式,一种是 XMLMapperBuilder类#parse方法,另一种是 MapperAnnotationBuilder类#parse方法

    【1.1.2.2.7.2.1】分析XMLMapperBuilder类#parse方法

    // XMLMapperBuilder类#parse方法
    public void parse() {
        // 判断当前的Mapper是否被加载过
        if (!configuration.isResourceLoaded(resource)) {
          // 真正的解析我们的 
          configurationElement(parser.evalNode("/mapper"));
          // 把资源保存到我们Configuration中
          configuration.addLoadedResource(resource);
    
          bindMapperForNamespace();
        }
    
        parsePendingResultMaps();
        parsePendingCacheRefs();
        parsePendingStatements();
    }
    
    
    // 方法实现说明:解析我们的节点
    private void configurationElement(XNode context) {
        try {
            // 解析我们的namespace属性 
            String namespace = context.getStringAttribute("namespace");
            if (namespace == null || namespace.equals("")) {
                throw new BuilderException("Mapper's namespace cannot be empty");
            }
            // 保存我们当前的namespace  并且判断接口完全类名==namespace
            builderAssistant.setCurrentNamespace(namespace);
    
            // 解析缓存引用
            // 示例说明:说明当前的缓存引用和DeptMapper的缓存引用一致,
            // 解析到org.apache.ibatis.session.Configuration#cacheRefMap<当前namespace,ref-namespace>
            // 异常下(引用缓存未使用缓存):org.apache.ibatis.session.Configuration#incompleteCacheRefs
            cacheRefElement(context.evalNode("cache-ref"));
    
            // 解析cache节点,如
            // 解析到:org.apache.ibatis.session.Configuration#caches
            // org.apache.ibatis.builder.MapperBuilderAssistant#currentCache
            cacheElement(context.evalNode("cache"));
    
            // 解析paramterMap节点(该节点mybaits3.5貌似不推荐使用了)
            parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    
            // 解析resultMap节点
            // 解析到:org.apache.ibatis.session.Configuration#resultMaps
            // 异常 org.apache.ibatis.session.Configuration#incompleteResultMaps
            resultMapElements(context.evalNodes("/mapper/resultMap"));
            
            // 解析sql节点
            // 解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments,其实等于 org.apache.ibatis.session.Configuration#sqlFragments,因为他们是同一引用,在构建XMLMapperBuilder 时把Configuration.getSqlFragments传进去了
            sqlElement(context.evalNodes("/mapper/sql"));
    
            // 解析我们的select | insert |update |delete节点,解析到org.apache.ibatis.session.Configuration#mappedStatements
            // 实际上便是将每个语句都构建成mappedStatement对象
            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);
        }
    }

    【1.1.2.2.7.2.1.1】MappedStatement类结构展示

    public final class MappedStatement {
        private String resource;//mapper配置文件名,如:UserMapper.xml
        private Configuration configuration;//全局配置
        private String id;//节点的id属性加命名空间,如:com.lucky.mybatis.dao.UserMapper.selectByExample
        private Integer fetchSize;
        private Integer timeout;//超时时间
        private StatementType statementType;//操作SQL的对象的类型
        private ResultSetType resultSetType;//结果类型
        private SqlSource sqlSource;//sql语句
        private Cache cache;//缓存
        private ParameterMap parameterMap;
        private List resultMaps;
        private boolean flushCacheRequired;
        private boolean useCache;//是否使用缓存,默认为true
        private boolean resultOrdered;//结果是否排序
        private SqlCommandType sqlCommandType;//sql语句的类型,如select、update、delete、insert
        private KeyGenerator keyGenerator;
        private String[] keyProperties;
        private String[] keyColumns;
        private boolean hasNestedResultMaps;
        private String databaseId;//数据库ID
        private Log statementLog;
        private LanguageDriver lang;
        private String[] resultSets;
    }

    【1.1.2.2.7.2.1.2】解析cache节点部分cacheElement方法解析

    //这里面便是二级缓存的产生
    private void cacheElement(XNode context) {
        if (context != null) {
            //解析cache节点的type属性
            String type = context.getStringAttribute("type", "PERPETUAL");
    
            //根据type的String获取class类型
            Class typeClass = typeAliasRegistry.resolveAlias(type);
    
            // 获取缓存过期策略:默认是LRU,
            // LRU - 最近最少使用:移除最长时间不被使用的对象(默认)
            // FIFO - 先进先出:按对象进入缓存的顺序来移除他们
            // SOFT - 软引用:基于垃圾回收器状态和软引用规则移除对象
            // WEAK - 弱引用:更积极的基于垃圾收集器状态和弱引用规则移除对象
            String eviction = context.getStringAttribute("eviction", "LRU");
            Class evictionClass = typeAliasRegistry.resolveAlias(eviction);
    
            //flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
            Long flushInterval = context.getLongAttribute("flushInterval");
            //size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
            Integer size = context.getIntAttribute("size");
            //只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false
            boolean readWrite = !context.getBooleanAttribute("readOnly", false);
            boolean blocking = context.getBooleanAttribute("blocking", false);
            Properties props = context.getChildrenAsProperties();
            //把缓存节点加入到Configuration中
            builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
        }
    }

    【1.1.2.2.7.2.1.2.0】分析缓存是如何封装的

    public Cache useNewCache(Class typeClass, Class evictionClass, Long flushInterval, Integer size, boolean readWrite, boolean blocking, Properties props) {
        Cache cache = new CacheBuilder(currentNamespace)
            .implementation(valueOrDefault(typeClass, PerpetualCache.class))
            .addDecorator(valueOrDefault(evictionClass, LruCache.class))
            .clearInterval(flushInterval)
            .size(size)
            .readWrite(readWrite)
            .blocking(blocking)
            .properties(props)
            .build();
        configuration.addCache(cache);
        currentCache = cache;
        return cache;
    }
    
    public Cache build() {
        setDefaultImplementations();
        //这里便是设置了PerpetualCache为最基础层的cache
        Cache cache = newBaseCacheInstance(implementation, id);
        setCacheProperties(cache);
        //在这里利用decorators作为包装器进行包装
        if (PerpetualCache.class.equals(cache.getClass())) {
          //便会存在LruCache【 delegate属性-》PerpetualCache】,利用了反射
          for (Class decorator : decorators) {
            cache = newCacheDecoratorInstance(decorator, cache);
            setCacheProperties(cache);
          }
          //这里又会包装别的
          cache = setStandardDecorators(cache);
        } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
          cache = new LoggingCache(cache);
        }
        return cache;
    }
    
    private Cache setStandardDecorators(Cache cache) {
        try {
            MetaObject metaCache = SystemMetaObject.forObject(cache);
            if (size != null && metaCache.hasSetter("size")) {
                metaCache.setValue("size", size);
            }
            if (clearInterval != null) {
                cache = new ScheduledCache(cache);//ScheduledCache:调度缓存,负责定时清空缓存
                ((ScheduledCache) cache).setClearInterval(clearInterval);
            }
            if (readWrite) {  // 将LRU 装饰到Serialized
                cache = new SerializedCache(cache); //SerializedCache:缓存序列化和反序列化存储
            }
            cache = new LoggingCache(cache);  //日志记录层包装
            cache = new SynchronizedCache(cache);  //线程同步层包装
            if (blocking) { //判断是否有进行防穿透设置
                cache = new BlockingCache(cache);
            }
            return cache;
        } catch (Exception e) {
            throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
        }
    }

    1.1.2.2.7.2.1.2.1对cache的分析

    //Cache本身只是一个接口,用于定义缓存
    public interface Cache {
        String getId();
    
        void putObject(Object key, Object value);
    
        Object getObject(Object key);
    
        Object removeObject(Object key);
    
        void clear();
    
        int getSize();
    
        default ReadWriteLock getReadWriteLock() { return null; }
    }
    
    //重点在于它的实现类
    //四种具备淘汰机制的cache
    LruCache:(最近最少使用)防溢出缓存区
    FifoCache:(先进先出)
    WeakCache:基于弱引用实现的缓存管理策略
    SoftCache:基于软引用实现的缓存管理策略
    
    
    //底层的cache
    PerpetualCache:真正存储缓存的地方
    
    //额外辅助功能的cache
    ScheduledCache:过期清理缓存区
    SynchronizedCache:线程同步缓存区
    LoggingCache:统计命中率以及打印日志
    SerializedCache:缓存序列化和反序列化存储
    BlockingCache:防穿透

    1.1.2.2.7.2.1.2.1.1对cache的最常用的两种淘汰策略分析

    【1.1.2.2.7.2.1.2.1.1.1】对LRU分析:【这个本质上就是利用LinkedHashMap】

    public class LruCache implements Cache {
    
      private final Cache delegate;
      private Map keyMap;
      private Object eldestKey;
    
      public LruCache(Cache delegate) {
        this.delegate = delegate;
        setSize(1024);
      }
    
      @Override
      public String getId() {
        return delegate.getId();
      }
    
      @Override
      public int getSize() {
        return delegate.getSize();
      }
    
      public void setSize(final int size) {
        keyMap = new LinkedHashMap(size, .75F, true) {
          private static final long serialVersionUID = 4267176411845948333L;
    
          //当put进新值方法但会true时,便移除该map中最老的键和值
          @Override
          protected boolean removeEldestEntry(Map.Entry eldest) {
            boolean tooBig = size() > size;
            if (tooBig) {
              eldestKey = eldest.getKey();
            }
            return tooBig;
          }
        };
      }
    
      @Override
      public void putObject(Object key, Object value) {
        delegate.putObject(key, value);
        cycleKeyList(key);
      }
    
      @Override
      public Object getObject(Object key) {
        keyMap.get(key); //touch
        return delegate.getObject(key);
      }
    
      @Override
      public Object removeObject(Object key) {
        return delegate.removeObject(key);
      }
    
      @Override
      public void clear() {
        delegate.clear();
        keyMap.clear();
      }
    
      private void cycleKeyList(Object key) {
        keyMap.put(key, key);
        if (eldestKey != null) {
          delegate.removeObject(eldestKey);
          eldestKey = null;
        }
      }
    
    }

    【1.1.2.2.7.2.1.2.1.1.2】对FIFO分析:【这个本质上就是利用LinkedList】

    public class FifoCache implements Cache {
    
        private final Cache delegate;
        private final Deque keyList;
        private int size;
    
        public FifoCache(Cache delegate) {
            this.delegate = delegate;
            this.keyList = new LinkedList<>();
            this.size = 1024;
        }
    
        @Override
        public String getId() {
            return delegate.getId();
        }
    
        @Override
        public int getSize() {
            return delegate.getSize();
        }
    
        public void setSize(int size) {
            this.size = size;
        }
    
        //可以看出都是移除头部节点,将新的塞入尾结点
        @Override
        public void putObject(Object key, Object value) {
            cycleKeyList(key);
            delegate.putObject(key, value);
        }
    
        @Override
        public Object getObject(Object key) {
            return delegate.getObject(key);
        }
    
        @Override
        public Object removeObject(Object key) {
            return delegate.removeObject(key);
        }
    
        @Override
        public void clear() {
            delegate.clear();
            keyList.clear();
        }
    
        //加到最后,移除最前
        private void cycleKeyList(Object key) {
            keyList.addLast(key);
            if (keyList.size() > size) {
                Object oldestKey = keyList.removeFirst();
                delegate.removeObject(oldestKey);
            }
        }
    
    } 
    

    1.1.2.2.7.2.1.3解析sql节点部分sqlElement方法解析

    private void buildStatementFromContext(List list) {
        // 判断有没有配置数据库厂商ID
        if (configuration.getDatabaseId() != null) {
          buildStatementFromContext(list, configuration.getDatabaseId());
        }
        buildStatementFromContext(list, null);
    }
    
    /**
    * 方法实现说明:解析select|update|delte|insert节点然后创建mapperStatment对象
    * @param list:所有的select|update|delte|insert节点
    * @param requiredDatabaseId:判断有没有数据库厂商Id
    */
    private void buildStatementFromContext(List list, String requiredDatabaseId) {
        // 循环select|delte|insert|update节点
        for (XNode context : list) {
          // 创建一个xmlStatement的构建器对象
          final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
          try {
            statementParser.parseStatementNode();
          } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
          }
        }
    }

    1.1.2.2.7.2.1.3.1】深入分析是怎么解析的:

    public void parseStatementNode() {
        // insert|delte|update|select 语句的sqlId
        String id = context.getStringAttribute("id");
        // 判断insert|delte|update|select  节点是否配置了数据库厂商标注
        String databaseId = context.getStringAttribute("databaseId");
    
        // 匹配当前的数据库厂商id是否匹配当前数据源的厂商id
        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
          return;
        }
    
        // 获得节点名称:select|insert|update|delete
        String nodeName = context.getNode().getNodeName();
        // 根据nodeName 获得 SqlCommandType枚举
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        // 判断是不是select语句节点
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        //  获取flushCache属性,默认值为isSelect的反值:查询:默认flushCache=false,增删改:默认flushCache=true
        boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
        // 获取useCache属性,默认值为isSelect:查询:默认useCache=true,增删改:默认useCache=false
        boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    
        // resultOrdered:  是否需要处理嵌套查询结果 group by (使用极少),可以将比如 30条数据的三组数据  组成一个嵌套的查询结果
        boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
    
        // 解析sql公用片段,如
        //
        //将  解析成sql语句 放在
     *    2层  WhereSqlNode 
     *    2层  IfSqlNode 
     *       test="条件表达式"
     *
     *  contexts= sql语句分: 1.TextSqlNode 带${}    2.StaticTextSqlNode
     */
    protected MixedSqlNode parseDynamicTags(XNode node) {
        List contents = new ArrayList<>();
        NodeList children = node.getNode().getChildNodes();  //获得
        select id,user_name,create_time from t_user 
        
            
               and id=#{id}
            
        
    
    
    //划分情况:
    MixedSqlNode
        StaticTextSqlNode
        WhereSqlNode
            MixedSqlNode
                IfSqlNode
                    MixedSqlNode
                        StaticTextSqlNode

    【1.1.2.2.7.2.2】分析MapperAnnotationBuilder#parse方法

    // MapperAnnotationBuilder类#parse方法
    public void parse() {
        String resource = type.toString();
        // 是否已经解析mapper接口对应的xml
        if (!configuration.isResourceLoaded(resource)) {
            // 根据mapper接口名获取 xml文件并解析,解析里面所有东西放到configuration
            loadXmlResource();
            // 添加已解析的标记
            configuration.addLoadedResource(resource);
            assistant.setCurrentNamespace(type.getName());
            parseCache();
            parseCacheRef();
            // 获取所有方法 看是不是用了注解
            Method[] methods = type.getMethods();
            for (Method method : methods) {
                try {
                  if (!method.isBridge()) {
                    // 是不是用了注解  用了注解会将注解解析成MappedStatement
                    parseStatement(method);
                  }
                } catch (IncompleteElementException e) {
                      configuration.addIncompleteMethod(new MethodResolver(this, method));
                }
            }
        }
        parsePendingMethods();
    }
  • 相关阅读:
    CentOS 9 Stream 上安装 WebStorm
    软件设计不是CRUD(23):在流式数据处理系统中进行业务抽象落地——详细编码
    群体-C++数组
    Springboot毕设项目餐厅后勤管理系统cxjgl(java+VUE+Mybatis+Maven+Mysql)
    数据结构——队列
    【送书活动】揭秘分布式文件系统大规模元数据管理机制——以Alluxio文件系统为例
    4、linux下文件的通用操作方法
    AAC音视频编码详解
    Serverless成技术新常态:降低开发门槛,让效率更快一点
    App自动化测试怎么做?实战分享App自动化测试全流程
  • 原文地址:https://blog.csdn.net/jh035/article/details/128110060