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

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

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的文件。如:

    
  • 3.添加实体类和接口。

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

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

  • 4.增加工具类。

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

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

工厂对象的获取思考:

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

MyBatis工具类设计思考:

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

SqlSession对象获取思考:

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

  • 5.添加测试类。

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

案例代码

pom.xml:

 
      junit
      junit
      4.12
      test
    
    
      org.mybatis
      mybatis
      3.4.6
    
    
      mysql
      mysql-connector-java
      5.1.40
    
    
      org.projectlombok
      lombok
      1.16.20
    

mybatis-config.xml:




   
   
        
    
    
        
            
            
                
                
                
                
            
        
        
    
       
    


db.properties:

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

DepartmentMapper.xml:





    
    


Department:

@Getter
@Setter
@ToString
public class Department {

    // 主键id
    private Long id;
    // 部门名称
    private String name;
    // 部门简写
    private String sn;
}

DepartmentMapper:

public interface DepartmentMapper {

   /**
     *  查询所有部门信息
     * @return 返回所有部门信息的集合
     */
    List 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 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映射文件的方式分析)