MyBatis 3学习笔记


MyBatis 3

一、MyBatis简介

优秀的持久层框架,支持支持自定义 SQL、存储过程以及高级映射,专注于SQL的编写。

? 为什么不使用工具类进行数据库操作:

? 功能简单,sql语句编写在Java代码里面,是一种硬编码高耦合的方式,不推荐。

Hibernate:

? 另一种持久层框架,全自动全映射ORM(Object Relation Mapping)框架,旨在消除SQL

? 主要特点:一个JavaBean对象与数据库中一条记录建立映射,中间过程对于程序员来讲是黑箱操作,无须关注SQL语句的编写。

? 缺点:框架自动生成SQL语句,程序员无法进行SQL优化;JavaBean中每一个字段都在数据库中有相应映射,加重了数据库的负担。

? 需求:SQL语句交给我们开发人员编写,希望SQL不失去灵活性。于是,诞生了MyBatis:

MyBatis官方文档

二、MyBatis接口式编程 Hello World

1. 搭建环境

? lombok介绍:

? Lombok 是一种 Java? 实用工具,可用来帮助开发人员消除 Java 的冗长,尤其是对于简单的 Java 对象(POJO)。它通过注解实现这一目的。

? 通过@Data注解,让被修饰的类自动生成getter和setter方法、构造器、equals方法、或者hash方法等。

? 如果觉得@Data这个注解有点简单粗暴的话,Lombok提供一些更精细的注解,比如@Getter,@Setter,(这两个是field注解),@ToString,@AllArgsConstructor(这两个是类注解)。

? log4j介绍:

? Apache的开源项目log4j是一个功能强大的日志组件,提供方便的日志记录。可以查看在debug时候的sql语句。

? 一般在resources目录下创建log4j.properties文件,进行配置。

1.1 Maven

导入依赖:

  
    
    
      junit
      junit
      4.11
      test
    
    
    
      org.projectlombok
      lombok
      RELEASE
      compile
    
    
    
      org.mybatis
      mybatis
      3.4.6
      compile
    
    
      org.junit.jupiter
      junit-jupiter
      RELEASE
      compile
    
    
    
      log4j
      log4j
      1.2.17
    
    
     
    
      mysql
      mysql-connector-java
      8.0.26
    
  

1.2 Bean

@Data
public class Employee {
    private int id;
    private String name;
    private String age;
}

1.3 配置文件

放在resources目录下:

<?xml version="1.0" encoding="UTF-8" ?>



    
    
    
    
    
    
        
            
            
                
                
                
                
                
            
        
    

    
    
        
    

数据库配置文件properties,同样也存放在resources目录下

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatisdb
username=root
password=zouwenhao

日志的配置文件properties,resources目录下:

log4j.rootLogger=DEBUG,A1

log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n

mapper配置文件,在这里面编写sql:



    

1.4 通过SqlSession建立与数据库的会话

老版本的编程方法:

    /* MyBatis操作数据库的整体思路:
     * 1、根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象,有数据源一些运行环境信息 (config)
     * 2、sql映射配置文件;配置了每一个sql,以及sql的封装规则等。 (mapper)
     * 3、将sql映射文件注册在全局配置文件中 (config配置文件中的mapper标签进行注册)
     * 4、写代码:
     * 		1)、根据全局配置文件得到SqlSessionFactory;
     * 		2)、使用sqlSession工厂,获取到sqlSession对象使用他来执行增删改查(一个与数据库操作的代理对象)
     * 			一个sqlSession就是代表和数据库的一次会话,用完关闭,使用close方法
     * 		3)、使用sql的唯一标志来告诉MyBatis执行哪个sql。sql都是保存在sql映射文件中的。
     */
    // 返回SqlSessionFactory对象
    public SqlSessionFactory getSqlSessionFactory() throws IOException {
        // MyBatis全局配置文件路径
        String resource = "mybatis_config.xml";
        // 获取MyBatis全局配置文件的输入流
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 获取SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        return  sqlSessionFactory;
    }

    // 查询测试,老的方法
    @Test
    public void testHello() throws IOException {
        // 1.获取SqlSessionFactory实例
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

        // 2.获取SqlSession实例,能直接执行已经映射的sql语句
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            // 为了避免mapper映射出现重名的情况,一般使用 命名空间+查询id
            /*
            *   第一个参数:sql的唯一标识,statement Unique identifier matching the statement to use.
            *   第二个参数:执行sql要用的参数,parameter A parameter object to pass to the statement.
            * */
            Employee employee = sqlSession.selectOne("com.atguigu.mybatis.mapper.selectEmp", 1001);
            System.out.println(employee);
        } finally {
            // sqlSession表示与数据库的一次对话,用完之后必须手动关闭
            sqlSession.close();
        }
    }

1.5 接口式编程(推荐)

? 使用接口与配置文件进行动态绑定,接口中的方法与select标签进行绑定

  1. 第一步:创建接口,getEmpById方法返回Employee对象

    public interface EmployeeMapper {
    
        public Employee getEmpById(Integer id);
    
    }
    
  2. 第二步:修改原EmployeeMapper.xml文件

    
     
        
    
    
  3. 第三步:编写测试类

    思路是:拿到接口的实现类对象,然后调用接口的方法,进行数据库的查询操作

        @Test
        public void testHelloByInterface() throws IOException {
            // 1.获取SqlSessionFactory实例
            SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
            // 2.获取SqlSession实例,与数据库建立会话
            SqlSession sqlSession = sqlSessionFactory.openSession();
            try {
                // 3. 获取接口的实现类对象
                // 会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
                EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);//获取代理对象
                Employee emp = mapper.getEmpById(1001);
                System.out.println(emp);
            } finally {
                sqlSession.close();
            }
        }
    

    ? 使用接口式编程的好处:

    ? 拥有更强的类型检查,以及返回值的限制,能够将DAO层与实现进行分离出来。

1.6 总结

  1. SqlSession和connection一样都不是线程安全的,因此不能共享,每次使用都应该去获取新的对象。

  2. SqlSession代表和数据库的一次会话,用完就必须关闭。

  3. mapper接口没有实现类,但是MyBatis会为这个接口生成一个代理对象:(将接口与xml进行绑定)

    ? EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);

  4. 两个重要的配置文件:

    ? mybatis的全局配置文件:包含数据库连接池的信息、事务管理器信息等

    ? sql映射文件:保存了每一个sql语句的映射信息,包括这个sql语句的标识、返回的信息类型(select标签)

