美文网首页JAVA基础springboot
MyBatis中通过package标签加载mapper映射文件的

MyBatis中通过package标签加载mapper映射文件的

作者: 叩丁狼教育 | 来源:发表于2019-01-05 09:13 被阅读155次

    本文作者:孔维胜,叩丁狼高级讲师。原创文章,转载请注明出处。

    MyBatis中通过package标签加载mapper映射文件的方式分析

    看文章前的要求

    在学习MyBatis的初级篇之前,有两个前提要求,第一.必须学会使用IDEA,因为在文章中,使用的工具为IDEA,文章中的案例也都是基于IDEA的。第二.必须学会使用MAVEN,因为在案例中需要的jar包,都是通过MAVEN来管理的。

    文章中的案例的开发环境

    JDK 1.8

    IDEA 2017.3

    MySQL 5.1.38

    Apache Maven 3.5.0

    Tomcat 9.0.6

    MyBatis 3.4.6

    案例需要的表和数据

    我们使用MyBatis的目的最终是访问数据库,所以在数据库方面,我们先创建相应的数据库,表,导入相关的数据。如:

    1.创建mybatis数据库。

    2.在mybatis数据库中创建department(部门表)。

    DROP TABLE IF EXISTS `department`;
    CREATE TABLE `department` (
      `id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT '部门ID',
      `name` varchar(20) DEFAULT NULL COMMENT '部门名称',
      `sn` varchar(20) DEFAULT NULL COMMENT '部门缩写',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
    

    3.准备department(部门表相关的数据)

    INSERT INTO `department` VALUES (1, '人力资源', 'HR_DEPT');
    INSERT INTO `department` VALUES (2, '销售部', 'SALE_DEPT');
    INSERT INTO `department` VALUES (3, '开发部', 'DEVELOP_DEPT');
    INSERT INTO `department` VALUES (4, '财务部', 'FINANCE_DEPT');
    
    

    案例需求

    需求:使用Mapper动态代理的方式完成所有数据的查询操作。

    需求分析

      1. 导入相关jar依赖

    要使用MyBatis框架,首先需要导入mybatis的核心包,MyBatis主要是操作数据库,替换掉传统的JDBC方式访问数据库,所以需要导入mysql的驱动包。我们要在项目中使用单元测试进行测试,所以需要导入junit包,我们不想写javaBean的setter和getter方法,可以导入lombok的包。

    • 2.添加配置文件。

    我们使用MyBatis框架,需要两个配置文件,一个是MyBatis的主配置文件,主要用来配置事务管理器和数据库的连接信息,一个是封装SQL语句Mapper映射文件。我们为了数据库的连接信息不写死在主配置文件中,所以我们采用抽取的方式,把连接数据库的信息抽取到db.properties文件中,进行管理。通过package扫描的方式在主配置文件中挂载mapper的文件。如:

       <package name="cn.wolfcode.mapper"/> 
    
    • 3.添加实体类和接口。

    可能查询数据需要查询条件有很多,查询数据需要封装到对象中,所以我们可以定义一个JavaBean,来封装条件和查询的数据。

    定义一个接口,编写操作数据库方法。方法的名字保持和sql映射文件中的标签的id一一对应。

    • 4.增加工具类。

    通过加载主配置文件来获取SqlSessionFactory工厂对象,一般工厂对象都是单例模式的,所以这个操作只需要做一次即可。比如:我们不能每吃一次饭,都去建一所餐厅。两者的道理是一样的。

    而在MyBatis的官网给出的建议是SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建。因此 SqlSessionFactory 应该使用其单例模式,只创建一次在整个应用中,都可以使用。

    工厂对象的获取思考:

    那么把获取工厂对象的操作放在哪里合适呢?如果在本类中进行抽取,放在一个方法中,但是每个DAO的实现类都这样处理,还是会出现代码的冗余。所以最合适的方式定义一个MyBatisUtil工具类,把获取工厂对象的操作抽取到工具类中,那么工厂对象的获取只需要获取一次即可,所以在工具类中,定义在哪里,只会执行一次呢?静态代码块,我们都知道,类中的静态代码块,只会随着类的加载而加载,并且只执行一次。

    MyBatis工具类设计思考:

    何为工具类,一般我们在定义的工具类的时候,希望使用者只使用而不要修改此类,所以我们会设置这个类使用final进行修饰,这样这个类就是终结类,不能被继承。一般工具类不会让使用者去创建对象,而是采用提供静态方法的方式共使用者调用。

    SqlSession对象获取思考:

    定义一个方法供外部访问,获取SqlSession对象。这个方法设计成静态的这样,调用方法的时候不用再创建工具类对象。

    • 5.添加测试类。

    定义一个测试类,编写一个测试方法,通过调用工具类中的方法获取SqlSession对象,通过SqlSession对象调用getMapper方法获取对应的Mapper的代理对象,然后调用接口中的方法获取所有数据。

    案例代码

    pom.xml:

     <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.12</version>
          <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis</artifactId>
          <version>3.4.6</version>
        </dependency>
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>5.1.40</version>
        </dependency>
        <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>1.16.20</version>
        </dependency>
    

    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>
       <properties  resource="db.properties"/>
       <typeAliases>
            <package name="cn.wolfcode.domain"/>
        </typeAliases>
        <environments default="dev">
            <environment id="dev">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="${driverName}"/>
                    <property name="url" value="${url}"/>
                    <property name="username" value="${userName}"/>
                    <property name="password" value="${password}"/>
                </dataSource>
            </environment>
        </environments>    
        <mappers>
           <package name="cn.wolfcode.mapper"/>
        </mappers>
    
    </configuration>
    

    db.properties:

    driverName=com.mysql.jdbc.Driver
    url=jdbc:mysql:///mybatis
    userName=root
    password=root123
    

    DepartmentMapper.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="cn.wolfcode.mapper.DepartmetMapper">
    
        <!--
         select 表示查询语句的标签。标签体的内容即是查询的SQL语句
              id:SQL语句的唯一标识
              parameterType:传入这条SQL语句的参数的类的完全限定名或别名,
                     因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,故可以省略
              resultType:返回期望的类型(类的完全限定名或别名),用来接收查询的结果。
    
          -->
        <select id="selectAll" resultType="cn.wolfcode.domain.Department">
            SELECT id,name,sn FROM department
            WHERE id = #{id}
        </select>
    
    </mapper>
    

    Department:

    @Getter
    @Setter
    @ToString
    public class Department {
    
        // 主键id
        private Long id;
        // 部门名称
        private String name;
        // 部门简写
        private String sn;
    }
    

    DepartmentMapper:

    public interface DepartmentMapper {
    
       /**
         *  查询所有部门信息
         * @return 返回所有部门信息的集合
         */
        List<Department> selectAll();
    }
    

    MyBatisUtil:

    public final class MyBatisUtil {
    
        private static SqlSessionFactory factory = null;
    
        static {
            // 使用static静态代码块,随着类的加载而加载,只执行一次
            try {
                // 加载MyBatis的主配置文件
                InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
                // 通过构建器(SqlSessionFactoryBuilder)构建一个SqlSessionFactory工厂对象
                factory = new SqlSessionFactoryBuilder().build(inputStream);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        // 获取sqlSession对象
        public static SqlSession openSession() {
            return factory.openSession();
        }
    }
    

    DepartmentMapperTest:

    public class DepartmentMapperTest {
     @Test
        public void testQueryOne(){
            // 获取sqlSession对象
            SqlSession sqlSession = MyBatisUtil.openSession();
            // 获取Mapper对象
            DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
            List<Department> departmentList = departmentMapper.selectAll();
            // 关闭资源
            sqlSession.close();
            // 遍历结果
      departmentList.stream().forEach(System.out::println);
        }
    
    

    DepartmentMapper文件加载流程分析

    • 1 . 加载主配置文件,通过build方法构建工厂对象。如:
      MyBatisUtil.png
    • 2 . 创建XML配置构建器的对象(XMLConfigBuilder)。底层使用的是XPath解析器。 在这个方法的finally块中,对外部传入的流,进行了关闭。所以外部不需要进行关闭了。如:


      SqlSessionFactoryBuilder.png
    • 3 . 通过构建器对象调用parse方法,把解析的数据封装到Configuration对象中。我们主要是关心mapper文件的加载,所以继续往下看。如:


      XMLConfigBuilder.png
    • 4 .方法中定义了一个while死循环,主要是便利mappers节点下面的所有元素。因为我们采用的是在主配置文件中使用package扫描的方式挂载的mapper映射文件。所以跳入if代码块。在if块中通过获取name属性的值,拿到了mapper文件的所属的包名,通过configuration对象调用addMappers方法把mapper映射文件所在的包传入。如:
    XMLConfigBuilder.png
    • 5 .调用Mapper注册对象中(MapperRegistry)的addMappers方法,添加映射。如:
    Configuration.png
    • 6 .创建ResolverUtil工具类,通过调用find方法把包下面的字节码对象找出来,并存入到Set集合中,通过调用getClasses方法取出,进行遍历。把每一个字节码对象传入addMapper方法。如:
    MapperRegistry.png
    • 7 .在MapperRegistry(映射注册类)中定义一个map容器(knowMappers),用来存入映射。在addMapper方法中,先通过调用isInterface方法看看mapper是不是接口,必须是接口,才会添加。在通过调用hasMapper方法来判断是否已经添加过了,如果已经添加,就抛出一个绑定异常。通过标记loadCompleted,来确保添加成功。如果添加出现了异常,在finally块中删除map中存入的映射。把字节码对象作为key,创建该字节码对象的代理对象作为value,存入knowMappers中。并创建MapperAnnotationBuilder对象如:
    MapperRegistry.png
    • 8 .MapperAnnotationBuilder这个类总会优先解析xml配置文件,并且这个xml配置文件必须与Class对象所在的包路径一致,且文件名要与类名一致。在解析完xml配置文件后,才会开始解析Class对象中包含的注解。里面有个if判断,如果在主配置对象(configuration)添加过接口标记,表示解析过,就不再进入if语句。首先调用loadXmlResource方法,解析指定的xml配置文件。如:
    MapperAnnotationBuilder.png
    • 9 . 在这个方法中,先通过if判断之前是否解析过,如果没有解析过,则进入if语句,把包名中的"."替换成"/",这样变成了文件夹,然后在后面追加".xml"后缀。这样拼接成一个xml文件的资源路径。然后加载到内存。在通过调用parse方法进行解析xml文件。
      所以这也是为何如果使用package扫描的方式,必须要保证接口和mapper映射文件必须在同一个包中,名字也必须相同的原因。如:
    MapperAnnotationBuilder.png
    • 10 . 继续往下解析。如:
    XMLMapperBuilder.png

    DepartmentMapper文件加载整体流程图

    Mapper映射文件通过package方式解析流程图.png

    想获取更多技术干货,请前往叩丁狼官网:http://www.wolfcode.cn/all_article.html

    相关文章

      网友评论

        本文标题:MyBatis中通过package标签加载mapper映射文件的

        本文链接:https://www.haomeiwen.com/subject/rhxhrqtx.html