精华内容
下载资源
问答
  • Idea+Mybatis源码构建

    2021-03-18 17:24:55
    切换到你下载的mybatis源码目录 执行命令:mvn clean install -Dmaven.test.skip=true 报错: pdf插件问题 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-pdf-plugin:1.4:pdf (pdf)

    Idea+Mybatis源码构建



    准备工作:下载源码包

    注意:

    	Mybatis源码还依赖于一个父工程mybatis-parent,因此需要一起下载
    	并且需要注意mybatis中引入的mybatis-parent的版本的版本得和自己下载的mybatis-parent的版本一致
    

    mybatis-3-mybatis-3.5.5 源码下载
    提取码:5t68
    parent-mybatis-parent-31 源码下载
    提取码:0z7b

    如果想下载其他版本,可以去官方进行选择下载:
    GitHub地址:https://github.com/mybatis/mybatis-3



    一、将源码安装至本地仓库中

    1.1 切换到你下载的mybatis-parent目录

    在这里插入图片描述

    	进入dos窗口
    

    在这里插入图片描述

    	执行命令:mvn clean install -Dmaven.test.skip=true
    	看到BUILD SUCCESS  代表install成功
    

    在这里插入图片描述

    1.2 切换到你下载的mybatis源码目录(和1.1中的操作一致)

    	执行命令:mvn clean install -Dmaven.test.skip=true
    

    在这里插入图片描述

    报错:

    	pdf插件问题
    

    在这里插入图片描述[ERROR] Failed to execute goal org.apache.maven.plugins:maven-pdf-plugin:1.4:pdf (pdf) on project mybatis: Error during document generation: Error parsing C:\Users\sx\ws\mybatis_lg\ mybatis_four_source\mybatis-3-mybatis-3.5.5\mybatis-3-mybatis-3.5.5\target\pdf\site.tmp\xdoc\getting-started.xml: Error parsing the model: only whitespace content allowed before sta rt tag and not \ufeff (position: COMMENT seen ...rning permissions and\n limitations under the License.\n\n-->\n\ufeff... @18:2) -> [Help 1] [ERROR] [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch. [ERROR] Re-run Maven using the -X switch to enable full debug logging. [ERROR] [ERROR] For more information about the errors and possible solutions, please read the following articles: [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException

    解决:

    	打开pom.xml 文件注释掉 maven-pdf-plugin 插件(可以选择把项目导入到idea中进行相关内容的删除,或者直接使用文本编辑工具进行删除)
    

    在这里插入图片描述

    重新install一次

    	此时再次install,看到BUILD SUCCESS  代表install成功
    
    C:\Users\sx\ws\mybatis_lg\mybatis_four_source\mybatis-3-mybatis-3.5.5\mybatis-3-mybatis-3.5.5>mvn clean install -Dmaven.test.skip=true
    [INFO] Scanning for projects...
    [INFO]
    [INFO] ------------------------< org.mybatis:mybatis >-------------------------
    [INFO] Building mybatis 3.5.5
    [INFO] --------------------------------[ jar ]---------------------------------
    [INFO]
    [INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ mybatis ---
    [INFO] Deleting C:\Users\sx\ws\mybatis_lg\mybatis_four_source\mybatis-3-mybatis-3.5.5\mybatis-3-mybatis-3.5.5\target
    [INFO]
    [INFO] --- maven-enforcer-plugin:3.0.0-M1:enforce (enforce-java) @ mybatis ---
    [INFO]
    [INFO] --- jacoco-maven-plugin:0.8.5:prepare-agent (prepare-agent) @ mybatis ---
    [INFO] argLine set to -javaagent:C:\\Users\\sx\\sx_tools\\local_maven_repository\\org\\jacoco\\org.jacoco.agent\\0.8.5\\org.jacoco.agent-0.8.5-runtime.jar=destfile=C:\\Users\\sx\\ws
    \\mybatis_lg\\mybatis_four_source\\mybatis-3-mybatis-3.5.5\\mybatis-3-mybatis-3.5.5\\target\\jacoco.exec,excludes=org.apache.ibatis.ognl.*:org.apache.ibatis.javassist.*
    [INFO]
    [INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ mybatis ---
    [INFO] Using 'UTF-8' encoding to copy filtered resources.
    [INFO] Copying 2 resources to META-INF
    [INFO] Copying 4 resources
    [INFO]
    [INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ mybatis ---
    [INFO] Compiling 384 source files to C:\Users\sx\ws\mybatis_lg\mybatis_four_source\mybatis-3-mybatis-3.5.5\mybatis-3-mybatis-3.5.5\target\classes
    [INFO] /C:/Users/sx/ws/mybatis_lg/mybatis_four_source/mybatis-3-mybatis-3.5.5/mybatis-3-mybatis-3.5.5/src/main/java/org/apache/ibatis/executor/keygen/Jdbc3KeyGenerator.java: 某些输
    入文件使用或覆盖了已过时的 API。
    [INFO] /C:/Users/sx/ws/mybatis_lg/mybatis_four_source/mybatis-3-mybatis-3.5.5/mybatis-3-mybatis-3.5.5/src/main/java/org/apache/ibatis/executor/keygen/Jdbc3KeyGenerator.java: 有关详
    细信息, 请使用 -Xlint:deprecation 重新编译。
    [INFO] /C:/Users/sx/ws/mybatis_lg/mybatis_four_source/mybatis-3-mybatis-3.5.5/mybatis-3-mybatis-3.5.5/src/main/java/org/apache/ibatis/reflection/MetaObject.java: 某些输入文件使用了
    未经检查或不安全的操作。
    [INFO] /C:/Users/sx/ws/mybatis_lg/mybatis_four_source/mybatis-3-mybatis-3.5.5/mybatis-3-mybatis-3.5.5/src/main/java/org/apache/ibatis/reflection/MetaObject.java: 有关详细信息, 请使
    用 -Xlint:unchecked 重新编译。
    [INFO]
    [INFO] --- license-maven-plugin:3.0:format (default) @ mybatis ---
    [INFO] Updating license headers...
    [INFO]
    [INFO] --- animal-sniffer-maven-plugin:1.17:check (check-java-compat) @ mybatis ---
    [INFO] Checking unresolved references to org.codehaus.mojo.signature:java18:1.0
    [INFO]
    [INFO] --- maven-bundle-plugin:4.1.0:manifest (bundle-manifest) @ mybatis ---
    [INFO]
    [INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ mybatis ---
    [INFO] Not copying test resources
    [INFO]
    [INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ mybatis ---
    [INFO] Not compiling test sources
    [INFO]
    [INFO] --- maven-surefire-plugin:3.0.0-M3:test (default-test) @ mybatis ---
    [INFO] Tests are skipped.
    [INFO]
    [INFO] --- maven-jar-plugin:3.1.1:jar (default-jar) @ mybatis ---
    [INFO] Building jar: C:\Users\sx\ws\mybatis_lg\mybatis_four_source\mybatis-3-mybatis-3.5.5\mybatis-3-mybatis-3.5.5\target\mybatis-3.5.5.jar
    [INFO]
    [INFO] --- maven-shade-plugin:3.2.1:shade (default) @ mybatis ---
    [INFO] Including ognl:ognl:jar:3.2.14 in the shaded jar.
    [INFO] Including org.javassist:javassist:jar:3.27.0-GA in the shaded jar.
    [INFO] Excluding org.slf4j:slf4j-api:jar:1.7.30 from the shaded jar.
    [INFO] Excluding org.slf4j:slf4j-log4j12:jar:1.7.30 from the shaded jar.
    [INFO] Excluding log4j:log4j:jar:1.2.17 from the shaded jar.
    [INFO] Excluding org.apache.logging.log4j:log4j-core:jar:2.13.3 from the shaded jar.
    [INFO] Excluding org.apache.logging.log4j:log4j-api:jar:2.13.3 from the shaded jar.
    [INFO] Excluding commons-logging:commons-logging:jar:1.2 from the shaded jar.
    [INFO] Excluding cglib:cglib:jar:3.3.0 from the shaded jar.
    [INFO] Excluding org.ow2.asm:asm:jar:7.1 from the shaded jar.
    [INFO] Replacing original artifact with shaded artifact.
    [INFO] Replacing C:\Users\sx\ws\mybatis_lg\mybatis_four_source\mybatis-3-mybatis-3.5.5\mybatis-3-mybatis-3.5.5\target\mybatis-3.5.5.jar with C:\Users\sx\ws\mybatis_lg\mybatis_four_s
    ource\mybatis-3-mybatis-3.5.5\mybatis-3-mybatis-3.5.5\target\mybatis-3.5.5-shaded.jar
    [INFO]
    [INFO] --- maven-site-plugin:3.7.1:attach-descriptor (attach-descriptor) @ mybatis ---
    [INFO] Skipping because packaging 'jar' is not pom.
    [INFO]
    [INFO] --- modernizer-maven-plugin:1.7.1:modernizer (modernizer) @ mybatis ---
    [INFO]
    [INFO] --- maven-install-plugin:3.0.0-M1:install (default-install) @ mybatis ---
    [INFO] Installing C:\Users\sx\ws\mybatis_lg\mybatis_four_source\mybatis-3-mybatis-3.5.5\mybatis-3-mybatis-3.5.5\target\mybatis-3.5.5.jar to C:\Users\sx\sx_tools\local_maven_reposito
    ry\org\mybatis\mybatis\3.5.5\mybatis-3.5.5.jar
    [INFO] Installing C:\Users\sx\ws\mybatis_lg\mybatis_four_source\mybatis-3-mybatis-3.5.5\mybatis-3-mybatis-3.5.5\pom.xml to C:\Users\sx\sx_tools\local_maven_repository\org\mybatis\my
    batis\3.5.5\mybatis-3.5.5.pom
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time:  21.412 s
    [INFO] Finished at: 2021-03-18T17:37:35+08:00
    [INFO] ------------------------------------------------------------------------
    
    C:\Users\sx\ws\mybatis_lg\mybatis_four_source\mybatis-3-mybatis-3.5.5\mybatis-3-mybatis-3.5.5>
    


    二、在idea中导入mybatis-parent和mybatis源码

    在这里插入图片描述


    三、在idea中创建测试项目

    3.1 创建新的Module

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    	此时工程的目录结构如下
    

    在这里插入图片描述

    3.2 引入mybatis相应依赖,此处得引入我们上面构建的mybatis

    在这里插入图片描述在这里插入图片描述

    	测试项目需要的其他的完整依赖
    
    <dependencies>
            <!--mybatis坐标-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.5</version>
            </dependency>
            <!--mysql驱动坐标-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.6</version>
                <scope>runtime</scope>
            </dependency>
            <!--单元测试坐标-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
            <!--⽇志坐标-->
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.12</version>
            </dependency>
    
        </dependencies>
    
    
    	<build>
            <!--修改pom文件中加上此resources即可解决-->
            <resources>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.xml</include>
                    </includes>
                </resource>
            </resources>
        </build>
    
    3.3 新建Mapper接口和对应的映射文件

    在这里插入图片描述

    	AccountMapper.java
    
    public interface AccountMapper {
        List<Account> findAll() throws IOException;
    
        Account findByCondition(Account account) throws IOException;
    
        List<Account> findByIds(int [] ids) throws IOException;
    
        Account findById(int id) throws IOException;
    }
    
    	AccountMapper.xml
    
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.dabing.source.mapper.AccountMapper">
        <select id="findAll" resultType="account">
            select * from Account
        </select>
    
    
        <!--动态sql-->
        <select id="findByCondition" parameterType="account" resultType="account">
            select * from Account
            <where>
                <if test="id!=0">
                    and id=#{id}
                </if>
                <if test="username!=null">
                    and username=#{username}
                </if>
            </where>
        </select>
    
        <!--动态sql:循环执⾏sql的拼接操作-->
        <select id="findByIds" parameterType="list" resultType="account">
            select * from Account
            <where>
                <foreach collection="array" open="id in(" close=")" item="id"
                         separator=",">
                    #{id}
                </foreach>
            </where>
        </select>
    
    
        <!--抽取sql⽚段简化编写-->
        <sql id="selectAccount" >select * from Account</sql>
    
        <select id="findById" parameterType="int" resultType="account">
            <include refid="selectAccount"></include> where id=#{id}
        </select>
    
    
    </mapper>
    
    3.4 新建mybatis配置文件,在resource目录下创建sqlMapConfig.xml文件:

    在这里插入图片描述

    	sqlMapConfig.xml
    
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    
        <!--给实体类的全限定类名给别名-->
        <typeAliases>
            <!--批量起别名:该包下所有的类的本身的类名:别名还不区分大小写-->
            <package name="com.dabing.source.pojo"/>
        </typeAliases>
    
        <!--environments:运行环境-->
        <environments default="development">
            <environment id="development">
                <!--当前事务交由JDBC进行管理-->
                <transactionManager type="JDBC"></transactionManager>
                <!--当前使用mybatis提供的连接池-->
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql:///zdy_mybatis"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root"/>
                </dataSource>
            </environment>
        </environments>
    
        <!--引入映射配置文件-->
        <mappers>
            <package name="com.dabing.source.mapper"/>
        </mappers>
    
    </configuration>
    
    3.5 编写测试类测试
     @Test
        public void test() throws IOException {
            InputStream resourceAsStream =
                    Resources.getResourceAsStream("sqlMapConfig.xml");
            SqlSessionFactory sqlSessionFactory = new
                    SqlSessionFactoryBuilder().build(resourceAsStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
    
            //获得MyBatis框架⽣成的UserMapper接⼝的实现类
            AccountMapper accountMapper = sqlSession.getMapper(AccountMapper.class);
            List<Account> accountMapperAll = accountMapper.findAll();
    
            for (Account account : accountMapperAll) {
                System.out.println(account);
            }
            sqlSession.close();
        }
    
    	执行:报错
    

    在这里插入图片描述

    	解决:
    

    在这里插入图片描述

    	再次执行:执行成功
    

    在这里插入图片描述

    展开全文
  • 搞懂源码就是爽啊 mybatis是一款持久性的ORM框架,目的在于把数据库中的表中的信息转换成对象供我们操作,也就是说我们对数据库的操作有了mybatis可以转变为对对象的操作。 mybatis是怎么实现的呢? 要记到一点...

    emmm时隔一个多月没写博客了,我终于还是没忍住对mybatis这个框架下手了哈哈哈哈。搞懂源码就是爽啊,本文大致脉络基于下图分析

    在这里插入图片描述

    mybatis是一款持久性的ORM框架,目的在于把数据库中的表中的信息转换成对象供我们操作,也就是说我们对数据库的操作有了mybatis可以转变为对对象的操作。

    • mybatis是怎么实现的呢?

    要记到一点市面上所有的ORM框架无论如何都离不开JDBC操作,我们所谓的mybatis也好hibernate也罢其实本质都是对JDBC的包装而已。

    先来回顾一下传统的JDBC操作步骤

    1. 加载驱动
    2. 获取连接
    3. 创建statement对象
    4. statement参数填充
    5. 执行查询(更新、删除)操作
    6. 从resultSet中获取结果集

    在这里插入图片描述

    /**
         * 使用JDBC连接并操作mysql数据库
         */
        public static void main(String[] args) {
            // 数据库驱动类名的字符串
            String driver = "com.mysql.cj.jdbc.Driver";
            // 数据库连接串
            String url = "url";
            // 用户名
            String username = "username";
            // 密码
            String password = "password";
            Connection conn = null;
            Statement stmt = null;
            ResultSet rs = null;
            try {
                // 1、加载数据库驱动( 成功加载后,会将Driver类的实例注册到DriverManager类中)
                Class.forName(driver);
                // 2、获取数据库连接
                conn = DriverManager.getConnection(url, username, password);
                // 3、获取数据库操作对象
                stmt = conn.createStatement();
                // 4、定义操作的SQL语句
                String sql = "select * from user where id = 1";
                // 5、执行数据库操作
                rs = stmt.executeQuery(sql);
                // 6、获取并操作结果集
                while (rs.next()) {
                    System.out.println(rs.getInt("id"));
                    System.out.println(rs.getString("name"));
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 7、关闭对象,回收数据库资源
                if (rs != null) { //关闭结果集对象
                    try {
                        rs.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (stmt != null) { // 关闭数据库操作对象
                    try {
                        stmt.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (conn != null) { // 关闭数据库连接对象
                    try {
                        if (!conn.isClosed()) {
                            conn.close();
                        }
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    

    研究mybatis源码入口的选择

    如果从mybatisPlus项目分析mybatis的源码写如下demo即可直接debug了,毕竟springboot+mybatisPlus项目给我简化了很多配置,很多细节可能分析不到了,个人建议把mybatis源码克隆到本地,然后进行后续的源码研究。

     	@Autowired
        UserMapper userMapper;
        @Test
        public void test5() {
            User user = userMapper.queryManyparam("zzh", 1);
            User user2 = userMapper.queryManyparam("zzh", 1);
            //俩个不同的session,导致一级缓存失效
            System.out.println(user == user2);
        }
    

    原生mybatis操作数据库

    1. 初始化sqlSessionFactory()
    2. 利用sqlSessionFactory获取对应的mapper
    3. 利用mapper对数据库进行查询、更新、删除操作
      private Configuration configuration;
      private JdbcTransaction jdbcTransaction;
      private Connection connection;
      private Reader resourceAsReader;
      private SqlSessionFactory sqlSessionFactory;
      
      public void init() throws SQLException, IOException {
        connection = DriverManager.getConnection("url", "username", "password");
        resourceAsReader = Resources.getResourceAsReader("mybatis.xml");
        jdbcTransaction = new JdbcTransaction(connection);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsReader);
        configuration = sqlSessionFactory.getConfiguration();
      }
    
      /**
       * 相同的sql只会编译处理一次
       */
      @Test
      public void b() throws IOException, SQLException {
        init();
        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE);
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        System.out.println(mapper.query(1));
        System.out.println(mapper.query(1));
      }
    

    更加原生的mybatis操作数据库

    • 直接使用执行器操作数据库(simpleExecutor、cacheExecutor、reuseExecutor)
    • simpleExecutor:每次都会创建一个新的prepareStatement对象
    • cacheExecutor:针对mybatis二级缓存设计的执行器
    • reuseExecutor:相同的sql只会进行一次预处理,也就是说会进行prepareStatement的复用
      @Test
      public void a() throws SQLException, IOException {
        init();
        ReuseExecutor reuseExecutor = new ReuseExecutor(this.configuration, jdbcTransaction);
        MappedStatement mappedStatement = this.configuration.getMappedStatement("zzhTest.mybatis.mapper.UserMapper.queryManyparam");
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("id",1);
        hashMap.put("name","zzh");
        List<Object> objects1 = reuseExecutor.doQuery(mappedStatement, hashMap, RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, mappedStatement.getBoundSql(1));
        List<Object> objects2 = reuseExecutor.doQuery(mappedStatement, hashMap, RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, mappedStatement.getBoundSql(1));
        System.out.println(objects1.get(0));
        System.out.println(objects1.get(0) == objects2.get(0));
      }
    

    预处理次数的体现:控制台 Preparing: select * from user where id = ? 出现的次数即为预处理的次数
    在这里插入图片描述

    原生mybatis操作数据库源码分析

    废话不多说我这里直接用原生的mybatis debug源码了(其实本质都是一样的咯),如下图片中的代码
    在这里插入图片描述
    我们从sqlSession中获取到的mapper对象本质是一个代理对象,debug进去newInstance()看是如何创建我们的mapper代理对象的?

    在这里插入图片描述

    把defultSqlSession、目标对象实现的接口类、methodCache包装成一个InvocationHandler对象,继而利用jdk代理生成对应的代理对象
    在这里插入图片描述

    小结:通过sqlSession.getMapper(UserMapper.class)获取到的是一个通过jdk代理生成的代理对象

    分析mybatis查询(select)语句执行操作

    mapper中的查询语句操作如下
    在这里插入图片描述

    直接debug mapper.query(1)来到这里。根据对数据库不同的操作有相应的分支进行处理,由于我们是查询操作来到select分支
    在这里插入图片描述

    • select分支做了什么事情?
    1. 维护好paramName与参数paramValue的关系,包装成一个map。怎么解析的?下文有讲哦

    例如拿如下代码分析就是维护@Param(“id”) 中的id 与 1 的关系,此时并没有解析sql语句中的id哦

    @Select("select * from user where id = #{id}")
    User query(@Param("id") Integer Uid);
    query(1);
    

    在这里插入图片描述
    2. 根据这个param+查询方法的全路径限定名,进行数据库的查询操作(result=sqlSession.selectOne(command.getName(), param);)。怎么查询?下文有讲哦
    在这里插入图片描述

    convertArgsToSqlCommandParam(参数解析过程)

    public Object getNamedParams(Object[] args) {
        final int paramCount = names.size();
        if (args == null || paramCount == 0) {
          return null;
        } else if (!hasParamAnnotation && paramCount == 1) {
          Object value = args[names.firstKey()];
          return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
        } else {
          final Map<String, Object> param = new ParamMap<>();
          int i = 0;
          /**
           * 遍历所有的@params()中的参数name
           */
          for (Map.Entry<Integer, String> entry : names.entrySet()) {
            /**
             * args:方法中传入的参数value数组
             * entry.getValue():@params()中的参数name
             */
            param.put(entry.getValue(), args[entry.getKey()]);
            /**
             * add generic param names (param1, param2, ...)
             */
            final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
            if (!names.containsValue(genericParamName)) {
              param.put(genericParamName, args[entry.getKey()]);
            }
            i++;
          }
          /**
           * 建立paramName与paramValue的关系
           * void query(@param(id)int id,@param(name)String name)
           * query(1,"zzh")
           * 例如:id-1、name-zzh
           */
          return param;
        }
      }
    

    看源码可能有点绕,不过我注释也写了很全哦,不懂没关系再来梳理一遍参数解析过程。mybatis中有如下俩个map

    1. args:保存的是我们的查询方法的参数value值。例如(1,2,3)
    2. names:保存的是我们查询方法的参数name值。例如(id,name,age)

    由于下标都是一一对应的,通过args[names.getKey()]就可以获取到对应参数name的value值了。通过这样一个个维护一下关系,最终的param就形成了以参数name为key、以参数value为value的关系的一个map

    • 注意:mybatis显示的会加上例如param1-value这种类型的映射

    在这里插入图片描述
    在这里插入图片描述

    selectOne(查询过程核心)

    我们debug进去会来到这里,先获取对应的MappedStatement,然后走cacheExecutor执行器中的操作
    在这里插入图片描述

    mybatis使用的是一种装饰器的模式,在真正的查询数据库之前会依次从二级缓存(SynchronizedCache)、一级缓存(localCache)中获取数据,如果还获取不到才会走查询数据库的逻辑

    点进上图的executor.query()会来到下图的cacheExecutor中的query方法,在此之前cacheExecutor还干了俩件事

    1. 将当前查询sql相关的信息包装成一个boundSql对象
    2. 创建一个cacheKey。cacheKey = statementId+sql+sql参数+cacheId

    在这里插入图片描述

    cacheExcutor中的核心方法query()

    • 查询二级缓存的前提
      1. 我们的项目中开启了二级缓存
      2. 我们mapper类中对应的查询方法上没有加 @Options(useCache = false)
    • 没有开启二级缓存:直接走cacheExecutor中的装饰器simpleExecutor.query(),
    • 开启了正常的二级缓存相关的配置:会先判断是否需要清空二级缓存,然后从缓存管理器中的二级缓存中获取缓存,获取不到的话才会查询数据库。
      @Override
      public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
          throws SQLException {
        /**
         * 获取二级缓存:SynchronizedCache
         */
        Cache cache = ms.getCache();
        if (cache != null) {
          /**
           * 尝试清空二级缓存(标记 clearOnCommit = true)
           */
          flushCacheIfRequired(ms);
          /**
           * 可以设置 @Options(useCache = false) 关闭查询二级缓存
           * resultHandler = Executor.NO_RESULT_HANDLER = null
           */
          if (ms.isUseCache() && resultHandler == null) {
            ensureNoOutParams(ms, boundSql);
            @SuppressWarnings("unchecked")
            /**
             * 从TransactionalCacheManager中获取二级缓存中的数据
             */
              List<E> list = (List<E>) tcm.getObject(cache, key);
            if (list == null) {
              /**
               * 二级缓存中获取不到数据,进行查询数据库操作
               * delegate:SimpleExecutor
               */
              list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
              /**
               * 把数据填充进暂存区中的entriesToAddOnCommit的这个map里面
               * 暂存区:TransactionalCache
               */
              tcm.putObject(cache, key, list); // issue #578 and #116
            }
            return list;
          }
        }
        return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      }
    

    从上面这段代码可以知道我们平常使用如下配置时的生效时机以及在mybatis中的源码体现对应在哪

    • 如何配置开启二级缓存?

    在mapper类上加@CacheNamespace或者在mapper对应的xml文件中加 < cache></ cache>

    • 如何禁用二级缓存?
      @Select("select * from user where id = #{id}")
      @Options(useCache = false)
      User query(@Param("id") Integer Uid);
    
    • 如何每次查询都清空二级缓存(SynchronizedCache)?
      @Options(flushCache = Options.FlushCachePolicy.TRUE)
      @Select("select * from user where id = #{id}")
      User query(@Param("id") Integer Uid);
    

    配置在源码中的体现

    mybatis清空二级缓存的流程时设置 clearOnCommit = true 并且清空entriesToAddOnCommit 这个map

    private final Map<Object, Object> entriesToAddOnCommit;
    
    private void flushCacheIfRequired(MappedStatement ms) {
    	/**
         * 获取二级缓存
         */
        Cache cache = ms.getCache();
        /**
         * 如果开启对应的方法上面开启了 FlushCachePolicy = true
         * 那么此处会清除二级缓存(本质是标记 clearOnCommit = true)
         */
        if (cache != null && ms.isFlushCacheRequired()) {
          tcm.clear(cache);
        }
      }
    
     @Override
      public void clear() {
        clearOnCommit = true;
        entriesToAddOnCommit.clear();
      }
    

    怎么从二级缓存中获取数据?

    接着分析cacheExecutor.query()中的tcm.getObject()这行代码可以知道怎么从二级缓存中获取数据的哦。

    1. 首先从以SynchronizedCache为头的Cache链条依次getObject(cacheKey)获取缓存数据
    2. 然后判断是否缓存中有cacheKey对应的缓存,如果没有,将此key存入一个名字为entriesMissedInCache的map中,目的就是为了防止缓存穿透。相当于把这个人拉入黑名单了。emm可以看我以前写的 缓存问题系列文章
    3. 如果拿到了缓存,还有一个判断是否clearOnCommit被标记为true?这个是为了解决缓存一致性问题的。可以看我以前写的文章简单解决缓存+数据库数据一致性问题
    4. 最终才是返回从缓存中获取到的数据
    public Object getObject(Object key) {
        /**
         * delegate:Cache(以SynchronizedCache为头的Cache链条)
         * key:statementId~sql~参数~id组成
         */
        Object object = delegate.getObject(key);
        /**
         *
         * 防止缓存穿透:二级缓存中没有数据,将key放入entriesMissedInCache(HashSet)中
         * 
         */
        if (object == null) {
          entriesMissedInCache.add(key);
        }
        // issue #146
        /**
         * 如果此时有人清空二级缓存那么clearOnCommit = true
         * 此时的效果也相当于从二级缓存中获取不到数据
         */
        if (clearOnCommit) {
          return null;
        } else {
          return object;
        }
      }
    

    如果是我们第一次debug到这肯定二级缓存中没有数据撒,还记得我们分析到哪了咩,分析到下图这。本质如下箭头标注的query()是调用同一个query()方法,如果没有获取到缓存将会执行箭头标注的方法(simpleExecutor.query(…))。
    在这里插入图片描述

    simpleExecutor中的核心方法query()

    此query()大体思路分析如下:

    1. queryStack == 0 && 设置了FlushCachePolicy.TRUE那么每次都会先清空一级缓存
    2. 每次从一级缓存中获取数据前,记录查询次数(queryStack++)
    3. 查询一级缓存(localCache)
    4. 缓存中获取不到数据(list)查询数据库操作,反之不用
    5. 将延时map(deferredLoads)中的数据添加到list
    6. 设置了一级缓存的域范围为STATEMENT将会在每次查询完毕后清空一级缓存
    @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());
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        /**
         * 如果设置了flushCacheRequired属性将会清空缓存
         * 例如:@Options(flushCache = Options.FlushCachePolicy.TRUE)
         */
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
          /**
           * 清空一级缓存中的数据
           * 一级缓存:localCache
           */
          clearLocalCache();
        }
        List<E> list;
        try {
          queryStack++;
          /**
           * 从一级缓存中获取值
           */
          list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
          if (list != null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
          } else {
            /**
             * 从数据库中查询数据
             */
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
          }
        } finally {
          queryStack--;
        }
        if (queryStack == 0) {
          for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
          }
          /**
           * 清空延时加载map中的数据
           */
          deferredLoads.clear();
          /**
           * 设置了一级缓存的域范围为STATEMENT将会在每次查询完毕后清空一级缓存
           */
          if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            clearLocalCache();
          }
        }
        return list;
      }
    

    思路分析完了来扣细节~~~~~~~~~~~~

    • mybatis是如何清空一级缓存的?

    清空了localOutputParameterCache、localCache(一级缓存)俩个map
    在这里插入图片描述

    • 如何从一级缓存中获取数据?

    localCache.getObject(key),直接根据key来获取

    • mybatis是如何查询数据库的?

    下面通过分析queryFromDatabase()方法一趟究竟

    mybatis查询数据库源码

    点开queryFromDatabase()方法代码如下,注释写的很详细着重看看doQuery()看如何进行查询的

    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        /**
         * localCache.putObject(key, EXECUTION_PLACEHOLDER)和mybatis循环依赖有关
         */
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
          /**
           * 执行BaseExecutor的实现类中的doQuery方法
           */
          list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
          localCache.removeObject(key);
        }
        /**
         * 把查询到的数据放入一级缓存
         */
        localCache.putObject(key, list);
        /**
         * TODO 这个map作用暂时不清楚
         * 如果当前的mappedStatement的类型为CALLABLE,那么会往localOutputParameterCache中put一个数值
         */
        if (ms.getStatementType() == StatementType.CALLABLE) {
          localOutputParameterCache.putObject(key, parameter);
        }
        return list;
      }
    

    mybatis查询数据库必然绕不开jdbc,点进doQuery()来到如下代码。查询和普通的jdbc操作一样分为俩步

    1. 准备一个statement对象
    2. 执行statement.execute()
    @Override
      public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
          Configuration configuration = ms.getConfiguration();
          /**
           * 创建合适的statementHandler,主要研究prepareStatementHandler
           */
          StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
          /**
           * 利用statementHandler创建对应的statement对象
           */
          stmt = prepareStatement(handler, ms.getStatementLog());
          return handler.query(stmt, resultHandler);
        } finally {
          /**
           * 关闭statement
           */
          closeStatement(stmt);
        }
      }
    

    准备一个statement代码分为如下三步:mybatis中的prepareStatement()和我们熟悉的jdbc操作差不多,亦是如此就是多了几层包装而已。

    1. 准备连接
    2. 创建statement
    3. 为statement填充参数
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        /**
         * 获取连接
         */
        Connection connection = getConnection(statementLog);
        /**
         * 实例化prepareStatement
         */
        stmt = handler.prepare(connection, transaction.getTimeout());
        /**
         * 为prepareStatement 填充参数,涉及到sql参数解析
         */
        handler.parameterize(stmt);
        return stmt;
      }
    

    准备statement第二步:源码就是封装了jdbc而已,就不贴代码贴几张图片了(本质都是connection.prepareStatement()),以下图片是的代码
    在这里插入图片描述

    在这里插入图片描述

    准备statement第三步:填充statement中的参数,将我们的查询条件塞入sql语句中,即 select * from user where id = #{id} 转换成 select * from user where id = 1 的过程
    在这里插入图片描述

    public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
          /**
           * 挨个设置参数
           */
          for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            if (parameterMapping.getMode() != ParameterMode.OUT) {
              Object value;
              /**
               * select * from user where id = #{id}
               * 获取#{id}中的name,例如:这种情况下面获取到的propertyName = id
               */
              String propertyName = parameterMapping.getProperty();
              if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
                value = boundSql.getAdditionalParameter(propertyName);
              } else if (parameterObject == null) {
                value = null;
    
              } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                value = parameterObject;
              } else {
                /**
                 * 参数对象包装成一个MetaObject对象
                 * parameterObject:即使我们sql中只有一个参数,mybatis也会默认额外生成param1这种
                 */
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                /**
                 * 获取参数的具体值
                 */
                value = metaObject.getValue(propertyName);
              }
              TypeHandler typeHandler = parameterMapping.getTypeHandler();
              JdbcType jdbcType = parameterMapping.getJdbcType();
              if (value == null && jdbcType == null) {
                jdbcType = configuration.getJdbcTypeForNull();
              }
              try {
                /**
                 * 本质就是调用prepareStatement.setInt(0,value)
                 */
                typeHandler.setParameter(ps, i + 1, value, jdbcType);
              } catch (TypeException | SQLException e) {
                throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
              }
            }
          }
        }
      }
    

    上面的代码可能有点绕,现在重新理一遍思路。确定一个ps.setInt(i, value)操作分为三步:

    1. 获取对应占位符上的value
    2. 型获取填充占位符的下标 i
    3. 获取对应占位符上的参数类型
    4. 然后才是开始填充

    第一步确定流程:由于我们一开始封装的boundSql对象包括了#{id} 的相关参数名称信息,且我们一开始就维护了一个map(params中维护了key:id、value :1的关系),我们通过解析#{ id } ,然后以id为key,从params中获取对应的value。
    第二步确定流程:由于里面是一个for循环,例如select * from user where id = #{1} and name = “zzh”。第一次填充#{ id } ,i = 0,遍历到填充 #{ name }时,对应的 i = 1。
    第三步确定流程:直接从parameterMapping中可以获取到参数类型

    到此一个statement就成功创建好了接下来就是开始执行了execute操作了
    在这里插入图片描述

    查询结果集处理

    来到handleResultSets()方法,观察到无论走哪个分支都会调用其中的handleResultSet()方法
    在这里插入图片描述
    在这里插入图片描述
    来到handleResultSet()方法,观察无论如何都会调用handleRowValues()方法在这里插入图片描述

    来到handleRowValues()方法,这里如果结果集只是简单的属性映射将会走简单结果集处理逻辑,如果存在嵌套结果集那么会走嵌套结果集的逻辑
    在这里插入图片描述

    简单结果集处理

    针对结果一行行进行处理,本质创建一个空的行接收对象封装成metaObject对象,然后基于metaObject对象进行属性填充。
    在这里插入图片描述

    在这里插入图片描述

    自动属性填充

    获取对应列的值(rs.getInt(“id”)),然后把值填充到metaObject对应的property上面。

    在这里插入图片描述

    手动属性填充(applyPropertyMappings)

    1. 遍历所有的属性字段,先获取对应字段的value然后把value填充进metaObject中。
    private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
        throws SQLException {
        final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
        boolean foundValues = false;
        final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
        /**
         * 遍历所有的属性字段进行属性填充
         */
        for (ResultMapping propertyMapping : propertyMappings) {
          String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
          if (propertyMapping.getNestedResultMapId() != null) {
            // the user added a column attribute to a nested result map, ignore it
            column = null;
          }
          if (propertyMapping.isCompositeResult()
            || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
            || propertyMapping.getResultSet() != null) {
            /**
             * 获取要填充的某个属性字段的value值
             */
            Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
            // issue #541 make property optional
            final String property = propertyMapping.getProperty();
            if (property == null) {
              continue;
            }
            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')
              metaObject.setValue(property, value);
            }
          }
        }
        return foundValues;
      }
    
    获取属性值的过程(getPropertyMappingValue)

    按照属性类型的不同分别对应三种不同的获取方式

    1. 获取子查询类型属性
    2. 获取resultset类型的结果值
    3. 获取简单属性
    private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
        throws SQLException {
        /**
         * 存在子查询,先走嵌套子查询逻辑,进行相应的子查询操作,查到相应的子查询中的数值然后返回
         */
        if (propertyMapping.getNestedQueryId() != null) {
          return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
        }
        /**
         * 存在ResultSet类型的结果映射,进行相应的属性填充操作
         */
        else if (propertyMapping.getResultSet() != null) {
          addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?
          return DEFERRED;
        }
        /**
         * 简单单属性填充
         */
        else {
          final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
          final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
          return typeHandler.getResult(rs, column);
        }
      }
    

    简单属性值的获取

    int result = rs.getInt(columnName);
    

    获取获取子查询类型属性

    先执行子查询中的逻辑,直至子查询中的简单属性填充完毕,然后返回子查询的结果

    private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
        throws SQLException {
        /**
         * 子查询语句的全路径限定名
         */
        final String nestedQueryId = propertyMapping.getNestedQueryId();
        /**
         * 需要填充属性字段的name
         */
        final String property = propertyMapping.getProperty();
        /**
         * 子查询所有信息包装成的一个MappedStatement对象
         */
        final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
        /**
         * 子查询的参数信息
         */
        final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
        /**
         * 传入子查询的参数value
         */
        final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix);
        Object value = null;
        if (nestedQueryParameterObject != null) {
          final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
          final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
          final Class<?> targetType = propertyMapping.getJavaType();
          /**
           * 如果一级缓存中有数值,且缓存值是一个占位符号,与循环依赖有关
           */
          if (executor.isCached(nestedQuery, key)) {
            /**
             * 把此数据添加到延时加载,为的是解决循环依赖问题
             */
            executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
            value = DEFERRED;
          } else {
            final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
            /**
             * 如果是懒加载执行相应逻辑
             */
            if (propertyMapping.isLazy()) {
              lazyLoader.addLoader(property, metaResultObject, resultLoader);
              value = DEFERRED;
            } else {
              /**
               * 其他情况正常加载,进行查询数据库操作
               */
              value = resultLoader.loadResult();
            }
          }
        }
        return value;
      }
    

    resultLoader.loadResult()会重复这段代码的执行
    在这里插入图片描述
    最终我们不论是简单属性还是子查询属性,都能获取到了,最后也就完成了我们的属性填充了

    在这里插入图片描述

    最终我们获取到了对应的值,然后就可以把值填充进metaObject中了。最终我们的这个metaObject会被包装然后返回。我们最终得到的数据也就是这个MetaObject的包装对象了。

    展开全文
  • 从头到尾手把手教你搭建阅读Mybatis源码的环境(程序员必备技能)

    先放上编译好的mybatis:
    https://github.com/truedei/mybatis-notes

    自己下载哦,还有注解。

    一、下载Mybatis源码

    Mybatis的GitHub开源地址:
    https://github.com/mybatis/mybatis-3/

    进入github官网后打开Releases,可以通过Releases找到最新的或者不同的版本进行下载源码
    image.png

    直接下载Source code
    image.png

    这是解压之后的内容:
    image.png

    下载Mybatis需要一个依赖的项目:
    https://github.com/mybatis/parent/releases

    image.png

    二、配置Maven

    一、简介

    maven是什么?

    二、在Windows下配置Maven环境

    Maven 3.3+需要JDK 1.7或更高版本才能执行

    想要安装Maven需要下载maven压缩包,无需安装,在windows下只需要配置windows环境变量。

    言归正传

    开始!

    本次教程我所使用的各个版本号(希望能和我一样):

    1、JDK 1.8
    
    
    2、Maven3.5.3 (自己下载最新版一般都没有问题)
    
    
    3、win10  64位操作系统
    

    JDK环境,Maven环境省略。这里假设你已经会了。

    我使用的版本号:

    C:\Users\zhenghui>java -version
    java version "1.8.0_172-ea"
    Java(TM) SE Runtime Environment (build 1.8.0_172-ea-b03)
    Java HotSpot(TM) 64-Bit Server VM (build 25.172-b03, mixed mode)
    
    C:\Users\zhenghui>
    C:\Users\zhenghui>
    C:\Users\zhenghui>mvn -version
    Apache Maven 3.5.3 (3383c37e1f9e9b3bc3df5050c29c8aff9f295297; 2018-02-25T03:49:05+08:00)
    Maven home: E:\soft\apache-maven-3.5.3\apache-maven-3.5.3\bin\..
    Java version: 1.8.0_172-ea, vendor: Oracle Corporation
    Java home: C:\Program Files\Java\jdk1.8.0_172\jre
    Default locale: zh_CN, platform encoding: GBK
    OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"
    
    C:\Users\zhenghui>
    

    三、下载Maven

    Maven的官网:

    https://maven.apache.org/

    强烈推荐一定要到官网下载,又不花钱对吧,英语不好不要紧,有道随时都在呢

    把下载好的压缩文件解压出来

    最好放到你自己的安装目录下

    E:\soft\apache-maven-3.5.3\apache-maven-3.5.3

    四、配置Maven环境变量

    右键 “计算机”,选择 “属性”,之后点击 “高级系统设置”,点击"环境变量",来设置环境变量,有以下系统变量需要配置:

    1、新建系统变量 MAVEN_HOME,变量值:E:\soft\apache-maven-3.5.3\apache-maven-3.5.3

    2、编辑系统变量 Path,添加变量值:%MAVEN_HOME%\bin

    测试是否成功

    Microsoft Windows [版本 10.0.17134.648]
    (c) 2018 Microsoft Corporation。保留所有权利。
    
    C:\Users\zhenghui>mvn -v
    Apache Maven 3.5.3 (3383c37e1f9e9b3bc3df5050c29c8aff9f295297; 2018-02-25T03:49:05+08:00)
    Maven home: E:\soft\apache-maven-3.5.3\apache-maven-3.5.3\bin\..
    Java version: 1.8.0_172-ea, vendor: Oracle Corporation
    Java home: C:\Program Files\Java\jdk1.8.0_172\jre
    Default locale: zh_CN, platform encoding: GBK
    OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"
    
    C:\Users\zhenghui>
    

    五、其他注意事项

    1、Maven仓库

    在 Maven 的术语中,仓库是一个位置(place)。

    Maven 仓库是项目中依赖的第三方库,这个库所在的位置叫做仓库。

    在 Maven 中,任何一个依赖、插件或者项目构建的输出,都可以称之为构件。

    Maven 仓库能帮助我们管理构件(主要是JAR),它就是放置所有JAR文件(WAR,ZIP,POM等等)的地方。

    Maven 仓库有三种类型:

    • 本地(local)
    • 中央(central)
    • 远程(remote)

    详细介绍请点击:http://www.runoob.com/maven/maven-repositories.html

    1、修改Maven的本地仓库

    为什么修改:

    因为本地仓库默认在C盘,所以不建议默认在C盘,以后C盘会越来越大的。

    1)、打开settings.xml文件

    2)、修改为下图所示的:

    2、修改Maven的中央仓库

    我使用的是阿里云的:

    在上面打开的文件里找到mirrors添加上就行

    <mirrors>
        <mirror>
          <id>alimaven</id>
          <name>aliyun maven</name>
          <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
          <mirrorOf>central</mirrorOf>        
        </mirror>
    </mirrors>
    

    六、settings.xml文件全部

    过滤掉注释的:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
    
    <localRepository>E:\soft\apache-maven-3.5.3\local</localRepository>
    
      <pluginGroups>
       
      </pluginGroups>
    
      <proxies>
      
      </proxies>
    
      <servers>
       
      </servers>
    
      <mirrors>
      
        <mirror>  
          <id>aliyun</id>  
          <name>aliyun</name>  
          <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
          <mirrorOf>central</mirrorOf> 
        </mirror>  
      </mirrors>
    
      <profiles>
    
      </profiles>
    
    </settings>
    

    未过滤注释的:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <!--
    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at
    
        http://www.apache.org/licenses/LICENSE-2.0
    
    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.
    -->
    
    <!--
     | This is the configuration file for Maven. It can be specified at two levels:
     |
     |  1. User Level. This settings.xml file provides configuration for a single user,
     |                 and is normally provided in ${user.home}/.m2/settings.xml.
     |
     |                 NOTE: This location can be overridden with the CLI option:
     |
     |                 -s /path/to/user/settings.xml
     |
     |  2. Global Level. This settings.xml file provides configuration for all Maven
     |                 users on a machine (assuming they're all using the same Maven
     |                 installation). It's normally provided in
     |                 ${maven.conf}/settings.xml.
     |
     |                 NOTE: This location can be overridden with the CLI option:
     |
     |                 -gs /path/to/global/settings.xml
     |
     | The sections in this sample file are intended to give you a running start at
     | getting the most out of your Maven installation. Where appropriate, the default
     | values (values used when the setting is not specified) are provided.
     |
     |-->
    <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
      <!-- localRepository
       | The path to the local repository maven will use to store artifacts.
       |
       | Default: ${user.home}/.m2/repository
      <localRepository>/path/to/local/repo</localRepository>
      本地仓库配置
      -->
    <localRepository>E:\soft\apache-maven-3.5.3\local</localRepository>
    
      <!-- interactiveMode
       | This will determine whether maven prompts you when it needs input. If set to false,
       | maven will use a sensible default value, perhaps based on some other setting, for
       | the parameter in question.
       |
       | Default: true
      <interactiveMode>true</interactiveMode>
      -->
    
      <!-- offline
       | Determines whether maven should attempt to connect to the network when executing a build.
       | This will have an effect on artifact downloads, artifact deployment, and others.
       |
       | Default: false
      <offline>false</offline>
      -->
    
      <!-- pluginGroups
       | This is a list of additional group identifiers that will be searched when resolving plugins by their prefix, i.e.
       | when invoking a command line like "mvn prefix:goal". Maven will automatically add the group identifiers
       | "org.apache.maven.plugins" and "org.codehaus.mojo" if these are not already contained in the list.
       |-->
      <pluginGroups>
        <!-- pluginGroup
         | Specifies a further group identifier to use for plugin lookup.
        <pluginGroup>com.your.plugins</pluginGroup>
        -->
      </pluginGroups>
    
      <!-- proxies
       | This is a list of proxies which can be used on this machine to connect to the network.
       | Unless otherwise specified (by system property or command-line switch), the first proxy
       | specification in this list marked as active will be used.
       |-->
      <proxies>
        <!-- proxy
         | Specification for one proxy, to be used in connecting to the network.
         |
        <proxy>
          <id>optional</id>
          <active>true</active>
          <protocol>http</protocol>
          <username>proxyuser</username>
          <password>proxypass</password>
          <host>proxy.host.net</host>
          <port>80</port>
          <nonProxyHosts>local.net|some.host.com</nonProxyHosts>
        </proxy>
        -->
      </proxies>
    
      <!-- servers
       | This is a list of authentication profiles, keyed by the server-id used within the system.
       | Authentication profiles can be used whenever maven must make a connection to a remote server.
       |-->
      <servers>
        <!-- server
         | Specifies the authentication information to use when connecting to a particular server, identified by
         | a unique name within the system (referred to by the 'id' attribute below).
         |
         | NOTE: You should either specify username/password OR privateKey/passphrase, since these pairings are
         |       used together.
         |
        <server>
          <id>deploymentRepo</id>
          <username>repouser</username>
          <password>repopwd</password>
        </server>
        -->
    
        <!-- Another sample, using keys to authenticate.
        <server>
          <id>siteServer</id>
          <privateKey>/path/to/private/key</privateKey>
          <passphrase>optional; leave empty if not used.</passphrase>
        </server>
        -->
      </servers>
    
      <!-- mirrors
       | This is a list of mirrors to be used in downloading artifacts from remote repositories.
       |
       | It works like this: a POM may declare a repository to use in resolving certain artifacts.
       | However, this repository may have problems with heavy traffic at times, so people have mirrored
       | it to several places.
       |
       | That repository definition will have a unique id, so we can create a mirror reference for that
       | repository, to be used as an alternate download site. The mirror site will be the preferred
       | server for that repository.
       |-->
      <mirrors>
        <!-- mirror
         | Specifies a repository mirror site to use instead of a given repository. The repository that
         | this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
         | for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
         |
        <mirror>
          <id>mirrorId</id>
          <mirrorOf>repositoryId</mirrorOf>
          <name>Human Readable Name for this Mirror.</name>
          <url>http://my.repository.com/repo/path</url>
        </mirror>
         -->
        <mirror>  
          <id>aliyun</id>  
          <name>aliyun</name>  
          <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
          <mirrorOf>central</mirrorOf> 
        </mirror>  
      </mirrors>
    
      <!-- profiles
       | This is a list of profiles which can be activated in a variety of ways, and which can modify
       | the build process. Profiles provided in the settings.xml are intended to provide local machine-
       | specific paths and repository locations which allow the build to work in the local environment.
       |
       | For example, if you have an integration testing plugin - like cactus - that needs to know where
       | your Tomcat instance is installed, you can provide a variable here such that the variable is
       | dereferenced during the build process to configure the cactus plugin.
       |
       | As noted above, profiles can be activated in a variety of ways. One way - the activeProfiles
       | section of this document (settings.xml) - will be discussed later. Another way essentially
       | relies on the detection of a system property, either matching a particular value for the property,
       | or merely testing its existence. Profiles can also be activated by JDK version prefix, where a
       | value of '1.4' might activate a profile when the build is executed on a JDK version of '1.4.2_07'.
       | Finally, the list of active profiles can be specified directly from the command line.
       |
       | NOTE: For profiles defined in the settings.xml, you are restricted to specifying only artifact
       |       repositories, plugin repositories, and free-form properties to be used as configuration
       |       variables for plugins in the POM.
       |
       |-->
      <profiles>
        <!-- profile
         | Specifies a set of introductions to the build process, to be activated using one or more of the
         | mechanisms described above. For inheritance purposes, and to activate profiles via <activatedProfiles/>
         | or the command line, profiles have to have an ID that is unique.
         |
         | An encouraged best practice for profile identification is to use a consistent naming convention
         | for profiles, such as 'env-dev', 'env-test', 'env-production', 'user-jdcasey', 'user-brett', etc.
         | This will make it more intuitive to understand what the set of introduced profiles is attempting
         | to accomplish, particularly when you only have a list of profile id's for debug.
         |
         | This profile example uses the JDK version to trigger activation, and provides a JDK-specific repo.
        <profile>
          <id>jdk-1.4</id>
    
          <activation>
            <jdk>1.4</jdk>
          </activation>
    
          <repositories>
            <repository>
              <id>jdk14</id>
              <name>Repository for JDK 1.4 builds</name>
              <url>http://www.myhost.com/maven/jdk14</url>
              <layout>default</layout>
              <snapshotPolicy>always</snapshotPolicy>
            </repository>
          </repositories>
        </profile>
        -->
    
        <!--
         | Here is another profile, activated by the system property 'target-env' with a value of 'dev',
         | which provides a specific path to the Tomcat instance. To use this, your plugin configuration
         | might hypothetically look like:
         |
         | ...
         | <plugin>
         |   <groupId>org.myco.myplugins</groupId>
         |   <artifactId>myplugin</artifactId>
         |
         |   <configuration>
         |     <tomcatLocation>${tomcatPath}</tomcatLocation>
         |   </configuration>
         | </plugin>
         | ...
         |
         | NOTE: If you just wanted to inject this configuration whenever someone set 'target-env' to
         |       anything, you could just leave off the <value/> inside the activation-property.
         |
        <profile>
          <id>env-dev</id>
    
          <activation>
            <property>
              <name>target-env</name>
              <value>dev</value>
            </property>
          </activation>
    
          <properties>
            <tomcatPath>/path/to/tomcat/instance</tomcatPath>
          </properties>
        </profile>
        -->
      </profiles>
    
      <!-- activeProfiles
       | List of profiles that are active for all builds.
       |
      <activeProfiles>
        <activeProfile>alwaysActiveProfile</activeProfile>
        <activeProfile>anotherAlwaysActiveProfile</activeProfile>
      </activeProfiles>
      -->
    </settings>
    

    祝你学习愉快!

    三、IDEA导入Mybatis源码

    务必提前配置好maven,可参考上面的内容,也可以参考,我写的这篇文章:
    https://blog.csdn.net/qq_17623363/article/details/88858907

    打开IDEA后,点击Open or Import
    image.png

    或者:如果已经打开了其他的项目,可以点击File—>Open
    image.png

    选择自己解压的位置
    image.png

    导入之后就别动了哦。

    初次导入可能会不能立马显示出来全部的内容,例如我的:
    image.png

    我们看到下面正在下载相关的maven依赖,所以不用管的。我们现在所做的就是“等待”
    image.png

    稍等片刻,如果网络没问题的话,一会就下载好:下面这是正常的界面,可以看到1号位置的进度条没了,就可以了。

    如果你的maven下载的慢,那么可能需要配置一下maven的阿里云仓库地址或者网易的等等,自行百度即可,或者参考我写的maven环境的配置。

    image.png

    四、IDEA导入Mybatis依赖的项目

    image.png

    五、编译项目

    (一)先编译Mybatis依赖的项目

    image.png

    (二)再编译Mybatis源码

    1、编译之先关闭代码的检查

    image.png

    image.png

    2、注释掉一个maven-pdf-plugin的依赖

    image.png

    3、修改依赖的mybatis-parent的pom文件位置

    image.png

    4、编译

    image.png

    编译完成之后target目录会有一个jar包:
    image.png

    就代表编译成功了。

    六、使用IDEA创建maven多模块项目并使用Mybatis源码

    (一)初始配置

    New Project
    image.png

    输入项目的名字
    image.png

    这样就创建了一个maven项目
    image.png

    为了不造成困扰,我们把src目录删除
    image.png
    image.png

    还剩下:
    image.png

    然后在pom文件添加:

     <packaging>pom</packaging>
    

    image.png

    (二)创建一个SpringBoot的Model

    image.png

    因为我们要创建SpringBoot项目,所以可以选择Spring Initializr:
    image.png

    image.png

    可以先选上Mybaits依赖、web依赖、Mysql驱动依赖:
    image.png

    Finish
    image.png

    image.png

    创建成功的:
    image.png

    1、在启动之前创建测试接口

    image.png

    package com.truedei;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @SpringBootApplication
    @RequestMapping("/truedei")
    public class TruedeiApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(TruedeiApplication.class, args);
        }
    
    
        @RequestMapping("/test1")
        @ResponseBody
        public String trueDei(){
            return "大家好";
        }
        
    }
    

    2、启动测试

    如果直接启动的话,会出现:
    image.png
    原因是我们引入的Mybatis依赖,并没有去配置JDBC驱动,数据库账号等。

    解决办法:pom文件暂时注释掉Mybatis的依赖

    别忘记刷新哦。
    image.png

    再次启动:
    image.png
    出现上图所示的结果,就启动成功啦。

    3、访问测试

    image.png

    (三)导入编译好的Mybatis源码为此项目的一个Module

    image.png
    选上之前的项目目录即可
    image.png

    image.png

    下面这是成功的界面:
    image.png

    七、测试编译后的Mybatis是否正常可用

    整个目录结构如下:

    image.png

    (一) 配置项目依赖

    此时应该是项目依赖咱们编译后的这个mybatis项目,而不是依赖maven仓库的mybatis

    image.png

    具体步骤如下:
    image.png

    选择自己编译后的mybatis项目:
    image.png
    出现如下图所示的就完事了:
    image.png

    测试是否依赖成功:
    image.png
    **image.png

    (二)创建Mysql数据表

    -- auto-generated definition
    create table student
    (
        id       int auto_increment comment 'id'
            primary key,
        sno      varchar(20) null comment '学号',
        sname    varchar(10) null comment '学生姓名',
        password varchar(20) null comment '密码',
        perms    varchar(20) null comment '权限'
    );
    

    (三)创建Student对象类

    public class Student {
    
        private String id;
        private String sno;
        private String sname;
        private String password;
    
        @Override
        public String toString() {
            return "Student{" +
                    "id='" + id + '\'' +
                    ", sno='" + sno + '\'' +
                    ", sname='" + sname + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    
       //....省略get 和set方法
    }
    

    (四)创建StudentMapper接口

    public interface StudentMapper {
    
        Student selectById(String id);
    
    }
    

    (五)创建StudentMapper.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.truedei.test1.StudentMapper">
    
        <select id="selectById" resultType="com.truedei.test1.Student">
          select * from student where id = #{id}
        </select>
    
    </mapper>
    

    (六)创建mybatis-config.xml文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration  PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC">
                </transactionManager>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://47.105.166.27:3306/test"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
    <!--        <mapper resource="mapper/StudentMapper.xml" url="" class=""/>-->
            <package name="com.truedei.test1"/>
        </mappers>
    </configuration>
    

    (七)创建测试程序

    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    
    import java.io.IOException;
    import java.io.Reader;
    
    public class MyTest {
    
        private static SqlSessionFactory sqlSessionFactory;
    
        public static void main(String[] args) throws IOException{
            //1、创建SqlSessionFactory
            String resource = "mybatis-config.xml";
    //        String resource = "md";
            final Reader reader = Resources.getResourceAsReader(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
            reader.close();
            //2、获取sqlSession
            SqlSession sqlSession = sqlSessionFactory.openSession();
    
            //3、获取mapper
            StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);//动态代理,代理对象
    
            //4、执行数据库操作,并处理结果集
            Student goods = mapper.selectById("2");
            System.out.println(goods);
        }
    }
    

    (八)测试

    如果能正常的读取数据库的数据,说明成功了:
    image.png

    八、可能遇到的问题总结

    1、Cannot enable lazy loading because Javassist is not available

    出现这个的原因是javassist的依赖并没有成功的加载。

    解决办法:

    在你测试的项目中引入即可

            <dependency>
                <groupId>org.javassist</groupId>
                <artifactId>javassist</artifactId>
                <version>3.24.1-GA</version>
            </dependency>
    

    2、NoClassDefFoundError: ognl/PropertyAccessor

    和上面javassist的情况一样,ognl没有加载成功

            <dependency>
                <groupId>ognl</groupId>
                <artifactId>ognl</artifactId>
                <version>3.2.10</version>
            </dependency>
    

    image.png

    这次你学废了吗?

    这次你学废了吗?
    这次你学废了吗?
    这次你学废了吗?

    关注我,继续学。

    展开全文
  • Mybatis源码解析

    2021-01-15 13:49:52
    1.mybatis源码结构分析 2.缓存Cache分析 3.SqlSession分析 4.Interceptor分析

    目录

    1.mybatis源码结构分析

    2.mybatis-spring源码结构分析

    3.缓存Cache分析

    4.SqlSession分析

    4.Interceptor分析

    5.总结

    6.参考文章


    1.mybatis源码结构分析

    通过maven的方式引入mybatis的不同版本的包,其中mybatis包主要是实现ORM机制。

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>${mybatis-version}</version>
    </dependency>

    通过mybatis访问数据库的执行步骤如下:

    1)初始化过程中,完成相关配置解析,建立SqlSessionFactory,用于后续创建SqlSession对象。

    2)当有调用数据库操作时,通过openSession方法,获取一个SqlSession对象。

    3)执行SqlSession的select/update等方法,执行相关SQL语句。

    4)执行SQL语句时,首先根据查询条件和映射语句,构建CacheKey对象,检查缓存中是否有对应的查询结果,如果有直接返回;如果没有就进一步从DB中查询数据。

    5)采用PrepareStatement方式获取数据库中的数据,首先获取数据库链接(一般是从数据库连接池中获取),然后进行预编译处理,再执行sql语句返回ResultSet,通过TypeHandler的对象进行映射解析处理。

    6)完成数据处理,返回SQL结果数据。在执行语句结束,会对SqlSession进行close相关操作。

    类的关键包说明:

    org.apache.ibatis.annotations:这里主要是SQL的相关注解,方便直接在代码层面书写sql语句,可以不依赖sql的xml文件。

    org.apache.ibatis.cache: 这里主要是关于mybatis的缓存相关,mybatis的缓存是session级别的,且通过namespace进行隔离的,缓存是采用HashMap实现的,源码内部底层是org.apache.ibatis.cache.impl.PerpetualCache类用来支撑缓存的实现,其中该模块主要用的设计模式是装饰者模式。

    org.apache.ibatis.datasource: 这里主要是与DataSource数据源相关的,实现了JNDI的数据源工厂;这里包含简易的数据源连接池和非数据源连接池的实现。

    org.apache.ibatis.executor: 这里主要实现对sql的执行处理,实现从sql的进入执行,并返回结果的全过程。

    org.apache.ibatis.plugin: 这里可以实现org.apache.ibatis.plugin.Interceptor接口,来对mybatis的sql执行进行拦截处理,常见的如打印日志,或者做分页拦截等。

    org.apache.ibatis.session: 这里主要是针对mybatis的session进行管理,这里有创建SqlSession的工厂,和Session管理类。

    org.apache.ibatis.transaction: 这里主要用于对数据库的事务进行相关操作,mybatis的事务隔离级别依赖数据库自身的隔离级别。

    org.apache.ibatis.type: 这里主要是针对各种类型的转换的处理,主要是java类型与数据库类型的映射关联管理。

    mybatis源码中,几个关键的类如下:

    org.apache.ibatis.session.SqlSession:数据库操作的会话接口类,对数据库的所有操作都是基于会话相关,通过SqlSessionManager类中的SqlSessionInterceptor实现对事务的commit。

    org.apache.ibatis.cache.Cache: mybatis的缓存接口类,如实现缓存的FIFO,LRU等等都是依赖这个接口的实现。

    org.apache.ibatis.session.Configuration: mybatis的所有配置归属在这个类中,作为mybatis的中心配置。

    org.apache.ibatis.executor.Executor: 该接口是操作数据库的抽象服务,是数据库操作的核心类。

    2.mybatis-spring源码结构分析

    通过maven的方式引入mybatis-spring的不同版本,注意与spring的版本相配套应用,该jar实现了将mybatis集成到Spring框架中。

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>${mybatis-spring-version}</version>
    </dependency>

    该jar中org.mybatis.spring.mapper.MapperScannerConfigurer类是关键,该类中实现了对mybatis和spring的关键集成,且实现了基于接口扫描的方式建立sql相关映射,通过扫描数据库的操作的interface就能实现生成spring的bean对象,减少了很多不必要的重复代码。我们看一下该类的关键说明,如下源码。类中通过basepackage和SqlSessionFactory实现了将对应的SqlSessionFactory与扫描的包的关联,通过org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry方法,在Spring容器的初始化的时候,完成了扫描以及加载相关的操作。

    /**
     * BeanDefinitionRegistryPostProcessor that searches recursively starting from a base package for
     * interfaces and registers them as {@code MapperFactoryBean}. Note that only interfaces with at
     * least one method will be registered; concrete classes will be ignored.
     * <p>
     * This class was a {code BeanFactoryPostProcessor} until 1.0.1 version. It changed to  
     * {@code BeanDefinitionRegistryPostProcessor} in 1.0.2. See https://jira.springsource.org/browse/SPR-8269
     * for the details.
     * <p>
     * The {@code basePackage} property can contain more than one package name, separated by either
     * commas or semicolons.
     * <p>
     * This class supports filtering the mappers created by either specifying a marker interface or an
     * annotation. The {@code annotationClass} property specifies an annotation to search for. The
     * {@code markerInterface} property specifies a parent interface to search for. If both properties
     * are specified, mappers are added for interfaces that match <em>either</em> criteria. By default,
     * these two properties are null, so all interfaces in the given {@code basePackage} are added as
     * mappers.
     * <p>
     * This configurer enables autowire for all the beans that it creates so that they are
     * automatically autowired with the proper {@code SqlSessionFactory} or {@code SqlSessionTemplate}.
     * If there is more than one {@code SqlSessionFactory} in the application, however, autowiring
     * cannot be used. In this case you must explicitly specify either an {@code SqlSessionFactory} or
     * an {@code SqlSessionTemplate} to use via the <em>bean name</em> properties. Bean names are used
     * rather than actual objects because Spring does not initialize property placeholders until after
     * this class is processed. 
     * <p>
     * Passing in an actual object which may require placeholders (i.e. DB user password) will fail. 
     * Using bean names defers actual object creation until later in the startup
     * process, after all placeholder substituation is completed. However, note that this configurer
     * does support property placeholders of its <em>own</em> properties. The <code>basePackage</code>
     * and bean name properties all support <code>${property}</code> style substitution.
     * <p>
     * Configuration sample:
     * <p>
     *
     * <pre class="code">
     * {@code
     *   <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
     *       <property name="basePackage" value="org.mybatis.spring.sample.mapper" />
     *       <!-- optional unless there are multiple session factories defined -->
     *       <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
     *   </bean>
     * }
     * </pre>
     *
     * @author Hunter Presnall
     * @author Eduardo Macarron
     *
     * @see MapperFactoryBean
     * @see ClassPathMapperScanner
     * @version $Id$
     */
    public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
      /**
       * {@inheritDoc}
       * 
       * @since 1.0.2
       */
      @Override
      public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        if (this.processPropertyPlaceHolders) {
          processPropertyPlaceHolders();
        }
    
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        scanner.setAddToConfig(this.addToConfig);
        scanner.setAnnotationClass(this.annotationClass);
        scanner.setMarkerInterface(this.markerInterface);
        scanner.setSqlSessionFactory(this.sqlSessionFactory);
        scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
        scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
        scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
        scanner.setResourceLoader(this.applicationContext);
        scanner.setBeanNameGenerator(this.nameGenerator);
        scanner.registerFilters();
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
      }
    
    }

    因此在Spring中mybatis的配置如下源码,通过配置DataSource,SqlSessionFactory和MapperScannerConfigurer即可完成spring与mybatis的集成。

        <bean id="druidDataSource" destroy-method="close"
              class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${jdbc.driverClassName}" />
            <property name="url" value="${jdbc.url}" />
            <property name="username" value="${jdbc.username}" />
            <property name="password" value="${jdbc.password}" />
            <!-- 最大连接数,缺省为8,建议值实例总连接数/服务数量 -->
            <property name="maxActive" value="${jdbc.pool.maxActive}" />
            <!-- 最小空闲连接,缺省为0,建议和initialSize一致 -->
            <property name="minIdle" value="${jdbc.pool.minIdle}" />
            <!-- 初始连接数,缺省为0,建议值实例总连接数/服务数量*0.2 -->
            <property name="initialSize" value="${jdbc.pool.initialSize}" />
            <!-- 异步Evict的定时检测连接是否可用,关闭无效链接 -->
            <property name="testWhileIdle" value="${jdbc.pool.testWhileIdle}" />
            <!-- Evict线程检测时间,单位毫秒 -->
            <property name="timeBetweenEvictionRunsMillis" value="${jdbc.pool.timeBetweenEvictionRunsMillis}" />
            <!--最大等待时间,当没有可用连接时,连接池等待连接释放的最大时间,超过该时间限制会抛出异常,如果设置-1表示无限等待(默认为无限,调整为2000ms,避免因线程池不够用,而导致请求被无限制挂起)-->
            <property name="maxWait" value="${jdbc.pool.maxWait}"/>
            <!--是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个-->
            <property name="testOnBorrow" value="${jdbc.pool.testOnBorrow}" />
            <property name="validationQuery" value="select 0" />
            <property name="KeepAlive" value="true" />
        </bean>
    
        <!-- mybatis.配置SqlSessionFactory对象 -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!-- 注入数据库连接池 -->
            <property name="dataSource" ref="druidDataSource" />
            <!-- 配置MyBaties全局配置文件:mybatis-config.xml -->
            <property name="configLocation" value="classpath:mybatis-config.xml" />
            <!-- 扫描entity包 使用别名 -->
            <!--		<property name="typeAliasesPackage" value="com.grassland.discover.entity" />-->
            <!-- 扫描sql配置文件:mapper需要的xml文件 -->
            <property name="mapperLocations" value="classpath:mybatis-mapper/*.xml" />
            <property name="plugins">
                <array>
                    <bean class="com.github.pagehelper.PageInterceptor">
                        <property name="properties">
                            <value>
                                helperDialect=mysql
                                reasonable=true
                                supportMethodsArguments=true
                                autoRuntimeDialect=true
                            </value>
                        </property>
                    </bean>
                </array>
            </property>
        </bean>
    
       <!-- mybatis.配置事务管理器 -->
        <bean id="transactionManager"
              class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- 注入数据库连接池 -->
            <property name="dataSource" ref="druidDataSource" />
        </bean>
    
        <!-- mybatis.配置扫描Dao接口包,动态实现Dao接口,注入到spring容器中 -->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <!-- 注入sqlSessionFactory -->
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
            <!-- 给出需要扫描Dao接口包 -->
            <property name="basePackage" value="com.xxx.dao" />
        </bean>
    

    3.缓存Cache分析

    接口类org.apache.ibatis.cache.Cache是Mybatis缓存的实现关键,缓存是基于SqlSession级别的,其中关键实现类是:org.apache.ibatis.cache.impl.PerpetualCache,如源码。该类是基于HashMap实现了sql查询的缓存。

    
    package org.apache.ibatis.cache.impl;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import org.apache.ibatis.cache.Cache;
    import org.apache.ibatis.cache.CacheException;
    
    /**
     * @author Clinton Begin
     */
    public class PerpetualCache implements Cache {
    
      private final String id;
    
      private final Map<Object, Object> cache = new HashMap<>();
    
      public PerpetualCache(String id) {
        this.id = id;
      }
    
      @Override
      public String getId() {
        return id;
      }
    
      @Override
      public int getSize() {
        return cache.size();
      }
    
      @Override
      public void putObject(Object key, Object value) {
        cache.put(key, value);
      }
    
      @Override
      public Object getObject(Object key) {
        return cache.get(key);
      }
    
      @Override
      public Object removeObject(Object key) {
        return cache.remove(key);
      }
    
      @Override
      public void clear() {
        cache.clear();
      }
    
      @Override
      public boolean equals(Object o) {
        if (getId() == null) {
          throw new CacheException("Cache instances require an ID.");
        }
        if (this == o) {
          return true;
        }
        if (!(o instanceof Cache)) {
          return false;
        }
    
        Cache otherCache = (Cache) o;
        return getId().equals(otherCache.getId());
      }
    
      @Override
      public int hashCode() {
        if (getId() == null) {
          throw new CacheException("Cache instances require an ID.");
        }
        return getId().hashCode();
      }
    
    }
    

    关于缓存,mybatis实现了多种缓存策略,均在org.apache.ibatis.cache.decorators包中,该包中的类是采用装饰者模式,将缓存的操作委托到更底层的实现,如上面的PerpetualCache类。常见的有同步缓存SynchronizedCache对缓存的每一个方法都加上了synchronized方法;有LRU缓存LruCache类实现了基于LRU算法操作的缓存,该缓存中主要是基于LinkedHashMap类,重写了removeEldestEntry方法,改写了移除过期的key的策略方式;还有基于队列的FIFO的缓存策略FifoCache,基于CountDownLatch的BlockingCache类实现了阻塞的缓存策略,基于弱引用ReferenceQueue实现的WeakCache类等,非常丰富。

    这里我们就拿一个源码做展示,即LruCache类的源码如下,这里两个点值得关注: 1)setSize方法初始化的时候,将keyMap设置为LinkedHashMap并重写了removeEldestEntry方法,实现LRU算法; 2)在putObject方法(底层LinkedHashMap会调用removeEldestEntry方法,设置eldestKey属性)之后调用cycleKeyList方法,该方法中会进行根据putObject执行的结果,进行决定是否需要从缓存中移除对应的key,如果需要就从缓存中通过委托对象delegate进行remove操作。

    public class LruCache implements Cache {
    
      private final Cache delegate;
      private Map<Object, Object> 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<Object, Object>(size, .75F, true) {
          private static final long serialVersionUID = 4267176411845948333L;
    
          @Override
          protected boolean removeEldestEntry(Map.Entry<Object, Object> 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;
        }
      }
    
    }
    

    4.SqlSession分析

    SqlSession的关键操作类位于org.apache.ibatis.session包中,通过SqlSessionFactory打开数据库SQL操作的相关会话,关键点位于SqlSessionManager类中,该类采用了代理proxy模式,采用了SqlSessionInterceptor类拦截了sql的所有操作,这里有两个非常巧妙的地方:1) 采用ThreadLocal<SqlSession> localSqlSession变量,采用ThreadLocal的形式,保证在同一个线程中采用同一个SqlSession对象进行数据库的操作; 2)在org.apache.ibatis.session.SqlSessionManager.SqlSessionInterceptor#invoke方法中,即拦截所有的数据库操作,如果没有session则进行开启会话,根据需要创建事务的开始,如果有session直接从ThreadLocal中获取对应的SqlSession对象进行处理,处理完成进行事务的commit操作。具体的关于SqlSessionManager的关键源码如下,非关键代码没有展示出来。

    public class SqlSessionManager implements SqlSessionFactory, SqlSession {
    
      private final SqlSessionFactory sqlSessionFactory;
      private final SqlSession sqlSessionProxy;
    
      private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();
    
      private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
        this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
            SqlSessionFactory.class.getClassLoader(),
            new Class[]{SqlSession.class},
            new SqlSessionInterceptor());
      }
     @Override
      public void rollback(boolean force) {
        final SqlSession sqlSession = localSqlSession.get();
        if (sqlSession == null) {
          throw new SqlSessionException("Error:  Cannot rollback.  No managed session is started.");
        }
        sqlSession.rollback(force);
      }
    
      @Override
      public List<BatchResult> flushStatements() {
        final SqlSession sqlSession = localSqlSession.get();
        if (sqlSession == null) {
          throw new SqlSessionException("Error:  Cannot rollback.  No managed session is started.");
        }
        return sqlSession.flushStatements();
      }
    
      @Override
      public void close() {
        final SqlSession sqlSession = localSqlSession.get();
        if (sqlSession == null) {
          throw new SqlSessionException("Error:  Cannot close.  No managed session is started.");
        }
        try {
          sqlSession.close();
        } finally {
          localSqlSession.set(null);
        }
      }
    
      private class SqlSessionInterceptor implements InvocationHandler {
        public SqlSessionInterceptor() {
            // Prevent Synthetic Access
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
          if (sqlSession != null) {
    //说明ThreadLocal中已经有了,直接执行
            try {
              return method.invoke(sqlSession, args);
            } catch (Throwable t) {
              throw ExceptionUtil.unwrapThrowable(t);
            }
          } else {
    //说明ThreadLocal中没有,则开启新的Session,并执行相关的SQL语句
            try (SqlSession autoSqlSession = openSession()) {
              try {
                final Object result = method.invoke(autoSqlSession, args);
                autoSqlSession.commit();
                return result;
              } catch (Throwable t) {
                autoSqlSession.rollback();
                throw ExceptionUtil.unwrapThrowable(t);
              }
            }
          }
        }
      }
    
    }

    在mybatis-spring的包中,org.mybatis.spring.SqlSessionTemplate对象做了mybatis包中的SqlSessionManager类中类似的方法。

    4.Interceptor分析

    接口类org.apache.ibatis.plugin.Interceptor可以通过实现该接口可以为sql定制相关的切面操作。可以通过在mybatis的xml配置文件中增加对应plugins的interceptor配置,实现增加拦截器处理。这里拦截处理主要是通过org.apache.ibatis.plugin.InterceptorChain#pluginAll建立一个责任链模式,其中在方法中依赖的plugin方法中依赖Plugin.wrap方法,该方法实际上是封装了一层代理,实现通过层层调用拦截相关操作。关于InterceptorChain参考如下源码:

    //拦截责任链
    public class InterceptorChain {
    
      private final List<Interceptor> interceptors = new ArrayList<>();
    
      public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptors) {
          target = interceptor.plugin(target);
        }
        return target;
      }
    
      public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
      }
    
      public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(interceptors);
      }
    
    }
    
    //拦截接口
    public interface Interceptor {
    
      Object intercept(Invocation invocation) throws Throwable;
    
      default Object plugin(Object target) {
        return Plugin.wrap(target, this);
      }
    
      default void setProperties(Properties properties) {
        // NOP
      }
    
    }
    
    
    //插件类
    public class Plugin implements InvocationHandler {
    
      private final Object target;
      private final Interceptor interceptor;
      private final Map<Class<?>, Set<Method>> signatureMap;
    
      private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
        this.target = target;
        this.interceptor = interceptor;
        this.signatureMap = signatureMap;
      }
    
      public static Object wrap(Object target, Interceptor interceptor) {
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        if (interfaces.length > 0) {
    //注意这里是封装生成了代理类,返回Plugin对象
          return Proxy.newProxyInstance(
              type.getClassLoader(),
              interfaces,
              new Plugin(target, interceptor, signatureMap));
        }
        return target;
      }
    
     @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          Set<Method> methods = signatureMap.get(method.getDeclaringClass());
    //注意这里才是真正的拦截之后的处理,按需调用拦截器的intercept方法
          if (methods != null && methods.contains(method)) {
            return interceptor.intercept(new Invocation(target, method, args));
          }
          return method.invoke(target, args);
        } catch (Exception e) {
          throw ExceptionUtil.unwrapThrowable(e);
        }
      }
    }

    5.总结

    mybatis基于JDBC实现的ORM框架,为研发提供了不少的便捷性,熟悉mybatis的能力和局限性,明确其边界和定位,方便后续研发。积跬步,致千里。

    6.参考文章

    1.https://blog.csdn.net/top_code/article/details/55657776

    2.https://www.jianshu.com/p/48dfeb0b79b6

    展开全文
  • MyBatis源码本地编译

    2021-11-02 13:38:38
    为什么需要在本地编译源码 我们在看框架源码的时候,如果没有注释,看起来会比较吃力。所以如果能够一边看源码一边自己加中文注释,下次阅读的时候就会轻松很多。 问题是:通过maven下载的jar,查看源码,实际上看到...
  • 阅读源码后的感受 福 利 一MyBatis简介 二MyBaits入D 三配置 四映射器 五动态SQL 六MyBatis的解析和运行原理 七插件 八MyBatis-Spring 九实用的场最 总结 前言 MyBatis 是一款优秀的持久层框架,它支持...
  • 1.下载mybatis源码 mybatis源码地址 下载后解压,文件名带resource的为源码 2.引入源码模块 找到源码中的pom.xml(由于mybatis也是maven工程,直接选该文件,idea会自动识别) 引入后,找到我们自己的工程,在依赖一...
  • 前言 本文不介绍springboot如何集成tkMybatis,只介绍下集成的原理 ...源码 我们直接从入口开始,tkMybatis的扫描注解MapperScan,该注解使用时需要配置扫描的路径,参数为value @Retention(RetentionPolicy.R
  • Mybatis源码分析--初相识 Mybatis版本:3.5.6 本人的GitHub地址:https://github.com/KeminaPera/mybatis-3 分支名称:study/mybatis-3.5.6 个人建议:将Mybatis的源码fork到个人仓库,按照官网文档编写demo,...
  • Spring 和MyBatis整合之后 为什么一级缓存会失效 因为Spring在使用完dao之后会立即关闭sqlSession对象。如果让它生效的话,打开事务就会生效。 二级缓存中要注意的点 二级缓存里面的数据不能存那种一直...
  •   讲道理,如果mybatismybatis-parent的版本号一致的话,大概率是不会遇到坑的。但是不排除某些人的自残倾向,非要用不同版本的mybatismybatis-parent,这样的话可能会遇到以下这些坑: “cannot resolve ...
  • Mybatis源码解析--ParameterHandler 背景 在Mybatis源码解析--StatementHandler中我们知道StatementHandler是对JDBC中Statement功能的封装,而针对3种Statement提供了对应的StatementHandler,而StatementHandler...
  •   最近有把MyBatis源码down下来做了注释处理,为了方便小伙伴拿到源码,在这记录下操作的流程 1.gitee中创建仓库   这个比较简单请自行申请账号,然后先创建parent项目即可 2.idea中操作   在VCS中操作如下...
  • 模板方法模式在MyBatis源码中的应用目录概 述Executor总结:相关工具如下:分析:小结:参考资料和推荐阅读 LD is tigger forever,CG are not brothers forever, throw the pot and shine forever. Modesty is ...
  • mybatismybatis-spring和tkmybatis的关系 定义 MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis ...
  • 全面系统完整 业界热门技术 知识全覆盖Java架构师之源码分析专题SpringBoot2.x、Spring5、SpringMVC、Mybatis源码分析源码中我们可以学到很多东西,学习别人高效的代码书写、学习别人对设计模式的熟练使用、学习别人...
  • Mybatis 源码编译

    2021-04-27 10:47:35
    该项目包含两个project,即parent-master(33-SNAPSHOT)和mybatis-master(3.3.0-SNAPSHOT),IDE使用Intellij 2020.3社区版,parent-master下载链接:https://github.com/mybatis/parent,mybatis-master下载链接:...
  • mybatis源码下载mybatis源码下载 mybatis源码下载 从gitee下载mybatis源码 下载地址:https://gitee.com/mirrors/mybatis.git 从idea中直接下载源码 指定mybatis源码地址和本地存放路径。点击Clone直接下载
  • mybatis的transaction包是负责进行事务管理的包,该包内包含2个子包:jdbc子包中包含基于jdbc进行事务管理的类,managed子包中包含基于容器进行事务管理的类。 1.事务概述 事务即数据库事务,是数据库执行过程中...
  • 下载源码GitHub地址:https://github.com/mybatis/mybatis-3 复制上面的地址执行下列命令:git clone https://github.com/mybatis/mybatis-3.git mybatis-source注:GitHub可能会很慢,但是可以借助国内的Gitee进行...
  • 文章目录一、开篇二、源码分析1.selectList()2.CachingExecutor3、createCacheKey1)为什么CachingExecutor要调用SimpleExecutor的方法去创建缓存key呢?4、创建好缓存key之后:query查询二级缓存5、如果二级缓存为...
  • Mybatis源码学习01——源码环境搭建 图难于其易,为大于其细。天下难事,必作于易;天下大事,必作于细。从Spring源码退一步学习mybatis源码未尝不是一个正确的决定。 包含了myatis源码下载,导入idea,编写配置文件...
  • Mybatis源码分析(一)

    千次阅读 2021-02-24 09:57:13
    mybatis源码1、回顾JDBC1.1 jdbc执行流程1.2 SqlSessionFactory & SqlSession1.2.1 获取SqlSession1.2.1.1 源码解析1.3 MapperProxy1.4 Excutor源码交流群 8671577531 1、回顾JDBC 1.1 jdbc执行流程 实现代码:...
  • Mybatis源码分析与技术原理

    千次阅读 2021-06-15 16:42:34
    Mybatis框架属于ORM框架,全称(Object Relational Mapping)。用于实现面向对象main车工语言里不同类型系统之间的数据之间的转换。我们在开发中Mybatis框架通常作为Java链接MySQL的工具,那么mytais底层是如何查询...
  • 一.概述:常见的数据源组件...设计模式:为什么要使用工厂模式数据源模块类图 :三.MyBatis源码数据源实现MyBatis数据源通过工厂模式实现了,非连接池的数据连接,和数据库连接池。非连接池的数据连接:MyBatis数据...
  • mybatis源码解析

    2021-05-31 11:11:51
    一、mybatis版本:3.2.8(运行代码版本)、3.51(部分直接查看的源码版本) 二、测试源码: 三、主要运行流程 流程图:下面的分析也是以流程图为基础进行的。 3.13、构建Configuration对象:从parser....
  • 关注公众号“java后端技术全栈”回复“000”获取优质面试资料大家好,我是老田。之前,我给大家分享给很多MyBatis源码分析的一系列文章。今天,就自己的感受来做一个整体的总结。众所周...
  • 环境准备 需要的环境有:jdk1.8,maven>3.25 下载源码 下载mybatis parent pom工程,并且切换到tag ...下载mybatis 源码工程 git clone https://github.com/zhangbeiwjl/mybatis-3.git 构建工程 构建mybatis pa
  • IDEA部署Mybatis源码环境 流程: 1、下载mybatis-3 + mybatis-parent源码
  • Mybatis源码解析--SqlSession SqlSession介绍 Mybatis框架的主要目的就是简化JDBC操作数据库的繁琐流程,只需要提供sql语句和相关参数即可,不用再对参数手动设置,以及遍历结果集将其手动封装成目标对象,不用...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 119,669
精华内容 47,867
关键字:

mybatis源码