三、MyBatis-全局配置文件

? 全局配置文件中的dtd约束,对应了语法规则,在编写时,会有相关提示信息


1. properties_引入外部配置文件


	
	 
    
    ......
    
    

如果属性在不只一个地方进行了配置,那么 MyBatis 将按照下面的顺序来加载:

? – 在 properties 元素体内指定的属性首先被读取。

? – 然后根据 properties 元素中的 resource 属性读取类路径下属性文件或根据 url 属性指定的路径读取属性文件,并覆盖已读取的同名属性。

? – 最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。

2. settings_运行时行为设置

? 以是否开启驼峰命名规则映射为例:

	
	
		
	

3. typeAliases_别名处理器(不推荐使用)

? 将全类名与别名建立映射关系,这样在一些使用全类名的条件下,可以使用别名。

	
	
		
		
		
		
		
		
		
        
	

4. typeHandlers_类型处理器(了解)

? MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。部分如下所示:

5. plugins_插件(本章了解)

? MyBatis 允许你在映射SQL语句执行过程中的某一过程进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

  • ParameterHandler (getParameterObject, setParameters)

  • ResultSetHandler (handleResultSets, handleOutputParameters)

  • StatementHandler (prepare, parameterize, batch, update, query)

    通常使用插件来修改MyBatis的一些核心行为,插件通过动态代理机制

6. environments_运行环境

? MyBatis可以配置多种环境,比如开发、测试和生 产环境需要有不同的配置。

? 每种环境使用一个environment标签进行配置并指 定唯一标识符。

? 可以通过environments标签中的default属性指定一个环境的标识符来快速的切换环境。

? 配置当前运行环境,比如数据库、事务管理器的信息,可以创建多个运行环境,如下所示:有MySQL和Oracle两种数据库环境。

	
		 
	 
        
		
			
			
				
				
				
				
			
		
	
		
			
			
				
				
				
				
			
		
        
	

? type: JDBC | MANAGED | 自定义

– JDBC:使用了 JDBC 的提交和回滚设置,**依赖于从数据源得到的连接来管理事务范围**。 		

? JdbcTransactionFactory

? – MANAGED:不提交或回滚一个连接,让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下 文)。

? ManagedTransactionFactory

? – 自定义:实现TransactionFactory接口,type=全类名/ 别名

? type: UNPOOLED | POOLED | JNDI | 自定义

? – UNPOOLED:不使用连接池, UnpooledDataSourceFactory

? – POOLED:使用连接池, PooledDataSourceFactory

? – JNDI: 在EJB或应用服务器这类容器中查找指定的数据源

? – 自定义:实现DataSourceFactory接口,定义数据源的获取方式。

? 实际开发中我们使用Spring管理数据源,并进行事务控制的配置来覆盖上述配置。

7. databaseProvider_多数据库支持

? 下图databaseId指定了在什么数据库环境下,才会执行这条sql语句。至于当前是在什么环境下,则在environments标签下设置default属性值,用于设置当前数据库运行环境。

? 例如:当前在MySQL环境下,因此一旦需要执行这条语句,那么就会执行这条语句,如果是Oracle环境下,就不会执行这条语句,而是会执行另一条databaseId为oracle的语句。

	
	
		
		
		
		
	

8. mappers_sql映射注册

? 将sql映射注册到全局配置中来

	
	
	
		
		
        
		
		
		
		
	

? 注解方式的举例:将sql语句写在注解上,注意在全局配置文件中使用class属性进行接口注册

public interface EmployeeMapperAnnotation {
  @Select({"select * from tbl_employee where id=#{id}"})
  Employee getEmpById(Integer paramInteger);
}

? 测试方法的编写:

    @Test
    public void testHelloByAnno() throws IOException {
        // 1.获取SqlSessionFactory实例
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

        // 2.获取SqlSession实例,与数据库建立会话
        SqlSession sqlSession = sqlSessionFactory.openSession();

        try {
            // 3. 获取接口的实现类对象
            EmployeeMapperAnnotation mapper = sqlSession.getMapper(EmployeeMapperAnnotation.class);
            Employee emp = mapper.getEmpById(1001);
            System.out.println(emp);
        } finally {
            sqlSession.close();
        }
    }

四、MyBatis-映射文件(重要)

1. 增删改查

? 前提,MyBatis配置环境已经像前文一样配置完毕!

1.1 接口方法

public interface EmployeeMapper {

    Employee getEmpById(Integer paramInteger);

    // mybatis允许增删改直接定义以下类型返回值
    //      Integer、Long、Boolean、void
    Long addEmp(Employee employee);

    Long updateEmp(Employee employee);

    Long deleteEmp(Integer id);

}

1.2 Mapper配置文件

? 在mapper配置文件中,建立与接口方法的映射,同时记得在MyBatis全局配置文件中,进行注册:

    
         
    

? EmployeeMapper.xml:

	
    

    
    
    
        insert into info(name, age) values (#{name}, #{age})
    

    
    
        update info
        set name=#{name}, age=#{age}
        where id=#{id}
    

    
    
        delete from info where id=#{id}
    

1.3 测试

    @Test
    public void EmpTest() throws IOException {
        // 1. 获取sqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        // 2. 获取sqlSession对象,但并不会自动提交
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 自动提交:sqlSessionFactory.openSession(true);

        try {
            // 3. 获取接口的代理对象
            EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);

            // 4. 更新数据
//            Long result = mapper.updateEmp(new Employee(1001, "Paul", 23));

            // 4. 添加数据,id是自增的,所以可以传一个null
            Long result = mapper.addEmp(new Employee(null, "Dick", 21));

            // 4. 删除操作,指定id值
//            Long result = mapper.deleteEmp(1005);

            System.out.println(result > 0 ? "操作成功!" : "操作失败!");

            // 5. 手动提交事务
            sqlSession.commit();
        } finally {
            // 5. 关闭会话
            sqlSession.close();
        }
    }

注意:

  1. openSession()获取到的SqlSession实例是不会自动提交的,需要在sql语句执行完,手动commit提交,如果想自动提交,则使用openSession(true)方法获取实例。

  2. 关于MySQL数据库使用自增id,当我们需要使用自增id时,类对象只需要将自增id传入一个null值即可,但一定要保证数据库中该键是开启了自动增长。如果报以下错误:

    java.sql.SQLException: Field 'id' doesn't have a default value

    ? 说明没有开启自动增长,开启后,需要重启以下数据库!

2. Insert

  1. MySQL数据库获取自增主键的值:
	
	
	
		insert into tbl_employee(last_name,email,gender) 
		values(#{lastName},#{email},#{gender})
	
  1. Oracle数据库不支持自增:

    使用序列来模拟自增,就需要手动执行sql语句,才查询下一个应该自增的值。

    有两种解决思路:

    1. 先执行查询sql,获取到自增主键值后,再进行insert插入语句。

      ? 用BEFORE修饰selectKey标签,并通过 .nextva 获取自增后的主键值,再进行插入操作

    2. 先执行insert插入语句,使用 .nextval 表示新记录自增主键值,然后执行AFTER修饰的selectKey标签,使用 .currval 获取当前新纪录自增后的主键值。

	
	
	
		
		
			
			
			select EMPLOYEES_SEQ.nextval from dual 
			
		
		
		
		
		insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL) 
		values(#{id},#{lastName},#{email}) 
		
		
	

3. 参数处理

3.1 单个参数/多个参数/命名参数

? 在mapper配置文件中,在将接口方法与CRUD标签进行映射的时候,需要使用接口方法中的参数作为sql语句查询的条件。MyBatis对于单个参数和多个参数有着不一样的处理:

? 单个参数:

? MyBatis不会做特殊处理,

? #{参数名/任意名}:取出参数值。

? 多个参数:

? Mybatis会做特殊处理。

? 多个参数会被封装成 一个map,规则如下:

? key:param1...paramN,或者参数的索引也可以

? value:传入的参数值

? #{}就是从map中获取指定的key的值;

异常:
org.apache.ibatis.binding.BindingException: 
Parameter 'id' not found. 
Available parameters are [1, 0, param1, param2]

错误操作:
	方法:public Employee getEmpByIdAndLastName(Integer id,String lastName);
	取值:#{id}, #{lastName} (正确取值操作:#{param1}, #{param2})

? 命名参数:

? 明确指定封装参数时map的key;在接口方法中使用@Param注解;@Param("id")

? 多个参数会被封装成 一个map,

? key:使用@Param注解指定的值

? value:参数值

? #{指定的key}取出对应的参数值,如 #{id}, #{lastName}

Long deleteEmp(@Param("id") Integer id, @Param("name") String name)

3.2 POJO/Map/TO

? POJO:

? 如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo;

? #{属性名}:取出传入的pojo的属性值

? 就像上文中测试,因为数据参数传入的是一个实例对象,就可以直接使用属性值来获取值。

? Map:

? 如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,我们也可以传入map

? #{key}:取出map中对应的值

? 示例:

? TO:
? 如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(Transfer Object)数据传输对象:

? Page{
? int index;
? int size;
? }

3.3 MyBatis的参数封装(源码理解)

? MyBatis如何处理参数的:

? 底层调用的sqlSession对象的一些增删改查的API

? 总结:参数多的时候,MyBatis会封装map,但为了不混乱,我们可以使用@Param来指定封

装时使用的key;

? #{key}就可以取出map中的值;

? (@Param("id")Integer id,@Param("lastName")String lastName);

ParamNameResolver解析参数封装map的:

? 该类有参构造器:

/**  names参数的确定流程:

        1.获取每一个被@Param注解修饰的参数的值,如:id,lastName的值,并赋值给name;
        
		2.每次解析一个参数给map中保存信息:(key:参数索引,value:name的值)
    		name的值要分两种情况:
    			第一种:标注了param注解,那么name的值就是注解指定的值。
    			第二种:没有标注(下面代码)
            		1.全局配置:useActualParamName(jdk1.8):name=参数名
					2.name=map.size();相当于当前元素的索引,所以最终保存形式如下:
    					{0=id, 1=lastName,2=2}
*/

    public ParamNameResolver(Configuration config, Method method) {
        ................
                if (name == null) {
                    if (config.isUseActualParamName()) { // 全局配置过没有?
                        name = this.getActualParamName(method, paramIndex);
                    }

                    if (name == null) {
                        name = String.valueOf(map.size());
                    }
                }

                map.put(paramIndex, name);
            }
        }

        this.names = Collections.unmodifiableSortedMap(map);
    }

? getNamedParams()方法:对args参数,用map类型进行封装

? 在遍历names集合的时候,进行了两次封装:

? 第一次封装:使得用户可以使用指定的key去获取参数,往往是用@Param进行修饰的,满足用户自定义param的需求。

? 第二次封装:是使用MyBatis提供的一种默认的方式,通过#{param1...paramN}这种方式进行参数值的获取。

// args传的参数:args[1,"Tom",'hello']
public Object getNamedParams(Object[] args) {
    final int paramCount = names.size(); 
    //1、参数为null直接返回
    if (args == null || paramCount == 0) {
      return null;
     
    //2、如果只有一个元素,并且没有Param注解;args[0]:单个参数直接返回
    } else if (!hasParamAnnotation && paramCount == 1) {
      return args[names.firstKey()];
      
    //3、多个元素或者有Param标注
    } else {
      final Map param = new ParamMap();
      int i = 0;
      
      //4、遍历names集合;{0=id, 1=lastName, 2=2}
      for (Map.Entry entry : names.entrySet()) {
         
        // 封装一次:@Param指定key
      	// key:names集合的value
        // value:names集合的key又作为取值的参考args[0]:args【1,"Tom"】:
      	// 封装效果:Map集合param = {id=args[0]:1, lastName=args[1]:Tom, 2=args[2]:2}
        param.put(entry.getValue(), args[entry.getKey()]);//相当于将args参数集合用map进行封装了
        
        // 封装一次:默认方式,Param1...ParamN
        // add generic param names (param1, param2, ...)param
        // 额外的将每一个参数也保存到map中,使用新的key:param1...paramN
        final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
        // ensure not to overwrite parameter named with @Param
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      // 效果:有Param注解可以#{指定的key},或者#{param1}
      return param;
    }
  }
}

3.4 参数值的获取——#与$取值的区别

? #{}:可以获取map中的值或者pojo对象属性的值;

? ${}:可以获取map中的值或者pojo对象属性的值;

主要区别:$是先拼接后编译,#是先编译后拼接,所以#没有SQL注入的风险。

select * from tbl_employee where id=${id} and last_name=#{lastName}

Preparing: select * from tbl_employee where id=2 and last_name=?

? 对比:

? #{}:是以预编译的形式,将参数设置到sql语句中;PreparedStatement;防止sql注入

? ${}:取出的值直接拼装在sql语句中;会有安全问题;

? 大多情况下,我们去取参数的值都应该去使用 #{} ;

? 原生jdbc不支持占位符的地方,我们就可以使用 ${} 进行取值

? 比如分表、排序。。。。;按照年份分表拆分

select * from ${year}_salary where xxx;

select * from tbl_employee order by ${f_name} ${order}

# 取值时指定参数相关规则

{}:更丰富的用法:

? 规定参数的一些规则:

? javaType、 jdbcType、 mode(存储过程)、 numericScale、

? resultMap、 typeHandler、 jdbcTypeName、 expression(未来准备支持的功能);

? jdbcType通常需要在某种特定的条件下被设置:

? 在我们数据为null的时候,有些数据库可能不能识别mybatis对null的默认处理。比如Oracle(报错);

? JdbcType OTHER:无效的类型;因为mybatis对所有的null都映射的是原生Jdbc的OTHER类型,oracle不能

正确处理;

? 由于全局配置中:jdbcTypeForNull=OTHER;Oracle并不支持,解决的两种办法:

? 1、#{email, jdbcType=NULL}; 在sql语句中进行设置

? 2、jdbcTypeForNull=NULL:直接在全局配置文件中,通过setting标签配置

			

4. Select

4.1 返回记录封装在List

接口方法

public interface EmployeeMapper {

    Employee getEmpById(Integer paramInteger);

    Long addEmp(Employee employee);

    Long updateEmp(Employee employee);

    // List
    List getEmpByAge(String paramName);

}

Mapper配置文件中的sql

? 使用模糊查询

    
    
    

测试方法

? 在测试方法中,传入模糊查询的条件:查姓名是J开头的记录

    @Test
    public void ListTest() throws IOException {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        try {
            EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
            List employeeList = mapper.getEmpByAge("J%");
            System.out.println(employeeList);
        } finally {
            sqlSession.close();
        }
    }

结果

[main] [com.atguigu.mybatis.dao.EmployeeMapper.getEmpByAge]-[DEBUG] ==>  Preparing: select * from info where name like ? 
[main] [com.atguigu.mybatis.dao.EmployeeMapper.getEmpByAge]-[DEBUG] ==> Parameters: J%(String)
[main] [com.atguigu.mybatis.dao.EmployeeMapper.getEmpByAge]-[DEBUG] <==      Total: 2
[Employee(id=1005, name=Jack, age=23), Employee(id=1006, name=James, age=26)]

4.2 返回记录封装在Map

接口方法

    // 封装到map对象中,单个对象
    Map getEmpByMap(Integer paramId);

    // 多条记录封装到map对象中,Map
    @MapKey("id") // 这个注解设置了mybatis在封装为map的过程中,将查询结果的id属性作为map的key
    Map getEmpByNameReturnMap(String name);

Mapper配置xml文件

    
    
    

    
    
    

测试方法

    @Test
    public void MapTest() throws IOException {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        try {
            EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);

            // Map employeeMap = mapper.getEmpByMap(1003); // 单条记录
            Map employeeMap = mapper.getEmpByNameReturnMap("J%"); // 返回名字为J开头的记录
            System.out.println(employeeMap); // 打印map,调用的是实现子类的toString()方法
        } finally {
            sqlSession.close();
        }
    }

结果

// 单条记录封装在Map中
[main] [com.atguigu.mybatis.dao.EmployeeMapper.getEmpByMap]-[DEBUG] ==>  Preparing: select * from info where id = ? 
[main] [com.atguigu.mybatis.dao.EmployeeMapper.getEmpByMap]-[DEBUG] ==> Parameters: 1003(Integer)
[main] [com.atguigu.mybatis.dao.EmployeeMapper.getEmpByMap]-[DEBUG] <==      Total: 1
{name=Mary, id=1003, age=21}
// 多条记录封装在Map中,在接口方法中,设置了map中id值作为key键
[main] [com.atguigu.mybatis.dao.EmployeeMapper.getEmpByNameReturnMap]-[DEBUG] ==>  Preparing: select * from info where name like ? 
[main] [com.atguigu.mybatis.dao.EmployeeMapper.getEmpByNameReturnMap]-[DEBUG] ==> Parameters: J%(String)
[main] [com.atguigu.mybatis.dao.EmployeeMapper.getEmpByNameReturnMap]-[DEBUG] <==      Total: 2
{1005=Employee(id=1005, name=Jack, age=23), 1006=Employee(id=1006, name=James, age=26)}

4.3 自定义结果集映射规则(重要)

? resultType:是跟自动封装有关的,在mapper配置文件中指定封装为什么类型,MyBatis就将查询结果封装为对应类型的数据格式。

? 但存在这样的情况,就是查询出来的结果集中,列名跟JavaBean的属性字段名不一样,这个时候是封装不成功的(打印出来,属性值为null),对此上文提到过有两种解决方案:

? 第一种:使用@Param注解,起别名

? 第二种:开启驼峰命名法,如果符合要求的话

? 第三种:使用自定义结果集映射

自定义resultMap,实现高级结果集的映射。

    
    
        
         
        
        
        
        
        
    

    
    
    

4.4 关联查询

级联属性封装结果

Mapper配置文件:

	
	 
	
	
		
		
		
		 
		 
	

	
	

association定义关联对象封装规则

association可以指定联合的JavaBean对象:在resultType标签中



	
	

相当于使用嵌套的方式:在mapper配置文件中完整的代入如下所示:

	
	
		
		
		
		
		
		
			
			
		
	
	
	

association分步查询

? 注意分步查询与级联封装中,association的用法区别。主要是根据sql语句来的。

JavaBean对象:

@Data
public class Department {
    private Integer id;
    private String departmentName;
    private List emps;

    public Department(Integer id, String departmentName, List emps) {
        this.id = id;
        this.departmentName = departmentName;
        this.emps = emps;
    }
}
@Data
public class Employee {
    private Integer id;
    private String name;
    private Integer age;
    private Department dept;

    public Employee(Integer id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
}

Department:

接口方法:

public interface DepartmentMapper {
    Department getDeptById(Integer id);
}

mapper配置文件:将查询department信息的sql语句与接口方法进行绑定,准备后面的分步查询

<?xml version="1.0" encoding="UTF-8" ?>



    
    


在EmployeeMapper的配置文件中:


	 
	 
	 
	 	
	 	
	 	
	 	
	 	
 		
 		
	 

	 
	 

结果:

? 查1号员工信息,在执行第一条查询sql语句的时候,也去执行了查部门信息的sql语句,然后获取到该员工的部门信息,最后打印输出。

延迟加载

? 当前的问题:

? 因为在Employee这个Bean对象中,有一个Department类属性Dept,而我们每次查询Employee对象的时候,都会将其Dept属性一起查询出来,如果我们并不立即使用这个属性的信息的话,这样会导致性能的下降。所以想在使用部门信息的时候,再去查询。用延迟加载解决:

? 延迟加载,就是按需加载,当需要使用某个关联属性的时候,如输出打印操作,才会进行执行。

? 在分段查询的基础上,全局配置中设置两个参数:

? 对aggressiveLazyLoading:开启时候,所有属性都会加载,关闭时,才会按需加载。

		
		
		
		
	

注意如果报异常:

Error creating lazy proxy. Cause: java.lang.NullPointerException

? 需要把mybatis的包换成Mybatis-3.5.1.jar及其以上的版本

collection定义关联集合封装规则

association:定义关联对象封装规则。

collection:定义关联集合封装规则。

上文都是只关联一个对象,现在是这个类的某个属性,关联多个对象的情况:

? 场景二:

? 查询部门的时候将部门对应的所有员工信息也查询出来

使用传统SQL语句:查询开发部的员工信息

mapper配置文件:

	
	 
	
	
		
		
        
		
		
			
			
			
			
			
		
        
	

	
	

测试:

collection分步查询

? 场景二:

? 查询部门的时候将部门对应的所有员工信息也查询出来,用分步查询的方法

MySQL数据库

? info表:员工信息表

? department表:部门信息表

大致思路
  1. 先执行查询特定部门的sql语句
  2. 然后嵌套查询在这个部门里面的员工信息
Bean代码
@Data
public class Department {
    private Integer id;
    private String departmentName;
    private List emps;

    public Department() {
    }

    public Department(Integer id, String departmentName, List emps) {
        this.id = id;
        this.departmentName = departmentName;
        this.emps = emps;
    }

}
@Data
public class Employee {
    private Integer id;
    private String name;
    private Integer age;
    private Department dept;

    public Employee() {
    }

    public Employee(Integer id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
Dao与其映射配置文件

DepartmentMapper接口方法:

public interface DepartmentMapper {
    Department getDeptById(Integer id);
    Department getDeptByIdStep(Integer id);
}

建立DepartmentMapper接口与其mapper配置文件的映射:

<?xml version="1.0" encoding="UTF-8" ?>




    
    
        
        
         
        
    
    
    
    
    
    


嵌套:EmployeeMapperPlus接口方法:用于查询某部门的员工信息

public interface EmployeeMapperPlus {
    List getEmpsByDeptId(Integer paramInteger);
}

? 建立EmployeeMapperPlus接口与其mapper配置文件的映射:

<?xml version="1.0" encoding="UTF-8" ?>


    
    
    


测试
    @Test
    public void CollectionTest() throws IOException {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        try {
            DepartmentMapper mapper = sqlSession.getMapper(DepartmentMapper.class);
            Department deptByIdStep = mapper.getDeptByIdStep(1); // 查询部门ID为1的部门信息

            System.out.println(deptByIdStep); // 打印部门信息

        } finally {
            sqlSession.close();
        }
    }
结果
[main] [com.atguigu.mybatis.dao.DepartmentMapper.getDeptByIdStep]-[DEBUG] ==>  Preparing: select id, departmentName from department where id=? 
[main] [com.atguigu.mybatis.dao.DepartmentMapper.getDeptByIdStep]-[DEBUG] ==> Parameters: 1(Integer)
[main] [com.atguigu.mybatis.dao.DepartmentMapper.getDeptByIdStep]-[DEBUG] <==      Total: 1
[main] [com.atguigu.mybatis.dao.EmployeeMapperPlus.getEmpsByDeptId]-[DEBUG] ==>  Preparing: select * from info where depId = ? 
[main] [com.atguigu.mybatis.dao.EmployeeMapperPlus.getEmpsByDeptId]-[DEBUG] ==> Parameters: 1(Integer)
[main] [com.atguigu.mybatis.dao.EmployeeMapperPlus.getEmpsByDeptId]-[DEBUG] <==      Total: 3
Department(id=1, departmentName=业务部, emps=[Employee{id=1001, name='Smith', age=23}, Employee{id=1003, name='Mary', age=21}, Employee{id=1005, name='Jack', age=23}])

discriminator鉴别器(了解)

判断某列的值,然后进行重新封装,改变原来的封装行为。

	
	
	 
	 	
	 	
	 	
	 	
         
	 	
	 	
	 		
            
	 		
	 			
		 		
	 		
	 		
	 		
		 		
			 	
			 	
			 	
	 		
	 	
         
	 

五、MyBatis-动态SQL

? 动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

? 是基于OGNL表达式的


1. if

	 
	 
	 

2. where

引出问题:

? 在上一节中,如果在测试方法中,没有传入id 的值但传入了lastName的值,那么就会出现类似于:

... where and last_name like #{lastName} 

? 这样的sql语句,肯定是错误的。两个解决方法:

? 第一种:第一条语句where后面加上 1 = 1,而下面的每条if标签,最前面都写上and,如下

	 

? 第二种:MyBatis推荐!使用where标签

? 用where标签会将所有的查询条件包括在内,MyBatis就会对where标签中拼接的sql语句进行处理,将sql语句多出来的and或or去掉。(仅限于sql语句的开头,即where标签中只会去掉第一个多出来的and或者or)

	 

3. trim_自定义字符串截取

? 在上一节的情况下,如果用户在if标签的内的sql语句的末尾加上and,即使在where标签内,mybatis并不会处理sql语句尾巴的and,此时会出现sql语句的错误:

	 

? 若id、lastName和gender均为空,而email有值,则sql:

... where last_name like #{lastName} and

? sql出错!

trim标签的介绍:

	 
	 

4. choose_分支选择

? 相当于Java中的 switch-case

	 
	 

? 如果在测试方法中,传入了多个参数,此时就类似于switch-case一样,哪个参数在前面先判断,只会进入一个分支,会选择该参数条件成立下的sql语句,并拼接执行,结束。

5. set与if结合的动态更新

原来的更新,是写死了,必须提供sql语句set后的字段的值,下面举个例子:

    
    
        update info
        set name=#{name}, age=#{age}
        where id=#{id}
    

现在要实现动态更新,即测试方法中,只需要提供需要更新的字段属性名称,实现效果:

第一种方法:使用set标签

	 
	 
	 	
	 	update info 
		
			
				name=#{name},
			
			
				age=#{age}
			
		
		where id=#{id} 
	 

set标签会自动去掉sql语句末尾多的逗号,如果有逗号的话。

第二种方法:使用trim标签

? 添加前缀set,后缀覆盖,去掉所有的逗号

	 
	 
		update info 
		
			
				last_name=#{lastName},
			
			
				email=#{email},
			
			
				gender=#{gender}
			
		
		where id=#{id}
	 

6. foreach_遍历集合

	 
	 

想实现的动态sql效果:

select * from tbl_employee where id in(?, ..., ?)

用户传入参数:两个或多个
select * from tbl_employee where id in(1, 2)
select * from tbl_employee where id in(2, 3, 4)

7. MySQL下foreach批量插入的两种方式

实现一条sql语句插入多个value值,根据用户输入的value值数目,进行动态插入。

第一种方式:有多少条想插入的记录,就执行多少次insert插入语句,或者执行一次,一次插入多条记录

注意:

? 第一种方法:执行多条插入语句的方法中,foreeach间隔符是分号 ; ,因为多条插入语句sql用分号进行隔开。

? 第二种方法:而执行一条插入语句但values有多条记录的方法中,foreeach的间隔符是逗号, ,因为是分隔多条value记录。

第一种方式:需要这种方式需要数据库连接属性allowMultiQueries=true

	 
	 

	
		insert into tbl_employee(last_name,email,gender,d_id)
		values(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
	

第二种方式:推荐这种方法,效率更高!


	insert into tbl_employee() 
    values
	
		(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
	

8. Oracle下foreach批量插入的两种方式(了解)

	 

	
	 
         
	 	
	 		insert into employees(employee_id,last_name,email) 
			    values(employees_seq.nextval,#{emp.lastName},#{emp.email});
	 	
     
	 	
	
	
	 	insert into employees(employee_id,last_name,email)
        	select employees_seq.nextval,lastName,email from(
	 		
	 			select #{emp.lastName} lastName,#{emp.email} email from dual
	 		
        )
	 
	<--或者-->
      
         insert into employees(employee_id,last_name,email)
	 		
	 			select #{emp.lastName} lastName,#{emp.email} email from dual
	 		
	  

9. _parameter & _databaseId & bind

	 
	  
	  
	  

10. sql_抽取可重用的sql片段

定义sql抽取:

	  
	  
	  		
	  			employee_id,last_name,email
	  		
	  		
	  			last_name,email,gender,d_id
	  		
	  

使用示例:

	
	 	insert into tbl_employee(
	 		
	 	) 
		values
		
			(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
		
	 

六、MyBatis-缓存机制

? MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。

MyBatis系统中默认定义了两级缓存。

一级缓存和二级缓存。 

? 1、默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。

? 2、二级缓存需要手动开启和配置,他是基于namespace级别的缓存。

	3、为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过**实现Cache接口来自定**

义二级缓存。

1. 体验封装的存在

? 都是来查1号员工,最后判断输出 true,说明emp02是直接使用之前查询的结果,使用了缓存。

2. 两级缓存

两级缓存:

? 一级缓存:(本地缓存)sqlSession级别的缓存,一级缓存是一直开启的;类似于一个Map,查询之前去看看这个要查的数据,有没有在map中。

? 与数据库同一次会话期间查询到的数据,将会放在本地缓存中

? 以后如果需要获取相同数据的话,直接就会去缓存中拿去,就没必要再去访问数据库

? 如果开启新的sqlSession,那么两个sqlSession会有属于自己的缓存

? 一级缓存失效的四种情况:(没有使用到当前一级缓存的情况,还需要再向数据库发出查询)

? 1. sqlSession不同,数据存放在各自的缓存中

? 2. sqlSession相同,查询条件不同(当前缓存中没有这个数据)

? 3. sqlSession相同,但两次查询之间执行了增删改操作(因为这次增删改操作可能会对当前数据有影响,所以不能选择还继续用缓存中的数据)

? 4. sqlSession相同,但手动清除了一级缓存。( openSession.clearCache() )

? 二级缓存:(全局缓存)基于namespace级别的缓存,一个namespace对应一个二级缓存。

? 工作机制:

? 1. 一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中

? 2. 如果会话关闭,那么一级缓存中的数据会被保存到二级缓存中,新的会话查询信息,就可以先去参照二级缓存

? 3. sqlSession == EmployeeMapper ==》Employee数据

? DepartmentMapper==》Department数据

? 不同namespace查出来的数据,会放在自己对应的缓存中,如上面的两个mapper配置文件,由于定义了各自的命名空间,所以也就是两个二级缓存(底层实现其实是map)

3. 二级缓存使用&细节

? 默认是开启二级缓存的,但为了防止被版本更新改为默认关闭,所以还是最好在MyBatis全局配置中手动开启。

? 需要注意的,只有会话关闭之后,一级缓存的数据才会写入到二级缓存中去。

? 使用步骤:

? 第一步:在MyBatis全局配置文件中,显示开启全局二级缓存配置

    
        
    

? 第二步:在mapper配置xml文件中,配置使用二级缓存


    

? cache标签属性:

	
	
	

? 和缓存有关的设置/属性:

? 1)默认cacheEnabled=true;

? 如果false的话:关闭缓存(二级缓存关闭,一级缓存一直可用)

? 2)每个select标签都默认useCache="true";

? 如果false的话:不使用缓存(二级缓存关闭,一级缓存依然可以使用)

? 3)每个增删改标签默认flushCache="true";查询标签是默认为false,查询完不清空缓存

? 增删改执行完成后就会清除缓存

? 如果flushCache="false"的话, 一级缓存、二级缓存都会被清空,虽然去查了,也就是命中了key,但没有对应value值

? 4)openSession.clearCache();只会清除当前sqlSession的一级缓存

? 5)localCacheScope:全局设置setting中的属性,本地缓存作用域(相当于一级缓存)

? SESSION:当前会话的所有数据都会被保存到一级缓存中

? STATEMENT:禁用一级缓存

第三步:我们的POJO需要实现序列化接口

测试一级缓存的测试方法:

  @Test
  public void testFirstLevelCache() throws IOException {
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try {
      EmployeeMapper mapper = (EmployeeMapper)openSession.getMapper(EmployeeMapper.class);
        
      Employee emp01 = mapper.getEmpById(Integer.valueOf(1));
      System.out.println(emp01);
        
      Employee emp02 = mapper.getEmpById(Integer.valueOf(1));
      System.out.println(emp02);
        
      System.out.println((emp01 == emp02));
    } finally {
      openSession.close();
    } 
  }

? 如果一级缓存开启的话,最终结果会打印 true 。

测试二级缓存的测试方法:

  @Test
  public void testSecondLevelCache() throws IOException {
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    SqlSession openSession2 = sqlSessionFactory.openSession();
    EmployeeMapper mapper = (EmployeeMapper)openSession.getMapper(EmployeeMapper.class);
    EmployeeMapper mapper2 = (EmployeeMapper)openSession2.getMapper(EmployeeMapper.class);
      
    Employee emp01 = mapper.getEmpById(Integer.valueOf(1));
    System.out.println(emp01);
    openSession.close();
      
    Employee emp02 = mapper2.getEmpById(Integer.valueOf(1));
    System.out.println(emp02);
    openSession2.close();
  }

? 如果二级缓存开启的话,那么在debug打印的信息中,可以发现只执行了一条sql语句并显示有命中信息,也就是第二条语句去查询的时候,命中了二级缓存中的数据。

? 需要注意的是,由于sqlSession会话只有关闭的时候,才会把一级缓存的数据写入到二级缓存中,因为如果将上面测试方法中两个session会话给放到最后查询执行完才关闭的话,那么debug显示出来的信息是执行了两条sql语句。

4. 缓存原理示意图

5. 第三方缓存框架EhCache整合

? MyBatis缓存的底层就是map,由于在MyBatis中cache是接口类型,往往需要使用第三方强大的缓存框架进行整合。

? EhCache:

? EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认

CacheProvider。Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。

  Spring 提供了对缓存功能的抽象:即允许绑定不同的缓存解决方案(如Ehcache),但本身

不直接提供缓存功能的实现。它支持注解方式使用缓存,非常方便。

? pom.xml引入依赖:



    net.sf.ehcache
    ehcache
    2.10.6

七、MyBatis-Spring整合(SSM整合)

后面会更新SSM整合的学习文档,翻翻我博客看看,如果没有就说明还没有更新

八、MyBatis-逆向工程(代码生成器)

MyBatis Generator:

? 简称MBG,是一个专门为MyBatis框架使用者定制的代码生成器,可以快速的根据表生成对

应的映射文件、接口、以及bean类。支持基本的增删改查,以及QBC风格的条件查询。但是表连

接、 存储过程等这些复杂sql的定义需要我们手工编写。

maven导入依赖:



    org.mybatis.generator
    mybatis-generator-core
    1.3.7

? 简单来说,就是能够根据数据库的表,然后生成对应的JavaBean、mapper接口方法以及sql与接口映射配置文件。

1. MBG配置文件

注意:

1. 根据项目需求生成不同级别的CRUD版本
2. 注意生成文件的输出地址
<?xml version="1.0" encoding="UTF-8"?>


    
    
    
        
        
        

        
        
            
        

        
        
            
            
        

        
        
            
        

        
        
            
        

        
        

2. 执行逆向生成

    @Test
    public void testMBG() throws XMLParserException, IOException, InvalidConfigurationException, SQLException, InterruptedException {
        List warnings = new ArrayList();
        boolean overwrite = true;
        File configFile = new File("src/main/resources/mbg.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null);
    }

3. 测试

targetRuntime="MyBatis3Simple"

效果展示:

? 主要看接口类,生成了哪些支持的CRUD操作:

? sql映射配置xml文件:

<?xml version="1.0" encoding="UTF-8"?>


  
    
    
    
  
  
    
    delete from department
    where id = #{id,jdbcType=INTEGER}
  
  
    
    insert into department (id, departmentName)
    values (#{id,jdbcType=INTEGER}, #{departmentname,jdbcType=VARCHAR})
  
  
    
    update department
    set departmentName = #{departmentname,jdbcType=VARCHAR}
    where id = #{id,jdbcType=INTEGER}
  
  
  

测试查询所有记录:

    @Test
    public void test() throws IOException {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
            List list = mapper.selectAll();
            System.out.println(list);
        } finally {
            sqlSession.close();
        }
    }

结果:

[main] [com.atguigu.mybatis.dao.EmployeeMapper.selectAll]-[DEBUG] ==>  Preparing: select id, name, age, depId from info
[main] [com.atguigu.mybatis.dao.EmployeeMapper.selectAll]-[DEBUG] ==> Parameters: 
[main] [com.atguigu.mybatis.dao.EmployeeMapper.selectAll]-[DEBUG] <==      Total: 6
[Employee{id=1001, name='Smith', age=23, depid=1}, Employee{id=1002, name='Tom', age=23, depid=2}, Employee{id=1003, name='Mary', age=21, depid=1}, Employee{id=1004, name='Dick', age=21, depid=2}, Employee{id=1005, name='Jack', age=23, depid=1}, Employee{id=1006, name='James', age=26, depid=2}]

targetRuntime="MyBatis3"

支持复杂的CRUD操作

与simple的最明显差别,就是多了个两个Example的bean对象。

支持的操作:

测试:

  @Test
  public void testMyBatis3() throws IOException {
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try {
      EmployeeMapper mapper = (EmployeeMapper)openSession.getMapper(EmployeeMapper.class);
      // xxxExample这个类,是用来封装查询条件的
      EmployeeExample example = new EmployeeExample();
        
      // criteria是用来拼接查询条件的
      EmployeeExample.Criteria criteria = example.createCriteria();
      // 拼接Name的模糊查询,%a%
      criteria.andNameLike("%a%"); // 查询员工名字中有字母a的
      // 拼接部门id为1
      criteria.andDepidEqualTo("1"); // 查询部门id为1的
      
      // 如果查询条件由or连接两个条件组成,则还需要创建criteria用来拼接另一个条件
      EmployeeExample.Criteria criteria2 = example.createCriteria();
      criteria2.andAgeBetween(22, 23); // age在22到23岁
      
      // 用 or 连接
      example.or(criteria2);
      
      List list = mapper.selectByExample(example);
	  System.out.println(list);
    } finally {
      openSession.close();
    } 
  }

最后的sql语句如下:

通过上面的例子,就可以举一反三,使用框架进行复杂的CRUD操作。

九、MyBatis-运行原理

1. 框架分层架构

2. SQLSessionFactory的初始化

		// 获取SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

测试过程:

1)进入SqlSessionFactoryBuilder()

2)调用SqlSessionFactoryBuilder().build(),创建解析器parser,进入XMLConfigBuilder(),对配置文件进行解析

3)XMLConfigBuilder()中,调用parseConfiguration()方法

解析全局配置文件,其中this.mapperElement将会解析mapper映射配置文件

创建mapperParse解析器,然后调用parse()方法解析映射xml配置文件:

configuration对象保存了所有配置文件的详细信息:

3. openSession获取SqlSession对象

        // 2.获取SqlSession实例,与数据库建立会话
        SqlSession sqlSession = sqlSessionFactory.openSession();

4. getMapper获取到接口的代理对象

            // 3. 获取接口的实现类对象
            EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);

5. 查询实现

            Employee emp = mapper.getEmpById(1001);

底层其实是JDBC操作,MyBatis对其进行封装

查询流程总结:重要

? 首先,创建一个代理对象进行增删改查操作,但实际上这个代理对象是用DefaultSqlSession进行增删改查,而这个DefaultSqlSession是通过Executor进行增删改查,而Executor会创建一个StatementHandler对象,用于处理sql语句、预编译和设置参数等相关工作。StatementHandler对象在进行处理工作的时候,创建了ParameterHandler和ResultSetHandler,前者是用来设置预编译参数用的,执行操作,此时,后者是用来处理执行返回的结果集。而设置参数和处理结果集执行,实际上是二者是通过TypeHandler进行设置参数并执行。而TypeHandler实际执行,是通过调用底层原生JDBC操作。

十、MyBatis-插件

? 拦截目标对象,用插件为目标对象创建一个动态代理,然后使用AOP的思想,通过动态代理对目标对象的目标方法进行拦截,同时对目标方法可以进行增强的操作,然后执行。从而实现控制sql语句执行的过程。

十一、MyBatis-扩展

1. PageHelper插件进行分页

MyBatis 分页插件 PageHelper

首先需要在全局配置文件中添加依赖:

    
    
        
        
            
            
        
    

测试方法:

    @Test
    public void testSelectAll() throws IOException {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession();

        try {
            EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);

            Page page = PageHelper.startPage(3, 2);

            List list = mapper.getEmpAll();

            // 传入要连续显示多少页
            PageInfo info = new PageInfo<>(list, 2);

            for (Employee employee : list) {
                System.out.println(employee);
            }
            System.out.println("当前页码:" + page.getPageNum());
            System.out.println("总记录数:" + page.getTotal());
            System.out.println("每页记录数:" + page.getPageSize());
            System.out.println("总页码:" + page.getPages());

            System.out.println();
            System.out.println("当前页码:" + info.getPageNum());
            System.out.println("总记录数:" + info.getTotal());
            System.out.println("每页记录数:" + info.getPageSize());
            System.out.println("总页码:" + info.getPages());
            System.out.println("是否是第一页:" + info.isIsFirstPage());
            System.out.println("连续显示的页码:");
            int[] nums = info.getNavigatepageNums();
            for (int i : nums) {
                System.out.println(i);
            }

        } finally {
            sqlSession.close();
        }
    }

查询结果展示:

[main] [com.atguigu.mybatis.dao.EmployeeMapper.getEmpAll_COUNT]-[DEBUG] ==>  Preparing: SELECT count(0) FROM info
[main] [com.atguigu.mybatis.dao.EmployeeMapper.getEmpAll_COUNT]-[DEBUG] ==> Parameters: 
[main] [com.atguigu.mybatis.dao.EmployeeMapper.getEmpAll_COUNT]-[DEBUG] <==      Total: 1
[main] [com.atguigu.mybatis.dao.EmployeeMapper.getEmpAll]-[DEBUG] ==>  Preparing: select * from info LIMIT ?, ?
[main] [com.atguigu.mybatis.dao.EmployeeMapper.getEmpAll]-[DEBUG] ==> Parameters: 4(Long), 2(Integer)
[main] [com.atguigu.mybatis.dao.EmployeeMapper.getEmpAll]-[DEBUG] <==      Total: 2
Employee{id=1005, name='Jack', age=23}
Employee{id=1006, name='James', age=26}
当前页码:3
总记录数:6
每页记录数:2
总页码:3

当前页码:3
总记录数:6
每页记录数:2
总页码:3
是否是第一页:false
连续显示的页码:
2
3

2. 批量操作

openSession(ExecutorType.BATCH); 开启一个批处理的session

  @Test
  public void testBatch() throws IOException {
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    
      // 在创建sqlSession的时候,设置这是一个批处理的session
    SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
      
    long start = System.currentTimeMillis();
    try {
      EmployeeMapper mapper = (EmployeeMapper)openSession.getMapper(EmployeeMapper.class);
      for (int i = 0; i < 10000; i++)
        mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0, 5), "b", "1")); 
      openSession.commit();
      long end = System.currentTimeMillis();
      System.out.println("+ (end - start));
    } finally {
      openSession.close();
    } 
  }

? 非批处理:执行流程是,每进行一次插入语句,就 发送sql语句—》预编译设置参数—》执行 ,每一次的插入都是一次完成的数据库访问操作,消耗大量的预编译时间。

? 批处理:发一条sql语句给数据库,数据库预编译好之后,批处理不断传参数,数据库只需要等待这条sql语句需要执行多少遍的参数,然后再一次性执行。