(正式篇)MyBatis使用教程、MyBatis批量新增、MyBatis的in条件查询(foreach使用),MyBatis二级缓存,Spring整合MyBatis,MyBatis工作原理和插件使用

文章目录

一、认识、了解MyBatis

这篇文章介绍讲解一下MyBatis这个框架。这里基于 Mybatis3 来进行讲解。MyBatis3以前的版本,在名字上也不叫MyBatis,而是叫 iBatis 。这个原本是Apache下的一个项目,但是后来这个项目的开发团队转战到了谷歌旗下,就把这个 iBatis 框架改名为 MyBatis 了。而且他们转战的时候呢,正值Mybatis3 .0发布的时期,所以说呢,我们以后所说的 MyBatis 其实就是 iBatis3.0 以后的版本。大家在网上或者在招聘上看到的 iBatis、MyBatis 其实这两个东西是一回事。

MyBatis 它是一个框架,而且是一个持久化层的框架。持久化层框架,也就是所谓的跟数据库进行交互的框架。

说到和数据库交互,这里得要说一说 JDBC

用 JDBC 来执行 SQL 有如下几个步骤:
在这里插入图片描述

  1. 先编写一个sql语句
  2. 用 PreparedStatement 对象预编译一下这个sql语句
  3. 整个sql语句预编译完了以后,接下来去设置参数
  4. 用 PreparedStatement 对象的 executeUpdate 、execute 等方法执行sql
  5. sql执行完了以后就进行封装结果(如果是一个查询我们就封装结果)

在实际项目开发中,基于业务功能的需要,会有很多SQL需要执行,如果每执行一个 SQL 都要操作上面这些步骤代码量也比较多,编写也比较烦。后来程序员就会使用基于以上步骤封装好的执行 SQL 的工具类,来和数据库进行交互操作。比如:Apache 做的一个工具类 Dbutils 它里面有一个QueryRuner 用它来执行增删改查是非常方便的,还有 Spring 框架也有一个对 JDBC 封装好的模块叫 JdbcTemplate 用它执行增删改查也是非常方便。或者开发者自己编写的执行 SQL 的工具类,也能很好的和数据库数据进行交互。

这些Dbutils 、Spring 的 JdbcTemplate 等也都只是一个工具,工具类的出现只是方便了开发者操作数据库数据,但是总的来说它的功能简单,考虑得不周到。比如说:编写代码要写SQL语句的,虽然有了工具类,但是还是和JDBC的方式一样需要将SQL语句编写在java代码里面,这样SQL语句和业务代码是耦合在一起的,这种硬编码,高耦合的方式在任何时候都是不推荐的方式,因为这样对后续的升级维护是不方便的。这样框架就出现了。

框架要考虑到众多的因素,比如:如何进行事务控制、结果怎样封装、如何实现查询缓存、查询部分字段怎样映射等等。而工具就简单了,发个 SQL 一执行,封装结果就完事,更多的其他情况是不考虑的,需要按照具体的情况自己手工的去处理。这就像我们洗衣服的时候,你手工用搓衣板虽然替代了你一部分工作,和你把衣服放进洗衣机进行洗衣的情况一样,那些Dbutils 、Spring 的 JdbcTemplate 等就像是洗衣板,框架就像是个洗衣机。所以说,小工具跟框架的区别还是挺大的。

说到和数据库交互的框架,这里得来说一说 Hibernate

Hibernate 它是 java 对象持久化到数据库的一个优秀的,全自动的ORM框架,所谓的ORM指的是 Object Relatino Mapping (对象关系映射)。这个 ORM 通俗一点来说呢就是将 java 对象跟数据库表里面的记录进行映射,也就是说一个 java 对象对应数据库表的一条记录。体现在 java 代码上就是:一个类对应一张表,类的一个实例对象可以对应表的一条记录(也就是表的一行),而类的一个属性对应表的一列,这就是ORM的对应关系。hibernate就是基于这种ORM的思想,将 JDBC 执行SQL语句的步骤封装做成了框架,让程序员把对数据库表中的记录操作转化为对 java 对象的操作

在使用 hibernate 做项目开发的时候,开发者通过使用 hibernate 提供的 API 来对实体类(持久化类)对象进行操作就能操作数据库表记录,它内部底层还是使用 jdbc 的那几个步骤来操作数据的;使用 hibernate 开发项目的时候只需要告诉 hibernate 哪个实体类(持久化类)对应数据库的哪个数据表,实体类(持久化类)对象中的哪个属性对应数据表的哪个列,这样通过 hibernate 的 API 操作对象的时候框架自动的就生成 SQL语句 、那些预编译、给 SQL 设置参数、执行SQL、封装结果,这些个步骤 hibernate 框架内部就直接做好了,整个过程就是个黑箱操作,全部都是hibernate自动来搞。

使用 hibernate 框架来做项目开发,JDBC 的那些步骤被hibernate做成了黑箱操作,全部都是hibernate自动来搞,连 SQL 都是框架内部生成,而 SQL 语句又是我们和数据库交互的命脉,框架自动生成 SQL 语句的话,会导致我们在编写 SQL 这个关键环节我们没法自定义。在实际项目开发中,有些复杂的查询,我们都是要定制 SQL 的,而不用框架自己生成的,我们自己定制的SQL可以做一些优化,优化过的 SQL 在运行的时候是能够加快系统的运行速度,而框架自己生成 SQL 在运行速度上可能会差一些。

hibernate 还有一个特点就是全自动、全映射,比如说:在 java 实体类(持久化类)中假设有100个属性,那么数据库的这一条记录就会有100列,也是有100个字段与这个类中的属性对应,在查询的时候,一查就是100个字段,然后框架内部就会根据实体类(持久化类)中的属性和数据库字段的映射关系将查到数据封装成对象。可能在实际的业务功能中只需要其中几个字段属性而已,这样一下子查了全部的列,又在框架内部将这些值封装成对象,这样对计算机的计算资源是一种浪费。

使用 hibernate 做SQL优化、定制 SQL 也是可以的,这就的要学习 hibernate 中的 hql,要学习 hibernate 的一些查询策略,充分掌握 hibernate 的内部原理,这样就能做到定制SQL 、优化SQL和查询部分字段等一些自定义的操作了。实际项目开发中都会牵扯到 SQL 优化和定制SQL,hibernate的各种查询机制,内部原理等该学的还是得要学。这是要付出一定的学习成本的。

实际项目开发中都会牵扯到 SQL 优化和定制SQL,开发者希望在使用框架的时候不要自动地生成 SQL 语句,由开发人员来编写,因为 SQL 语句是与数据库交互的命脉,开发者自己编写的 SQL 语句能够灵活的定制和优化。这样 MyBatis 就出来了。

MyBatis 和 Hibernate 相比,内部底层 JDBC的那几个步骤就没有完全黑箱操作。MyBatis 的做法是将最重要的编写 SQL 这个部分做成一个配置文件让开发者自己来定义编写,剩下的预编译、设置参数、执行SQL、封装结果工作都由 MyBatis 来自动完成。也正是由于 SQL 可以由开发者来编写而不是自动生成的原因, MyBatis 也被称之为半自动的ORM框架。

MyBatis 将编写 SQL 做成了一个配置文件让开发者来编写,也就是把和数据库交互的命脉交给了开发者,这样也就实现了开发者想要定制和优化 SQL 的需求。而且这种方式也让 SQL 与 JAVA 编码分离开了,这个编写 SQL 的配置文件就专注于跟数据库里边数据交互这一块,而 JAVA 编码部分就专注于业务逻辑,也就是让数据库交互和业务编码给解耦了。这样就分得非常清楚,用起来就非常好用。在使用 MyBatis 来做项目开发的时候,就只需要把 SQL 语句写在配置文件中,MyBatis 就会帮你执行和封装结果。所以说,使用 MyBatis 主要就是学习和掌握好 SQL ,不像hibernate那样有很多的内部原理和查询机制需要学习。这样学习成本相较于 hibernate 就低一些。

MyBatis 除了学习代价低的特点之外还有一个特点就是轻量级,mybatis 使用一个依赖就能使用,不像hibernate那样需要好多个jar包依赖才能使用。

操作数据库数据从原生的JDBC发展到框架,是因为单纯用JDBC来操作会有很多重复的代码,代码量也比较多,编写也比较烦;然后就发展到JDBC工具类,但它的功能简单,考虑得不周到、sql语句和JDBC方式一样是编写在java代码里面的,这样SQL和业务代码是耦合在一起的,这种硬编码,高耦合的方式在任何时候都是不推荐的方式,因为这样对后续的升级维护是不方便的。

综合以上所述并结合其他的资料可有以下的认识

MyBatis历史

原是Apache的一个开源项目iBatis,2010年6月这个项目由Apache Software Foundation 迁移到了Google Code,随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis ,代码于2013年11月迁移到Github(下载地址见后)。

iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。 iBatis提供的持久层框架包括SQL Maps和 Data Access Objects(DAO)

MyBatis简介

MyBatis 是一个流行的持久层框架,用于在 Java 应用程序中简化数据库操作。它允许开发者通过 XML 或注解配置 SQL 语句,实现对数据库的直接访问。MyBatis 的核心优势在于其灵活性和性能,它支持自定义 SQL 映射,允许开发者编写高效的查询,并且提供了丰富的 ORM 功能来简化数据模型与数据库表之间的映射。

MyBatis 还提供了强大的结果映射能力,可以处理复杂的关联查询和结果集。此外,它支持插件机制,允许开发者扩展框架的功能,如实现自定义的缓存策略或日志插件。MyBatis 可以与 Spring 框架无缝集成,利用 Spring 的声明式事务管理和依赖注入功能,进一步提高开发效率。 MyBatis 的简洁设计和对 SQL 的直接控制使其成为需要精细调整数据库访问逻辑的应用程序的理想选择。尽管它可能需要比一些全功能的 ORM 框架更多的手动配置,但这种控制也带来了优化查询性能和减少不必要的数据库交互的可能性。

使用MyBatis的一些好处

  • MyBatis 是一个半自动化的持久化层框架,可以由开发者自定义 SQL 语句,这为复杂查询提供了极大的灵活性,同时也正因如此开发者可以写出更高效的查询,从而提高应用程序的性能。
  • MyBatis 可以将SQL语句编写在配置文件里,不用和业务代码耦合在一起方便维护,也利于阅读。
  • MyBatis 可以由开发者自定义 SQL 语句,所以对于熟悉 SQL 的开发者来说,上手容易,学习成本较低。
  • MyBatis 提供了丰富的插件接口,允许开发者扩展其功能,如分页插件、性能分析插件等。
  • MyBatis 的配置可以通过 XML 文件或注解来完成,提供了灵活的配置方式。
  • MyBatis 可以很容易地与 Spring、Spring Boot 等流行框架集成,形成强大的后端开发组合。
  • MyBatis 提供了一级缓存(SqlSession级别)和二级缓存(Mapper级别),可以结合实际的业务需求来使用MyBatis内置的缓存,可以显著的提高数据访问层的性能。

MyBatis和其他一些持久层技术的比较

  • MyBatis 和 JDBC 操作数据库数据的比较
    • JDBC 方式 SQL 语句是以硬编码的方式写在 Java 代码块里的,耦合度高,这样会造成后期维护,升级不方便,因为实际开发需求中或者项目上线运行后业务需求可能会有变化,曾经写好的 SQL 语句也是有可能发生变化的,频繁修改的情况也比较多见,耦合在 Java 代码块里的 SQL 语句要修改是不方便的。MyBatis的方式是把 SQL 语句编写在一个XML的配置文件里,这样可以让数据库交互和业务编码给解耦了,方便了维护,也利于阅读。
  • MyBatis 和 Hibernate、JPA操作数据库数据的比较
    • Hibernate、JPA 对于一些长的、难的、复杂的 SQL处理起来是不容易的。MyBatis可以由开发者自定义 SQL 语句,对于各种长的、难的、复杂的 SQL 语句是可以灵活定制的。
    • Hibernate、JPA 是内部自动生产的SQL,不容易做特殊优化。MyBatis可以由开发者自定义 SQL 语句, 这意味着开发者可以根据需要对 SQL 语句进行精细的优化,从而提升数据库查询的性能和效率。
    • Hibernate 和 JPA 作为基于全映射的全自动 ORM 框架,它们通过将数据库表映射为 Java 类(POJO),为开发者提供了便捷的数据库操作方式。然而,当处理具有大量字段的 POJO 时,如果需要进行部分字段映射,可能会遇到一些复杂性,并且可能会因为 ORM 框架的自动映射机制而导致数据库性能的一定下降。 相比之下,MyBatis 提供了更为灵活的结果类型映射机制。无论是查询数据库中的部分字段,还是需要处理复杂的多表关联查询,MyBatis 都能够通过自定义的 SQL 映射和结果映射策略,有效地实现精确的映射。这种灵活性使得 MyBatis 在处理复杂查询和结果集映射时,能够保持较高的性能,并且能够更精细地控制 SQL 语句的执行,从而优化数据库的访问效率。

总的来说 MyBatis 的出现为开发者提供了编写自定义 SQL 的能力,这种灵活性使得 SQL 语句与 Java 业务逻辑能够分离,从而实现了功能上的明确划分。在这种工作模式下,开发者可以专注于业务逻辑的实现,而将数据处理的细节交由 MyBatis 管理。这样的分离不仅提高了代码的可维护性,也使得项目结构更加清晰,有助于团队成员各司其职,分别深入各自领域的优化和开发。

下载 MyBatis

MyBatis 迁移到Github下,可以到Github去下载 https://github.com/mybatis/mybatis-3/
在这里插入图片描述

二、入门案例

1、先创建一个数据表

-- mysql 为例
create table employee(
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `last_name` varchar(128) DEFAULT NULL,
  `email` varchar(128) DEFAULT NULL,
  `gender` varchar(128) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4;

insert into employee (`last_name`,`email`,`gender`) values('员工1','123','0');
insert into employee (`last_name`,`email`,`gender`) values('员工2','hj','1');

2、创建普通的java工程。

eclipse 或者其他的开发工具都行,用Maven的方式创建一个普通的java工程,其他方式也行。只要将必要的依赖jar包加入到项目工程中就行。

3、引入依赖

主要是引入 MyBatis和数据库驱动的依赖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	
	<modelVersion>4.0.0</modelVersion>
	
	<groupId>com.xxx.mybatisdemo</groupId>
	<artifactId>mybatis-demo01-gettingstarted</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	
	<name>mybatis-demo01-gettingstarted</name>
	
	<packaging>jar</packaging>
	
	<description>mybatis入门</description>
	
	<dependencies>
		<!-- MyBatis -->	
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.4.1</version>
		</dependency>
		
		<!-- 数据库驱动 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.6</version>
			<scope>runtime</scope>
		</dependency>
		
		<!-- 为了方便查看Mybatis的日志,这里引入一下log4j。 还要引入一个log4j.xml的文件  不引入没有日志查看 -->
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.17</version>
		</dependency>
	</dependencies>
	
	<build>
		<plugins>  
	      <plugin>  
	        <groupId>org.apache.maven.plugins</groupId>  
	        <artifactId>maven-compiler-plugin</artifactId>  
	        <configuration>  
	          <source>1.8</source>  
	          <target>1.8</target>  
	        </configuration>  
	      </plugin>  
	    </plugins>
	</build>
	
</project>

4、编写持久化 Java 类

也就是编写一个和数据库表中字段对应的java类。也叫实体类。

package com.xxx.mybatis.entity;

public class Employee {

	private Integer id;
	private String lastName;
	private String email;
	private String gender;
	
	// 写上getter、setter、toString 方法
}

5、创建 Mybatis 全局配置文件

在 MyBatis 应用中需要用到一个 sqlSessionFactory 对象,而这个 sqlSessionFactory 需要一个XML文件(也就是全局配置文件)来创建。

这个全局配置文件里面的内容包含了数据库连接信息,其他一些设置项信息等。

(当然也有不用xml创建 sqlSessionFactory 的方式。官网有,他就是new一些类 。这里就不着重介绍这个方式)

这个文件应该位于应用的 classpath 下,一般命名为:mybatis-config.xml。

在这个案例里把这个文件写在了 /src/main/resources/mybatis/mybatis-config.xml 目录下,也就是类路径下的 mybatis 目录下。(这个mybatis目录是自己创建的)

<?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">
<!-- 
	官方文档说需要从一个xml中构建一个sqlSessionFactory。 当然也有不用xml创建sqlSessionFactory的方式。官网有,他就是new一些类 。
	每一个mybatis的应用都是围绕sqlSessionFactory实例
-->
<configuration>
	<!-- environments表示环境的意思 -->
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<!-- 数据源 -->
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.jdbc.Driver" /> <!-- 数据库驱动 -->
				<property name="url" value="jdbc:mysql://localhost:3306/mybatis" /> <!-- 连接的url -->
				<property name="username" value="root" /> <!-- 数据库用户名 -->
				<property name="password" value="root" /> <!-- 数据库密码 -->
			</dataSource>
		</environment>
	</environments>
	
	<!-- 这个节点用来注册编写好的 SQL 映射文件 -->
	<mappers>
		<!-- 多层包,在这里用斜杠表示,有的地方用点表示,需要注意。 -->
		<mapper resource="mybatis/mapper/EmployeeMapper.xml" />
	</mappers>
</configuration>

6、编写映射文件

编写映射文件也就是编写数据库表记录映射关系的文件,里面的内容就是配置每一个sql,以及sql的封装规则等。

文件的命名可以随便写,但是规范来说一般都是按照 实体类名+Mapper.xml 的方式来起名。

在这个案例里把这个文件命名为 EmployeeMapper.xml, 并把这个文件写在了 /src/main/resources/mybatis/mapper/EmployeeMapper.xml 目录下(这个mybatis/mapper 目录是自己创建的)

<?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">
 <!-- namespace:名称空间; 在这个示例中名称空间可以随意写,因为在使用的时候是用指定的命名空间去查询 -->
<mapper namespace="com.mybatis.namespace.EmployeeMapper">
 
 
   <!-- 
	   id:                     唯一标识              sql的唯一标识
	   parameterType:          这个属性的意思是参数的类型,写上传入参数的全类名或者别名。也可以省略不写。 一般还是写上吧。
	   resultType:             返回值类型,将查询出来的结果封装成什么类型的对象
	   #{id}:                  从传递过来的参数中取出id值
    -->
	<select id="getEmpById" parameterType="java.lang.Integer" resultType="com.xxx.mybatis.entity.Employee">
		<!-- 
             在这个示例中:Employee类里面的 lastName 属性和数据库表中的 last_name 字段是不对应的,
                          所以在执行查询的时候 Employee 类的 lastName 属性是没有值的。

             在这个示例中想要让这个 Employee 类的 lastName 属性能有值,这个属性需要和数据库表中的字段相同才能将查询结果赋值和插入数据库。
             当然可以在查询的时候可以在sql语句中写上字段的别名,让别名和类中的属性名相同的方式。

	    	 因为没配置实体类字段和数据库字段映射规则,默认就是类中的属性对应数据库表的字段。例如:数据库的字段有下划线,类的属性也要有下划线。
		 -->
		select * from employee where id = #{id}
	</select>
</mapper>

7、将映射文件注册到全局配置文件

在全局配置文件的 <mappers> 节点里面加入mapper.xml的位置;就是说将 sql映射文件注册到全局配置文件当中。
在这里插入图片描述

8、编写 log4j 日志输出文件(可选)

为了方便在控制台查看执行 SQL 的日志记录,需要引入 log4j 依赖和编写 log4j 日志输出文件。这是一个可选的步骤。

在这个案例里把log4j.xml文件写在了 /src/main/resources/log4j.xml 。也就是写在了类路径下。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
 
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
 
 <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
   <param name="Encoding" value="UTF-8" />
   <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m  (%F:%L) \n" />
   </layout>
 </appender>
 <logger name="java.sql">
   <level value="debug" />
 </logger>
 <logger name="org.apache.ibatis">
   <level value="info" />
 </logger>
 <root>
   <level value="debug" />
   <appender-ref ref="STDOUT" />
 </root>
</log4j:configuration>

9、编写操作数据库表记录的代码

package com.xxx.mybatis.maintest;

import java.io.InputStream;

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 com.xxx.mybatis.entity.Employee;

public class MainTest {
	/**
	 * 操作:
	 * 1、引入依赖
	 * 
	 * 2、创建entity实体类;
	 *   (在这个示例中:实体类的字段应和数据库中的字段相同才能将查询结果赋值和插入数据库。
	 *     因为没配置实体类字段和数据库字段映射规则,默认就是类中的字段对应数据库的字段。例如:数据库的字段有下划线,类的属性也要有下划线。
	 *     当然可以在查询的时候可以在sql语句中写上字段的别名,让别名和类中的属性名相同的方式)
	 *
	 * 
	 * 3、编写全局配置文件;mybatis-config.xml配置文件。当然也有不用xml创建sqlSessionFactory的方式。官网有,他就是new一些类 。如下面第6点示例:
	 *    (里面的内容就是一些运行环境信息)
	 * 
	 * 
	 * 4、编写实体类和数据库映射的mapper.xml文件;
	 *    (里面的内容就是配置每一个sql,以及sql的封装规则等。)
	 * 
	 * 
	 * 5、在全局配置文件的<mappers>节点里面加入mapper.xml的位置;就是说将 sql映射文件注册到全局配置文件当中
	 * 
	 * 
	 * 6、编写代码
	 *    6-1、根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象 。
	 *         当然也有不用xml创建sqlSessionFactory的方式。官网有,他就是new一些类 。如下面示例:
	 *         DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
	 *		   TransactionFactory transactionFactory = new JdbcTransactionFactory();
	 *		   Environment environment = new Environment("development", transactionFactory, dataSource);
	 *		   Configuration configuration = new Configuration(environment);
	 *		   configuration.addMapper(BlogMapper.class);
	 *		   SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
	 * 
	 *    6-2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句,
	 *    6-3、就是调用SqlSession里面的增删改查的api进行数据库的操作,
	 * 
	 *    6-4、最后关闭sqlSession
	 */
	
	public static void main(String[] args) throws Exception {
		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		try {
			//3、调用SqlSession中的API方法来进行数据库操作
			/**
			 * 第一个参数:Unique identifier matching the statement to use. (可以理解为SQL语句的唯一标识符)
			 * 第二个参数:A parameter object to pass to the statement.     (可以理解为执行SQL语句要用的参数)
			 * 
			 * 第一个参数详细说明:
			 * 第一个参数就是映射文件的命名空间打点去找对应的sql语句的ID  
			 *     例如:namespace.id;  “com.mybatis.namespace.EmployeeMapper” 这一段是namespace  “getEmpById”是sql的ID 
			 *           当然不写namespace也行,但是如果其他的mapper文件也有相同的ID会报错,为了保险起见,我们还是使用namespace.id
			 */
			Employee employee = sqlSession.selectOne("com.mybatis.namespace.EmployeeMapper.getEmpById", 1);
			System.out.println(employee);
		} finally {
			//4、关闭
			sqlSession.close();
		}
		
	}

}

执行结果如下:
在这里插入图片描述

10、接口式编程(推荐)

上面把入门案例是写好了,也能正常运行。但是上面的写法是旧时的写法,是有一些问题的,比如:使用 sqlSession.selectOne("SQL唯一标识", 执行SQL的参数); 的方式来进行增删改查,"SQL唯一标识" 可能要写一长串,"执行SQL的参数" 是个Object类型,什么都能传,类型就不安全,容易出错。mybatis又提供了一种更高级的方式:接口式编程方式来进行增删改查操作。

操作如下:

  1. 按照上面入门案例的 步骤1 到 步骤8 做一遍

  2. 编写接口

    package com.xxx.mybatis.mapper;
    
    import com.xxx.mybatis.entity.Employee;
    
    //映射文件的命名空间要和这里的全类名相同    接口的编程方式,所有要写成一样
    public interface EmployeeMapper {// 为了看起来这个接口也是操作 employee 表的,把接口的名字也命名为EmployeeMapper
    
    	//映射文件的 sql语句ID要和这里的方法名相同
    	public Employee findEmpById(Integer id);
    }
    /**
     * 在这个案例里:这个接口就是用来查出 employee 表的记录数据,并封装成 Employee 对象的
     */
    
  3. 让接口和映射文件进行动态绑定。(也就是修改xxxMapper.xml文件的内容)

    <!-- 
     在这个案例里:这个 mapper.xml 文件他的这个功能也就是从 employee 表中查出数据封装 Employee 对象的。
                  所以 Mybatis 就提供了一个功能,接口可以和映射文件进行动态绑定。
    
     绑定的步骤:
       1、<mapper> 节点的 namespace 属性值就不要随便写了,让这个属性值指定为接口的全类名,mybatis就知道当前这个映射文件是和对应的接口进行绑定的
       2、接口里面的方法要和 <select> <insert> <update> <delete> 节点的 id 属性值一致,mybatis就知道接口中的哪个方法和这个文件中的哪个SQL进行绑定
    -->
    
    <?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">
    <!-- 
       namespace属性(命名空间):要和 mapper接口的全类名相同  接口的编程方式,所有要写成一样
       <mapper> 节点的 namespace 属性就不要随便写了,让这个属性值指定为接口的全类名,mybatis就知道当前这个映射文件是和对应的接口进行绑定的
    -->
    <mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
     
    <!-- 
    	id:                  唯一标识       sql的唯一标识                     要和 mapper接口的方法名相同
    	parameterType:       这个属性的意思是参数的类型,写上传入参数的全类名或者别名。也可以省略不写。 一般还是写上吧。
    	resultType:          返回值类型,将查询出来的结果封装成什么类型的对象    要和 mapper接口的相同的方法名字的返回值类型相同
    	#{id}:               从传递过来的参数中取出id值
    
        (接口里面的方法要和这个节点的 id 属性值一致,mybatis就知道接口中的哪个方法和这个文件中的哪个SQL进行绑定,
          可以理解为这个<select>标签是EmployeeMapper接口中 findEmpById 方法的实现)
     -->
    	<select id="findEmpById" parameterType="java.lang.Integer" resultType="com.xxx.mybatis.entity.Employee">
    		<!-- 
    			 在这个示例中:实体类的字段应和数据库中的字段相同才能将查询结果赋值和插入数据库。
    	    	 因为没配置实体类字段和数据库字段映射规则,默认就是类中的字段对应数据库的字段。例如:数据库的字段有下划线,类的属性也要有下划线。
    	    	 当然可以在查询的时候可以在sql语句中写上字段的别名,让别名和类中的属性名相同的方式
    		 -->
    		select * from employee where id = #{id}
    	</select>
    </mapper>
    
  4. 编写测试代码

    package com.xxx.mybatis.maintest;
    
    import java.io.InputStream;
    
    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 com.xxx.mybatis.entity.Employee;
    import com.xxx.mybatis.mapper.EmployeeMapper;
    
    public class MainTest {
    	
    	public static void main(String[] args) throws Exception {
    		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
    		String resource = "mybatis/mybatis-config.xml";
    		InputStream inputStream = Resources.getResourceAsStream(resource);
    		
    		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    		
    		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
    		SqlSession sqlSession = sqlSessionFactory.openSession();
    		try {
    			
    			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
    			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
    			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
                
    			// 调用接口内的方法,就能执行对应的sql语句了
    			Employee employee = employeeMapper.findEmpById(1);
    			System.out.println(employee);
    		} finally {
    			//关闭
    			sqlSession.close();
    		}
    		
    	}
    
    }
    

11、总结和注意事项

  • 接口式编程方式是用得最多的方式。这种方式有挺多的好处,例如:它有更强的类型检查,因为接口里面的方法参数规定了传递给 SQL 语句的参数类型。还有例如解耦等其他的好处。

  • 使用 MyBatis 的接口式编程和传统的接口编程不一样,传统的接口编程需要自己写一个实现类(Dao ==> DaoImpl),而使用 MyBatis 的接口式编程则不需要手写实现类,只需要将接口和映射文件(xxxMapper.xml)进行绑定就行了(也可以理解为这个xxxMapper.xml就是这个接口的实现)。实际上,MyBatis会为这个接口自动创建一个代理对象,代理对象去执行增删改查方法。在这里插入图片描述

  • SqlSession 代表和数据库的一次会话;用完必须关闭

  • SqlSession 和 connection 一样他都是非线程安全。每次使用都应该去获取新的对象。(不能写成成员变量,应该写成局部变量,由于是非线程安全,有可能其中一个线程将SqlSession关了,另外一个又进行数据库操作,就出问题了。)

  • 在 MyBatis 中有两个重要的文件:全局配置文件 和 SQL映射文件。

    • MyBatis 的全局配置文件:包含数据库连接池信息,事务管理器信息等…系统运行环境信息。(不写这个文件,可以写代码的方式给他创建出来)
    • SQL 映射文件:保存了每一个 SQL 语句的映射信息

一个小知识点:

给eclipse配置上MyBatis 配置文件的提示功能(也就是配置MyBatis 配置文件的约束文件)

在全局配置文件里边,在文档声明处引入了一个 “http://mybatis.org/dtd/mybatis-3-config.dtd” 这就是xml文件的dtd约束文件,他是规定xml中标签语法规则的。这个约束文件只有引入正确,在eclipse之中编写代码就会有代码补全的提示。

在联网的情况下,点一下让他自己下载下来就好了。

没联网就自己手动配一下:

1、在org.apache.ibatis.builder.xml包下有MyBatis 的xml dtd约束文件。我们可以将他们复制出来放到一个目录下。

2、将配置文件中的dtd文件的路径复制出来,就是http://xxx/xxx.dtd 复制出来

3、点击window --> preferences --> XML --> XML Catalog --> Add --> 点击File System按钮 选择dtd文件所在的位置选上文件。当然如果约束文件在当前工作空间里可以选择Workspace

在eclipse中 SQL 映射文件的 dtd约束文件配置也和上面的操作步骤一样。
在这里插入图片描述

三、MyBatis-全局配置文件

MyBatis 的全局配置文件包含了影响 MyBatis 行为甚深的 设置 和 属性 信息。文档的结构如下所示:

<?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>
	<!-- 关于这个全局的配置文件,在官方文档中有详细介绍。也可以去查看官方文档。官方文档的configuration XML章节就是介绍这个全局配置文件的。 -->
	
	<!-- 重要的标签就几个<settings>、<typeAliases>、 <mappers>、<databaseIdProvider>、<plugins>。还有注意这些所有标签的顺序。 -->
	<!-- 重要的标签就几个<settings>、<typeAliases>、 <mappers>、<databaseIdProvider>、<plugins>。还有注意这些所有标签的顺序。 -->
	<!-- 重要的标签就几个<settings>、<typeAliases>、 <mappers>、<databaseIdProvider>、<plugins>。还有注意这些所有标签的顺序。 -->
	<!-- 重要的标签就几个<settings>、<typeAliases>、 <mappers>、<databaseIdProvider>、<plugins>。还有注意这些所有标签的顺序。 -->
	
	<!-- 1、mybatis可以使用 <properties> 标签来引入外部properties配置文件的内容; -->
	<properties resource="..."></properties>
	
	
	<!-- 
		2、<settings>   包含很多重要的设置项,这些设置项都能影响mybatis运行时的行为。 官方文档里面列举了很多,也有设置项说明;
		    <setting>:  用来设置每一个设置项
			name属性:   设置项名
			value属性:  设置项取值
			
			按需要进行配置,许多设置都有默认值的。
	 -->
	<settings>
		<!-- 这个设置项的作用是:开启类字段的驼峰命名规则; (
		          数据库里面字段带下划线,然后会自动的将字段映射为类中的驼峰命名。如:数据库为user_name,会自动映射为类里面的userName,
		          这样在没有配置数据库字段和类中属性的映射规则下,带下划线的数据库字段就能给类的属性赋值了。就不会出现userName=null的情况了)
		 -->
		<setting name="mapUnderscoreToCamelCase" value="true"/>
		
		<!-- 这个设置是解决  某些数据库可能不能识别mybatis对null的默认处理;比如Oracle(报错);在下面的第四章节的4.6有详细的介绍 -->
		<!-- <setting name="jdbcTypeForNull" value="NULL"/> -->
		
		<!-- 
			这两个是用于分步查询懒加载的。详细可以查看官方文档,自己测试
			<setting name="lazyLoadingEnabled" value="true"/>
			<setting name="aggressiveLazyLoading" value="false"/>
		 -->
		 
		 <!-- 这个配置是用于开启二级缓存的。 -->
		 <!-- <setting name="cacheEnabled" value="true"/> -->
	</settings>
	
	<!-- 
		3、<typeAliases>:别名处理器:他的作用是可以为我们的java类型起一个简单一点,短一点的别名。以后我们xml文件中引用全类名的地方就可以写别名了。
	       例如:我们的mapper文件<select>标签的resultType属性需要写上全类名,这里设置的别名的话,就不用写全类名了,直接写别名。
	       别名不区分大小写
	-->
	<typeAliases>
		<!-- 
		  3-1、<typeAlias>: 为某个java类型起别名
				type:      指定要起别名的类型全类名;   alias属性不指定的话,默认别名就是类名小写;employee
				alias:     指定新的别名              alias属性不指定的话,默认别名就是类名小写;employee
		 
		  <typeAlias type="com.xxx.mybatis.entity.Employee" alias="emp"/>
		  
		 -->
		
		<!-- 
		  3-2、<package>: 为某个包下的所有类批量起别名; 用<typeAlias>起别名的话,如果很多类,就要写很多个,我们可以用 <package> 批量起别名
			   name属性:   指定包名(为当前包以及下面所有的子包的每一个类都起一个默认别名(类名小写),别名不区分大小写)
			   
			   name=com.xxx.mybatis.entity就是给这个包,和他的所以子包起别名。
		-->
		<package name="com.xxx.mybatis.entity"/>
		
		<!--   <typeAlias>、<package>这两个节点选择其中一个配置就行了。 -->
	</typeAliases>
	
    
    
	<!-- 4、<typeHandlers>叫类型处理器。作用就是:架起java类型和数据库类型映射的一个桥梁。
		    比如说我们将java的string类型如何保存成数据库兼容的varchar,
		    比如说我们查出一个数据库类型是integer的数据,我们又要转成java的int或者integer。

		    mybatis里面已经有一些 typeHandlers 可以看一下官方文档;StringTypeHandler  IntegerTypeHandler 等。在configuration XML章节
	-->
	<!-- <typeHandlers></typeHandlers> -->
	

	<!-- 5、插件。比如用于分页的插件 -->
	<!-- <plugins></plugins> -->


	<!-- 
		6、<environments>表示环境的意思,带有s所以说可以配置多个环境;可以配置多个<environment> 。
		
		
		<environments>:环境,mybatis可以配置多个环境 ,
		  default属性:用于指定使用某种环境(就是<environment>的id值)。通过切换default属性值,可以达到快速切换环境。
		
		<environment>: 配置一个具体的环境信息;必须有 <transactionManager> 和 <dataSource> 两个标签,才算完整的环境;
		  id属性:代表当前环境的唯一标识
			                                                  
		<transactionManager>:事务管理器;
		  type属性: 事务管理器的类型; 官方文档给出了两种取值 JDBC 和 MANAGED。 这两个其实是别名了;
		                      分别对应的是(JdbcTransactionFactory)和(ManagedTransactionFactory),在Configuration类中可以看到;
		            JDBC呢就是代表使用JDBC的方式进行提交和回滚的这种事务控制;MANAGED呢就是使用J2EE服务器 容器的这种方式来进行事务控制
		                      对于事务控制,我们学完spring之后,用spring来控制事务才是我们的解决方案。
					当然也可以自定义事务管理器:实现TransactionFactory接口。type指定为全类名。后面呢事务管理都交给spring来做,这一块呢先按照官方复制过来。
				
		<dataSource>:              数据源;
		  type属性:  数据源类型;官方文档给出的取值为:UNPOOLED | POOLED | JNDI
		                       分别对应的是(UnpooledDataSourceFactory)和(PooledDataSourceFactory)和(JndiDataSourceFactory),在Configuration类中可以看到;
					 UNPOOLED:就是不使用连接池的技术,每一次的增删改查他都从数据库拿一次新的连接;
					 POOLED:  就是使用连接池技术,查看PooledDataSourceFactory代码可以发现他 就是 new PooledDataSource();创建了一个基于连接池技术的数据源
					 JNDI      就是使用JNDI技术    
					  当然也可以自定义数据源:实现DataSourceFactory接口,type是全类名。 后面呢数据源交给spring来做,这一块呢先按照官方复制过来。
	-->
	
	<environments default="development">
	
		<environment id="development">
			<transactionManager type="JDBC" />
			<!-- 数据源 -->
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc.driver}" />
				<property name="url" value="${jdbc.url}" />
				<property name="username" value="${jdbc.username}" />
				<property name="password" value="${jdbc.password}" />
			</dataSource>
		</environment>
		
		<environment id="test"><!-- <environments>节点的default属性写了 development 所以是不会用到这个环境的。 -->
			<transactionManager type="JDBC" />
			<!-- 数据源 -->
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc.driver}" />
				<property name="url" value="${jdbc.url}" />
				<property name="username" value="${jdbc.username}" />
				<property name="password" value="${jdbc.password}" />
			</dataSource>
		</environment>
	</environments>
	
	
	<!-- 
		7、databaseIdProvider:支持多数据库厂商的;可以根据不同的数据库厂商,执行不同的sql语句。
		    type="DB_VENDOR":DB_VENDOR也是个别名,在Configuration类中可以看到;对应的是VendorDatabaseIdProvider;作用就是得到数据库厂商的标识;
		 	(是驱动自带的,我们可以根据jdbc规范,查看Connection中的getMateDat()方法,查看他的返回值DatabaseMateData,就有 getDatabaseProductName()获取数据库厂商标识),
		 	mybatis就能根据数据库厂商标识来执行不同的sql;
		 	
		 	下面是常见的几种数据库的标识:
		 	MySQL,Oracle,SQL Server,(可以看到每个数据库的标识都不一样。)
	  -->
	<databaseIdProvider type="DB_VENDOR">
		<!-- 
			为不同的数据库厂商起别名 
			然后去mapper文件里告诉mybatis这个sql是哪个数据库厂商发出的就行了。
			
			操作:比如在<select>节点上的databaseId属性写上这里配置的别名 <select databaseId="mysql">
			    <select id="findEmpById" resultType="employee"> select * from table </select>
			    <select id="findEmpById" resultType="employee" databaseId="mysql"> select * from table </select>
			    <select id="findEmpById" resultType="employee" databaseId="oracle"> select * from table </select>
			    
			这样写了databaseId属性,就可以写多个 id 值相同的<select>了。如果像上面写了3个,他会优先去找带有合适 databaseId="mysql" 的去执行
			至于不配别名,然后在mapper文件执行不同数据库的语句起不起效果不清楚
			
			每次写多个相同 id 属性的<select> 会有些繁琐。我们也可以用Mybatis的内置参数来判断。
			例如:
			    <if test="_databaseId=='mysql'"> select * from tbl_employee </if>     _databaseId 是Mybatis的内置参数,用这个前台需要配置<databaseIdProvider>
			    <if test="_databaseId=='oracle'"> select * from employees </if>       _databaseId 是Mybatis的内置参数,用这个前台需要配置<databaseIdProvider>
		-->
		<property name="MySQL" value="mysql"/><!-- 为MySQL标识的数据库起一个别名叫小mysql -->
		<property name="Oracle" value="oracle"/>
		<property name="SQL Server" value="sqlserver"/>
	</databaseIdProvider>
	
	
	<!-- 
		8、实体类和数据库的映射文件 ;<mappers>将sql映射注册到全局配置中
	-->
	<mappers>
		<!-- 
			<mapper>:注册一个sql映射 
				1、注册配置文件
					resource属性:引用类路径下的mapper映射文件  例如:<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
					
					url属性:     引用网络路径或者磁盘路径下的mapper映射文件  例如:<mapper url="file:///var/mappers/AuthorMapper.xml"/>
					
					
				2、注册接口
					class属性:   引用(注册)接口,写接口的全类名 例如:<mapper class="com.xxx.mybatis.mapper.EmployeeMapperAnnotation"/>
					注意:
					    2-1、有sql映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下;
					    2-2、没有sql映射文件(基于注解的方式),所有的sql都是利用注解写在接口上;
					    	 例如:
					         public interface EmployeeMapperAnnotation {
					         	@Select("select * from tbl_employee where id=#{id}")
					         	public Employee getEmpById(Integer id);
					         }
					                然后
					         EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapperAnnotation.class);
					         Employee empById = mapper.getEmpById(1);
					    
					    
					推荐:
						比较重要的,复杂的Dao接口我们来写sql映射文件
						不重要,简单的Dao接口为了开发快速可以使用注解;
						
						
				3、批量注册: <package name="com.xxx.mybatis.mapper"/>  
				      写包名进行批量注册的方式   用基于注解的方式的自然能找到;但是有sql映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下;
		                                                                          (如果嫌xml和接口放一个包不好看,可以在开发工具上设置让同一个包在视觉上分开,他编译或打包都会在同一个目录下)
		-->
		<!-- 多层包,在这里用斜杠表示,有的地方用点表示,需要注意。 -->
		<mapper resource="mybatis/mapper/EmployeeMapper.xml" />
	</mappers>
</configuration>

1、properties节点介绍

mybatis可以使用 <properties> 标签来引入外部properties配置文件的内容;

有两个属性:

  • resource属性: 引入类路径下的资源

  • url属性: 引入网络路径或者磁盘路径下的资源

  • resource 和 url 这两个属性都是用来引入外部资源的。两个属性选择其中一个就行了。

  • 引入了 properties 就可以用 ${} 的方式获取值来使用了;例如下面的数据源的写法。

    <?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="dbconfigproperties/dbconfig.properties"></properties>
        
        <!-- 其他节点  -->
        
        <environments default="development">
    		<environment id="development">
    			<transactionManager type="JDBC" />
    			<!-- 数据源 -->
    			<dataSource type="POOLED">
                    <!-- 引入了外部 properties 就可以用 ${} 的方式获取值来使用了 -->
    				<property name="driver" value="${jdbc.driver}" /> <!-- 数据库驱动 -->
    				<property name="url" value="${jdbc.url}" /> <!-- 连接的url -->
    				<property name="username" value="${jdbc.username}" /> <!-- 数据库用户名 -->
    				<property name="password" value="${jdbc.password}" /> <!-- 数据库密码 -->
    			</dataSource>
    		</environment>
    	</environments>
        
        <!-- 其他节点  -->
    </configuration>
    

    在这里插入图片描述

    一般 <properties> 节点就是这么用了,更丰富的用法可以看官网

2、settings节点介绍(重要)

<settings>节点是MyBatis全局配置文件中用于定义MyBatis运行时的配置参数的XML元素。这个节点下面有个<setting>子节点,这个<setting>子节点它允许开发者自定义MyBatis的行为,例如开启或关闭缓存、设置延迟加载策略、配置日志级别、控制SQL语句的执行等。通过调整<settings>节点中的配置,可以优化MyBatis的性能和行为,以适应不同的应用场景和需求。

<settings>节点下面的<setting>子节点可以写多个,这个<setting>子节点的配置是很重要的设置项,因为这些设置项能影响MyBatis运行时的行为。

这个<setting>子节点有两个属性:

  • name属性: 设置项名
  • value属性: 设置项取值

按需要进行配置,许多设置都有默认值的。

这里拿一个来示例一下

<?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>
    
    <!-- 其他节点  -->
    
    <settings>
		<!-- 
            这个设置项的作用是:开启类字段的驼峰命名规则;
            解释:
		        数据库里面字段带下划线,然后会自动的将字段映射为类中的驼峰命名(如:数据库为user_name,会自动映射为类里面的userName)。

		        这样,在没有配置数据库字段和类中属性的映射规则下,带下划线的数据库字段就能给类的属性赋值了。就不会出现userName=null的情况了
		        (也就是说查询数据库字段是 user_name 能给java类中的 userName 属性赋值。在插入数据到数据库中能将java对象的userName属性值插入到书刊表中的user_name字段)
		 -->
		<setting name="mapUnderscoreToCamelCase" value="true"/>
		
		<!-- <setting name="其他设置项" value="..."/> -->
	</settings>
    
    <!-- 其他节点  -->
</configuration>

其他各种配置项的说明

设置项名称描述有效可选值默认值
cacheEnabled开启或者关闭二级缓存。全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。true | falsetrue
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。需要配合aggressiveLazyLoading配置项才能有懒加载的效果true | falsefalse
aggressiveLazyLoading开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。配合lazyLoadingEnabled配置项使用可以实现懒加载效果。从名字上看,它就叫做积极的、激进的懒加载,当设为true时,使用对象的任何属性也就是使用对象的getxxx()方法都会发送SQL语句查询关联的另外一方;设置为 false 的时候只有当使用到关联的另外一方的时候才会发送SQL语句查询关联的另外一方的数据true | falsefalse (在 3.4.1 及之前的版本中默认为 true)
multipleResultSetsEnabled是否允许单个语句返回多结果集(需要数据库驱动支持)。true | falsetrue
useColumnLabel使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。true | falsetrue
useGeneratedKeys允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。true | falseFalse
autoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。NONE, PARTIAL, FULLPARTIAL
autoMappingUnknownColumnBehavior指定发现自动映射目标未知列(或未知属性类型)的行为。NONE: 不做任何反应WARNING: 输出警告日志('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARNFAILING: 映射失败 (抛出 SqlSessionException)Note that there could be false-positives when autoMappingBehavior is set to FULL.NONE, WARNING, FAILINGNONE
defaultExecutorType配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。SIMPLE REUSE BATCHSIMPLE
defaultStatementTimeout设置超时时间,它决定数据库驱动等待数据库响应的秒数。任意正整数未设置 (null)
defaultFetchSize为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。任意正整数未设置 (null)
defaultResultSetType指定语句默认的滚动策略。(新增于 3.5.2)FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(等同于未设置)未设置 (null)
safeRowBoundsEnabled是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。true | falseFalse
safeResultHandlerEnabled是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。true | falseTrue
mapUnderscoreToCamelCase是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。true | falseFalse
localCacheScopeMyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。SESSION | STATEMENTSESSION
jdbcTypeForNull当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。有些数据库可能不能识别mybatis对null的默认处理;比如Oracle(会报错);我们对Oracle的表插入NULL的数据,或者将数据改为null 会报错。因为MyBatis对所有的null都映射的是原生Jdbc的OTHER类型,Oracle不能正确处理。就是说Mybatis默认的将为null的值 指定数据库对应的jdbc类型为 JdbcType=OTHER #{xxx,JdbcType=OTHER}JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。OTHER
lazyLoadTriggerMethods指定对象的哪些方法触发一次延迟加载。用逗号分隔的方法列表。equals,clone,hashCode,toString
defaultScriptingLanguage指定动态 SQL 生成使用的默认脚本语言。一个类型别名或全限定类名。org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5)一个类型别名或全限定类名。org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。true | falsefalse
returnInstanceForEmptyRow当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2)true | falsefalse
logPrefix指定 MyBatis 增加到日志名称的前缀。任何字符串未设置
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J | LOG4J(3.5.9 起废弃) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING未设置
proxyFactory指定 Mybatis 创建可延迟加载对象所用到的代理工具。CGLIB (3.5.10 起废弃) | JAVASSISTJAVASSIST (MyBatis 3.3 以上)
vfsImpl指定 VFS 的实现自定义 VFS 的实现的类全限定名,以逗号分隔。未设置
useActualParamName允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1)也就是使用参数名来获取参数值给到sql语句去执行。要求jdk1.8以上true | falsetrue
configurationFactory指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3)一个类型别名或完全限定类名。未设置
shrinkWhitespacesInSql从SQL中删除多余的空格字符。请注意,这也会影响SQL中的文字字符串。 (新增于 3.5.5)true | falsefalse
defaultSqlProviderType指定一个拥有 provider 方法的 sql provider 类 (新增于 3.5.6). 这个类适用于指定 sql provider 注解上的type(或 value) 属性(当这些属性在注解中被忽略时)。 (e.g. @SelectProvider)类型别名或者全限定名未设置
nullableOnForEach为 ‘foreach’ 标签的 ‘nullable’ 属性指定默认值。(新增于 3.5.9)true | falsefalse
argNameBasedConstructorAutoMapping当应用构造器自动映射时,参数名称被用来搜索要映射的列,而不再依赖列的顺序。(新增于 3.5.10)true | falsefalse

3、typeAliases节点介绍

这个节点也叫别名处理器。

他的作用是可以为我们的 java 类型起一个简单一点,短一点的别名。配置了这个节点后,可以在SQL映射文件中(xxxMapper.xml文件)引用全类名的地方就可以写别名了。例如:我们的mapper文件<select>标签的 resultType 属性需要写上全类名,这里设置的别名的话,就不用写全类名了,直接写别名。(别名不区分大小写)

这个节点下有两个子节点:

  • <typeAlias>:为某个java类型起别名
  • <package>: 为某个包下的所有类批量起别名(包括子包)
  • 这两个节点选择其中一个配置就行了。
<?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>
		<!-- 
		  typeAlias: 为某个java类型起别名
			 type:      指定要起别名的类型全类名;  alias属性不指定的话,默认别名就是类名小写;employee
			 alias:     指定新的别名             alias属性不指定的话,默认别名就是类名小写;employee
		 
		  <typeAlias type="com.xxx.mybatis.entity.Employee" alias="emp"/>
		  
		 -->
		
		<!-- 
		  <package>: 为某个包下的所有类批量起别名; 用<typeAlias>起别名的话,如果很多类,就要写很多个,我们可以用 <package> 批量起别名
			 name属性: 指定包名(为当前包以及下面所有的子包的每一个类都起一个默认别名(类名小写),别名不区分大小写)

			 name=com.xxx.mybatis.entity就是给这个包,和他的所以子包起别名。
		-->
		<package name="com.xxx.mybatis.entity"/>
		
		<!-- 
		  注意:
		      批量起别名的情况下,如果当前包有一个Employee,他的子包又有一个Employee。这是会报错的,别名冲突了。
		      可以使用在对应的类名上用@Alias("别名名字")注解为某个类型指定新的别名;就能避免别名冲突了
		            
		      Mybatis中有许多已经取好的别名,可以在官方文档中看到。
		   
		      起自定义别名的时候,不要和 MyBatis 定义了的别名冲突。

		      起别名有时候不好定位到某个类,起得不好得情况下,也不太清晰明了的知道这个别名对应的是哪个类。用与不用这个看具体情况吧
		 -->
	</typeAliases>
    
    <!-- 其他节点  -->
</configuration>

在这里插入图片描述

MyBatis起好的别名。

别名映射的类型
_bytebyte
_char (since 3.5.10)char
_character (since 3.5.10)char
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
char (since 3.5.10)Character
character (since 3.5.10)Character
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
bigintegerBigInteger
objectObject
date[]Date[]
decimal[]BigDecimal[]
bigdecimal[]BigDecimal[]
biginteger[]BigInteger[]
object[]Object[]
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

4、typeHandlers节点介绍

<typeHandlers>节点是MyBatis全局配置文件中用于注册类型处理器(TypeHandler)的XML元素(可以是自定义的类型处理器也可以是MyBatis内置的类型处理器,MyBatis内置的类型处理器可以不用写,但是自定义的类型处理器一定要写)。这些处理器负责在MyBatis执行数据库操作时,将Java类型与数据库中的JDBC类型之间进行转换。类型处理器需要通过这个<typeHandlers>节点下面的<typeHandler>子节点来配置。

无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。(也就是说:它的作用是架起 JAVA 类型和数据库类型映射的一个桥梁)。比如说我们将java的 String 类型如何保存成数据库兼容的varchar,比如说我们查出一个数据库类型是 integer 的数据,我们又要转成java的int或者integer。

第一段话也介绍了通过在这个<typeHandlers>节点下面的<typeHandler>子节点是可以自定义类型处理器的,开发者通过自定义的类型处理器可以指定Java类型与对应的TypeHandler实现类,这样就能使得MyBatis能够处理那些默认情况下不被支持或者需要特殊处理的Java类型。

<typeHandlers>节点下面还有一个<package>子节点,用于指定包含多个TypeHandler类的包名(就是有一个包里面写了多个TypeHandler类),使得MyBatis能够自动扫描并注册这些包中的所有TypeHandler。这种机制极大地增强了MyBatis的灵活性和扩展性,允许开发者自定义数据类型与数据库字段之间的映射关系。

mybatis里面已经有一些 typeHandlers 可以看一下官方文档

需要注意的是日期类型

日期和时间的处理,JDK1.8以前一直是个头疼的问题。时间日期类库不丰富,用起来也挺麻烦的,包括时间计算以及判断等等都挺麻烦的。而在 JDK1.8 之后,完全实现了一个叫 JSR310 标准,添加了更丰富的日期类型库,在这个时候 MyBatis 就为这些日期类型也写了新的类型处理器。这些类型处理器在MyBatis低版本是没有的,也就是在 MyBatis3.4 以前的版本需要我们手动注册这些处理器, MyBatis3.4 以后的版本都是自动注册的。

<?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>
    
    <!-- 其他节点  -->
    
    <typeHandlers>
        <!-- 在 MyBatis3.4 以前的版本需要我们手动注册这些处理器, MyBatis3.4 以后的版本都是自动注册的就不用写  -->
        <typeHandler handler="org.apache.ibatis.type.InstantTypeHandler"/>
        <typeHandler handler="org.apache.ibatis.type.LocalDateTimeTypeHandler"/>
        <typeHandler handler="org.apache.ibatis.type.LocalDateTypeHandler"/>
        <typeHandler handler="org.apache.ibatis.type.LocalTimeTypeHandler"/>
        <typeHandler handler="org.apache.ibatis.type.OffsetDateTimeTypeHandler"/>
        <typeHandler handler="org.apache.ibatis.type.OffsetTimeTypeHandler"/>
        <typeHandler handler="org.apache.ibatis.type.ZonedDateTimeTypeHandler"/>
        <typeHandler handler="org.apache.ibatis.type.YearTypeHandler"/>
        <typeHandler handler="org.apache.ibatis.type.MonthTypeHandler"/>
        <typeHandler handler="org.apache.ibatis.type.YearMonthTypeHandler"/>
        <typeHandler handler="org.apache.ibatis.type.JapaneseDateTypeHandler"/>
    </typeHandlers>
    去官网看看怎么写的 https://github.com/mybatis/typehandlers-jsr310
    
    <!-- 其他节点  -->
</configuration>

下面是MyBatis内置的一些类型处理器。写实体类的时候也可以照着这个来写了,比如说:java的String对应着数据库的CHAR、或者varchar。

类型处理器Java 类型JDBC 类型
BooleanTypeHandlerjava.lang.Boolean, boolean数据库兼容的 BOOLEAN
ByteTypeHandlerjava.lang.Byte, byte数据库兼容的 NUMERICBYTE
ShortTypeHandlerjava.lang.Short, short数据库兼容的 NUMERICSMALLINT
IntegerTypeHandlerjava.lang.Integer, int数据库兼容的 NUMERICINTEGER
LongTypeHandlerjava.lang.Long, long数据库兼容的 NUMERICBIGINT
FloatTypeHandlerjava.lang.Float, float数据库兼容的 NUMERICFLOAT
DoubleTypeHandlerjava.lang.Double, double数据库兼容的 NUMERICDOUBLE
BigDecimalTypeHandlerjava.math.BigDecimal数据库兼容的 NUMERICDECIMAL
StringTypeHandlerjava.lang.StringCHAR, VARCHAR
ClobReaderTypeHandlerjava.io.Reader-
ClobTypeHandlerjava.lang.StringCLOB, LONGVARCHAR
NStringTypeHandlerjava.lang.StringNVARCHAR, NCHAR
NClobTypeHandlerjava.lang.StringNCLOB
BlobInputStreamTypeHandlerjava.io.InputStream-
ByteArrayTypeHandlerbyte[]数据库兼容的字节流类型
BlobTypeHandlerbyte[]BLOB, LONGVARBINARY
DateTypeHandlerjava.util.DateTIMESTAMP
DateOnlyTypeHandlerjava.util.DateDATE
TimeOnlyTypeHandlerjava.util.DateTIME
SqlTimestampTypeHandlerjava.sql.TimestampTIMESTAMP
SqlDateTypeHandlerjava.sql.DateDATE
SqlTimeTypeHandlerjava.sql.TimeTIME
ObjectTypeHandlerAnyOTHER 或未指定类型
EnumTypeHandlerEnumeration TypeVARCHAR 或任何兼容的字符串类型,用来存储枚举的名称(而不是索引序数值)
EnumOrdinalTypeHandlerEnumeration Type任何兼容的 NUMERICDOUBLE 类型,用来存储枚举的序数值(而不是名称)。
SqlxmlTypeHandlerjava.lang.StringSQLXML
InstantTypeHandlerjava.time.InstantTIMESTAMP
LocalDateTimeTypeHandlerjava.time.LocalDateTimeTIMESTAMP
LocalDateTypeHandlerjava.time.LocalDateDATE
LocalTimeTypeHandlerjava.time.LocalTimeTIME
OffsetDateTimeTypeHandlerjava.time.OffsetDateTimeTIMESTAMP
OffsetTimeTypeHandlerjava.time.OffsetTimeTIME
ZonedDateTimeTypeHandlerjava.time.ZonedDateTimeTIMESTAMP
YearTypeHandlerjava.time.YearINTEGER
MonthTypeHandlerjava.time.MonthINTEGER
YearMonthTypeHandlerjava.time.YearMonthVARCHARLONGVARCHAR
JapaneseDateTypeHandlerjava.time.chrono.JapaneseDateDATE

如果遇到不支持的或非标准的类型可以重写已有的类型处理器或创建自定义的类型处理器。 具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 并且可以(可选地)将它映射到一个 JDBC 类型。具体的可以查看官网。(点击查看)

关于枚举类型处理器官网上也有,在下面的内容也有介绍。点击查看

5、plugins节点介绍

<plugins>节点是 MyBatis 全局配置文件中用于定义插件的XML元素。通过这个节点,开发者可以指定一个或多个插件类,这些插件类在 MyBatis 运行的初始阶段,也就是MyBatis框架工作的最开始的时候被加载(注册)到 MyBatis 中,成为 MyBatis 工作中的一部分。

MyBatis的插件系统允许开发者在不修改MyBatis源码的情况下,通过拦截器(Interceptor)的方式扩展MyBatis的功能。详细一点来说就是 MyBatis 的插件可以拦截到SQL语句执行的一些核心步骤,然后可以进行一些例如:日志记录、性能分析、事务管理、缓存控制等多种操作,从而增强MyBatis的功能和性能。

MyBatis插件可以由开发者自定义,然后将定义好的插件全类名配置到<plugins>里面的<plugin>子节点,就能使定义好的插件类成为MyBatis工作中的一部分。

MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)。这个称之为执行器 ,括号里面的就是这个Executor 拥有的方法,插件就能拦截到这些方法,这个方法里面有增删改都用的 update 方法以及查询用的query方法等等,这样的话开发者就可以利用插件机制在 MyBatis 这个Executor 对象执行真正增删改之前改变一下我们这个MyBatis的一些默认行为,以达到我们这个自定义的一些效果。
  • ParameterHandler (getParameterObject, setParameters)。这个叫参数处理器,他的作用就是它后面的两个方法,用来设置预编译参数之类的。
  • ResultSetHandler (handleResultSets, handleOutputParameters)。这个叫结果集处理器,他的作用就是把查询出来的数据 封装为javabean对象
  • StatementHandler (prepare, parameterize, batch, update, query)

配置方式如下所示:

<?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>
    <!-- 按照实际需要,配置其他节点 -->
    
	<plugins>
        <!-- 配置插件,也就是配置实现 org.apache.ibatis.plugin.Interceptor 接口的类,可以配置多个 -->
		<plugin interceptor="com.xxx.mybatis.plugin.MyPluginOne">
            <!-- 配置插件的需要的属性,插件类里面会有方法被执行这里的属性就能被插件类得到 -->
			<property name="attrName" value="attrName"/>
			<property name="attrValue" value="attrValue"/>
		</plugin>
	</plugins>

	<!-- environments表示环境的意思 -->
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<!-- 数据源 -->
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.jdbc.Driver" />
				<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
				<property name="username" value="root" />
				<property name="password" value="root" />
			</dataSource>
		</environment>
	</environments>
    
    <!-- 按照实际需要,配置其他节点 -->
	
	<!-- 实体类和数据库的映射文件 -->
	<mappers>
		<!-- 多层包,在这里用斜杠表示,有的地方用点表示,需要注意。 -->
		<mapper resource="mybatis/mapper/EmployeeMapper.xml" />
	</mappers>
</configuration>

这里不过多介绍插件的内容,摸清MyBatis的内在原理后对插件的理解就会更为深刻。

可以查看官网,或者点击这里查看示例

6、environments节点介绍

这个节点用来配置环境用的,英语翻译过来他就是环境的意思。在这个节点下可以配置多个环境;也就是可以配置多个<environment>子节点

default属性:用于指定使用某种环境(就是<environment>子节点的 id 属性值)。通过切换default属性值,可以达到快速切换环境。比如说:开发和测试可能用的环境不同,就可以在这里使用<environment>子节点配置两个环境,一个是开发环境,一个是测试环境,开发和测试想切换不同的环境可以改掉这个属性值就可以切换不同的环境了。

<?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>: 表示环境,mybatis可以配置多个环境 ,
		  default属性:用于指定使用某种环境(就是<environment>的id值)。通过切换default属性值,可以达到快速切换环境。
		
		<environment>:  配置一个具体的环境信息;必须有 <transactionManager> 和 <dataSource> 两个标签,才算完整的环境;
		  id属性:代表当前环境的唯一标识
			                                                  
		<transactionManager>:事务管理器;
		  type属性: 事务管理器的类型; 官方文档给出了两种取值 JDBC 和 MANAGED。 这两个其实是别名了;
		                      分别对应的是(JdbcTransactionFactory)和(ManagedTransactionFactory),在Configuration类中可以看到;
		            JDBC呢就是代表使用JDBC的方式进行提交和回滚的这种事务控制;MANAGED呢就是使用J2EE服务器 容器的这种方式来进行事务控制
		                      对于事务控制,我们学完spring之后,用spring来控制事务才是我们的解决方案。
					当然也可以自定义事务管理器:实现TransactionFactory接口。type指定为全类名。后面呢事务管理都交给spring来做,这一块呢先按照官方复制过来。
				
		<dataSource>:              数据源;
		  type属性:  数据源类型;官方文档给出的取值为:UNPOOLED | POOLED | JNDI
		                       分别对应的是(UnpooledDataSourceFactory)和(PooledDataSourceFactory)和(JndiDataSourceFactory),在Configuration类中可以看到;
					 UNPOOLED:就是不使用连接池的技术,每一次的增删改查他都从数据库拿一次新的连接;
					 POOLED:  就是使用连接池技术,查看PooledDataSourceFactory代码可以发现他 就是 new PooledDataSource();创建了一个基于连接池技术的数据源
					 JNDI	   就是使用JNDI技术    
					  当然也可以自定义数据源:实现DataSourceFactory接口,type是全类名。 后面呢数据源交给spring来做,这一块呢先按照官方复制过来。
	-->
	
	<environments default="development">
	
		<environment id="development">
			<transactionManager type="JDBC" />
			<!-- 数据源 -->
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc.driver}" />
				<property name="url" value="${jdbc.url}" />
				<property name="username" value="${jdbc.username}" />
				<property name="password" value="${jdbc.password}" />
			</dataSource>
		</environment>
		
		<environment id="test"><!-- <environments>节点的default属性写了 development 所以是不会用到这个环境的。 -->
			<transactionManager type="JDBC" />
			<!-- 数据源 -->
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc.driver}" />
				<property name="url" value="${jdbc.url}" />
				<property name="username" value="${jdbc.username}" />
				<property name="password" value="${jdbc.password}" />
			</dataSource>
		</environment>
	</environments>
    
    <!-- 其他节点  -->
</configuration>

7、databaseIdProvider节点介绍

通过这个节点的配置可以根据不同的数据库厂商,执行不同的sql语句。

type属性:需要把这个值写为 DB_VENDOR ,这是个固定写法。DB_VENDOR也是个别名,在Configuration类中可以看到;对应的是VendorDatabaseIdProvider;作用就是能通过数据库驱动中得到数据库厂商的标识,然后将这个标识通过<property>子节点映射成一个别名,然后就可以将别名用到映射文件(xxxMapper.xml)中,mybatis就能根据数据库厂商标识来执行不同的sql

<?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>
    
    <!-- 其他节点  -->
    
    <!--  -->
    <databaseIdProvider type="DB_VENDOR">
        
        <!-- 这个数据库厂商的标识是驱动自带的,我们可以根据jdbc规范,查看Connection中的getMateDat()方法,查看他的返回值DatabaseMateData,就有 getDatabaseProductName()获取数据库厂商标识 -->
		<!-- 为常见的几种数据库的标识配置别名。 -->
		<property name="MySQL" value="mysql"/><!-- 为MySQL标识的数据库起一个别名叫小mysql -->
		<property name="Oracle" value="oracle"/>
		<property name="SQL Server" value="sqlserver"/>
	</databaseIdProvider>
    
    <!-- 其他节点  -->
</configuration>

这样配置了这些个数据库厂商的别名还不能让不同的数据库执行不同的SQL,还需要 mapper文件里告诉 MyBatis 这个 SQL 是哪个数据库厂商发出的。

在这里插入图片描述

8、mappers节点介绍

这个节点的作用就是把写好的SQL映射文件用它的<mapper>子节点注册到全局配置中。

<?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>
    
    <!-- 其他节点  -->
    
    <mappers>
		<!-- 
			<mapper>:注册一个sql映射 
				1、注册配置文件
					resource属性:引用类路径下的mapper映射文件  例如:<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
					
					url属性:     引用网络路径或者磁盘路径下的mapper映射文件  例如:<mapper url="file:///var/mappers/AuthorMapper.xml"/>
					
					
				2、注册接口
					class属性:   引用(注册)接口,写接口的全类名 例如:<mapper class="com.xxx.mybatis.mapper.EmployeeMapperAnnotation"/>
					注意:
					    2-1、有sql映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下;
					    2-2、没有sql映射文件(基于注解的方式),所有的sql都是利用注解写在接口上;
					    	 例如:
					         public interface EmployeeMapperAnnotation {
					         	@Select("select * from tbl_employee where id=#{id}")
					         	public Employee getEmpById(Integer id);
					         }
					                然后
					         EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapperAnnotation.class);
					         Employee empById = mapper.getEmpById(1);
					    
					    
					推荐:
						比较重要的,复杂的Dao接口我们来写sql映射文件
						不重要,简单的Dao接口为了开发快速可以使用注解;
						
						
				3、批量注册: <package name="com.xxx.mybatis.mapper"/>  
				           写包名进行批量注册的方式,如果使用基于注解的方式的自然能找到对应的接口;
				           但是有sql映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下;
		                  (如果嫌xml和接口放一个包不好看,可以在开发工具上设置让同一个包在视觉上分开,他编译或打包都会在同一个目录下)
		-->
		<!-- 多层包,在这里用斜杠表示,有的地方用点表示,需要注意。 -->
		<mapper resource="mybatis/mapper/EmployeeMapper.xml" />
	</mappers>
</configuration>

四、使用MyBatis操作数据库数据

使用MyBatis数据操作,需要编写一个映射文件,这个映射文件指导着MyBatis如何进行数据库增删改查,它有着非常重要的意义,不可缺少;一般命名为xxxMapper.xml也就是实体类名拼接Mapper.xml,存放的位置可以放在类路径下,也可以放在和Mapper接口同一个包下。

1、新增操作

基于接口式编程的方式在映射文件中使用<insert>节点就行。

1.1、普通的新增

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 
		新增示例1、(普通的新增)
		    用<insert>标签。写一个 parameterType 属性。这个属性的意思是参数的类型,写上传入参数的全类名或者别名。也可以省略不写。 一般还是写上吧。
		    #{xxx}里面写的就是参数类型的  字段 属性;这个例子中写的是 Employee 类中的属性,表示使用这个类的实例的属性值。
		    
		  在绑定的java接口中写上 addEmpDemo01 方法。 public void addEmpDemo01(Employee employee)
	 -->
	<insert id="addEmpDemo01" parameterType="com.xxx.mybatis.entity.Employee">
		<!-- id是用mysql的自增,不用管 -->
		insert into employee(last_name,email,gender) 
		values(#{lastName},#{email},#{gender})
	</insert>
</mapper>

1.2、获取数据库自增的主键值的新增

在实际的业务场景中可能会有新增一条数据后,希望能得到这个新增数据的主键ID,方便用于后面的代码逻辑。这时可以使用<insert>节点的 useGeneratedKeys 属性和 keyProperty 属性来获取

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 
		新增示例2、(获取数据库自增的主键值的新增。在这里主键都是mysql自增的,我们想要获取自增的主键ID,需要以下设置)
		    用<insert>标签。写一个 parameterType 属性。这个属性的意思是参数的类型,写上传入参数的全类名或者别名。也可以省略不写。 一般还是写上吧。
		    #{xxx}里面写的就是参数类型的  字段 属性;这个例子中写的是 Employee 类中的属性,表示使用这个类的实例的属性值。
		    
		    再写一个属性:useGeneratedKeys="true"。 它默认是false,调整为true就行了。他的意思是,告诉mybatis使用自动生成的主键策略,获取数据库自动增长的主键值
		    同时写上一个属性:keyProperty="xxx" 告诉mybatis获取到自动增长的主键值以后,将这个字值封装给javaBean的哪个属性。
		    这样com.xxx.mybatis.entity.Employee 里面的id属性就会有数据库自增的主键值了。可以用在关联插入等场景中了
		    
		    (在原生的JDBC里面,Statement对象执行完增删改以后,我们可以调里面getGeneratedKeys() 获取自动生成的主键,他能返回自增的主键值。
		   mybatis获取自增主键值也是利用Statement.getGeneratedKeys()方法的。)
		   
		   
		  在绑定的java接口中写上 addEmpDemo02 方法。 public void addEmpDemo02(Employee employee)
	 -->
	<insert id="addEmpDemo02" parameterType="com.xxx.mybatis.entity.Employee" useGeneratedKeys="true" keyProperty="id">
		<!-- id是用mysql的自增,不用管 -->
		insert into employee(last_name,email,gender) 
		values(#{lastName},#{email},#{gender})
	</insert>
</mapper>

1.3、获取非自增主键的值的新增

有些数据库不支持自增生成主键,又或者使用uuid的方式生成主键ID,这时候就不能用<insert>节点的 useGeneratedKeys 属性和 keyProperty 属性来获取主键ID值了,可以使用<insert>节点下的<selectKey>子节点来获取。

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 
		新增示例3、(获取非自增主键的值,比如:Oracle不支持自增;Oracle使用序列来模拟自增,每次插入的数据的主键是从序列中拿到的值;如何获取到这个值;)
		用<insert>标签。写一个 parameterType 属性。这个属性的意思是参数的类型,写上传入参数的全类名或者别名。也可以省略不写。 一般还是写上吧。
		#{xxx}里面写的就是参数类型的  字段 属性;这个例子中写的是 Employee 类中的属性,表示使用这个类的实例的属性值。
		    
		在<insert>里写上<selectKey>子节点
		
		在绑定的java接口中写上 addEmpDemo03 方法。 public void addEmpDemo03(Employee employee)
	 -->
	<insert id="addEmpDemo03" parameterType="com.xxx.mybatis.entity.Employee" >
		<!-- 
			keyProperty属性: 查出的主键值封装给javaBean的哪个属性
			
			order属性:"BEFORE":当前sql在插入sql之前运行;BEFORE运行顺序-》先运行selectKey查询id的sql;查出id值封装给javaBean的id属性,在运行insert的sql;就可以取出id属性对应的值
				       "AFTER":当前sql在插入sql之后运行;AFTER运行顺序-》 先运行insert的sql(从序列中取出新值作为id),再运行selectKey查询id的sql
				       (用AFTER会有一些问题,比如:如果同时插入好多条记录以后,这个currval获取的就是最后一次记录的值了,推荐用BEFORE)
				             
				             
			resultType属性:   查出的数据的返回值类型
			
			那些uuid的主键也能用这种方式
		 -->
		<selectKey keyProperty="id" order="BEFORE" resultType="Integer">
			<!-- 编写查询主键的sql语句 -->
			<!-- BEFORE-->
			select EMPLOYEES_SEQ.nextval from dual 
		</selectKey>
		
		<!-- 插入时的主键是从序列中拿到的 -->
		<!-- BEFORE:-->
		insert into employees(id,last_name,email) 
		values(#{id},#{lastName},#{email}) 
		
		
		<!-- 这一段注释的代码也了解一下 -->
		<!-- 
		AFTER:写order="AFTER" 会在insert执行之后在执行<selectKey>
		<selectKey keyProperty="id" order="AFTER" resultType="Integer">
			编写查询主键的sql语句
			 select EMPLOYEES_SEQ.currval from dual
		</selectKey>
		
		insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL) 
		values(employees_seq.nextval,#{lastName},#{email}) 
		
		关键的代码不同  EMPLOYEES_SEQ.currval  和    employees_seq.nextval
		-->
	</insert>
</mapper>

1.4、动态拼接语句新增

在实际业务中,插入到数据库表的数据可能只是插入表中的几个字段,并不需要全部的字段都要插入数据,这种情况下可以动态的拼接 insert 语句

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 
		新增示例4:MySql数据库下根据条件动态地对某些字段插入数据,也就是动态的拼接 insert 语句。
		还需要在在绑定的java接口中写上 addEmpDemo04 方法。 public void addEmpDemo04(Employee employee)
	 -->
	<insert id="addEmpDemo04" parameterType="com.xxx.mybatis.entity.Employee" useGeneratedKeys="true" keyProperty="id">
		<!-- id是用mysql的自增,不用管 -->
        <!-- useGeneratedKeys="true" 告诉mybatis使用自动生成的主键策略,获取数据库自动增长的主键值 -->
        <!-- keyProperty="xxx" 告诉mybatis获取到自动增长的主键值以后,将这个字值封装给javaBean的哪个属性。 -->
        
		insert into employee
        <!-- suffixOverrides 从英语上看它就是后缀覆盖的意思,将后缀的','覆盖为空 -->
        <trim prefix="(" suffix=")" suffixOverrides=","> 
             <if test="lastName != null">
                last_name,
             </if>
             <if test="email != null">
                email,
             </if>
             <if test="gender != null">
                gender,
             </if>
        </trim>
        <!-- suffixOverrides 从英语上看它就是后缀覆盖的意思,将后缀的','覆盖为空 -->
        <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="lastName != null">
                #{lastName},
             </if>
             <if test="email != null">
                #{email},
             </if>
             <if test="gender != null">
                #{gender},
             </if>
        </trim>
	</insert>
    
    
    <!-- 
		新增示例5:Oracle数据库下根据条件动态地对某些字段插入数据,也就是动态的拼接 insert 语句。
		Oracle 数据库有一些版本是不支持自增,需要用序列来作为主键。这里介绍的就是序列的方式,动态拼接 insert 语句。
		还需要在在绑定的java接口中写上 addEmpDemo05 方法。 public void addEmpDemo05(Employee employee)
	 -->
	<insert id="addEmpDemo05" parameterType="com.xxx.mybatis.entity.Employee" >
		<!-- 
			keyProperty属性: 查出的主键值封装给javaBean的哪个属性
			
			order属性:"BEFORE":当前sql在插入sql之前运行;BEFORE运行顺序-》先运行selectKey查询id的sql;查出id值封装给javaBean的id属性,在运行insert的sql;就可以取出id属性对应的值
				       "AFTER":当前sql在插入sql之后运行;AFTER运行顺序-》 先运行insert的sql(从序列中取出新值作为id),再运行selectKey查询id的sql
				       (用AFTER会有一些问题,比如:如果同时插入好多条记录以后,这个currval获取的就是最后一次记录的值了,推荐用BEFORE)
				             
			resultType属性:   查出的数据的返回值类型
			
			那些uuid的主键也能用这种方式
		 -->
		<selectKey keyProperty="id" order="BEFORE" resultType="Integer">
			<!-- 编写查询主键的sql语句 -->
			select EMPLOYEES_SEQ.nextval from dual 
		</selectKey>
		
		<!-- 插入时的主键是从序列中拿到的 -->
		insert into employee
        <trim prefix="(" suffix=")" suffixOverrides=",">
             <if test="id != null">
                id,
             </if>
             <if test="lastName != null">
                last_name,
             </if>
             <if test="email != null">
                email,
             </if>
             <if test="gender != null">
                gender,
             </if>
        </trim>
        <!-- suffixOverrides 从英语上看它就是后缀覆盖的意思,将后缀的','覆盖为空 -->
        <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="id != null">
                #{id},
             </if>
             <if test="lastName != null">
                #{lastName},
             </if>
             <if test="email != null">
                #{email},
             </if>
             <if test="gender != null">
                #{gender},
             </if>
        </trim>
		
	</insert>
    
</mapper>

java接口

package com.xxx.mybatis.mapper;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.MapKey;

import com.xxx.mybatis.entity.Employee;

// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
    
    /** 
     * mybatis允许增、删、改直接定义 void、Integer、Long、Boolean 几种类型的返回值。他的基本类型或者包装类型都行。
	 * 返回boolean的话 如果受影响行数为0是false,大于0返回true。Integer、Long、Boolean作为返回值类型的话,返回的是受影响的行数。(这里指插入了多少行)
	 * 直接在这个接口的方法上写返回值类型就行了,无需在mapper文件的<insert>中写返回值类型
	 */
    
    // 新增示例1
	public void addEmpDemo01(Employee employee);
    // 新增示例2
    public int addEmpDemo02(Employee employee);
    // 新增示例3
    public int addEmpDemo03(Employee employee);
    // 新增示例4
    public int addEmpDemo04(Employee employee);
    
    // 新增需要注意提交事务。(sqlSessionFactory.openSession(true);会自动提交事务;不带参数的或者参数为false的需要手动提交事务)
}

测试代码

package com.xxx.mybatis.maintest;

import java.io.InputStream;

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 com.xxx.mybatis.entity.Employee;

public class MainTest {
	/**
	 * 测试MyBatis新增(普通的新增)
	 * @throws IOException
	 */
	private static void testMybatisAdd() throws IOException {
		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
        
		//注意:如果用带有 boolean 参数的 openSession 是一个自动提交的Session。 不带参数的需要自己手动提交
		//SqlSession sqlSession = sqlSessionFactory.openSession(true);
        
		try {
			/**
			 * 按照入门案例中非接口式编程的方式会有一些问题,比如:sqlSession.selectOne(命名空间,参数) 要写命名空间,而且参数是Object类型,什么都能传;容易出错
			 * 
			 * mybatis 提供了一个功能, 接口可以映射文件进行动态绑定。
			 *     1、让映射文件的命名空间指定为接口的全类名
			 *     2、在接口里声明的方法名要与映射文件的 sql语句 的id相同。  当调用方法的时候代理对象就知道执行哪个sql了。
			 * 
			 */
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
			
			/** 
			 * mybatis允许增、删、改直接定义 void、Integer、Long、Boolean 几种类型的返回值。他的基本类型或者包装类型都行。
			 * 返回boolean的话 如果受影响行数为0是false,大于0返回true。Integer、Long、Boolean作为返回值类型的话,返回的是受影响的行数。(这里指插入了多少行)
			 * 直接在这个接口的方法上写返回值类型就行了,无需在mapper文件的<insert>中写返回值类型
			 */
			employeeMapper.addEmpDemo01(new Employee(null,"测试01","Email","测试01"));
			
			sqlSession.commit();//手动提交
		} finally {
			//关闭
			sqlSession.close();
		}
	}
}

1.5、批量新增(保存)

批量新增可以使用<foreach>节点来实现,<foreach>节点的介绍在后面的 5.5,这个节点需要用到集合类型的参数,集合类型参数的设置和原理在4.4有介绍

1.5.1、Mysql下的批量新增
<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
 
 	<!-- 
 		示例1:Mysql下的批量保存
 		MySQL下批量保存:可以利用foreach遍历   然后利用MySql的values(),(),()语法进行批量保存
 	 -->
	<insert id="mySqlBatchAddEmps_v1" >
	 	insert into employee( last_name,email,gender,dep_id ) values
	 	<!--
	 		collection属性: 指定要遍历的集合。这个属性值只传递一个集合参数并且不用@Param的话就写 list 或者 collection就行了
	 		这个属性值需要注意:这是批量新增在绑定的java接口中传递一个实体类的集合就行了,不要传递多余的其他参数。如果一定要传多个参数
	 		                  或者使用@Param注解的话就去查看下面的4.5的内容认识了MyBatis内部对于参数的处理后就知道这个属性值该怎么写了
	 			              
	 		item属性:       将当前遍历出的元素赋值给指定的变量
	 		separator属性:   每个元素之间的分隔符
	 		open属性:       遍历出所有结果拼接一个开始的字符
	 		close属性:       遍历出所有结果拼接一个结束的字符
	 		index属性:       索引。遍历list的时候是index就是索引,item就是当前值; 如果遍历的是map的时候index表示的就是map的key,item就是map的值
	 				      
	 		#{变量名}就能取出变量的值也就是当前遍历出的元素
	 	  -->
		<foreach collection="emps" item="emp" separator=",">
			(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
		</foreach>
	 </insert>
	
    
	
	<!-- 
		示例2:Mysql下的批量保存
		            这种方式需要数据库连接设置属性allowMultiQueries=true;例如: jdbc:mysql://localhost:3306/mybatis?allowMultiQueries=true
	 	            这种方式是以分号";"分隔多个sql 执行的批量操作(用这种分号";"方式进行批量删除,修改都行)
	 	            
	 	            他的执行sql语句是这样的:
	 	            insert into employee(last_name,email,gender,dep_id) values (?,?,?,?) ; 
	 	            insert into employee(last_name,email,gender,dep_id) values (?,?,?,?) ; 
	 	            insert into employee(last_name,email,gender,dep_id) values (?,?,?,?)
	 	            这样操作的话,都是分开执行sql了,他的返回值的受影响行数是 1 
	 -->
	 <insert id="mySqlBatchAddEmps_v2">
	 	<!--
	 		collection属性: 指定要遍历的集合。这个属性值只传递一个集合参数并且不用@Param的话就写 list 或者 collection就行了
	 		这个属性值需要注意:这是批量新增在绑定的java接口中传递一个实体类的集合就行了,不要传递多余的其他参数。如果一定要传多个参数
	 		                  或者使用@Param注解的话就去查看下面的4.5的内容认识了MyBatis内部对于参数的处理后就知道这个属性值该怎么写了
	 			              
	 		item属性:       将当前遍历出的元素赋值给指定的变量
	 		separator属性:   每个元素之间的分隔符
	 		open属性:       遍历出所有结果拼接一个开始的字符
	 		close属性:       遍历出所有结果拼接一个结束的字符
	 		index属性:       索引。遍历list的时候是index就是索引,item就是当前值; 如果遍历的是map的时候index表示的就是map的key,item就是map的值
	 				      
	 		#{变量名}就能取出变量的值也就是当前遍历出的元素
	 	  -->
	 	<foreach collection="emps" item="emp" separator=";">
	 		insert into employee(last_name,email,gender,dep_id) values 
	 		(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
	 	</foreach>
	 </insert>
</mapper>

java接口

import java.util.List;
import org.apache.ibatis.annotations.Param;

import com.xxx.mybatis.entity.Employee;

// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
	
	public int mySqlBatchAddEmps_v1(@Param("emps")List<Employee> emps);
	
	public int mySqlBatchAddEmps_v2(@Param("emps")List<Employee> emps);
}

测试代码

package com.xxx.mybatis.maintest;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import com.xxx.mybatis.entity.Department;
import com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {

	/**
	 * 测试MyBatis新增(批量的新增)
	 * @throws IOException
	 */
	public static void main(String[] args) throws Exception {
		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		//注意:如果用带有 boolean 参数的 openSession 是一个自动提交的Session。 不带参数的需要自己手动提交
		//SqlSession sqlSession = sqlSessionFactory.openSession(true);
		try {
			
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
			
			/** mybatis允许增、删、改直接定义以下几种类型的返回值  void Integer Long Boolean。他的基本类型或者包装类型都行。
			 *	返回boolean的话 如果受影响行数为0是false,大于0返回true。   直接在这个接口的方法上写返回值类型就行了,无需在mapper文件的<insert>中写返回值类型
			 */
			List<Employee> emps = new ArrayList<Employee>();
			emps.add(new Employee(null,"测试批量新增02","Email","测试批量新增02",new Department(3,null)));
			emps.add(new Employee(null,"测试批量新增02","Email","测试批量新增02",new Department(2,null)));
			emps.add(new Employee(null,"测试批量新增02","Email","测试批量新增02",new Department(3,null)));
			//int row = employeeMapper.mySqlBatchAddEmps_v1(emps);
			int row = employeeMapper.mySqlBatchAddEmps_v2(emps);
			System.out.println(row);
			
			sqlSession.commit();//手动提交
		} finally {
			//关闭
			sqlSession.close();
		}
	}
}
1.5.2、Oracle下的批量新增
<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
	<!-- 
		示例1:Oracle数据库批量保存
		
		Oracle不支持values(),(),();我们利用oracle的begin xxx end;的方式来批量插入
		
		多个insert放在begin - end里面
	 		begin
			    insert into employees(employee_id,last_name,email) 
			    values(employees_seq.nextval,'test_001','test_001@atguigu.com');
			    insert into employees(employee_id,last_name,email) 
			    values(employees_seq.nextval,'test_002','test_002@atguigu.com');
			end;
		
	 -->
	<insert id="oracleBatchAddEmps_v1">
	 	<foreach collection="emps" item="emp" open="begin" close="end;">
	 		insert into employees(employee_id,last_name,email) 
			    values(employees_seq.nextval,#{emp.lastName},#{emp.email});
	 	</foreach>
	</insert>
	
	
	
	<!-- 
		示例2:Oracle数据库批量保存
		
		Oracle不支持values(),(),();我们利用利用中间表(虚拟表 dual)的方式来批量插入
		
			insert into employees(employee_id,last_name,email)
		       select employees_seq.nextval,lastName,email from(
		              select 'test_a_01' lastName,'test_a_e01' email from dual
		              union
		              select 'test_a_02' lastName,'test_a_e02' email from dual
		              union
		              select 'test_a_03' lastName,'test_a_e03' email from dual
		       )	
	 -->
	 <insert id="addEmps">  
	 	insert into employees(employee_id,last_name,email)
	 	<!-- 需要注意这里的 open 属性,需要根据自己的具体情况来写 -->
		<foreach collection="emps" item="emp" separator="union" open="select employees_seq.nextval,lastName,email from(" close=")">
			select #{emp.lastName} lastName,#{emp.email} email from dual
		</foreach>
	 </insert>
	
</mapper>

java接口

import java.util.List;
import org.apache.ibatis.annotations.Param;

import com.xxx.mybatis.entity.Employee;

// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
	
	public int oracleBatchAddEmps_v1(@Param("emps")List<Employee> emps);
	
	public int oracleBatchAddEmps_v2(@Param("emps")List<Employee> emps);
}

1.5.3、批量执行器方式(大数据量推荐)

上面介绍的使用<foreach>节点的方式进行动态的拼接SQL语句的方式实现批量新增它有个弊端,因为这样拼接可能会拼接一个很长很长的SQL语句,数据库那边是不能接收特别长的SQL语句的。如果要批量新增的数据量比较大,可以使用MyBatis的批量执行器来进行批量操作。

批量执行器介绍

在全局配置文件的<settings>节点里面有一个配置项<setting name="defaultExecutorType" value="SIMPLE"/>这个配置写或者不写它都默认为value="SIMPLE"这个取值的意思是说让MyBatis创建一个普通的、简单的执行器,让 MyBatis 用正常的、普通的、简单的方式去执行增、删、改、查操作。它还有另外两个取值value="REUSE" 和 value="BATCH",要想让MyBatis创建批量执行器,可以将这个配置设为<setting name="defaultExecutorType" value="BATCH"/>。但是在全局配置文件里面设置会使得所有SQL语句的执行都是用批量执行器来执行,一般也不这么做。不能通过全局配置的方式创建批量执行器,也是能通过编码的方式来按需要创建。代码如下

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 
		    用<insert>标签。写一个 parameterType 属性。这个属性的意思是参数的类型,写上传入参数的全类名或者别名。也可以省略不写。 一般还是写上吧。
		    #{xxx}里面写的就是参数类型的  字段 属性;这个例子中写的是 Employee 类中的属性,表示使用这个类的实例的属性值。
		    
		  在绑定的java接口中写上 addEmpDemo01 方法。 public void addEmpDemo01(Employee employee)
	 -->
	<insert id="sqlSessionBatchAdd" parameterType="com.xxx.mybatis.entity.Employee">
		<!-- id是用mysql的自增,不用管 -->
		insert into employee(last_name,email,gender) 
		values(#{lastName},#{email},#{gender})
	</insert>
</mapper>

java 接口

import com.xxx.mybatis.entity.Employee;
// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
    public int sqlSessionBatchAdd(Employee emp);
}

测试代码

package com.xxx.mybatis.maintest;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import com.xxx.mybatis.entity.Department;
import com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {
	
	/**
	 * 
	 * Mybatis批量添加(批量操作)
	 *     第一种方式:在mapper.xml文件里面利用<foreach>标签进行循环的方式;这种方式会拼接一个很长很长的SQL语句发给数据库,数据库服务器是不能接收太长的SQL语句的。可以参考下面的几种方式
	 *     
	 *     第二种方式:在全局配置文件中<settings>节点里配置defaultExecutorType 为 BATCH;这样Mybatis就会创建一个能批量执行sql的批量执行器;
	 *                但是如果我们在全局配置文件里将执行器改成批量的执行器的话,那么所有sql语句执行都会被改成批量执行器,所以我们不这么做
	 *                
	 *     第三种方式:我们可以在每次获取Sqlsession的时候,去获取一个批量执行的Sqlsession;
	 *                例如:SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
	 *                      可以看一下下面的testMybatisMySqlBatchAdd_v2();方法
	 *
	 */
	public static void main(String[] args) throws Exception {
		testMybatisMySqlBatchAdd_v2();
	}

	
	public static void testMybatisMySqlBatchAdd_v2() throws IOException{
		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		//注意:带有ExecutorType.BATCH参数的是一个可以执行批量操作的SqlSession  它会创建批量执行器
		SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
		
		//注意:不带参数的需要自己提交事务。并且也不会用批量执行器的方式进行批量操作
		//SqlSession sqlSession = sqlSessionFactory.openSession();
		
		//注意:如果用带有 boolean 参数的 openSession 是一个自动提交的Session。 不带参数的需要自己手动提交
		//SqlSession sqlSession = sqlSessionFactory.openSession(true);
		
		long start = System.currentTimeMillis();
		try{
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			
			for (int i = 0; i < 10; i++) {
				employeeMapper.sqlSessionBatchAdd(new Employee(null,"测试sqlSessionBatch批量添加"+i, "email@"+i, "1",new Department(2,null)));
			}
			sqlSession.commit();
			
			long end = System.currentTimeMillis();
			//批量:(预编译sql一次==>设置参数===>10000次===>执行(1次))
			//Parameters: 616c1(String), b(String), 1(String)==> 用时:4598
			
			//非批量:(预编译sql=设置参数=执行)==》10000次    用时:10200
			System.out.println("执行时长:"+(end-start));
		}finally{
			sqlSession.close();
		}
	}
}

和Spring整合进行批量操作

如果和spring整合呢我们可以在spring的配置文件里创建一个可以批量执行的SqlSession的Bean;如下所示

<!--配置一个可以进行批量执行的sqlSession  -->
  <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
      <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"></constructor-arg>
      <constructor-arg name="executorType" value="BATCH"></constructor-arg>
  </bean>

然后在service层里

...
@Autowired
private SqlSession sqlSession;   //让他自动装配这个可以批量操作的SqlSession
...

然后在具体的方法里面

...
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class); //就可以用employeeMapper进行批量操作了
...

注意

  • 批量操作是在session.commit()以后才发送sql语句给数据库进行执行的
  • 如果我们想让其提前执行,以方便后续可能的查询操作获取数据,我们可以使用sqlSession.flushStatements()方法,让其直接冲刷到数据库进行执行。

2、更新操作

基于接口式编程的方式在映射文件中使用<update>节点就行。

2.1、普通的更新操作

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 
		更新示例:
			用<update>标签。写一个 parameterType 属性。这个属性的意思是参数的类型,写上传入参数的全类名或者别名。也可以省略不写。 一般还是写上吧。这里就不写了
		    #{xxx}里面写的就是参数类型的  字段 属性;这个例子中写的是 Employee 类中的属性,表示使用这个类的实例的属性值。
		    
		  在绑定的java接口中写上 updateEmp 方法。 public void updateEmp(Employee employee);
	  -->
	<update id="updateEmp" parameterType="com.xxx.mybatis.entity.Employee">
		update employee 
		set last_name=#{lastName},email=#{email},gender=#{gender}
		where id=#{id}
	</update>
</mapper>

2.2、动态拼接语句更新

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 
		动态更新示例:
			用<update>标签。写一个 parameterType 属性。这个属性的意思是参数的类型,写上传入参数的全类名或者别名。也可以省略不写。 一般还是写上吧。这里就不写了
		    #{xxx}里面写的就是参数类型的  字段 属性;这个例子中写的是 Employee 类中的属性,表示使用这个类的实例的属性值。
		    
		  在绑定的java接口中写上 updateEmp 方法。 public void updateEmp(Employee employee);
	  -->
	<update id="dynamicUpdateEmp" parameterType="com.xxx.mybatis.entity.Employee">
		update employee 
        <!-- prefixOverrides 从英语上看它就是前缀覆盖的意思,将前缀的','覆盖为空 -->
        <trim prefix="set" prefixOverrides=",">
            <if test="lastName != null">
                ,last_name=#{lastName}
             </if>
             <if test="email != null">
                ,email=#{email}
             </if>
             <if test="gender != null">
                ,gender=#{gender}
             </if>
        </trim>
		where id=#{id}
	</update>
</mapper>

java接口

package com.xxx.mybatis.mapper;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.MapKey;

import com.xxx.mybatis.entity.Employee;

// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
    /** mybatis允许增、删、改直接定义以下几种类型的返回值  void Integer Long Boolean。他的基本类型或者包装类型都行。
	 * 返回boolean的话 如果受影响行数为0是false,大于0返回true。  直接在这个接口的方法上写返回值类型就行了,无需在mapper文件的<update>中写返回值类型
	 * 
	 * 更新需要注意提交事务。(sqlSessionFactory.openSession(true);会自动提交事务;不带参数的或者参数为false的需要手动提交事务)
	 */
	public void updateEmp(Employee employee);
    
    public void dynamicUpdateEmp(Employee employee);
}

测试代码

package com.xxx.mybatis.maintest;

import java.io.InputStream;

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 com.xxx.mybatis.entity.Employee;

public class MainTest {
	/**
	 * 测试mybatis修改 更新
	 * @throws IOException
	 */
	private static void testMybatisUpdate() throws IOException {
		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		//注意:如果用带有 boolean 参数的 openSession 是一个自动提交的Session。 不带参数的需要自己手动提交
		//SqlSession sqlSession = sqlSessionFactory.openSession(true);
		try {
			/**
			 * 按照入门案例中非接口式编程的方式会有一些问题,比如:sqlSession.selectOne(命名空间,参数) 要写命名空间,而且参数是Object类型,什么都能传;容易出错
			 * 
			 * mybatis 提供了一个功能, 接口可以映射文件进行动态绑定。
			 *     1、让映射文件的命名空间指定为接口的全类名
			 *     2、在接口里声明的方法名要与映射文件的 sql语句 的id相同。  当调用方法的时候代理对象就知道执行哪个sql了。
			 */
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
			
			/** mybatis允许增、删、改直接定义以下几种类型的返回值  void Integer Long Boolean。他的基本类型或者包装类型都行。
			 * 返回boolean的话 如果受影响行数为0是false,大于0返回true。  直接在这个接口的方法上写返回值类型就行了,无需在mapper文件的<update>中写返回值类型
			 */
			employeeMapper.updateEmp(new Employee(3,"测试01修改","Email","测试01修改"));
			
			sqlSession.commit();//手动提交
		} finally {
			//关闭
			sqlSession.close();
		}
	}
}

3、删除操作

基于接口式编程的方式在映射文件中使用<delete>节点就行。

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 
		删除示例:
			用<delete>标签。写一个 parameterType 属性。这个属性的意思是参数的类型,写上传入参数的全类名或者别名。也可以省略不写。这里就不写了
		    #{xxx}里面写的就是参数类型的  字段 属性
		    
		  在绑定的java接口中写上 deleteEmpById 方法。 public void deleteEmpById(Integer id);
	 -->
	<delete id="deleteEmpById" parameterType="java.lang.Integer">
		delete from employee where id=#{id}
        
        <!-- 
            一般情况下都是通过主键删除数据就行了;通过不同的字段组合来删除数据也是可以的,这种情况较少;
            想动态的拼接 delete 语句进行删除数据也是可以通过<if>节点来拼接。 这里就不写出来了
        -->
	</delete>
</mapper>

java接口

package com.xxx.mybatis.mapper;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.MapKey;

import com.xxx.mybatis.entity.Employee;

// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
    /** mybatis允许增、删、改直接定义以下几种类型的返回值  void Integer Long Boolean。他的基本类型或者包装类型都行。
	 * 返回boolean的话 如果受影响行数为0是false,大于0返回true。  直接在这个接口的方法上写返回值类型就行了,无需在mapper文件的<delete>中写返回值类型
	 * 
	 * 删除需要注意提交事务。(sqlSessionFactory.openSession(true);会自动提交事务;不带参数的或者参数为false的需要手动提交事务)
	 */
	public void deleteEmpById(Integer id);
}

测试代码

package com.xxx.mybatis.maintest;

import java.io.InputStream;

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 com.xxx.mybatis.entity.Employee;

public class MainTest {
	/**
	 * 测试mybatis删除
	 * @throws IOException
	 */
	private static void testMybatisDelete() throws IOException {
		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		//注意:如果用带有 boolean 参数的 openSession 是一个自动提交的Session。 不带参数的需要自己手动提交
		//SqlSession sqlSession = sqlSessionFactory.openSession(true);
		try {
			/**
			 * 按照入门案例中非接口式编程的方式会有一些问题,比如:sqlSession.selectOne(命名空间,参数) 要写命名空间,而且参数是Object类型,什么都能传;容易出错
			 * 
			 * mybatis 提供了一个功能, 接口可以映射文件进行动态绑定。
			 *     1、让映射文件的命名空间指定为接口的全类名
			 *     2、在接口里声明的方法名要与映射文件的 sql语句 的id相同。  当调用方法的时候代理对象就知道执行哪个sql了。
			 * 
			 */
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
			
			/** mybatis允许增、删、改直接定义以下几种类型的返回值  void Integer Long Boolean。他的基本类型或者包装类型都行。
			 * 返回boolean的话 如果受影响行数为0是false,大于0返回true。  直接在这个接口的方法上写返回值类型就行了,无需在mapper文件的<delete>中写返回值类型
			 */
			employeeMapper.deleteEmpById(3);
			
			sqlSession.commit();//手动提交
		} finally {
			//关闭
			sqlSession.close();
		}
	}
}

4、查询操作

基于接口式编程的方式在映射文件中使用<select>节点就行。

4.1、无参数的查询

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 这里的resultType属性值写的是 com.xxx.mybatis.entity.Employee 类的别名,需要在全局配置文件配一下,不配就写全类名 -->
    <select id="findEmpById_no_parameter" resultType="employee">
		select * from employee where id = 5144
	</select>
</mapper>

java接口

package com.xxx.mybatis.mapper;

import java.util.Map;
import org.apache.ibatis.annotations.Param;
import com.xxx.mybatis.entity.Employee;
// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
    //映射文件的 sql语句ID要和这里的方法名相同
	public Employee findEmpById_no_parameter();
}

测试代码

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

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 com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {
    public static void main(String[] args) throws Exception {
        //1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		try {
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
			
			Employee employee = employeeMapper.findEmpById_no_parameter();
			System.out.println(employee);
		} finally {
			//关闭
			sqlSession.close();
		}
    }
}

4.2、一个参数的查询

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 这里的resultType属性值写的是 com.xxx.mybatis.entity.Employee 类的别名,需要在全局配置文件配一下,不配就写全类名 -->
    <select id="findEmpById_no_parameter" parameterType="java.lang.Integer" resultType="employee">
        <!-- 
			 在这个示例中:实体类的字段应和数据库中的字段相同才能将查询结果赋值和插入数据库。
	    	 因为没配置实体类字段和数据库字段映射规则,默认就是类中的字段对应数据库的字段。例如:数据库的字段有下划线,类的属性也要有下划线。
	    	 当然可以在查询的时候可以在sql语句中写上字段的别名,让别名和类中的属性名相同的方式
	    	 
	    	 参数处理:
			     单个参数:就是绑定的mapper接口的方法上只有一个参数,映射的sql也只有一个参数,
			              mybatis不会做特殊处理,直接用 #{参数名 或者 任意名} 就能取到值给到sql
		         
		 -->
		select * from employee where id = #{id} <!-- #{id}这个地方 可以写成#{id} 也可以写成#{xxcdsxx}  都能取出参数值。一般都按参数名写,见名之意原则 -->
	</select>
</mapper>

java接口

package com.xxx.mybatis.mapper;

import java.util.Map;
import org.apache.ibatis.annotations.Param;
import com.xxx.mybatis.entity.Employee;
// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
    /**
	 * 映射文件的 sql语句ID要和这里的方法名相同
	 * 
	 * 参数处理:
	 *   单个参数:就是绑定的mapper接口的方法上只有一个参数,映射的sql也只有一个参数。 mybatis不会做特殊处理,直接用#{参数名 或者 任意名} 就能取到值给到sql
	 * 	 #{参数名|任意名}:取出参数值。
	 *   
	 *   例如:select * from employee where id = #{id}  或者  select * from employee where id = #{idabd}
	 */
	public Employee findEmpById(Integer id);
}

测试代码

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

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 com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {
    public static void main(String[] args) throws Exception {
        //1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		try {
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
			
			Employee employee = employeeMapper.findEmpById(1);
			System.out.println(employee);
		} finally {
			//关闭
			sqlSession.close();
		}
    }
}

4.3、多个参数的查询

在实际开发过程中,多个参数作为查询条件很常见,基于接口式编程为例,可以在接口里面的方法定义多个参数,也可以使用实体类(持久化类),也可以使用Map等。在MyBatis映射文件中取值就会有所差别。

4.3.1、多参数默认查询方式
<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 这里的resultType属性值写的是 com.xxx.mybatis.entity.Employee 类的别名,需要在全局配置文件配一下,不配就写全类名 -->
    <!-- 这里的parameterType属性值写的是 Map 的别名,因为多个参数会被封装为Map -->
    <select id = "getEmpByIdAndEmail" parameterType="map" resultType="employee">
		<!-- 
			多个参数:mybatis会做特殊处理。如果不安规则去取值会报异常:org.apache.ibatis.binding.BindingException: Parameter 'xxx' not found.Available parameters are [1, 0, param1, param2]
			例如下面的操作:
				方法:public Employee getEmpByIdAndEmail(Integer id,String email);
				取值:#{id},#{email}
				报异常:
					org.apache.ibatis.binding.BindingException: 
					Parameter 'id' not found. 
					Available parameters are [1, 0, param1, param2]
					
			正确的取值应该是   #{param1},#{param2}  或者  #{0} #{1}

			因为:
			多个参数会被封装成 一个map,
				map的key就变成了param拼接上参数的索引。 例如:key:param1,param2...paramN;
				map的value就是真正的传入的参数值
			#{}就是从map中获取指定的key的值;
			或者
			参数的索引也可以 #{0} #{1}
		 -->
		select * from employee where id = #{param1} and email = #{param2}
		<!-- select * from employee where id = #{0} and email = #{1} -->
        <!-- 可以看到使用默认的获取参数值的方式,不方便阅读。一般不用的,推荐命名参数或者实体类的方式传参取值 -->
	</select>
</mapper>

java接口

package com.xxx.mybatis.mapper;

import java.util.Map;
import org.apache.ibatis.annotations.Param;
import com.xxx.mybatis.entity.Employee;
// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
    /**
	 *多个参数:mybatis会做特殊处理。如果不按规则去取值会报异常:org.apache.ibatis.binding.BindingException: Parameter 'xxx' not found.Available parameters are [1, 0, param1, param2]
	 *	例如下面的操作:
	 *		方法:public Employee getEmpByIdAndEmail(Integer id,String email);
	 *		取值:#{id},#{email}
	 *		报异常:
	 *			org.apache.ibatis.binding.BindingException: 
	 *			Parameter 'id' not found. 
	 *			Available parameters are [1, 0, param1, param2]
	 *			
	 *	正确的取值应该是   #{param1},#{param2}  或者  #{0} #{1}
	 *	因为:
	 *	多个参数会被封装成 一个map,
	 *		map的key就变成了param拼接上参数的位置。 例如:key:param1,param2...paramN;
	 *		map的value就是真正的传入的参数值
	 *	#{}就是从map中获取指定的key的值;
	 *	或者
	 *	参数的索引也可以 #{0} #{1}
	 *
	 *  例如:
	 *      select * from employee where id = #{param1} and email = #{param2}
	 *      select * from employee where id = #{0} and email = #{1}
	 *      可以看到使用默认的获取参数值的方式,不方便阅读。一般不用的,推荐命名参数或者实体类的方式传参取值
	 */
	public Employee getEmpByIdAndEmail(Integer id,String email);
}

测试代码

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

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 com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {
    public static void main(String[] args) throws Exception {
        //1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		try {
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
			
			Employee employee = employeeMapper.getEmpByIdAndEmail(1, "123");
			System.out.println(employee);
		} finally {
			//关闭
			sqlSession.close();
		}
    }
}
4.3.2、命名参数方式查询
<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 这里的resultType属性值写的是 com.xxx.mybatis.entity.Employee 类的别名,需要在全局配置文件配一下,不配就写全类名 -->
    <select id = "findEmpByIdAndEmail" resultType="employee">
		<!-- 
			【命名参数】:
			    前面说过多个参数的情况下MyBatis会将这些个参数封装为Map,取值时用的是Map的Key,Key是按照MyBatis自己的规则创建的,
			    使用命名参数的方式就是明确指定封装参数时map的key;@Param("id")。就是在接口的方法的参数上用 @Param("xxx") 注解。
			多个参数会被封装成 一个map,
				key:使用@Param注解指定的值
				value:参数值
			#{指定的key}取出对应的参数值
		 -->
		select * from employee where id = #{id} and email = #{email}
	</select>
</mapper>

java接口

package com.xxx.mybatis.mapper;

import java.util.Map;
import org.apache.ibatis.annotations.Param;
import com.xxx.mybatis.entity.Employee;
// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
    /**
	 * 【命名参数】:
	 *      前面说过多个参数的情况下MyBatis会将这些个参数封装为Map,取值时用的是Map的Key,Key是按照MyBatis自己的规则创建的,
	 *      使用命名参数的方式就是明确指定封装参数时map的key;@Param("id")。就是在接口的方法的参数上用 @Param("xxx") 注解。
	 *
	 *		多个参数会被封装成 一个map,
	 *			key:使用@Param注解指定的值
	 *			value:参数值
	 *		#{指定的key}取出对应的参数值
	 * 例如:
	 *    select * from employee where id = #{id} and email = #{email}
	 */
	public Employee findEmpByIdAndEmail(@Param("id")Integer id,@Param("email")String email);
}

测试代码

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

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 com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {
    public static void main(String[] args) throws Exception {
        //1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		try {
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
			
			Employee employee = employeeMapper.findEmpByIdAndEmail(1, "123");
			System.out.println(employee);
		} finally {
			//关闭
			sqlSession.close();
		}
    }
}
4.3.3、实体类作为参数方式查询

如果sql有多个参数,这些个参数正好是我们业务逻辑的数据模型(entity,pojo),我们就可以直接传入pojo,entity;

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 这里的 parameterType 属性值写的是 com.xxx.mybatis.entity.Employee 类的别名,需要在全局配置文件配一下,不配就写全类名。当然这个属性不写也是可以的 -->
    <!-- 这里的 resultType 属性值写的是 com.xxx.mybatis.entity.Employee 类的别名,需要在全局配置文件配一下,不配就写全类名 -->
    <select id = "findEmpByEntity" parameterType="employee" resultType="employee">
		<!-- 
			如果sql有多个参数,这些个参数正好是我们业务逻辑的数据模型(entity,pojo),我们就可以直接传入pojo,entity;
				#{entity类里面的属性名}:取出传入的pojo,entity的属性值	
		 -->
		select * from employee where id = #{id} and email = #{email}
	</select>
</mapper>

java接口

package com.xxx.mybatis.mapper;

import java.util.Map;
import org.apache.ibatis.annotations.Param;
import com.xxx.mybatis.entity.Employee;
// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
    /**
	 * 如果sql有多个参数,这些个参数正好是我们业务逻辑的数据模型(entity,pojo),我们就可以直接传入pojo,entity;
	 *		#{entity类里面的属性名}:取出传入的pojo,entity的属性值	
	 * 例如:
	 *     select * from employee where id = #{id} and email = #{email}
	 */
	public Employee findEmpByEntity(Employee employee);
}

测试代码

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

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 com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {
    public static void main(String[] args) throws Exception {
        //1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		try {
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
			
			Employee entityParam =  new Employee(1,null,"123",null);
			Employee employee = employeeMapper.findEmpByEntity(entityParam);
			System.out.println(employee);
		} finally {
			//关闭
			sqlSession.close();
		}
    }
}
4.3.4、Map作为参数方式查询

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

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 这里的 parameterType 属性值写的是 java.util.Map 类的别名,这个别名 MyBatis 内部定义好了的,写全类名也行。当然这个属性不写也是可以的 -->
    <!-- 这里的 resultType 属性值写的是 com.xxx.mybatis.entity.Employee 类的别名,需要在全局配置文件配一下,不配就写全类名 -->
    <select id = "findEmpByMap" parameterType="map" resultType="employee">
		<!-- 
			Map:
			如果多个参数不是业务模型中的数据,没有对应的(entity,pojo),不经常使用,为了方便,我们也可以传入map
				#{key}:取出map中对应的值
			
			例如:
			    Map<String,Object> paramMap = new HashMap<String,Object>();
				paramMap.put("id", 1);
				paramMap.put("email", "123");
				Employee employee = employeeMapper.findEmpByMap(paramMap);
		 -->
		select * from employee where id = #{id} and email = #{email}
	</select>
</mapper>

java接口

package com.xxx.mybatis.mapper;

import java.util.Map;
import org.apache.ibatis.annotations.Param;
import com.xxx.mybatis.entity.Employee;
// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
    /**
	 * 如果多个参数不是业务模型中的数据,没有对应的(entity,pojo),不经常使用,为了方便,我们也可以传入map
	 *		#{Map的key}:取出map中对应的值
	 *
	 * 例如:
	 *     select * from employee where id = #{id} and email = #{email}
	 *     
	 *     Map<String,Object> paramMap = new HashMap<String,Object>();
	 *	   paramMap.put("id", 1);
	 *	   paramMap.put("email", "123");
	 *	   Employee employee = employeeMapper.findEmpByMap(paramMap);
	 */
	public Employee findEmpByMap(Map<String,Object> map);
}

测试代码

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

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 com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {
    public static void main(String[] args) throws Exception {
        //1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		try {
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
			
			Map<String,Object> paramMap = new HashMap<String,Object>();
			paramMap.put("id", 1);
			paramMap.put("email", "123");
			Employee employee = employeeMapper.findEmpByMap(paramMap);
			System.out.println(employee);
		} finally {
			//关闭
			sqlSession.close();
		}
    }
}
4.3.5、自定义Java类作为参数方式查询

如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(Transfer Object)数据传输对象(就是自定义一个类,让这个类的实例对象作为参数传递给MyBatis,MyBatis就会把这个对象的数据作为SQL语句的参数)。

这里用分页参数举个例子,分页参数他就是一个经常被使用到,但它又不是业务模型中的数据(像name、age、idCard等就是业务模型的数据,因为这些数据因为业务需求可能要求模糊掉敏感信息给用户显示。而分页信息就是个数字用来传递给SQL作为查询用的,用不着做什么业务上的处理,就是单单计算一下分页的数量,所以不算业务模型中的数据)

这只是个例子,真正的分页一般都用插件的方式

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 这里的 parameterType 属性值写的是传入的参数类型。当然这个属性不写也是可以的 -->
    <!-- 这里的 resultType 属性值写的是 com.xxx.mybatis.entity.Employee 类的别名,需要在全局配置文件配一下,不配就写全类名 -->
    <select id = "findDataByTO" parameterType="com.xxx.mybatis.entity.to.EmployeeTO" resultType="employee">
		<!-- 
			传入一个自定义的java类对象,取值方式如下所示:
			#{java类里面的属性名}就能取出传入的java类的属性值;(javaType和jdbcType可以写也可以不写)
		 -->
		select * from employee where last_name=#{lastName,javaType=String,jdbcType=VARCHAR} 
	    <if test="index != null and pageSize != null and pageSize &gt; 0">
	        limit #{index,javaType=Integer,jdbcType=INTEGER},#{pageSize,javaType=Integer,jdbcType=INTEGER}
	    </if>
	</select>
</mapper>

自定义java类

public class EmployeeTO {
	
	private String lastName;
	private String email;
	private String gender;
	private Integer index;
	private Integer pageSize;
    // 写上 getter、setter 方法,写上构造器,写上toString 方法。
}

java接口

import com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.entity.to.EmployeeTO;
import java.util.List;

//映射文件的命名空间要和这里的全类名相同    接口的编程方式,所有要写成一样
public interface EmployeeMapper {// 为了看起来这个接口也是操作 employee 表的,把接口的名字也命名为EmployeeMapper
	public List<Employee> findDataByTO(EmployeeTO employeeTO);
}

测试类

import java.io.InputStream;

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.util.List;

import com.xxx.mybatis.mapper.EmployeeMapper;
import com.xxx.mybatis.entity.to.EmployeeTO;

public class MainTest {
	
	public static void main(String[] args) throws Exception {
		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession(true);
		try {
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象

			List<Employee> list = employeeMapper.findDataByTO(new EmployeeTO("员工1",null,null,0,5));
			
			System.out.println(list);
		} finally {
			//关闭
			sqlSession.close();
		}
	}
}
4.3.6、复合参数方式查询

示例1:

java接口中 public Employee getEmp01(@Param("id")Integer id,String lastName);

在映射文件中的取值方式:取出 id 值的写法:#{id 或者 param1} ; 取出 lastName 值的写法: #{param2} (只能是#{param2}这种写法了)

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 这里的resultType属性值写的是 com.xxx.mybatis.entity.Employee 类的别名,需要在全局配置文件配一下,不配就写全类名 -->
    <select id="getEmp01" resultType="employee">
        <!-- 
            绑定的java接口为:public Employee getEmp(@Param("id")Integer id,String lastName);
            取出 id 值的写法:可以是 #{id} ,也可以是 #{param1}
            取出 lastName 值的写法: #{param2} 或者 #{1} 这种写法;一般写 #{param2} 就行了,这样也方便人看懂。#{1}没试过,理论上可行
        -->
	    select * from employee where id = #{id} and last_name=#{param2}
	</select>
</mapper>

示例2:

java接口中 public Employee getEmp02(Integer id,Employee emp);有一个实体类

在映射文件中的取值方式:取出 id 值的写法:#{param1} ; 取出 lastName 值的写法: #{param2.lastName}

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 这里的resultType属性值写的是 com.xxx.mybatis.entity.Employee 类的别名,需要在全局配置文件配一下,不配就写全类名 -->
    <select id="getEmp02" resultType="employee">
        <!-- 
            绑定的java接口为:public Employee getEmp02(Integer id,Employee emp);
            取出 id 值的写法: #{param1} 或者 #{0} 
            取出 lastName 值的写法: #{param2.lastName} 或者 #{1.lastName};一般写 #{param2.xxx} 就行了,这样也方便人看懂。#{1.xxx}没试过,理论上可行
        -->
	    select * from employee where id = #{param1} and last_name = #{param2.lastName}
	</select>
</mapper>

示例3:

java接口中 public Employee getEmp03(Integer id,@Param("e")Employee emp);有一个实体类

在映射文件中的取值方式:取出 id 值的写法:#{param1} ; 取出 lastName 值的写法: #{param2.lastName 或者 e.lastName}

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 这里的resultType属性值写的是 com.xxx.mybatis.entity.Employee 类的别名,需要在全局配置文件配一下,不配就写全类名 -->
    <select id="getEmp03" resultType="employee">
        <!-- 
            绑定的java接口为:public Employee getEmp03(Integer id,@Param("e")Employee emp);
            取出 id 值的写法: #{param1} 或者 #{0} 
            取出 lastName 值的写法: #{param2.lastName} 或者 #{e.lastName}
        -->
	    select * from employee where id = #{param1} and last_name = #{e.lastName}
	</select>
</mapper>

java接口

import org.apache.ibatis.annotations.Param;

import com.xxx.mybatis.entity.Employee;

//映射文件的命名空间要和这里的全类名相同    接口的编程方式,所有要写成一样
public interface EmployeeMapper {// 为了看起来这个接口也是操作 employee 表的,把接口的名字也命名为EmployeeMapper

	public Employee getEmp(@Param("id")Integer id,String lastName);
	
	public Employee getEmp02(Integer id,Employee emp);
	
	public Employee getEmp03(Integer id,@Param("e")Employee emp);
}

4.4、集合或数组类型参数的查询

如果在java接口里面有方法的参数是Collection(List、Set)类型或者是数组,MyBatis也会特殊处理。

这里介绍一下下面两种情况

  • 在 java 接口中声明的方法只有一个参数,而且这个参数是 Collection(List、Set)类型或者是数组并且参数没有使用@Param注解,MyBatis内部会自动识别这个参数类型,在xxxMapper.xml文件中就不能随便写了(不能用 #{随便写} 来取值),也不能用 #{param1} 来取值了;他的取值方式变成了 #{collection} ;如果是精确的List,还可以使用这个#{list}(也就是说可以写 #{collection} 或者 #{list} 来取值)。如果这个参数是数组类型它的 key 就是 array;需要通过#{array}来取值
  • 如果是多个参数或者是单个参数的情况下使用了@Param注解,MyBatis会把参数封装在一个 Map 里面,在xxxMapper.xml文件中使用Map的key来取值就行了。Map的key跟@Param注解的值有关,也跟参数的下标位置有关。详细的可以看源码的介绍(点击查看

例如:

传入一个集合,取出集合第一个值作为查询的参数

java接口中 public Employee getEmpById(List<Integer> ids);

在映射文件中的取值方式:取出集合中的第一个值的写法:#{list[0]} 或者 #{collection[0]}

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    
    <!-- 
      示例1:
        这里的resultType属性值写的是 com.xxx.mybatis.entity.Employee 类的别名,
        (需要在全局配置文件配一下,不配就写全类名)。
        
        这里的parameterType 属性:可以写 list、collection、arraylist; 它就是个别名。
        (写 collection 的话能兼容 java中的set集合还有list集合,可以避免一些错误)
    -->
    <select id="getEmpInQuery" parameterType = "collection" resultType="employee">
	    <!-- 	        
	        参数取值:可以写 #{list[0]},#{list[1]} 或者 #{collection[0]},#{collection[1]} 因为它是个key,不是MyBatis的别名
	        (需要注意的是:写list[1]、collection[0]写的是key,不是别名。)
	    -->
	    select * from employee where id in (#{list[0]},#{list[1]})
	</select>
    
    <!-- 
      示例2:
        这里的resultType属性值写的是 com.xxx.mybatis.entity.Employee 类的别名,
        (需要在全局配置文件配一下,不配就写全类名)。
        
        parameterType = "int[]" 是int数组的别名写法,表示传入的参数是一个int数组。
    -->
    <select id="getEmpInQueryArr" parameterType = "int[]" resultType="com.xxx.mybatis.entity.Employee">
	    <!-- 
	        取出数组值得key是array
	    -->
	    select * from employee where id in (#{array[0]},#{array[1]})
	</select>
    
    <!-- 
      示例3:
        这里的resultType属性值写的是 com.xxx.mybatis.entity.Employee 类的别名,
        (需要在全局配置文件配一下,不配就写全类名)。

        集合作为参数传入一般不会使用下标的方式来取值,使用下标取值写着麻烦,如果集合里面的值(元素)数量是不确定的那就容易出错了。
        一般来说可以使用 <foreach> 节点来动态的取出集合里面的值。
        
        这里的parameterType 属性:可以写 list、collection、arraylist; 它就是个别名。
        (写 collection 的话能兼容 java中的set集合还有list集合,可以避免一些错误)
    -->
    <select id="getEmpsByConditionForeach_list" parameterType = "collection" resultType="com.xxx.mybatis.entity.Employee">
	 	select * from employee where id in
	 	<!--
	 		collection属性: 指定要遍历的集合。
	 		这个参数需要特别注意:
	 			            绑定的java接口是单个集合参数并且没有使用@Param注解的情况下可以写collection, list
	 			            绑定的java接口是多参数的情况下最好是使用@Param注解给他弄一个key;
	 			            所以这个属性值可以写:collection、list、@Param注解的值、是数组的话可以写array
	 			            这个是取值的key,不是别名。不要混淆了!!!更详细的介绍可以查看4.5章节
	 			              
	 		item属性:       将当前遍历出的元素赋值给指定的变量
	 		separator属性:   每个元素之间的分隔符
	 		open属性:        遍历出所有结果拼接一个开始的字符
	 		close属性:       遍历出所有结果拼接一个结束的字符
	 		index属性:       索引。遍历list的时候是index就是索引,item就是当前值; 如果遍历的是map的时候index表示的就是map的key,item就是map的值
	 				      
	 		#{变量名}就能取出变量的值也就是当前遍历出的元素
	 	  -->
	 	<foreach collection="ids" item="item_id" separator="," open="(" close=")">
	 		#{item_id}   <!-- 需要注意别名和取值的key;写在parameterType是别名,写在collection是取值的key 。 -->
	 	</foreach>
	 </select>
    
    <!-- 
        需要注意别名和取值的key;写在parameterType是别名,写在collection是取值的key 。
        这两个可能会相同
    -->
    <select id="getEmpsByConditionForeach_arr" parameterType = "int[]" resultType="com.xxx.mybatis.entity.Employee">
	 	select * from employee where id in
	 	<foreach collection="array" item="item_id" separator="," open="(" close=")">
	 		#{item_id}
	 	</foreach>
	 </select>
</mapper>

java接口

import java.util.List;

import com.xxx.mybatis.entity.Employee;

//映射文件的命名空间要和这里的全类名相同    接口的编程方式,所有要写成一样
public interface EmployeeMapper {// 为了看起来这个接口也是操作 employee 表的,把接口的名字也命名为EmployeeMapper
    
    public List<Employee> getEmpInQuery(List<Integer> ids);
    
    public List<Employee> getEmpInQueryArr(int[] ids);
    
    public List<Employee> getEmpsByConditionForeach_list(List<Integer> ids);
    
    public List<Employee> getEmpsByConditionForeach_arr(int[] ids);
}

测试代码

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

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 com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {
	
	public static void main(String[] args) throws Exception {
		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession(true);
		try {
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
 
			List<Integer> ids = new ArrayList<>();
			ids.add(1);
			ids.add(2);
			List<Employee> list = employeeMapper.getEmpInQuery(ids);
			
			System.out.println(list);
		} finally {
			//关闭
			sqlSession.close();
		}
	}
}

4.5、结合源码认识MyBatis参数处理

在执行增、删、改、查的时候把断点打在 org.apache.ibatis.binding.MapperProxy 类的 public Object invoke(Object proxy, Method method, Object[] args) 方法里面, Debug 跟踪 方法的执行就能很好的知道MyBatis是怎样进行参数设置和处理的了。

在这里插入图片描述

在这里插入图片描述

4.6、获取参数值的两种方式 #{} 和 ${}

#{} 和 ${}:都可以获取到数据值,然后给到 SQL ;

两者的区别:

  • #{}:是以预编译的形式,将参数设置到sql语句中(就是sql语句带 ? 的那种);这种方式可以防止sql注入。
  • ${}:取出的值直接拼装在sql语句中(根本上来说就是sql语句的字符串拼接);这种方式会有安全问题(会被注入攻击);
  • 大多情况下,我们去参数的值都应该去使用#{};
  • 可能也有一些情况需要使用 ${} 方式进行取值的,但是能不用就不要用,尽量想其他方式去实现。
    • 举个例子方便理解这个 ${} 方式进行取值:在分表的情况下表名称需要通过参数传递进来,这时候用 #{tableName} 这种预编译的方式是不行的,就可以用 ${} 方式进行取值,有其他方法就不要用。

#{}取值方式可以规定参数的一些规则:

  • javaType (取参数值的时候指定这个值的java类型)
  • jdbcType (取参数值的时候指定数据库对应的jdbc类型)jdbcType是用来指定Java类型到JDBC类型的映射。MyBatis 支持多种JDBC类型,这些类型对应于JDBC规范中定义的SQL类型。jdbcType的值可以在JDBC API中找到,具体在java.sql.Types这个类中定义。
  • mode (存储过程)
  • numericScale (规定保留几位小数) 例如: #{height,javaType=double,jdbcType=NUMBRIC,numericScale=2}
  • resultMap (规定封装的结果类型)
  • typeHandler (规定处理这个数据的类型处理器)
  • jdbcTypeName (和jdbcType一样)
  • expression (未来准备支持的功能);

在MyBatis的配置中,jdbcType通常用于#{}表达式中,以确保正确的数据类型转换。例如:

<!-- 假设我们有一个字段名为 'age',其Java类型为Integer,JDBC类型为VARCHAR -->
<select id="selectUser" resultType="map">
    SELECT age FROM users WHERE age = #{age, javaType=INTEGER, jdbcType=VARCHAR}
</select>

在这个例子中,#{age}是一个参数,javaType=INTEGER指定了Java类型,而jdbcType=VARCHAR指定了JDBC类型。MyBatis将根据这些信息来处理参数和SQL语句的类型转换。如果你不确定应该使用哪种jdbcType,可以根据数据库表中相应列的数据类型来选择。

其实也不是说一定要设置,在某些特殊的条件下需要被设置;

比如说:有些数据库可能不能识别mybatis对null的默认处理;比如Oracle(会报错);我们对Oracle的表插入NULL的数据,或者将数据改为null 会报错。因为MyBatis对所有的null都映射的是原生Jdbc的OTHER类型,Oracle不能正确处理。就是说Mybatis默认的将为null的值 指定数据库对应的jdbc类型为 JdbcType=OTHER #{xxx,JdbcType=OTHER}

解决这个有两种办法

  1. 在使用#{}取出参数值的时候加上JdbcType,例如:#{email,jdbcType=NULL}。表示的意思是 如果这个值为空应该用的jdbcType是NULL 而不是 OTHER
  2. 在全局配置文件中的<settings>节点里面加上下面这句话:<setting name="jdbcTypeForNull" value="NULL"/>

4.7、查询结果返回List

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 这里的resultType属性值写的是 com.xxx.mybatis.entity.Employee 类的别名,需要在全局配置文件配一下,不配就写全类名 -->
	<select id="queryEmployeeList" parameterType="java.lang.String" resultType="employee">
		<!-- 
			如果返回的是一个集合 resultType 写集合中元素的类型(别名或者全类名);也就是集合的泛型。然后在绑定的接口中用集合作为返回值;
			例如:
			    public List<Employee> queryEmployeeList(String lastName);
		 -->
		select * from employee where last_name like #{lastName}
	</select>
    
    <!-- 查询多条记录封装为一个List<Map<String,Object>>,在绑定的java接口里直接用List接收就行。 -->
    <select id="queryEmployeeMapList" parameterType="java.lang.String" resultType="map">
		select * from employee where last_name like #{lastName}
	</select>
</mapper>

java接口

package com.xxx.mybatis.mapper;

import com.xxx.mybatis.entity.Employee;
// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
    public List<Employee> queryEmployeeList(String lastName);
    
    // 查询出来的多条记录也是可以封装为一个List<Map<String,Object>>,在这里直接用List接收就行。
    public List<Map<String,Object>> queryEmployeeMapList(String lastName);
}

测试代码

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

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 com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {
    public static void main(String[] args) throws Exception {
        //1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		try {
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
			
			List<Employee> list = employeeMapper.queryEmployeeList("%1%");
            //List<Map<String,Object>> list = employeeMapper.queryEmployeeMapList("%1%"); 打开注释测试
			System.out.println(list);
		} finally {
			//关闭
			sqlSession.close();
		}
    }
}

4.8、查询结果返回Map

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    
    <!-- 示例1:查询单条记录封装为一个Map -->
    <!-- 这里的resultType属性值写的是 com.xxx.mybatis.entity.Employee 类的别名,需要在全局配置文件配一下,不配就写全类名 -->
    <select id="queryEmployeeMap" parameterType="java.lang.Integer" resultType="map">
		<!-- 
			如果想返回的是一个Map resultType写Map的全类名或者别名;然后在绑定的接口中用Map<String,Object>作为返回值; 写Object 是为了能接收返回回来的所有类型
			有时候想查询的结果在(pojo 或者 entity)没有对应的类,我们就可以用Map来进行返回。比如,查询特定的字段,连表查询,统计等等
			例如:
			    public Map<String,Object> queryEmployeeMap(Integer id);
		 -->
		select * from employee where id = #{id}
	</select>
    
    
    <!-- 示例2:查询多条记录封装为一个Map -->
    <!-- 这里的resultType属性值写的是 com.xxx.mybatis.entity.Employee 类的别名,需要在全局配置文件配一下,不配就写全类名 -->
    <select id="getEmpByLastNameLikeReturnMap" parameterType="java.lang.String" resultType="employee">
		<!-- 
			如果想将返回的多条记录也是一个Map resultType写的依然是你想让Mybatis把查出来的记录封装成为的类型。 写的是Map集合里元素的类型
			所以这里写 employee 的意思是说我想将查询出来的结果封装成employee, 然后写一个注解,让Mybatis用查询出来的 employee 去构建符合规则的Map
			
			有时候想查询的结果在(pojo|entity)没有对应的类,我们就可以用Map来进行返回。比如,查询特定的字段,连表查询,统计等等
			例如:
			    @MapKey("id") //告诉Mybatis封装这个Map的时候 key 用记录的哪个属性 ;   当然你想用lastName作为key也行,但是需要将返回值Map<Integer,Employee>改为Map<String,Employee>
				public Map<Integer,Employee> getEmpByLastNameLikeReturnMap(String lastName);
		 -->
		select * from employee where last_name like #{lastName}
	</select>
    
    <!-- 示例3:查询多条记录封装为一个List<Map<String,Object>>,在绑定的java接口里直接用List接收就行。 -->
    <select id="queryEmployeeMapList" parameterType="java.lang.String" resultType="map">
		select * from employee where last_name like #{lastName}
	</select>
</mapper>

java接口

package com.xxx.mybatis.mapper;

import com.xxx.mybatis.entity.Employee;
// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
    
    // 有时候想查询的结果在(pojo 或者 entity)没有对应的类,我们就可以用Map来进行返回。比如,查询特定的字段,连表查询,统计等等。 写Object 是为了能接收返回回来的所有类型
    public Map<String,Object> queryEmployeeMap(Integer id);
    
    /**
	 *  将查询出来的多条记录封装为一个Map    
	 *  例如:这里将记录的主键(id)作为Map的Key ,  值是记录封装的后的javaBean(Entity)  详细看映射文件
	 *  @MapKey("id") //告诉Mybatis封装这个Map的时候 key 用记录的哪个属性;   当然你想用lastName作为key也行,但是需要将返回值Map<Integer,Employee>改为Map<String,Employee>
	 */
	@MapKey("id") //告诉Mybatis封装这个Map的时候 key 用记录的哪个属性;   当然你想用lastName作为key也行,但是需要将返回值Map<Integer,Employee>改为Map<String,Employee>
	public Map<Integer,Employee> getEmpByLastNameLikeReturnMap(String lastName);
    
    // 查询出来的多条记录也是可以封装为一个List<Map<String,Object>>,在这里直接用List接收就行。
    public List<Map<String,Object>> queryEmployeeMapList(String lastName);
}

测试类

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

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 com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {
    public static void main(String[] args) throws Exception {
        testqueryMap_v1();
        testqueryMap_v2();
    }
    
    
    /**
	 * 查询单条记录封装为一个Map。   
	 * @throws IOException
	 */
	private static void testqueryMap_v1() throws IOException {
		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		try {
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
			
			Map<String, Object> map = employeeMapper.queryEmployeeMap(1);
			System.out.println(map);
		} finally {
			//关闭
			sqlSession.close();
		}
	}
    
    
    /**
	 * 查询多条记录封装为一个Map。  
	 * @throws IOException
	 */
	private static void testqueryMap_v2() throws IOException {
		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		try {
			
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
			
			Map<Integer, Employee> map = employeeMapper.getEmpByLastNameLikeReturnMap("%1%");
			System.out.println(map);
		} finally {
			//关闭
			sqlSession.close();
		}
	}
}

4.9、自定义结果映射规则方式查询

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    
    <!-- 利用resultMap节点自定义封装查询结果(resultMap自定义结果集映射规则) -->
	<resultMap type="employee" id="MyEnp">
		<!-- 
			<resultMap>节点:用于自定义某个javaBean的封装规则,javaBean里的哪个属性对应查询结果的哪个列,由这里来决定
			       type属性:写的是javaBean(entity|pojo)的类型 ; 写全类名 或者 别名
			 	    id 属性:就是为这个映射规则起一个唯一的名字  因为resultMap节点可以写多个
			 	 

			     <id>子节点:表示主键。
			     column属性:表示数据库的哪一列是主键
			   property属性:表示java类型中的哪个属性名
			   (也就是说column属性表示 sql 查询出来的列,property属性表示 javaBena 的属性。就是说查询出来的列 你想要 赋值给javaBean中的哪个属性)
			              
              
			 <result>子节点:表示普通列的映射规则,主键列也可以用<result>来映射;但是用<id>节点Mybatis有自己的优化。 
			     同样的 column 属性表示数据库的哪一列,
			     property 属性表示java类型中的哪个属性名
			     (也就是说column属性表示:sql查询出来的列,property属性表示:javaBena的属性。就是说查询出来的列 你想要 赋值给javaBean中的哪个属性)
		-->
		
		<!-- <id>节点:           表示主键,column属性表示数据库的哪一列,property属性表示java类型中的哪个属性名 -->
		<!-- column属性表示:sql查询出来的列,property属性表示:javaBena的属性。就是说查询出来的列 你想要 赋值给javaBean中的哪个属性 -->
		<id column="id" property="id"/>
        
		<!-- <result>节点:    表示普通列的映射规则,主键列也可以用<result>来映射;但是用<id>节点Mybatis有自己的优化。 同样的 column属性表示数据库的哪一列,property属性表示java类型中的哪个属性名 -->
		<result column="last_name" property="lastName"/>
		<result column="email" property="email"/>
		<result column="gender" property="gender"/>
        
		<!-- 其他不指定的列会自动封装,但还是推荐将其他的列一起全写在这吧,便于在开发中进行检查 -->
	</resultMap>
    
	<select id="resultMapQuery" parameterType="java.lang.Integer" resultMap="MyEnp">
		<!-- 
			前面的查询示例都是用 <select> 节点的 resultType 属性来告诉 MyBatis 将查询的结果封装为 (entity|pojo)或者 map
			resultType 方式会进行的自动封装结果,将查询的结果封装为 (entity|pojo)或者 map。
			resultMap  方式会按照自定义的映射规则来封装结果
			
			使用 resultType 方式会有一个问题:
			就是数据库查询出来的列名需要和(entity | pojo)的属性名一样,如果不一样封装的结果那些不一样的属性将会为null。
			解决方式可以写别名(让查询出来的列名和java类的属性名一样),或者在全局配置文件里面 开启驼峰命名法。
			如果遇到要改java的属性名的时候,你又不能改数据库字段,就不好弄了。
			
			如果用resultMap 方式将会方便一些。他可以自定义结果封装规则。resultMap自定义结果集映射规则
			
			resultMap  和  resultType两者只能用其中一个
		 -->
		 select * from employee where id = #{id}
	</select>
</mapper>

java接口

package com.xxx.mybatis.mapper;

import java.util.Map;
import org.apache.ibatis.annotations.Param;
import com.xxx.mybatis.entity.Employee;
// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
    //利用resultMap自定义封装查询结果(resultMap自定义结果集映射规则)
	public Employee resultMapQuery(Integer id);
}

测试代码

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

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 com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {
    /**
	 * 利用resultMap自定义封装查询结果
	 * @throws Exception
	 */
    public static void main(String[] args) throws Exception {
        //1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		try {
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
			
			Employee emp = employeeMapper.resultMapQuery(1);
			System.out.println(emp);
		} finally {
			//关闭
			sqlSession.close();
		}
    }
}

4.10、关联查询

创建两张有关联关系的表,例如:员工和部门表。

-- 部门表
CREATE TABLE `department` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `department_name` varchar(64) DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;

-- 员工表
CREATE TABLE `employee` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `last_name` varchar(128) DEFAULT NULL,
  `email` varchar(128) DEFAULT NULL,
  `gender` varchar(128) DEFAULT NULL,
  `dep_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_emp_dept` (`dep_id`),
  CONSTRAINT `fk_emp_dept` FOREIGN KEY (`dep_id`) REFERENCES `department` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4;

-- 外键约束要与不要都没关系

-- 插入一些关联的数据
...

创建这两张表对应的实体类(持久化类)

import java.util.List;
public class Department {

	private Integer id;
	private String departmentName;
	
	private List<Employee> emps;//查询部门信息同时获取部门的员工信息用到这个属性,没有这个需求这个属性也可以不写
    // 写上构造器、getter、setter、toString
}
public class Employee {
	
	private Integer id;
	private String lastName;
	private String email;
	private String gender;
    
    //这个属性用于查询单个员工信息的同时查出他的部门信息,没有这个需求这个属性也可以不写
	private Department dept;
    
    // 写上构造器、getter、setter、toString
}
4.10.1、多对一级联属性查询方式
<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 
		示例:查询Employee的同时查询员工对应的部门信息。 
		
		这种联合查询方式叫:级联属性封装结果集
	 -->
	<resultMap type="com.xxx.mybatis.entity.Employee" id="MyDifEmp">
		<!-- 
			<resultMap>节点:用于自定义某个javaBean的封装规则,javaBean里的哪个属性对应查询结果的哪个列,由这里来决定
			       type属性:写的是javaBean(entity|pojo)的类型 ; 写全类名 或者 别名
			 	    id 属性:就是为这个映射规则起一个唯一的名字  因为resultMap节点可以写多个
			 	 

			     <id>子节点:表示主键。
			     column属性:表示数据库的哪一列是主键
			   property属性:表示java类型中的哪个属性名
			   (也就是说column属性表示 sql 查询出来的列,property属性表示 javaBena 的属性。就是说查询出来的列 你想要 赋值给javaBean中的哪个属性)
			              
              
			 <result>子节点:表示普通列的映射规则,主键列也可以用<result>来映射;但是用<id>节点Mybatis有自己的优化。 
			     同样的 column 属性表示数据库的哪一列,
			     property 属性表示java类型中的哪个属性名
			     (也就是说column属性表示:sql查询出来的列,property属性表示:javaBena的属性。就是说查询出来的列 你想要 赋值给javaBean中的哪个属性)
		-->
		<id column="id" property="id"/>
		<result column="last_name" property="lastName"/>
		<result column="gender" property="gender"/>
        
		<!-- dept.id表示: com.xxx.mybatis.entity.Employee 这个类里面的dept属性中的id属性。  -->
		<result column="d_id" property="dept.id"/>
		<result column="department_name" property="dept.departmentName"/>
	</resultMap>
    
    
	<select id="getEmpAndDept" parameterType="java.lang.Integer" resultMap="MyDifEmp">
		<!-- 写resultType是没用的,关联的Department是不会有值的;因为他不知道怎么赋值。所有要用resultMap自定义结果封装规则 -->
		select 
			a.id,
			a.last_name,
			a.email,
			a.gender,
			b.id as d_id,  <!-- 对于重名的列名应该起一个别名,不然 <resultMap> 节点里面描述的结果集封装会不知道是哪一个 -->
			b.department_name 
		from employee a left join department b 
			on a.dep_id = b.id 
		where 
			a.id = #{id}
	</select>
</mapper>

java接口

package com.xxx.mybatis.mapper;

import java.util.List;

import com.xxx.mybatis.entity.Employee;
// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
    //级联属性关联查询  详细看映射文件
	public Employee getEmpAndDept(Integer id);
}

测试代码

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;

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 com.xxx.mybatis.entity.Department;
import com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.DepartmentMapper;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {
	
	public static void main(String[] args) throws Exception {
		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		try {
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
			
			Employee employee = employeeMapper.getEmpAndDept(1);     //级联属性关联查询  详细看映射文件
			System.out.println(employee.getEmail());
			System.out.println(employee.getDept());
		} finally {
			//关闭
			sqlSession.close();
		}
	}
}
4.10.2、多对一嵌套结果集查询方式
<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 
		示例:使用<association>节点来定义关联的单个对象的封装规则; 也叫嵌套结果集
	 -->
	<resultMap type="com.xxx.mybatis.entity.Employee" id="MyDifEmp2">
		<!-- 
		    column  属性表示:sql查询出来的列,
		    property属性表示:javaBena的属性。
		    就是说查询出来的列 你想要 赋值给javaBean中的哪个属 
		-->
		<id column="id" property="id"/>
		<result column="last_name" property="lastName"/>
		<result column="gender" property="gender"/>
		<result column="email" property="email"/>
		
		<!--  
			<association>节点: 可以指定联合(关联)的javaBean对象
			property="dept":  指定哪个属性是联合(关联)的对象
			javaType:          指定这个属性对象的类型[不能省略]
		-->
		<association property="dept" javaType="com.xxx.mybatis.entity.Department">
			<!-- 将查询出来的 d_id 这一列 赋值给  com.xxx.mybatis.entity.Employee 这个类里面的dept属性中的id属性。 -->
			<id column="d_id" property="id"/>
			<!-- 将查询出来的 department_name 这一列 赋值给  com.xxx.mybatis.entity.Employee 这个类里面的dept属性中的departmentName属性。 -->
			<result column="department_name" property="departmentName"/>
			<!-- 查出来的列不要重名,也不要用错列名。 比如:将id列写在关联对象的映射里面,他只会拿第一个。因为d_id才是关联对象的id -->
		</association>
	</resultMap>
    
    
	<select id="findEmpAndDept" parameterType="java.lang.Integer" resultMap="MyDifEmp2">
		<!-- 写resultType是没用的,关联的Department是不会有值的;因为他不知道怎么赋值。所有要用resultMap自定义结果封装规则 -->
		select 
			a.id,
			a.last_name,
			a.email,
			a.gender,
			b.id as d_id,  <!-- 对于重名的列名应该起一个别名,不然 <resultMap> 节点里面描述的结果集封装会不知道是哪一个 -->
			b.department_name 
		from employee a left join department b 
			on a.dep_id = b.id 
		where 
			a.id = #{id}
	</select>
</mapper>

java接口

package com.xxx.mybatis.mapper;

import java.util.List;

import com.xxx.mybatis.entity.Employee;
// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
    //使用<association>节点来关联查询  详细看映射文件
	public Employee findEmpAndDept(Integer id);
}

测试代码

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;

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 com.xxx.mybatis.entity.Department;
import com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.DepartmentMapper;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {
	
	public static void main(String[] args) throws Exception {
		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		try {
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
			
			Employee employee = employeeMapper.findEmpAndDept(1);    //使用<association>节点来关联查询  详细看映射文件
			System.out.println(employee.getEmail());
			System.out.println(employee.getDept());
		} finally {
			//关闭
			sqlSession.close();
		}
	}
}
4.10.3、多对一分步查询方式
<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 
		 示例:使用<association>进行分步查询:
		      操作:1、写关联的Department实体类
		         2、写关联的DepartmentMapper的接口  和  DepartmentMapper映射文件
		         3、在DepartmentMapper映射文件文件里写上根据id查询的sql语句,以及在DepartmentMapper的接口中写上根据id查询的方法
		         4、就像下面写的,在<resultMap>节点中写上子节点<association> 写上property属性 和  select属性  还有  column属性
		            property属性:表示关联类的属性名;
		            select属性:  表示分步查询所调用的方法;就是根据关联id去分步查询关联对象; 写的是 DepartmentMapper的命名空间打点调唯一的sql语句ID。
		                          (例如这个例子中  拿查询 employee 表所得到的 dept_id去调用DepartmentMapper的getDepartmentById方法将结果赋值给Employee类的dept属性)
		            column属性:  表示用哪一列去分步查询
		
		
		       他的执行流程:
				1、先按照员工id查询 employee 信息
				2、根据查询得到的 employee 信息中的dep_id值去 department 表查出对应的 department 信息
				3、department 信息设置到 employee 中;
			所以他会发送2个sql语句
			select * from employee where id=?
			select * from department where id = ?
			
			好处:
				一些复杂难搞的查询就可以用这种方式来进行查询
			
				<association>分步查询还能设置延迟加载,就像hibernate的懒加载一样;
				上面的几种查询方式,我们每次查询Employee对象的时候,都将部门一起查询出来了;我们希望部门信息在我们使用的时候再去查询,这样就能节省一些服务器资源了
				需要在全局配置文件中加上两个设置
				<setting name="lazyLoadingEnabled" value="true"/>        从名字上看,就叫懒加载开启  默认是false,我们将他改为true。新版他的默认值会是true,但如果要用的话还是显式写出来吧,好阅读理解
				<setting name="aggressiveLazyLoading" value="false"/>    开启的话每一个属性都会全部加载出来,关闭的话属性就会按需加载。
		               从测试上来看,aggressiveLazyLoading设为true的话Employee.getEmail()都会全部加载了,部门也查询了。设为false的话调用getEmail()是不会加载部门的。只有当用到部门的时候就会加载
				
	 -->
	 
	 <resultMap type="com.xxx.mybatis.entity.Employee" id="MyEmpByStep">
	 	<!-- 
		    column  属性表示:sql查询出来的列,
		    property属性表示:javaBena的属性。
		    就是说查询出来的列 你想要 赋值给javaBean中的哪个属 
		-->
	 	<id column="id" property="id"/>
	 	<result column="last_name" property="lastName"/>
	 	<result column="email" property="email"/>
	 	<result column="gender" property="gender"/>
	 	<!-- 
	 		association定义关联对象的封装规则
	 		select:表明当前属性是调用select指定的方法查出的结果
	 		column:指定将哪一列的值传给这个方法
	 		
	 		流程:使用select指定的方法(传入column指定的这列参数的值)查出对象,并封装给property指定的属性
	 		
	 		就是说  Employee类中的dept属性 用DepartmentMapper.getDepartmentById()查询得到的结果,用查询到的dep_id列作为getDepartmentById()的参数
	 	 -->
 		<association property="dept" select="com.xxx.mybatis.mapper.DepartmentMapper.getDepartmentById" column="dep_id"></association>
 		
 		<!-- 
		     多列的值传递过去:
		          不管是<association> 还是 <collection> 他的分步查询是可以传多个参数的,就是说column属性是可以传递多个参数;例如column="{key1=column1,key2=column2}"
		      column的意思是把哪一列的值传给 select 属性中规定的方法;
		
			  Mybatis会将多列的值封装为map传递过去; 如:column="{key1=column1,key2=column2}" 这个key写的是select属性中规定的方法的sql语句的参数名
			
			   还有一个属性	fetchType 他用来设置延迟加载的。fetchType="lazy":表示使用延迟加载,fetchType="eager":表示使用立即加载
			    虽然开启了全局延迟加载,我们也可以在这儿用fetchType禁用掉   要想用延迟加载必须在全局配置文件配置上,单靠fetchType="lazy"是不行的
	 	-->
	 	
	 </resultMap>
	 <select id="getEmpAndDeptByIdStep" parameterType="java.lang.Integer" resultMap="MyEmpByStep">
	 	select * from employee where id=#{id}
	 </select>
</mapper>
<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.DepartmentMapper">
    
    <resultMap type="com.xxx.mybatis.entity.Department" id="departmentResult">
		<!-- 
		    column  属性表示:sql查询出来的列,
		    property属性表示:javaBena的属性。
		    就是说查询出来的列 你想要 赋值给javaBean中的哪个属 
		-->
		<id column="id" property="id"/>
		<result column="department_name" property="departmentName"/>
	</resultMap>
    
    <select id="getDepartmentById" parameterType="java.lang.Integer" resultMap="departmentResult">
		select * from department where id = #{id}
	</select>
</mapper>

java接口

package com.xxx.mybatis.mapper;

import java.util.List;

import com.xxx.mybatis.entity.Employee;
// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
	//使用<association>节点来分步查询  详细看映射文件
	public Employee getEmpAndDeptByIdStep(Integer id);
}
import com.xxx.mybatis.entity.Department;

// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface DepartmentMapper {
	// 分步查询,所需要用到的方法,这个方法还需要绑定对应的映射文件和
	public Department getDepartmentById(Integer Id);
}

测试代码

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;

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 com.xxx.mybatis.entity.Department;
import com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.DepartmentMapper;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {
	
	public static void main(String[] args) throws Exception {
		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		try {
			
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
			
			Employee employee = employeeMapper.getEmpAndDeptByIdStep(1);//使用<association>节点来分步查询  详细看映射文件
			System.out.println(employee.getEmail());
			System.out.println(employee.getDept());
		} finally {
			//关闭
			sqlSession.close();
		}
	}
}
4.10.4、一对多嵌套结果集查询方式
<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.DepartmentMapper">
    
    <!--嵌套结果集的方式,使用<collection>标签定义关联的集合类型的属性封装规则  -->
	<resultMap type="com.xxx.mybatis.entity.Department" id="MyDept">
		<!-- 
		    column  属性表示:sql查询出来的列,
		    property属性表示:javaBena的属性。
		    就是说查询出来的列 你想要 赋值给javaBean中的哪个属 
		-->
		<id column="did" property="id"/>
		<result column="department_name" property="departmentName"/>
        
		<!-- 
			<collection>:   定义关联集合类型的属性的封装规则 
			property属性:   com.xxx.mybatis.entity.Department类中的属性(集合 )
			ofType属性:       指定集合里面元素的类型
		-->
		<collection property="emps" ofType="com.xxx.mybatis.entity.Employee">
			<!-- 定义这个集合中元素的封装规则 -->
			<!-- 将查询出来的 eid 这一列 赋值给  com.xxx.mybatis.entity.Department 这个类里面的emps属性集合 中的类型的id属性。 -->
			<id column="eid" property="id"/>
			<result column="last_name" property="lastName"/>
			<result column="email" property="email"/>
			<result column="gender" property="gender"/>
		</collection>
	</resultMap>
    
    <!-- 写resultType是没用的,关联的Employee是不会有值的;因为他不知道怎么赋值。所有要用resultMap自定义结果封装规则 -->
	<select id="getDeptAndEmpById" parameterType="java.lang.Integer" resultMap="MyDept">
		SELECT 
			d.id as did,
			d.department_name,
			e.id as eid,
			e.last_name,
			e.email,
			e.gender
		FROM department d LEFT JOIN employee e 
			ON d.id=e.dep_id
		WHERE 
			d.id=#{id}
	</select>
    
</mapper>

java接口

import com.xxx.mybatis.entity.Department;

// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface DepartmentMapper {

	public Department getDeptAndEmpById(Integer Id);
	
}

测试代码

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;

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 com.xxx.mybatis.entity.Department;
import com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.DepartmentMapper;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {
	
	public static void main(String[] args) throws Exception {
		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		try {
			
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
			System.out.println(departmentMapper.getClass());//可以看到他返回的是一个代理对象
			
			Department department = departmentMapper.getDeptAndEmpById(2);
			System.out.println(department.getDepartmentName());
			System.out.println(department.getEmps());
		} finally {
			//关闭
			sqlSession.close();
		}
	}
}
4.10.5、一对多分步查询方式
<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.DepartmentMapper">
    <!-- 
		使用<collection>进行分步查询:
		      操作:1、写关联的Employee实体类
		         2、写关联的EmployeeMapper的接口  和  EmployeeMapper映射文件
		         3、在EmployeeMapper映射文件文件里写上根据dep_id查询的sql语句,以及在EmployeeMapper的接口中写上根据dep_id查询的方法
		         4、就像下面写的在<resultMap>节点中写上子节点<collection> 写上property属性 和  select属性  还有  column属性
		            property属性: 表示关联类(集合)的属性名;
		            select属性:     表示分步查询所调用的方法   就是根据关联id去分步查询关联对象(集合); 写的是 EmployeeMapper的命名空间打点调唯一的sql语句ID。
		                                                例如这个例子中  拿查询 department 表所得到的 id去调用EmployeeMapper的getEmpByDeptId方法将结果赋值给Department类的emps属性
		            column属性:     表示用哪一列去分步查询;如果需要传递多个列需要这么写column="{key1=column1,key2=column2}"
		
		
		       他的执行流程:
				1、先按照部门id查询部门信息
				2、根据查询得到的部门信息中的id值去员工表查出对应的员工信息
				3、员工信息设置到部门中;
			所以他会发送2个sql语句
			select * from department where id=?
			select * from employee where dep_id=? 
			
			好处:
				一些复杂难搞的查询就可以用这种方式来进行查询
			
				<association>分步查询还能设置延迟加载,就像hibernate的懒加载一样;
				嵌套查询方式,我们每次查询Department对象的时候,都将员工一起查询出来了;我们希望员工信息在我们使用的时候再去查询,这样就能节省一些服务器资源了
				需要在全局配置文件中加上两个设置
				<setting name="lazyLoadingEnabled" value="true"/>        从名字上看,就叫懒加载开启  默认是false,我们将他改为true。新版他的默认值会是true,但如果要用的话还是显式写出来吧,好阅读理解
				<setting name="aggressiveLazyLoading" value="false"/>    开启的话每一个属性都会全部加载出来,关闭的话属性就会按需加载。
		               从测试上来看,aggressiveLazyLoading设为true的话Department.getDepartmentNane()都会全部加载了,员工也查询了。设为false的话调用getDepartmentNane()是不会加载员工的。只有当用到员工的时候就会加载
				
	 -->
	<resultMap type="com.xxx.mybatis.entity.Department" id="MyDeptStep">
		<!-- 
		    column  属性表示:sql查询出来的列,
		    property属性表示:javaBena的属性。
		    就是说查询出来的列 你想要 赋值给javaBean中的哪个属 
		-->
		<id column="id" property="id"/>
		<result column="department_name" property="departmentName"/>
		
		<!-- 
	 		<collection>定义关联 多的一方(集合)的封装规则
	 		select:表明当前属性是调用select指定的方法查出的结果
	 		column:指定将哪一列的值传给这个方法
	 		
	 		流程:使用select指定的方法(传入column指定的这列参数的值)查出对象,并封装给property指定的属性
	 		
	 		就是说  Department类中的emps属性 用EmployeeMapper.getEmpByDeptId()查询得到的结果,用查询到的id列作为getEmpByDeptId()的参数
	 	 -->
		<collection property="emps" select="com.xxx.mybatis.mapper.EmployeeMapper.getEmpByDeptId" column="id" ></collection>
		
		
		<!-- 
		     多列的值传递过去:
		          不管是<association> 还是 <collection> 他的分步查询是可以传多个参数的,就是说column属性是可以传递多个参数;例如column="{key1=column1,key2=column2}"
		      column的意思是把哪一列的值传给 select 属性中规定的方法;
		
			  Mybatis会将多列的值封装为map传递过去; 如:column="{key1=column1,key2=column2}" 这个key写的是select属性中规定的方法的sql语句的参数名
			
			   还有一个属性	fetchType 他用来设置延迟加载的。fetchType="lazy":表示使用延迟加载,fetchType="eager":表示使用立即加载
			    虽然开启了全局延迟加载,我们也可以在这儿用fetchType禁用掉。要想用延迟加载必须在全局配置文件配置上,单靠fetchType="lazy"是不行的
	 	-->
		<!-- <collection property="emps" select="com.xxx.mybatis.mapper.EmployeeMapper.getEmpByDeptId" column="{deptId=id}" fetchType="lazy"></collection> -->
	</resultMap>
	
	<select id="getDeptByIdStep" parameterType="java.lang.Integer" resultMap="MyDeptStep">
		select id,department_name from department where id=#{id}
	</select>
</mapper>

java接口

import com.xxx.mybatis.entity.Department;

// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface DepartmentMapper {

	public Department getDeptByIdStep(Integer Id);
	
}

测试代码

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;

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 com.xxx.mybatis.entity.Department;
import com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.DepartmentMapper;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {
	
	public static void main(String[] args) throws Exception {
		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		try {
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
			System.out.println(departmentMapper.getClass());//可以看到他返回的是一个代理对象
			
			Department department = departmentMapper.getDeptByIdStep(2);
			System.out.println(department.getDepartmentName());
			System.out.println(department.getEmps());
		} finally {
			//关闭
			sqlSession.close();
		}
	}
}
4.10.6、鉴别器方式查询
<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.DepartmentMapper">
    <!-- =======================鉴别器,用的不是太多。============================ -->
	<!-- <discriminator javaType=""></discriminator>
	
		鉴别器:mybatis可以使用<discriminator>判断某列的值,然后根据某列的值改变结果封装行为
		例如:
		封装Employee:
			如果查出的是女生:就把部门信息查询出来,否则不查询;
			如果是男生,把last_name这一列的值赋值给email;
	 -->
	 <resultMap type="com.xxx.mybatis.entity.Employee" id="MyEmpDis">
	 	<id column="id" property="id"/>
	 	<result column="last_name" property="lastName"/>
	 	<result column="email" property="email"/>
	 	<result column="gender" property="gender"/>
	 	<!--
	 		column:指定判定的列名 javaType:列值对应的java类型(可以写别名)。 这里就是说 gender 这一列的值对的java类型是什么
	 	-->
	 	<discriminator column="gender" javaType="string" >
	 		
	 		<!-- resultType还是指定上面的封装的结果类型,也就是说我封装Employee就开始判断了,所以还是写Employee -->
	 		<!--女生  resultType:指定封装的结果类型;不能缺少。或者resultMap不能缺少;两种选其一 -->
	 		<case value="0" resultType="com.xxx.mybatis.entity.Employee">
	 			<association property="dept" select="com.xxx.mybatis.mapper.DepartmentMapper.getDepartmentById" column="dep_id"></association>
	 		</case>
	 		
	 		<!-- resultType还是指定上面的封装的结果类型,也就是说我封装Employee就开始判断了,所以还是写Employee -->
	 		<!--男生 ;如果是男生,把last_name这一列的值赋值给email; -->
	 		<case value="1" resultType="com.xxx.mybatis.entity.Employee">
		 		<id column="id" property="id"/>
			 	<result column="last_name" property="lastName"/>
			 	<!-- 如果是男生,这里改掉他原有的封装的列 -->
			 	<result column="last_name" property="email"/>
			 	<result column="gender" property="gender"/>
	 		</case>
	 	</discriminator>
	 </resultMap>
	 <select id="discriminatorQuery" parameterType="java.lang.Integer" resultMap="MyEmpDis">
	 	select * from employee where id=#{id}
	 </select>
</mapper>

java接口

import com.xxx.mybatis.entity.Employee;

// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
	//使用鉴别器查询
	public Employee discriminatorQuery(Integer id);
	
}

测试代码

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;

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 com.xxx.mybatis.entity.Department;
import com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.DepartmentMapper;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {
	
	public static void main(String[] args) throws Exception {
		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		try {
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
			
			Employee employee = employeeMapper.discriminatorQuery(1);
			System.out.println(employee);
			System.out.println(employee.getDept());
		} finally {
			//关闭
			sqlSession.close();
		}
	}
}

5、动态SQL

动态 SQL 指的是在MyBatis映射文件中动态的拼接SQL语句。

5.1、if 节点的使用

<if>节点可以使用在 <insert>、<update>、<delete> 等等节点里面,可以用来作为动态条件的拼接。

5.1.1、使用 if 节点动态拼接查询条件
<?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">
 
 <!-- 基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定 -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    
    <!-- 查询员工,要求,携带了哪个字段查询条件就带上这个字段的值 -->
	 <select id="getEmpsByConditionIf" parameterType="com.xxx.mybatis.entity.Employee" resultType="com.xxx.mybatis.entity.Employee">
	 	select * from employee
	 	<!-- 
		 	动态的拼接查询时的 and 条件有可能会被拼接出错误的语句;
		 	例如:select * from table_name and column = xxx 或者 select * from table_name column = xxx and ;会有多余的 and 出现。
		 	
		 	解决这个问题的方式有以下几种
		 	1、使用 where 1=1;
		 	   例如:select * from table_name where 1=1 <if test=“OGNL表达式”> and column = xx </if>。 'and' 需要写在 column 前面。

		 	2、使用<where>节点,在拼接sql的时候,他会默认的将拼接sql的第一个 and 给删除掉。 但是如果'and'写在'column'后面就删不掉了;
		 	   例如:select * from table_name <where><if test=“OGNL表达式”> and column = xx </if></where>。

		 	3、使用<trim>节点。
		 	   例如:select * from table_name <trim prefix="where" prefixOverrides="and"> <if test=“OGNL表达式”> and column = xx </if></trim>
		 	4、其他。
		 	推荐用<where>节点。
	 	-->
	 	<where>
		 	<!-- 
			 	test属性:判断表达式(写的是OGNL表达式) 这个OGNL表达式可以取一个值调用方法,数组还能通过索引取值,还能三元运算,或,与运算。还能调用静态方法,静态属性
			 	OGNL表达式的介绍和使用可以在百度上找到。
			 	这是一些例子:
			 	OGNL(Object Graph Navigation Language)对象图导航语言,这是一种强大的表达式语言 ,通过它可以非常方便的来操作对象属性。 类似于我们的 EL,SpEL等
				访问对象属性:         person.name
				调用方法:             person.getName()
				调用静态属性/方法:     @java.lang.Math@PI / @java.util.UUID@randomUUID()
				调用构造方法:          new com.atguigu.bean.Person(‘admin’).name
				运算符:               +,-*,/,%
				逻辑运算符:           in,not in,>,>=,<,<=,==,!=
				注意:                xml中特殊符号如",>,<等这些都需要使用转义字符
		 	-->
		 	<if test="id != null"><!-- 这里的操作是从参数中取值进行判断 -->
		 		and id=#{id}
		 	</if>
		 	
		 	<!-- 遇见特殊符号应该去写转义字符:例如:&&:&amp;&amp;  "":&quot;&quot; -->
		 	<if test="lastName != null &amp;&amp; lastName != &quot;&quot;">
		 		and last_name like #{lastName}
		 	</if>
		 	<!-- 并且(&&)可以用转义字符,也可以用and -->
		 	<if test="email != null and email.trim() != &quot;&quot;">
		 		and email=#{email}
		 	</if> 
		 	<!-- ognl会进行字符串与数字的转换判断  "0"==0 -->
		 	<if test="gender == 0 or gender == 1">
		 	 	and gender=#{gender}
		 	</if>
	 	</where>
	 </select>
</mapper>
5.1.2、使用 if 节点动态的 insert 数据

使用 <if> 节点动态的拼接 insert 语句进行插入数据的操作,前面有介绍。点击查看

5.1.3、使用 if 节点动态的 update 数据

使用 <if> 节点动态的拼接 insert 语句进行插入数据的操作,前面有介绍。点击查看

5.1.4、使用 if 节点判断数据库环境

在 MyBatis 的 xxxMapper 文件中除了可以获取方法传入进来的参数,还有 MyBatis 本身就内置的参数。如:_databaseId,他能得到当前使用的数据库环境

如果在全局配置文件中配置了 <databaseIdProvider>节点,当在environments节点切换不同的数据库环境,这里可以使用MyBatis内置的 _databaseId 参数来判断当前是哪个数据库环境,这样就能够根据不同的数据库执行不同的数据库语句了

<?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">
 
 <!-- 基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定 -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    
	 <select id="getEmpsByConditionIf" parameterType="com.xxx.mybatis.entity.Employee" resultType="com.xxx.mybatis.entity.Employee">
	 	<if test="_databaseId=='mysql'"> <!-- _databaseId是MyBatis内置的参数;mysql是<databaseIdProvider>节点配置的数据库厂商别名 -->
	 	    select column1,column2,column3 from mysql_table_name
	 	</if>
         <if test="_databaseId=='oracle'"> <!-- _databaseId是MyBatis内置的参数;oracle是<databaseIdProvider>节点配置的数据库厂商别名 -->
	 	     select column1,column2,column3 from oracle_table_name
	 	</if>
        <!-- 
	 	     当在全局配置文件的 environments 节点切换不同的数据库环境,
	 	     这里可以使用 MyBatis 内置的 _databaseId 参数来判断当前是哪个数据库环境,
	 	     这样就能够根据不同的数据库执行不同的数据库语句了
        -->
	 </select>
</mapper>
5.1.5、使用 if 节点判断是否有传递参数

在 MyBatis 的 xxxMapper 文件中除了可以获取方法传入进来的参数,还有 MyBatis 本身就内置的参数。如:_parameter,他代表整个参数

只传递一个参数的时候:_parameter就是这个参数本身

传递多个参数的时候:_parameter就是个map;(传递多个参数的时候MyBatis会将这些个参数封装为一个Map,这时使用 _parameter 就是使用整个Map)

<?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">
 
 <!-- 基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定 -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    
	 <select id="getEmpsByConditionIf" parameterType="com.xxx.mybatis.entity.Employee" resultType="com.xxx.mybatis.entity.Employee">
	 	<if test="_databaseId=='mysql'"> <!-- _databaseId是MyBatis内置的参数;mysql是<databaseIdProvider>节点配置的数据库厂商别名 -->
	 	    select column1,column2,column3 from mysql_table_name
            
             <!-- 
                if可以嵌套的使用。
                使用 _parameter 判断整个参数不为null的时候在进行拼接参数;如果不判断会出现 where column1 = null的情况;这样就查不到数据了
                当然在这个案例里只有一个参数(就是Employee对象),可以说_parameter就代表了Employee对象;我们还可以#{_parameter.xxx}方式取值
             -->
            <if test="_parameter != null"> <!-- 一般也不用,在一些特定的场景可能需要用到 -->
                where column1 = #{xxx}
            </if>
            
	 	</if>
         
         <if test="_databaseId=='oracle'"> <!-- _databaseId是MyBatis内置的参数;oracle是<databaseIdProvider>节点配置的数据库厂商别名 -->
	 	     select column1,column2,column3 from oracle_table_name
             
             <!-- 
                if可以嵌套的使用。
                使用 _parameter 判断整个参数不为null的时候在进行拼接参数;如果不判断会出现 where column1 = null的情况;这样就查不到数据了
                当然在这个案例里只有一个参数(就是Employee对象),可以说_parameter就代表了Employee对象;我们还可以#{_parameter.xxx}方式取值
             -->
             <if test="_parameter != null"> 
                where column1 = #{xxx}
             </if>
             
	 	</if>
        <!-- 
	 	     当在全局配置文件的 environments 节点切换不同的数据库环境,
	 	     这里可以使用 MyBatis 内置的 _databaseId 参数来判断当前是哪个数据库环境,
	 	     这样就能够根据不同的数据库执行不同的数据库语句了
        -->
	 </select>
</mapper>

5.2、使用 trim 节点处理多余字符

使用 <trim> 节点他有更强大的处理多余字符的能力,还能根据需求拼接上前缀或者后缀;

比如:

  • 这个 <trim> 节点用在<select>查询中,动态拼接条件不管是前面多出and、or 还是后面多出 and、or 都能删除掉,<where>节点不能删掉后面多出的 and、or <trim> 节点就可以。
  • 这个 <trim> 节点还可以用在<insert>点中,可以利用这个节点拼接上括号和去掉多余的逗号。
  • 这个 <trim> 节点还可以用在<update>点中,可以利用这个节点拼接上 set 关键字和去掉多余的逗号。
  • 还可以用在其他的节点里面,目的就是为了根据需求拼接上前缀或者后缀,还有处理多余字符
<?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">
 
 <!-- 基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定 -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 
		示例:<trim> 方式拼接sql进行查询。
		查询员工,要求,携带了哪个字段查询条件就带上这个字段的值
	 -->
	 <select id="getEmpsByConditionIf" parameterType="com.xxx.mybatis.entity.Employee" resultType="com.xxx.mybatis.entity.Employee">
	 	select * from employee
	 	<!-- 
		 	后面多出的and或者or <where>标签不能解决, 使用 <trim> 就能做到
		 	prefix="":               前缀:trim标签体中是整个字符串拼串 后的结果。(prefix给拼串后的整个字符串加一个前缀 )
		 			
		 	prefixOverrides="":      前缀覆盖: 去掉整个字符串前面多余的字符
		 			
		 	suffix="":               后缀 suffix给拼串后的整个字符串加一个后缀 
		 			
		 	suffixOverrides=""       后缀覆盖:去掉整个字符串后面多余的字符
	 	-->
	 	<trim prefix="where" prefixOverrides="and"><!-- 这里的操作是在前缀加上where,然后将拼接完成后的字符前缀的and给去掉 -->
        <!-- <trim prefix="where" prefixOverrides="and|or"> 这样写表示删除掉拼接完成后的字符最前面的 and 或者 or -->
	 		<if test="id != null"><!-- 这里的操作是从参数中取值进行判断 -->
		 		and id=#{id}
		 	</if>
		 	
		 	<!-- 遇见特殊符号应该去写转义字符:例如:&&:&amp;&amp;  "":&quot;&quot; -->
		 	<if test="lastName != null &amp;&amp; lastName != &quot;&quot;">
		 		and last_name like #{lastName}
		 	</if>
		 	<!-- 并且(&&)可以用转义字符,也可以用and -->
		 	<if test="email != null and email.trim() != &quot;&quot;">
		 		and email=#{email}
		 	</if> 
		 	<!-- ognl会进行字符串与数字的转换判断  "0"==0 -->
		 	<if test="gender == 0 or gender == 1">
		 	 	and gender=#{gender}
		 	</if>
	 	</trim>
	 </select>
</mapper>

insert

<?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">
 
 <!-- 基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定 -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <insert id="insertSelective" parameterType="com.xxx.mybatis.entity.Employee">
        insert into employee
        <trim prefix="(" suffix=")" suffixOverrides=","> <!-- 前缀拼接上'(',后缀拼接上')', 删除掉后面最后一个',' -->
            <if test="lastName != null">last_name, </if>
            <if test="email != null"> email, </if>
            <if test="gender != null"> gender,</if>            
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=","> <!-- 前缀拼接上'values (',后缀拼接上')', 删除掉后面最后一个',' -->
            <if test="lastName != null"> #{lastName,jdbcType=VARCHAR}, </if>
            <if test="email != null"> #{email,jdbcType=VARCHAR},</if>
            <if test="gender != null">#{gender,jdbcType=VARCHAR},</if>
            
        </trim>
    </insert>
</mapper>

update

<?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">
 
 <!-- 基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定 -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <update id="updateByKeySelective" parameterType="com.xxx.mybatis.entity.Employee">
        update employee
        <trim prefix="set" prefixOverrides=","> <!-- 前缀拼接上'set'关键字, 删除掉最前面',' -->
                ,last_name = #{lastName,jdbcType=VARCHAR}
                ,email = #{email,jdbcType=VARCHAR}
                ,gender = #{gender,jdbcType=VARCHAR} <!-- 如果这些需要动态拼接可以使用上 <if> 节点 -->
        </trim>
        where id = #{id}
    </update>
</mapper>

5.3、使用 choose 节点进行分支选择

使用<choose>节点并结合它的(when, otherwise)子节点可以做到拼接条件的分支选择。它类似于 java 中 swtich-case 。(可以拿来当如果否则来用

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <!-- 示例: choose (when, otherwise):分支选择查询。类似于java中swtich-case。 可以拿来当如果否则来用  -->
	<select id="getEmpsByConditionChoose" parameterType="com.xxx.mybatis.entity.Employee" resultType="com.xxx.mybatis.entity.Employee">
	 	select * from employee 
	 	<where>
	 		<!-- 如果带了id就用id查,如果带了lastName就用lastName查;  id和lastName都不为null 也只会进入其中一个(测试来看是进入第一个) -->
	 		<!-- 它类似于 java 中 swtich-case。可以拿来当如果否则来用 -->
            <choose>
	 			<when test="id != null">
	 				id=#{id}
	 			</when>
	 			<when test="lastName != null">
	 				last_name like #{lastName}
	 			</when>
	 			<when test="email != null">
	 				email = #{email}
	 			</when>
	 			<otherwise>
	 				gender = 0
	 			</otherwise>
	 		</choose>
	 	</where>
	 </select>
</mapper>

5.4、set 节点与 if 节点 组合使用实现动态更新

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    <update id="updateEmp" parameterType="com.xxx.mybatis.entity.Employee">
	 	update employee 
	 	<!-- 
	 		<set>标签的使用,一般都是用在更新操作。用<set>标签在字段拼接时会自动去掉后面携带的逗号。和 <where> 节点差不多。
	 		但是如果将逗号写在前面,例如: ,last_name=#{lastName},email=#{email}在拼接完成后会在前面有一个逗号。执行将会报错。另外,还可以用<trim>来操作
	     -->
		<set>
			<if test="lastName!=null"> last_name=#{lastName}, </if>
			<if test="email!=null"> email=#{email}, </if>
			<if test="gender!=null"> gender=#{gender}, </if>
		</set>
		where id=#{id} 
		
		<!--  
			<trim>方式拼接sql:更新拼串。这种方式就不怕 前面逗号还是后面逗号了
			
		update tbl_employee 
		<trim prefix="set" suffixOverrides=",">
			<if test="lastName!=null"> last_name=#{lastName}, </if>
			<if test="email!=null"> email=#{email}, </if>
			<if test="gender!=null"> gender=#{gender} </if>
		</trim>
		where id=#{id}  -->
	 </update>
</mapper>

5.5、foreach 节点,循环地拼接参数

<foreach>节点一般都是用在in查询,或者批量操作等场景。

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
	 <select id="getEmpsByConditionForeach" resultType="com.xxx.mybatis.entity.Employee">
	 	select * from employee where id in
	 	<!--
	 		collection属性: 指定要遍历的集合。
	 		这个参数需要特别注意:
	 			            绑定的java接口是单个集合参数并且没有使用@Param注解的情况下可以写collection, list
	 			            绑定的java接口是多参数的情况下最好是使用@Param注解给他弄一个key;
	 			            所以这个属性值可以写:collection、list、@Param注解的值、是数组的话可以写array
	 			            这个是取值的key,不是别名。不要混淆了!!!更详细的介绍可以查看4.5章节
	 			              
	 		item属性:       将当前遍历出的元素赋值给指定的变量
	 		separator属性:   每个元素之间的分隔符
	 		open属性:       遍历出所有结果拼接一个开始的字符
	 		close属性:       遍历出所有结果拼接一个结束的字符
	 		index属性:       索引。遍历list的时候是index就是索引,item就是当前值; 如果遍历的是map的时候index表示的就是map的key,item就是map的值
	 				      
	 		#{变量名}就能取出变量的值也就是当前遍历出的元素
	 	  -->
	 	<foreach collection="ids" item="item_id" separator="," open="(" close=")">
	 		
	 		#{item_id}
	 	</foreach>
	 </select>
</mapper>

java接口

package com.xxx.mybatis.mapper;

import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.xxx.mybatis.entity.Employee;

// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
    //<foreach>标签的使用, 一般都是用在in查询,或者批量插入等。
	public List<Employee> getEmpsByConditionForeach(@Param("ids")List<Integer> ids);
}

5.6、bind节点的使用

<bind>节点的作用:可以将OGNL表达式的值绑定到一个变量中,方便后面的使用。

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    
	<select id="getEmpsByConditionChoose" parameterType="com.xxx.mybatis.entity.Employee" resultType="com.xxx.mybatis.entity.Employee">
        <!-- 
            使用 <bind> 节点就相当于声明一个变量一样,用于后面需要用到的地方使用。
            在这个例子里从 Employee 对象中取出 lastName 的值,用OGNL表达式拼接上 %% 给到_lastName,后面就可以直接用 _lastName 来拼接SQL语句了。
            
            这里只是举一个使用<bind>节点的例子,模糊查询还是在代码里面拼接吧。这个节点一般也不用
         -->
        <bind name="_lastName" value="'%'+lastName+'%'" /> <!-- 有一个被拼接null的BUG,想办法处理一下 -->
        
        select * from employee 
        <if test="_parameter != null"> <!-- _parameter代表传递进来的整个参数,一般也不用,在一些特定的场景可能需要用到 -->
            where last_name like #{_lastName} 
        </if>
	 </select>
</mapper>

5.7、sql节点的使用

<sql>节点:用于抽取公共的sql部分。例如:抽取表中的字段;抽取一些公用的查询;抽取出来后可以方便后面引用。

编写好<sql>节点的内容后需要根据需求结合<include>节点来使用。(可以理解为使用 <sql> 节点就是在声明变量后面使用<include>节点来使用变量。)

可以使用在<insert>、<select>、<update>等节点里面。一般都常用在<insert>、<select>节点。

<?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">
 <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    
	<!-- 
	 	示例1:<sql>节点:用于抽取公共的sql部分。例如:抽取表中的字段;抽取一些公用的查询;抽取出来后可以方便后面引用 
	 	
	  	1、sql抽取:经常将要查询的列名,或者插入用的列名抽取出来方便引用;利用<include>节点来引用已经抽取的sql:
	  	
	  	2、<include>还可以自定义一些<property>,然后<sql>标签内部就能使用自定义的属性
	  			使用了<include> 节点下面的 <property>,然后在<sql>节点里面取值的正确方式${xxx}, 不能使用#{xxx}这种方式
	  			
	  -->
	 <sql id="publicSqlColumn_v1">
	 	<!-- _databaseId是Mybatis的内置属性   如果当前的数据库是Oracle我们就执行这个SQL。需要在全局配置文件配<databaseIdProvider>节点,详细查看全局配置文件databaseIdProvider章节 -->
  		<if test="_databaseId == 'oracle'">
  			employee_id,last_name,email
  		</if>
  		
  		<!-- _databaseId是Mybatis的内置属性   如果当前的数据库是mysql我们就执行这个SQL。需要在全局配置文件配<databaseIdProvider>节点,详细查看全局配置文件databaseIdProvider章节 -->
  		<if test="_databaseId=='mysql'">
  			last_name,email,gender,dep_id
  		</if>
	 </sql>
	 <select id="testSqlNodeQuery_v1" parameterType="java.lang.String" resultType="com.xxx.mybatis.entity.Employee">
	 	<!-- 引用公用的sql使用<include>节点, 用refid属性写上 <sql>节点的id属性 -->
	 	select <include refid="publicSqlColumn_v1"/> from employee where last_name like #{lastName}
	 </select>
	 
	 
	 
	 <!-- 
	 	示例2:<sql>节点:用于抽取公共的sql部分。例如:抽取表中的字段;抽取一些公用的查询;抽取出来后可以方便后面引用 
	 	在<include>里使用<property>节点可以自定义属性;
	 	使用了<include> 节点下面的 <property>,然后在<sql>节点里面取值的正确方式${xxx}, 不能使用#{xxx}这种方式
	 	这种方式一般也用得不多
	 -->
	 <sql id="publicSqlColumn_v2">
	 	email,${lastNameColumn},${genderColumn}
	 </sql>
	 <select id="testSqlNodeQuery_v2" parameterType="java.lang.String" resultType="com.xxx.mybatis.entity.Employee">
	 	<!-- 引用公用的sql使用<include>节点, 用refid属性写上 <sql>节点的id属性 -->
	 	select 
	 	<include refid="publicSqlColumn_v2"> 
	 		<!-- 相当于这里写一个数据库的列给到name属性,然后<sql>节点用${name属性}来进行引用。 -->
	 		<property name="lastNameColumn" value="last_name"/>   <!-- 这种方式的写法觉得是一种无聊的写法。在一些特殊的公共抽取可能有用吧;这里只是举例子。比如说一些特殊的公共查询 -->
	 		<property name="genderColumn" value="gender"/>
	 	</include>
	 	from employee where last_name like #{lastName}
	 </select>
</mapper>

6、调用存储过程

实际开发中,由于业务的需要的原因经常会写一些存储过程,MyBatis也支持对存储过程的调用,这里将介绍一下使用MyBatis调用Oracle存储过程。

存储过程可以看作是很多SQL语句的一个集合,数据库会编译一次把他保存起来,以后需要执行了再次调用就行了。

代码示例:

  1. 编写一个存储过程

    create or replace procedure hello_test(
        p_start in int,
        p_end in int,
        p_count out int,
        ref_cur out sys_refcursor
    ) as
    begin
    	select count(*) into p_count from table;
    	
    	open ref_cur for select * from (select e.*,rownum as r1 from table e where rownum < p_end ) where r1 >p_start;
    end hello_test;
    
  2. 编写实体类

    import java.util.List;
    
    /**
     * 用于调用存储过程时,传入参数和封装结果
     * 
     * 这里的示例是用一个entity的实体类,作为传入的参数,和封装结果。   也不知道Map行不行。
     */
    public class ProcedureEntity {
    
    	private int start;
    	private int end;
    	private int count;
    	private List<Employee> emps;
        // 写上getter、setter方法;toString() 构造器等按需要来写
    }
    
    public class Employee {
    	private Integer id;
    	private String lastName;
    	private String email;
    	private String gender;
        // 写上getter、setter方法;toString() 构造器等按需要来写
    }
    
  3. 编写 sql 映射文件

    <?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">
     <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
    <mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
    	
    	<!-- 
    	    创建一个oracle存储过程
    		   
    		public void getInfoByProcedure(ProcedureEntity entity);  
    
    		1、使用select标签定义调用存储过程
    		
    		2、要调用存储过程需要在<select>节点配置属性 statementType="CALLABLE", 不写的话他默认是PREPARED,所以需要改一下
    		
    		3、格式:{call procedure_name(params)},一定要写大括号
    		
    		如果有多数据环境还是把 databaseId="oracle" 写上,同时还是在全局配置环境里把<databaseIdProvider type="DB_VENDOR">xxx</databaseIdProvider>写上
    		
    		这里的示例是用一个entity的实体类,作为传入的参数,和封装结果。   也不知道Map行不行。
    	 -->
    	<select id="getInfoByProcedure" statementType="CALLABLE" parameterType="com.xxx.mybatis.entity.ProcedureEntity" >
    		<!-- 
    			解释:
    			    mode=IN  表示存储过程的输入参数; 例如:#{start,mode=IN,jdbcType=INTEGER} 表示 start这个值作为输入参数给到存储过程;
    			    mode=OUT 表示存储过程的输出参数;例如:#{count,mode=OUT,jdbcType=INTEGER} 表示count这个属性作为接收存储过程的输出参数的意思
    			    
    			    
    			    #{emps,mode=OUT,jdbcType=CURSOR,javaType=ResultSet,resultMap=PageEmp}表示emps这个属性作为接收存储过程的游标输出结果,
    			    mode=OUT           表示存储过程的输出参数;
    			    jdbcType=CURSOR    表示oracle的游标,其他数据库应该也能用;
    			    javaType=ResultSet 表示这个游标结果是java中的结果集 
    			    resultMap=PageEmp  表示结果的映射规则;这里表示使用 id="PageEmp" 的<resultMap>节点 作为结果映射规则
    			    
    			    jdbcType=xxx;可以从org.apache.ibatis.type.JdbcType中找到对应的。
    		 -->
            <!-- 输入输出的参数位置和类型要和数据库中存储过程一一对应上 -->
    		{call hello_test(
    			#{start,mode=IN,jdbcType=INTEGER},
    			#{end,mode=IN,jdbcType=INTEGER},
    			#{count,mode=OUT,jdbcType=INTEGER},
    			#{emps,mode=OUT,jdbcType=CURSOR,javaType=ResultSet,resultMap=PageEmp}
    		)}
    	</select>
        <!-- 这个结果映射是为了给存储过程输出的游标结果映射成一个实体类用的 -->
    	<resultMap type="com.atguigu.mybatis.bean.Employee" id="PageEmp">
    		<id column="id" property="id"/>
    		<result column="last_name" property="last_name"/>
    		<result column="email" property="email"/>
    	</resultMap>
    	
    </mapper>
    
  4. 编写 java 接口

    import com.xxx.mybatis.entity.Employee;
    import com.xxx.mybatis.entity.ProcedureEntity;
    
    // 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
    public interface EmployeeMapper {
    
    	//调用存储过程
    	public void getInfoByProcedure(ProcedureEntity entity); 
    }
    
  5. 编写测试代码

    import java.io.InputStream;
    
    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 com.xxx.mybatis.entity.Employee;
    import com.xxx.mybatis.entity.ProcedureEntity;
    import com.xxx.mybatis.mapper.EmployeeMapper;
    
    public class MainTest {
    	
    	public static void main(String[] args)  throws Exception{
    		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
    		String resource = "mybatis/mybatis-config.xml";
    		InputStream inputStream = Resources.getResourceAsStream(resource);
    		
    		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    		
    		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
    		SqlSession sqlSession = sqlSessionFactory.openSession();
    		try{
    			EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
    			ProcedureEntity procedureResult = new ProcedureEntity();
    			procedureResult.setStart(1);
    			procedureResult.setEnd(5);
    			mapper.getInfoByProcedure(procedureResult);
    			
    			System.out.println("总记录数:"+procedureResult.getCount());
    			System.out.println("查出的数据:"+procedureResult.getEmps().size());
    			System.out.println("查出的数据:"+procedureResult.getEmps());
    		}finally{
    			sqlSession.close();
    		}
    	}
    }
    

五、MyBatis缓存

对于任何一个持久化层框架来说缓存一定是要考虑在内的,MyBatis也有自己的缓存机制。缓存的作用就是为了提高系统的运行速度,提升查询效率。

比如一些经常使用,又不经常修改的数据(像省市区信息、地区的邮政编码、医院的科室名称)就很适合使用缓存,就不用经常的去查数据库了,数据库的压力也会小一些。

1、一级缓存

一级缓存(也叫本地缓存): sqlSession级别的缓存。一级缓存是一直开启的,没法关闭;SqlSession级别的一个Map

详细解释:

与数据库同一次会话期间(同一个SqlSession)查询到的数据会放在本地缓存(一级缓存)中。以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库;一个会话(SqlSession),查询一条数据,这个数据就会被放在当前会话(SqlSession)的一级缓存中;只要会话没关,当前会话再用这条数据就可以从一级缓存中拿,而且这个缓存的数据和其他会话是没有关系的;

一级缓存体验(代码示例):

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;

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 com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {
	
	//提高运行速度 提升查询效率
	public static void main(String[] args) throws Exception {
		cacheQuery();
	}
	
	private static void cacheQuery() throws IOException {
		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		try {
			
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
			
			Employee employee01 = employeeMapper.findEmpById(1);
			System.out.println(employee01);
			
			//业务逻辑做了一会,然后又拿一下数据。他只会发送一个sql,说明第二次他并没有在发sql去数据库查询,因为第二次要用的数据跟第一次的一样,所以他就从一级缓存中拿到了 
			Employee employee02 = employeeMapper.findEmpById(1);
			System.out.println(employee02);
			
			//可以看到这两个对象是一样的,说明他第二次并没有新创建对象,也就是并没有用数据库的数据封装新的结果。而是把第一次查询到的数据一看都是查询1号员工的,就直接把之前缓存的数据拿到使用
			System.out.println(employee01 == employee02);
			
		} finally {
			//关闭
			sqlSession.close();
		}
	}
	
}

一级缓存失效情况

  • sqlSession不同。比如说 :多次通过sqlSessionFactory.openSession();获取sqlSession,就算同一个方法内,同样的查询条件都是不能从缓存中获取到数据;因为一个sqlSession拥有他自己的一级缓存,新的sqlSession对象当然是新的一级缓存了,两个一级缓存是不能共用的
  • sqlSession相同,查询条件不同。例如:Employee emp = employeeMapper.findEmpById(1); 经过一些逻辑之后 Employee emp = employeeMapper.findEmpById(3);是没有一级缓存的,这时候就会发送新的sql。(当前一级缓存中还没有这个数据,这时候就会发送新的sql)
  • sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响);前提是sqlSession相同啊,(没试过删改和缓存中不一样的数据缓存会不会失效)
  • sqlSession相同,手动清除了一级缓存(缓存清空);比如说:用了**sqlSession.clearCache();一级缓存就失效了。测试的时候在两次查询的中间用sqlSession.clearCache();**就能看到效果了

2、二级缓存

上面介绍了一级缓存,也称为session级别的缓存,也就是说每一次会话他都对应他自己的一级缓存,这样的话这个一级缓存的范围就是比较小的,只在自己的会话里起作用。

举个例子说明:

假设查询一个部门,第一个会话查询一个1号部门,这时就会将查询到的1号部门的信息放到第一个会话的一级缓存中,一旦这个会话被关闭掉,来了第二个新的会话还是想查询1号部门,这时候还是要去数据库查询,因为这第二个新的会话的一级缓存中并没有1号部门的信息。就算第一个会话没有被关闭,只要是获取的新的会话和第一个会话不是同一个,新的会话的一级缓存也是没有1号部门的数据的。

像部门这样的信息保存之后一般也很少修改,第一个会话查询了1号部门之后就是被关闭了,新的第二个会话也能从缓存中得到这样就是最好的,不用再次查询数据库了;这时候用 MyBatis 的二级缓存就能做到了。

二级缓存

二级缓存他是一个全局范围的一个缓存,基于namespace级别的缓存(namespace对应的就是那些个xxxMapper.xml文件,每一个xml都有一个自己的namespace); 也可以理解为一个namespace对应一个二级缓存;有些资料说二级缓存是Mapper级别的缓存,其实和这里说的也是同一个意思。

二级缓存工作机制

一个会话(SqlSession),查询一条数据,这个数据就会被放在当前会话(SqlSession)的一级缓存中;只要会话没关没清空,当前会话再用这条数据就可以从一级缓存中拿,如果会话(SqlSession)关闭;一级缓存中的数据会被保存到二级缓存中;新的会话(新获取的SqlSession)进行查询信息,就可以参照二级缓存中的内容;

在mybatis的内部缓存他就是一个Map

如果这个 sqlSession 里面既有EmployeeMapper查询出来的Employee对象,又有DepartmentMappe查询出来的Department对象,虽然说二级缓存是全局的,但是这两个对象会被放不同的Map中(或者说放在了同一个Map中,只是key不同。这个原理这里就不介绍了,MyBatis内部有自己的机制处理,有兴趣自己研究),也就是说,不同namespace查出的数据会放在自己对应的缓存中(放在自己对应的map中,或者说放在了同一个Map中,map的 key 不一样而已),因为二级缓存是基于 namespace 的 EmployeeMapper查的数据会放在EmployeeMapper 这个namespace的map下,或者说放在了一个Map里面,这个map的ley和EmployeeMapper的namespace 相对应。

示意图

在这里插入图片描述

二级缓存使用案例

  1. 使用Eclipse或者其他的开发工具创建普通的java工程
  2. 引入依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	
	<modelVersion>4.0.0</modelVersion>
	
	<groupId>com.xxx.mybatisdemo</groupId>
	<artifactId>mybatis-demo10-two-level-cache</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	
	<name>mybatis-demo10-two-level-cache</name>
	
	<packaging>jar</packaging>
	
	<description>mybatis入门</description>
	
	<dependencies>
		<!-- MyBatis -->	
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.4.1</version>
		</dependency>
		
		<!-- 数据库驱动 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.6</version>
			<scope>runtime</scope>
		</dependency>
		
		<!-- 为了方便查看Mybatis的日志,这里引入一下log4j。 还要引入一个log4j.xml的文件  不引入没有日志查看 -->
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.17</version>
		</dependency>
	</dependencies>
	
	<build>
		<plugins>  
	      <plugin>  
	        <groupId>org.apache.maven.plugins</groupId>  
	        <artifactId>maven-compiler-plugin</artifactId>  
	        <configuration>  
	          <source>1.8</source>  
	          <target>1.8</target>  
	        </configuration>  
	      </plugin>  
	    </plugins>
	</build>
	
</project>
  1. 编写持久化类
import java.io.Serializable;

public class Employee implements Serializable{
    private static final long serialVersionUID = 1L;
    private Integer id;
	private String lastName;
	private String email;
	private String gender;
    // 编写构造器、getter、setter、toString
}
  1. 创建编写MyBatis全局配置文件(在src/main/resources路径下创建mybatis文件夹然后在这个文件夹里创建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>
	
	<!--
		1、mybatis可以使用 <properties> 标签来引入外部properties配置文件的内容;
		
		   <properties>有两个属性,这两个属性都是用来引入外部资源的。两个属性选择其中一个就行了。
		   resource属性: 引入类路径下的资源
		   url属性:          引入网络路径或者磁盘路径下的资源
		   
		   引入了  properties 就可以用 ${} 的方式获取值来使用了;例如下面的数据源的写法。

		   所以这里需要在src/main/resources路径下创建dbconfigproperties文件夹,然后在这个dbconfigproperties文件夹里创建dbconfig.properties文件,内容如下
		   jdbc.driver=com.mysql.jdbc.Driver
		   jdbc.url=jdbc:mysql://localhost:3306/mybatis
		   jdbc.username=root
		   jdbc.password=root
	  -->
	<properties resource="dbconfigproperties/dbconfig.properties"></properties>
	
	<settings>
		<!-- 这个设置项的作用是:开启类字段的驼峰命名规则; (
		          数据库里面字段带下划线 ,然后会自动的将字段映射为类中的驼峰命名。如:数据库为user_name,会自动映射为类里面的userName,
		          这样在没有配置数据库字段和类中属性的映射规则下,带下划线的数据库字段就能给类的属性赋值了。就不会出现userName=null的情况了)
		 -->
		<setting name="mapUnderscoreToCamelCase" value="true"/>
		
		<!-- 开启二级缓存,从官方文档来看,他默认就设为了true。但是我们还是应该显式给他写出来吧。 -->
		<setting name="cacheEnabled" value="true"/>
	</settings>
	
	<typeAliases>
		
		<!-- 
		      <package>: 为某个包下的所有类批量起别名; 
			   name属性: 指定包名(为当前包以及下面所有的子包的每一个类都起一个默认别名(类名首字母小写),别名不区分大小写,我在使用的时候发现首字母大小写没关系)
			   
			   name=com.xxx.mybatis.entity就是给这个包,和他的所有子包下面的类起别名。

			   批量起别名的情况下,如果当前包有一个Employee,他的子包又有一个Employee。这是会报错的,别名冲突了。
			   可以使用在对应的类名上用@Alias("别名名字")注解为某个类型指定新的别名;就能避免别名冲突了
		-->
		<package name="com.xxx.mybatis.entity"/>
		
		
	</typeAliases>
	
	
	
	<environments default="development">
	
		<environment id="development">
			<transactionManager type="JDBC" />
			<!-- 数据源 -->
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc.driver}" />
				<property name="url" value="${jdbc.url}" />
				<property name="username" value="${jdbc.username}" />
				<property name="password" value="${jdbc.password}" />
			</dataSource>
		</environment>
		
	</environments>
	
	
	<!-- 
		8、实体类和数据库的映射文件 ;<mappers>将sql映射注册到全局配置中
	-->
	<mappers>
		<!-- 
			<mapper>:注册一个sql映射 
				1、注册配置文件
					resource属性:引用类路径下的mapper映射文件  例如:<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
					
					url属性:         引用网络路径或者磁盘路径下的mapper映射文件  例如:<mapper url="file:///var/mappers/AuthorMapper.xml"/>
					
					
				2、注册接口
					class属性:      引用(注册)接口,写接口的全类名 例如:<mapper class="com.xxx.mybatis.mapper.EmployeeMapperAnnotation"/>
					注意:
					    2-1、有sql映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下;
					    2-2、没有sql映射文件(基于注解的方式),所有的sql都是利用注解写在接口上;
					    	 例如:
					         public interface EmployeeMapperAnnotation {
					         	@Select("select * from tbl_employee where id=#{id}")
					         	public Employee getEmpById(Integer id);
					         }
					                然后
					         EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapperAnnotation.class);
					         Employee empById = mapper.getEmpById(1);
					    
					    
					推荐:
						比较重要的,复杂的Dao接口我们来写sql映射文件
						不重要,简单的Dao接口为了开发快速可以使用注解;
						
						
				3、批量注册: <package name="com.xxx.mybatis.mapper"/>  
				      写包名进行批量注册的方式   用基于注解的方式的自然能找到;但是有sql映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下;
		                                                                          (如果嫌xml和接口放一个包不好看,可以在开发工具上设置让同一个包在视觉上分开,他编译或打包都会在同一个目录下)
		-->
		<!-- 多层包,在这里用斜杠表示,有的地方用点表示,需要注意。 -->
		<mapper resource="mybatis/mapper/EmployeeMapper.xml" />
	</mappers>
</configuration>
  1. 编写映射文件(在src/main/resources路径下创建mybatis文件夹然后在这个文件夹里创建创建mapper文件夹,映射文件就放这个mapper文件夹里)
<?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">
 <!-- namespace:这个例子 命名空间要和 mapper接口的全类名相同    接口的编程方式,所有要写成一样   -->
<mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
 
 	<!--  
		对EmployeeMapper的数据操作 使用二级缓存  (写上<cache></cache>节点就行了,什么属性都不写,他有自己的默认缓存策略。如果想要自定义策略,查看下面的配置。前提需要在全局配置文件开启二级缓存)
	
		eviction:缓存的回收策略:
			• LRU – 最近最少使用的:移除最长时间不被使用的对象。
			• FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
			• SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
			• WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
			• 默认的是 LRU。
			
		flushInterval:缓存刷新间隔;缓存多长时间清空一次,默认不清空,设置一个毫秒值
			
			
		readOnly:是否只读:
			true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
					 
			false:非只读:mybatis觉得获取的数据可能会被修改。mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
				   默认false
					
		size:缓存存放多少元素;
		
		type="":指定自定义缓存的全类名;实现org.apache.ibatis.Cache接口即可;想要自定义二级缓存就写上这个属性,(比如要些整合第三方缓存就要用到他了)
			
		这里的配置的意思是,每6000毫秒按照LRU策略进行情况缓存。(测试的时候用 Thread.sleep(5000); 是能用到缓存的,在Thread.sleep(7000);就用不到缓存了。默认不清空
		
		其实是有个疑问的。如果是 最近最少使用的:移除最长时间不被使用的对象。最近 指的是 6000毫秒内,那最长时间不用,怎样才是最长时间不用呢,6000毫秒内没用过的???
		                                                                     还是说,拿过一次缓存之后这个缓存的时间归0,重新计时,达到6000毫秒不用就删掉???
		                                                                     那如果是先进先出的话是不是就是最先进来的到了6000毫秒就删掉,其他后面进来的没到6000毫秒就不删???
		                                                                     
		                                                                     暂时就这么理解吧。可能也不对,反正测试的时候发现并没有重新归0重新计时的那样子
	-->
	<cache eviction="LRU" flushInterval="6000" readOnly="false" size="1024"></cache>
    <!-- 使用二级缓存  (写上<cache></cache>节点就行了,什么属性都不写,他有自己的默认缓存策略。如果想要自定义策略,查看上面的配置) 前提需要在全局配置文件开启二级缓存 -->
    
    
    <!-- 
     		二级缓存的一些说明:
	  			1)、全局配置文件的<setting name="cacheEnabled" value="true"/>:
	                	设为true:  则是开启二级缓存;
	                	设为false:关闭二级缓存 (一级缓存一直可用的。就是说设为false不影响一级缓存)
	  
	  
	  			2)、之所以能用二级缓存是因为每个<select>标签都默认带有useCache="true"的属性:可以显式的写出来,也可以不写出来。
	  					如果将useCache设为false: useCache="false":就是说当前的<select>不使用二级缓存,一级缓存依然使用(就是说设为false不影响一级缓存。)
	  
	  					在每个<select>标签都默认带有:flushCache="false": 不写他也带有
	  					       如果你显示的将<select>标签的flushCache属性设为了true,flushCache=true;每次查询之后都会清空缓存(一级二级都会清除);缓存是没有被使用的;
	  
	  
	  			3)、每个<insert> <delete> <update>标签都默认带有flushCache="true"的属性:意思是:增删改执行完成后就会清除缓存;(一级二级都会清除),当然清的是自己Mapper的缓存
	  					测试:flushCache="true":一级缓存就清空了;二级也会被清除;
	  
	  
	  			4)、使用sqlSession.clearCache();的话,只是清除当前session的一级缓存;(就是说不影响二级缓存。)
	  
	  
	  			5)、localCacheScope:叫做:本地缓存作用域;( 就是全局配置文件的一个配置 <setting name="localCacheScope" value="SESSION"/>)
	  			                本地缓存作用域:他影响一级缓存mybatis3.3之后有这个配置;有两种取值;默认是:SESSION
	  			             SESSION:     配置为 SESSION 表示当前会话(sqlSession)的所有数据保存在会话(sqlSession)缓存中; 
	  			             STATEMENT:  配置为 STATEMENT 相当于当前会话没有什么缓存了,官网说配置了 STATEMENT 没有数据将会被在当前会话里边进行共享。
	  			                                             配置 STATEMENT也就相当于禁用一级缓存;一般也不会去配置他。
     -->
 
 
 
	<!-- 
		id:                            唯一标识              sql的唯一标识                                    要和 mapper接口的方法名相同
		resultType:             返回值类型,将查询出来的结果封装成什么类型的对象     要和 mapper接口的相同的方法名字的返回值类型相同    写别名或者全类名
		parameterType      参数类型   可以不传,MyBatis会根据TypeHandler自动推断
		#{id}:                      从传递过来的参数中取出id值
	 -->
	<select id="findEmpById" resultType="employee">
		<!-- 查询示例1     简单的单参数查询 -->
		<!-- 
			 在这个示例中:实体类的字段应和数据库中的字段相同才能将查询结果赋值和插入数据库。
	    	 因为没配置实体类字段和数据库字段映射规则,默认就是类中的字段对应数据库的字段。例如:数据库的字段有下划线,类的属性也要有下划线。
	    	 当然可以在查询的时候可以在sql语句中写上字段的别名,让别名和类中的属性名相同的方式
		 -->
		select * from employee where id = #{id}
	</select>

</mapper>
  1. 编写java接口,用于绑定对应的映射文件
import com.xxx.mybatis.entity.Employee;

// 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
public interface EmployeeMapper {
	//映射文件的 sql语句ID要和这里的方法名相同
	public Employee findEmpById(Integer id);
}
  1. 编写测试代码
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;

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 com.xxx.mybatis.entity.Employee;
import com.xxx.mybatis.mapper.EmployeeMapper;

public class MainTest {
	
	//提高运行速度 提升查询效率
	public static void main(String[] args) throws Exception {
		cacheQuery();
	}

	
	
	/**
	 * Mybatis有两级缓存: 一级缓存:(也叫本地缓存)和 二级缓存(也叫全局缓存)
	 * 
	 * 二级缓存:(全局缓存):基于namespace级别的缓存:一个namespace对应一个二级缓存:
	 * 		工作机制:
	 * 		       一个会话(SqlSession),查询一条数据,这个数据就会被放在当前会话(SqlSession)的一级缓存中;只要会话没关没清空,
	 * 		       当前会话再用这条数据就可以从一级缓存中拿,如果会话(SqlSession)关闭;一级缓存中的数据会被保存到二级缓存中;
	 * 		       新的会话(新获取的SqlSession)进行查询信息,就可以参照二级缓存中的内容;
	 *
	 * 		       在mybatis的内部缓存他就是一个Map
	 * 		       
	 * 		       如果这个 sqlSession 里面既有EmployeeMapper查询出来的Employee对象,又有DepartmentMappe查询出来的Department对象,
	 * 		       虽然说二级缓存是全局的,但是这两个对象会被放不同的Map中
	 * 		       (或者说放在了同一个Map中,只是key不同。这个原理这里就不介绍了,MyBatis内部有自己的机制处理,有兴趣自己研究),
	 * 		       也就是说,不同namespace查出的数据会放在自己对应的缓存中(放在自己对应的map中,或者说放在了同一个Map中,map的 key 不一样而已),
	 * 		       因为二级缓存是基于 namespace 的  EmployeeMapper查的数据会放在EmployeeMapper 这个namespace的map下,或者说放在了一个Map里面,
	 * 		       这个map的ley和EmployeeMapper的namespace 相对应。
	 * 
	 * 			
	 * 		使用操作:
	 * 			1)、在去哪句配置文件中开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>
	 * 
	 * 			2)、去mapper.xml中配置使用二级缓存:在mapper文件中写上这个节点<cache></cache>;(直接写这个节点就行了,他有自己的默认缓存策略,想要自定义缓存策略,查看mapper文件详解)
	 *  			哪个mapper.xml写了<cache></cache>哪个mapper才有二级缓存。就像EmployeeMapper写了<cache></cache> EmployeeMapper 就能用二级缓存; 
	 *              Department没写<cache></cache>DepartmentMapper 就不能使用二级缓存
	 * 				
	 * 			3)、我们的POJO需要实现序列化接口(因为如果这个缓存安全期间 人家缓存技术要求 会用序列化和反序列化技术)
	 * 	
	 * 			效果:数据会从二级缓存中获取
	 * 			注意:
	 * 				查出的数据都会被默认先放在一级缓存中。只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中。
	 * 				(如果不关或者没提交,二级缓存是没有的数据的,不同的SqlSession进行查询依旧是发送sql到数据库,因为二级缓存找不到,自己又是新的SqlSession,新的一级缓存)
	 * 				
	 * 
	 * 		二级缓存的一些说明:
	 * 			1)、全局配置文件的<setting name="cacheEnabled" value="true"/>:
	 *               设为true:  则是开启二级缓存;
	 *               设为false:关闭二级缓存 (一级缓存一直可用的。就是说设为false不影响一级缓存)
	 * 
	 * 
	 * 			2)、之所以能用二级缓存是因为每个<select>标签都默认带有useCache="true"的属性:可以显式的写出来,也可以不写出来。
	 * 					如果将useCache设为false: useCache="false":就是说当前的<select>不使用二级缓存,一级缓存依然使用(就是说设为false不影响一级缓存。)
	 * 
	 * 					在每个<select>标签都默认带有:flushCache="false": 不写他也带有
	 * 					       如果你显示的将<select>标签的flushCache属性设为了true,flushCache=true;每次查询之后都会清空缓存(一级二级都会清除);缓存是没有被使用的;
	 * 
	 * 
	 * 			3)、每个<insert> <delete> <update>标签都默认带有flushCache="true"的属性:意思是:增删改执行完成后就会清除缓存;(一级二级都会清除),当然清的是自己Mapper的缓存
	 * 					测试:flushCache="true":一级缓存就清空了;二级也会被清除;
	 * 
	 * 
	 * 			4)、使用sqlSession.clearCache();的话,只是清除当前session的一级缓存;(就是说不影响二级缓存。)
	 * 
	 * 
	 * 			5)、localCacheScope:叫做:本地缓存作用域;( 就是全局配置文件的一个配置 <setting name="localCacheScope" value="SESSION"/>)
	 * 			                本地缓存作用域:他影响一级缓存mybatis3.3之后有这个配置;有两种取值;默认是:SESSION
	 * 			             SESSION:    配置为 SESSION 表示当前会话(sqlSession)的所有数据保存在会话(sqlSession)缓存中; 
	 * 			             STATEMENT:  配置为 STATEMENT 相当于当前会话没有什么缓存了,官网说配置了 STATEMENT 没有数据将会被在当前会话里边进行共享。
	 * 			                                             配置 STATEMENT也就相当于禁用一级缓存;一般也不会去配置他。
	 * 								
	 * 
	 */
	
	
	private static void cacheQuery() throws IOException {
		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
		String resource = "mybatis/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
		SqlSession sqlSession01 = sqlSessionFactory.openSession();
		SqlSession sqlSession02 = sqlSessionFactory.openSession();
		try {
			
			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
			EmployeeMapper employeeMapper01 = sqlSession01.getMapper(EmployeeMapper.class);
			//通过不同的会话(SqlSession)来还获取Mapper   以达到演示二级缓存起效果的结果
			EmployeeMapper employeeMapper02 = sqlSession02.getMapper(EmployeeMapper.class);
			
			Employee employee01 = employeeMapper01.findEmpById(1);
			System.out.println(employee01);
			
			//关闭   只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中.不然不起效果
			//(如果不关或者没提交,二级缓存是没有的数据的,不同的SqlSession进行查询依旧是发送sql到数据库,因为二级缓存找不到,自己又是新的SqlSession,新的一级缓存)
			sqlSession01.close();
			
			
			Employee employee02 = employeeMapper02.findEmpById(1);
			System.out.println(employee02);
			
			sqlSession02.close();
		} finally {
			
		}
	}
	
}
  1. 写一个日志打印配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
 
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
 
 <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
   <param name="Encoding" value="UTF-8" />
   <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m  (%F:%L) \n" />
   </layout>
 </appender>
 <logger name="java.sql">
   <level value="debug" />
 </logger>
 <logger name="org.apache.ibatis">
   <level value="info" />
 </logger>
 <root>
   <level value="debug" />
   <appender-ref ref="STDOUT" />
 </root>
</log4j:configuration>

2.1、MyBatis整合第三方缓存实现二级缓存

Mybatis只是一个做数据库交互的框架,他对缓存不专业,但是他提供了一个org.apache.ibatis.cache.Cache的接口,这样就可以把缓存的工作部分留给专业的缓存技术来做。

这个org.apache.ibatis.cache.Cache接口,Mybatis也有自己的实现,但是相对简单实现;缓存对于mybatis来说就是一个小Map;所以mybatis提供了一个标准规范接口,开发者在项目过程开发中决定选取哪种第三方缓存技术,那么就用专业的第三方缓存来实现这个接口;比如:现在缓存的数据比较流行存放在redis、ehcache等里面,如果想用这些作为MyBatis应用程序的缓存技术,就可以实现mybatis的cache接口,换掉内置的默认缓存。

打开org.apache.ibatis.cache.Cache接口,查看里面的源码,从方法的名称和方法相应的注释来看,想保存数据到自己整合的第三方缓存里就可以去实现里面的 void putObject(Object key,Object value);方法;想从自己整合的第三方缓存里取出数据就可以去实现里面的Object getObject(Object key)

自己写一个org.apache.ibatis.cache.Cache接口的实现;自己写实现还是挺麻烦的,还可能写不好。Mybatis其实也有帮我们写好了的实现;在 github 上进入MyBatis 开源项目 https://github.com/mybatis;里面有很多 MyBatis 的开源的整合,像spring整合,还有redis缓存的整合、还有ehcache的整合等等。

这里举个整合ehcache的第三方缓存的例子:

ehcache是一个专业而且成熟的第三方缓存,它提供了快速且专业的缓存解决方案,hibernate也是使用ehcache作为二级缓存的,使用起来也非常方便。更多关于 ehcache 的内容不在这里做介绍了。有兴趣的就单独找资料学习。

在 github 上的 MyBatis 开源项目有关于 ehcache 的整合,更多的内容也可以进入到下面图片中的网页进行研究和学习。

在这里插入图片描述

操作步骤介绍:

  1. 引入依赖;(看pom文件)

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    	
    	<modelVersion>4.0.0</modelVersion>
    	
    	<groupId>com.xxx.mybatisdemo</groupId>
    	<artifactId>mybatis-demo11-dsf-cache</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    	
    	<name>mybatis-demo11-dsf-cache</name>
    	
    	<packaging>jar</packaging>
    	
    	<description>mybatis入门</description>
    	
    	<dependencies>
    		<!-- MyBatis -->	
    		<dependency>
    			<groupId>org.mybatis</groupId>
    			<artifactId>mybatis</artifactId>
    			<version>3.4.1</version>
    		</dependency>
    		
    		 
    		<!-- 
    			为了方便查看日志,这里引入一下log4j12。
    				里面包含了log4j 和 slf4j-api的依赖。如果说想用不同的版本,就直接写依赖。不用这里面自带的。可以排除掉,也可以不排除。
    				
    		 	还要引入一个log4j.xml的文件  不引入没有日志查看。不引入不知道会不会打印日志。测试一下就知道了
    		 	
    		 	以后参考这个例子,进行版本升级等操作,缺失了依赖,记得根据实际情况的需要想办法加上。实际情况不在需要缺失的依赖了就不加
    		 -->
    		<dependency>
    		    <groupId>org.slf4j</groupId>
    		    <artifactId>slf4j-log4j12</artifactId>
    		    <version>1.6.2</version>
    		</dependency>
    		
    		<!-- 
    			mybatis-ehcache的整合的依赖;
    			    里面自己带有  ehcache-core 依赖;
    			       而这个自带的 ehcache-core 又默认带有 slf4j-api,上面的 slf4j-log4j12 依赖也带有slf4j-api,可以排除掉,也可以不排除。又或者说想用不同的版本,就直接写依赖。不用这里面自带的
    			
    			以后参考这个例子,进行版本升级等操作,缺失了依赖,记得根据实际情况的需要想办法加上。实际情况不在需要缺失的依赖了就不加
    			想要手动下载jar包可以进入releases  地址是:https://github.com/mybatis/ehcache-cache/releases;
    		-->
    		<dependency>
    		    <groupId>org.mybatis.caches</groupId>
    		    <artifactId>mybatis-ehcache</artifactId>
    		    <version>1.1.0</version>
    		</dependency>
    		
    		
    		<!-- 数据库驱动 -->
    		<dependency>
    			<groupId>mysql</groupId>
    			<artifactId>mysql-connector-java</artifactId>
    			<version>5.1.6</version>
    			<scope>runtime</scope>
    		</dependency>
    		
    	</dependencies>
    	
    	<build>
    		<plugins>  
    	      <plugin>  
    	        <groupId>org.apache.maven.plugins</groupId>  
    	        <artifactId>maven-compiler-plugin</artifactId>  
    	        <configuration>  
    	          <source>1.8</source>  
    	          <target>1.8</target>  
    	        </configuration>  
    	      </plugin>  
    	    </plugins>
    	</build>
    	
    </project>
    
  2. 在需要用到二级缓存的xxxmapper.xml文件里面写上<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

    • type的意思是指定一个自定义的 Cache 接口实现类,这里的示例是MyBatis整合 Ehcache ,所以 MyBatis 已经帮我们写好了。如果是自己写的缓存代码,就在这个type里面写上自己 写Cache接口实现类的全类名。
    • 还可以写<cache-ref namespace="com.xxx.mybatis.mapper.EmployeeMapper"/> 这个叫引用缓存 ,填写的namespace属性值就是指定当前的mapper所用的缓存和哪个名称空间下的缓存一样。(也就是说当前xxxMapper.xml所用的缓存和其他某个xxxMapper.xml的缓存是一样的可以用这个方式指定。一般不要这样写,就算相同的缓存配置多写一份也没什么。

用这个cache的时候动态的改变运行时ehcache的一些参数(用的是Ehcache作为第三方缓存,可以通过<property> 动态的改变运行时Ehacahe参数。其他缓存不知道)
<cache type="org.mybatis.caches.ehcache.EhcacheCache">
<property name="timeToIdleSeconds" value="3600"> <!-- 在这里边动态的改变运行参数 -->
<property name="timeToLiveSeconds" value="3600">
<property name="maxEntriesLocalHeap" value="1000">
<property name="maxEntriesLocalDisk" value="10000000">
<property name="memoryStoreEvictionPolicy" value="LUR">
</cache>

  1. 这里用的是Ehcache做第三方缓存,需要写一个encache.xml的缓存配置文件。内容参照官网写。mybatis教程内不进行详细研究encache

    <!-- 这里简单举个例子 -->
    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"
     updateCheck="false"
     >
     <!--  updateCheck="false" 关掉检查更新,不然会出现time out的异常 -->
     
     <!-- 磁盘保存路径 -->
     <diskStore path="D:\44\ehcache" />
     
     <defaultCache 
       maxElementsInMemory="10000"   
       maxElementsOnDisk="10000000"
       eternal="false" 
       overflowToDisk="true" 
       timeToIdleSeconds="120"
       timeToLiveSeconds="120" 
       diskExpiryThreadIntervalSeconds="120"
       memoryStoreEvictionPolicy="LRU">
     </defaultCache>
    </ehcache>
    <!-- 
    属性说明:
    这些属性说明可能不太准确,仅供参考
    l diskStore:指定数据在磁盘中的存储位置。
    l defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略
     
    以下属性是必须的:
    l maxElementsInMemory - 在内存中缓存的element的最大数目 
    l maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
    l eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
    l overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
     
    以下属性是可选的:
    l timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
    l timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
     diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
    l diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
    l diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
    l memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
     -->
    

六、Spring整合MyBatis

查看另一篇文章。点击查看

七、MyBatis Generator(MyBatis生成)

查看另一篇文章。点击查看

八、Mybatis工作原理说明

我们不仅要对Mybatis熟练应用,还应该要明白Mybatis内部的运行原理,运行原理弄清楚了,对于插件开发非常有帮助,对于研究MyBatis更高级的使用都是很有帮助的。

1、框架分成结构

基于MyBatis的代码内容和运行过程这里对整个框架做了一个分层示意图。

在这里插入图片描述

MyBatis从引导层启动开始,然后到框架支撑层读取每一个配置文件,配置文件里约定了增、删、改、查怎么做,规定了是什么数据源,然后数据处理层就会按照配置文件里设定的配置去做增、删、改、查、事务处理等,然后暴露给开发者的就是接口层。

2、框架工作过程–运行的原理

2.1、SqlSessionFactory的创建过程

将断点打在SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);这行代码上,然后跟踪他的创建过程就能很清楚的知道里面的过程了,这里通过下面的图片来做一下介绍。
在这里插入图片描述

总结

创建 SqlSessionFactory 的过程就是把配置文件(全局配置文件和所有的xxxMapper.xml映射文件)的信息解析并保存在 Configuration 对象中,这个Configuration对象中封装了所有配置文件的详细信息,最后返回包含了 Configuration 的 DefaultSqlSessionFactory 对象

2.2、获取 SqlSession 的过程

将断点打在SqlSession sqlSession = sqlSessionFactory.openSession();这行代码上,然后跟踪他的创建过程就能很清楚的知道里面的过程了,这里通过下面的图片来做一下介绍。

在这里插入图片描述

可以看到获取的 SqlSession 实际上得到的是它的实现类 DefaultSqlSession。他里面包含了 Executor 和 Configuration。关键点就是这里它创建了执行器(Executor)

2.3、获取xxxMapper接口的代理对象的过程

将断点打在XxxMapper xxxMapper = sqlSession.getMapper(XxxMapper.class);这行代码上,然后跟踪他的创建过程就能很清楚的知道里面的过程了,这里通过下面的图片来做一下介绍。

在这里插入图片描述

2.4、通过xxxMapper代理对象调用查询方法过程

将断点打在 MapperProxy 类里面的 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 这行代码上,然后跟踪方法的执行就能知道它的运行过程。因为得到的xxxMapper就是一个代理对象,通过 xxxMapper 调用 findxxx方法,还是queryxxx方法,还是其他的什么方法,实际上都是执行 MapperProxy 类里面的 invoke(…)方法

在这里插入图片描述

在这里插入图片描述

* 总结:
	 * 	1、根据配置文件(全局,sql映射)初始化出Configuration对象
	 * 
	 * 	2、创建一个DefaultSqlSession对象,他里面包含Configuration以及Executor(根据全局配置文件中的defaultExecutorType创建出对应的Executor)
	 * 		
	 *  3、DefaultSqlSession.getMapper():拿到Mapper接口对应的MapperProxy;
	 *  
	 *  4、MapperProxy里面有(DefaultSqlSession);
	 *  
	 *  5、执行增删改查方法:
	 *  		1)、调用DefaultSqlSession的增删改查(Executor);
	 *  		2)、会创建一个StatementHandler对象。
	 *  			(同时也会创建出ParameterHandler和ResultSetHandler)
	 *  		3)、调用StatementHandler预编译参数以及设置参数值;
	 *  			使用ParameterHandler来给sql设置参数
	 *  		4)、调用StatementHandler的增删改查方法;
	 *  		5)、ResultSetHandler封装结果
	 *  注意:
	 *  	四大对象每个创建的时候都有一个interceptorChain.pluginAll(parameterHandler);

九、插件

经过对源代码的研究可以知道在 MyBatis 工作的过程中会用到关键的 4 个对象来进行增、删、改、查操作,这4个对象分别为Executor(执行器)、ParameterHandler(参数处理器)、ResultSetHandler(结果集处理器)、 StatementHandler(语句处理器)。而且在四大对象的创建过程中,都会有插件进行介入,都会有interceptorChain.pluginAll(四大对象之一)这一行代码,这一行代码就是利用动态代理机制一层层的包装目标对象,从而实现在目标对象执行目标方法之前进行拦截的效果,这样就能在目标方法执行的前面或者后面执行自定义的逻辑。

1、插件原理

  1. 在四大对象创建的时候,这几个对象都不是直接返回的,而是执行了interceptorChain.pluginAll(四大对象之一)这一行代码后返回的。源码如下所示

    // 可以看到这个方法获取到所有的Interceptor(拦截器)(插件需要实现的接口); 调用interceptor.plugin(target);返回target包装后的对象
    public Object pluginAll(Object target) {
        // 获取到所有的Interceptor(拦截器)(插件需要实现的接口),
        // 只要在全局配置文件中配置了<plugins><plugin 属性="xxx">...</plugin></plugins> 这里就能获取到所有的Interceptor
        for (Interceptor interceptor : interceptors) {
            //调用interceptor.plugin(target);返回target包装后的对象,就是目标对象的代理对象;(就是四大对象的其中之一的代理对象,具体是哪个,这样看插件类中的@Intercepts注解)
            target = interceptor.plugin(target);
        }
        return target;
    }
    
  2. 四大对象在创建的时候,如果在全局配置文件配置了Interceptor(拦截器)那么都会执行target = interceptor.plugin(target),既然是这样那就可以自定义一个实现了Interceptor(拦截器)接口的类,在这个类中需要重写plugin(Object target)方法,这时就可以在这个方法中为目标对象创建一个代理对象,代理对象就可以拦截到四大对象的每一个执行;就像是 Spring 的AOP(面向切面)

2、插件代码示例

  1. 编写一个类然后让他实现 org.apache.ibatis.plugin.Interceptor接口

    import java.util.Properties;
    
    import org.apache.ibatis.executor.statement.StatementHandler;
    import org.apache.ibatis.plugin.Interceptor;
    import org.apache.ibatis.plugin.Intercepts;
    import org.apache.ibatis.plugin.Invocation;
    import org.apache.ibatis.plugin.Plugin;
    import org.apache.ibatis.plugin.Signature;
    import org.apache.ibatis.reflection.MetaObject;
    import org.apache.ibatis.reflection.SystemMetaObject;
    
    
    /**
     * 四大对象指的是 Executor ParameterHandler ResultSetHandler StatementHandler
     * 
     * • Executor         对象中可以拦截的方法包括 (update, query, flushStatements, commit, rollback,getTransaction, close, isClosed)
     * • ParameterHandler 对象中可以拦截的方法包括 (getParameterObject, setParameters)
     * • ResultSetHandler 对象中可以拦截的方法包括 (handleResultSets, handleOutputParameters)
     * • StatementHandler 对象中可以拦截的方法包括 (prepare, parameterize, batch, update, query)
     * 
     * 
     * @Intercepts注解的作用是完成插件签名:
     *		告诉MyBatis当前插件用来拦截四大对象中的哪个对象的哪个方法;
     *		例如:这里的意思是  拦截 StatementHandler 对象的 带有java.sql.Statement参数的 parameterize方法;因为可能有重载,所以mybatis设置了args参数。
     */
    @Intercepts(
    		{	//是一个数组,按道理来说是可以写多个的
    			@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
    		})
    public class MyPluginOne implements Interceptor{
    
    	/**
    	 * 执行目标方法的时候其实是执行这里的public Object intercept(Invocation invocation) 方法
    	 * intercept:拦截:
    	 * 		拦截目标对象的目标方法的执行;
    	 *          在这个示例里的意思是说 拦截StatementHandler对象的parameterize方法。在parameterize方法执行之前做一些操作
    	 */
    	@Override
    	public Object intercept(Invocation invocation) throws Throwable {
    		// TODO Auto-generated method stub
    		System.out.println("MyFirstPlugin...intercept:"+invocation.getMethod());
    		
    		//动态的改变一下sql运行的参数:以前1号员工,实际从数据库查询3号员工
    		Object target = invocation.getTarget();
    		System.out.println("当前拦截到的对象:"+target);
    		
    		//拿到:StatementHandler==>ParameterHandler===>parameterObject
    		//拿到target的元数据
    		MetaObject metaObject = SystemMetaObject.forObject(target);
    		Object value = metaObject.getValue("parameterHandler.parameterObject");
    		System.out.println("原来sql语句用的参数是:"+value);
    		//修改完sql语句要用的参数
    		metaObject.setValue("parameterHandler.parameterObject", 11);
    		
    		
    		//执行目标方法。在这个例子里就是说:执行一下StatementHandler对象的parameterize方法。
    		Object proceed = invocation.proceed();
    		
    		
    		//返回执行后的返回值
    		return proceed;
    	}
    
    	/**
    	 * 其实就是说创建四大对象的时候都会走 这里的public Object plugin(Object target) 方法,他会创建目标对象的代理对象。
    	 * plugin(...):
    	 * 		包装目标对象的:包装:为目标对象创建一个代理对象。就是说:为我们的目标对象(@Intercepts注解中所要拦截的对象)创建代理对象。
    	 *      如果不是@Intercepts注解中所要拦截的对象,就会按照原对象返回。因为如果在全局配置文件配置了插件,在创建4大对象时都会执行这个方法
    	 */
    	@Override
    	public Object plugin(Object target) {
    		// TODO Auto-generated method stub
    		System.out.println("MyFirstPlugin...plugin:mybatis将要包装的对象"+target);
    		
    		//我们可以借助Plugin的wrap方法来使用当前Interceptor包装我们目标对象,就是利用Mybatis的Plugin的wrap方法为我们的目标对象(@Intercepts注解中所要拦截的对象)创建代理对象
    		// 可以debug进入这个方法里面,可以看得到它如果不是@Intercepts注解中所要拦截的对象,就会按照原对象返回。
            Object wrap = Plugin.wrap(target, this);
    		
    		
    		//返回为当前target创建的动态代理
    		return wrap;
    	}
    
    	/**
    	 * setProperties:
    	 * 		将插件注册时 的property属性设置进来
    	 */
    	@Override
    	public void setProperties(Properties properties) {
    		// TODO Auto-generated method stub
    		System.out.println("插件配置的信息:"+properties);
    	}
    
    	/**
    	 * 控制台打印日志解析:
    	 * 
    	 * --插件中 setProperties(Properties properties)方法打印的
    	 * 插件配置的信息:{attrName=attrName, attrValue=attrValue}   
    	 * 
    	 * --这个是执行sqlSessionFactory.openSession();方法打印出来的。
    	 * MyFirstPlugin...plugin:mybatis将要包装的对象org.apache.ibatis.executor.CachingExecutor@73f792cf
    	 * 
    	 * --这个是sqlSession.getMapper(xxx.class);出来的
    	 * class com.sun.proxy.$Proxy3
    	 * 
    	 * --这个是创建ParameterHandler、ResultSetHandler、StatementHandler对象时打印出来的。。从这里也可以看出创建4大对象都会走public Object plugin(Object target)方法
    	 * MyFirstPlugin...plugin:mybatis将要包装的对象org.apache.ibatis.scripting.defaults.DefaultParameterHandler@2cb4c3ab
    	 * MyFirstPlugin...plugin:mybatis将要包装的对象org.apache.ibatis.executor.resultset.DefaultResultSetHandler@153f5a29
    	 * MyFirstPlugin...plugin:mybatis将要包装的对象org.apache.ibatis.executor.statement.RoutingStatementHandler@69d9c55
    	 * 
    	 * 
    	 * DEBUG 11-15 20:31:40,792 ==>  Preparing: select * from employee where id = ?   (BaseJdbcLogger.java:145) 
    	 * 
    	 * --预编译时设置参数前 也就是StatementHandler这个对象的parameterize(Statement)执行时会走到 public Object intercept(Invocation invocation) 方法里
    	 * MyFirstPlugin...intercept:public abstract void org.apache.ibatis.executor.statement.StatementHandler.parameterize(java.sql.Statement) throws java.sql.SQLException
    	 * 当前拦截到的对象:org.apache.ibatis.executor.statement.RoutingStatementHandler@69d9c55
    	 * sql语句用的参数是:1
    	 * 
    	 * DEBUG 11-15 20:31:40,899 ==> Parameters: 11(Integer)  (BaseJdbcLogger.java:145) 
    	 * DEBUG 11-15 20:31:41,143 <==      Total: 0  (BaseJdbcLogger.java:145) 
    	 * null
    	 */
    }
    
    
  2. 在全局配置文件里配置<plugins><plugin 属性="xxx">...</plugin></plugins>(也就是配置插件)

    <?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>
        <!-- 按照实际需要,配置其他节点 -->
        
    	<plugins>
            <!-- 配置插件,也就是配置实现 org.apache.ibatis.plugin.Interceptor 接口的类,可以配置多个 -->
    		<plugin interceptor="com.xxx.mybatis.plugin.MyPluginOne">
                <!-- 配置插件的需要的属性,插件类里面会有方法被执行这里的属性就能被插件类得到 -->
    			<property name="attrName" value="attrName"/>
    			<property name="attrValue" value="attrValue"/>
    		</plugin>
    	</plugins>
    
    	<!-- environments表示环境的意思 -->
    	<environments default="development">
    		<environment id="development">
    			<transactionManager type="JDBC" />
    			<!-- 数据源 -->
    			<dataSource type="POOLED">
    				<property name="driver" value="com.mysql.jdbc.Driver" />
    				<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
    				<property name="username" value="root" />
    				<property name="password" value="root" />
    			</dataSource>
    		</environment>
    	</environments>
        
        <!-- 按照实际需要,配置其他节点 -->
    	
    	<!-- 实体类和数据库的映射文件 -->
    	<mappers>
    		<!-- 多层包,在这里用斜杠表示,有的地方用点表示,需要注意。 -->
    		<mapper resource="mybatis/mapper/EmployeeMapper.xml" />
    	</mappers>
    </configuration>
    

上面介绍的是单个插件的情况。

注意

  • 如果是多个插件,然后让多个插件同时拦截相同的对象相同的方法,它会产生多层代理,因为在源码中它是一个循环创建的调用interceptor.plugin(target) 方法。给target创建代理后又赋值给target,循环的做法呢就会代理对象在次创建代理。就会形成多层代理
  • 顺序:创建动态代理的时候,是按照插件配置顺序创建层层代理对象。但是,执行目标方法的之后,按照逆向顺序执行。因为最后一个插件对象创建代理对象是在最外层

在这里插入图片描述

3、分页插件

这里将介绍一个叫 PageHelper 的第三方分页插件,他是一个优秀且强大好用的分页插件,MyBatis要做分页功能用这个插件来做将会比较简单好用。这个可以百度搜索PageHelper,然后进入一个叫https://github.com/pagehelper/Mybatis-PageHelper的网页,可以看到这个网站是一个github的网站,这个分页插件的源码和使用介绍这里面都有。在这里插入图片描述

使用操作步骤:

  1. 引入依赖

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    	
    	<modelVersion>4.0.0</modelVersion>
    	
    	<groupId>com.xxx.mybatisdemo</groupId>
    	<artifactId>mybatis-demo16-pagehelper</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    	
    	<name>mybatis-demo16-pagehelper</name>
    	
    	<packaging>jar</packaging>
    	
    	<description>mybatis入门</description>
    	
    	<dependencies>
    		<!-- MyBatis -->	
    		<dependency>
    			<groupId>org.mybatis</groupId>
    			<artifactId>mybatis</artifactId>
    			<version>3.4.1</version>
    		</dependency>
    		
    		<!-- Mybatis分页插件 -->
    		<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
    		<dependency>
    			<groupId>com.github.pagehelper</groupId>
    			<artifactId>pagehelper</artifactId>
    			<version>5.2.0</version>
    		</dependency>
    		
    		
    		<!-- 数据库驱动 -->
    		<dependency>
    			<groupId>mysql</groupId>
    			<artifactId>mysql-connector-java</artifactId>
    			<version>5.1.6</version>
    			<scope>runtime</scope>
    		</dependency>
    		
    		<!-- 为了方便查看Mybatis的日志,这里引入一下log4j。 还要引入一个log4j.xml的文件  不引入没有日志查看 -->
    		<dependency>
    			<groupId>log4j</groupId>
    			<artifactId>log4j</artifactId>
    			<version>1.2.17</version>
    		</dependency>
    	</dependencies>
    	
    	<build>
    		<plugins>  
    	      <plugin>  
    	        <groupId>org.apache.maven.plugins</groupId>  
    	        <artifactId>maven-compiler-plugin</artifactId>  
    	        <configuration>  
    	          <source>1.8</source>  
    	          <target>1.8</target>  
    	        </configuration>  
    	      </plugin>  
    	    </plugins>
    	</build>
    	
    </project>
    
  2. 编写持久化 Java 类。也就是编写一个和数据库表中字段对应的java类。也叫实体类。

    public class Employee {
        private Integer id;
    	private String lastName;
    	private String email;
    	private String gender;
        // 写上getter、setter、toString 方法
    }
    
  3. 创建 Mybatis 全局配置文件

    <?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>
    	<!-- 配置分页插件 -->
    	<plugins>
    		<plugin interceptor="com.github.pagehelper.PageInterceptor">
    			<!-- 这个插件有几个属性,这里就不配了。可以从源码看看。有配数据库方言什么的;也可以去github的文档上看 -->
    			<!-- <property name="" value=""/> -->
    		</plugin>
    	</plugins>
    
    	<!-- environments表示环境的意思 -->
    	<environments default="development">
    		<environment id="development">
    			<transactionManager type="JDBC" />
    			<!-- 数据源 -->
    			<dataSource type="POOLED">
    				<property name="driver" value="com.mysql.jdbc.Driver" />
    				<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
    				<property name="username" value="root" />
    				<property name="password" value="root" />
    			</dataSource>
    		</environment>
    	</environments>
    	
    	<!-- 实体类和数据库的映射文件 -->
    	<mappers>
    		<!-- 多层包,在这里用斜杠表示,有的地方用点表示,需要注意。 -->
    		<mapper resource="mybatis/mapper/EmployeeMapper.xml" />
    	</mappers>
    </configuration>
    
  4. 编写映射文件

    <?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">
      <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
    <mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
     
    	<select id="findEmpById" resultType="com.xxx.mybatis.entity.Employee">
    		select * from employee where last_name like #{lastName}
    	</select>
    </mapper>
    
  5. 编写 java 接口

    import com.xxx.mybatis.entity.Employee;
    
    // 基于接口式编程,映射文件的 namespace 属性值(命名空间)要和这里的全类名相同,让当前这个接口和映射文件绑定。
    public interface EmployeeMapper {
    
    	//映射文件的 sql语句ID要和这里的方法名相同
    	public java.util.List<Employee> findEmpById(String lastName);
    }
    
  6. 编写测试代码

    import java.io.InputStream;
    
    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 com.github.pagehelper.PageHelper;
    import com.xxx.mybatis.entity.Employee;
    import com.xxx.mybatis.mapper.EmployeeMapper;
    
    public class MainTest {
    	
    	/**
    	 * MyBatis分页插件的使用:  可以百度搜索PageHelper,然后进入一个叫https://github.com/pagehelper/Mybatis-PageHelper的网页。
    	 *                        点击如何使用分页插件,然后进入一个叫https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md
    	 *                        
    	 *    然后按照网页文档的提示进行操作:
    	 *        1、引入依赖
    	 *        2、在全局配置文件中 配置上插件信息
    	 *            <plugins>
    	 *                <plugin interceptor="com.github.pagehelper.PageInterceptor">
    	 *                    <!-- 这个插件有几个属性,这里就不配了。可以从源码看看。有配数据库方言什么的;也可以去github的文档上看 -->
    	 *                    <!-- <property name="" value=""/> -->
    	 *                </plugin>
    	 *            </plugins>
    	 *        3、如何在代码中使用分页插件。github的文档上介绍了好几种。但是推荐使用PageHelper.startPage(1, 5);就行了。
    	 *           在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能。pageNum:当前页的页码;pageSize:每页显示的条数
    	 *           
    	 *           想要获取一些分页的信息可以像下面的操作:
    	 *               第一种操作:
    	 *               	 //在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能。pageNum:当前页的页码;pageSize:每页显示的条数
    	 *                   Page<Object> page = PageHelper.startPage(1, 5);//设置分页,并接收返回值
    	 *                   
    	 *                   //获取分页信息。当然分页信息不止这么些,想要哪些可以到源码看
    	 *                   System.out.println("当前页码:"+page.getPageNum());
    	 *                   System.out.println("总记录数:"+page.getTotal());
    	 *                   System.out.println("每页的记录数:"+page.getPageSize());
    	 *                   System.out.println("总页码:"+page.getPages());
    	 *               
    	 *               第二种操作:
    	 *                   PageHelper.startPage(1, 5);//设置分页,可以不接收返回值
    	 *                   java.util.List<Employee> employee = employeeMapper.findEmpById("%测试%");
    	 *                   
    	 *                   //将查询结果构建一个PageInfo类实例。 第二个参数就是就是分页导航的意思,可以给你导航到当前页的前面两页,和当前页的后面两页;共5个导航;可以不设置。
    	 *                   PageInfo<Employee> info = new PageInfo<>(list, 5);
    	 *                   
    	 *                   
    	 *                   //获取分页信息。当然分页信息不止这么些,想要哪些可以到源码看
    	 *                   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());
    	 *                   
    	 *                   //这个内容需要设置new PageInfo<>(list, 5);第二个参数。  第二个参数就是就是分页导航的意思,可以给你导航到当前页的前面两页,和当前页的后面两页;共5个导航
    	 *                   System.out.println("连续显示的页码:");
    	 *                   int[] nums = info.getNavigatepageNums();
    	 *                   for (int i = 0; i < nums.length; i++) {
    	 *                   	 System.out.println(nums[i]);
    	 *                   }
    	 */
    	
    	public static void main(String[] args) throws Exception {
    		//1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
    		String resource = "mybatis/mybatis-config.xml";
    		InputStream inputStream = Resources.getResourceAsStream(resource);
    		
    		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    		
    		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
    		SqlSession sqlSession = sqlSessionFactory.openSession();
    		try {
    			/**
    			 * 推荐使用接口式编程操作数据库,不然会有一些问题,比如:sqlSession.selectOne(命名空间,参数) 要写命名空间,而且参数是Object类型,什么都能传;容易出错
    			 * 
    			 * mybatis 提供了一个功能, 接口可以映射文件进行动态绑定。
    			 *     1、让映射文件的命名空间指定为接口的全类名
    			 *     2、在接口里声明的方法名要与映射文件的 sql语句 的id相同。  当调用方法的时候代理对象就知道执行哪个sql了。
    			 * 
    			 */
    			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
    			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
    			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
    			
    			//在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能。pageNum:当前页的页码;pageSize:每页显示的条数
    			PageHelper.startPage(1, 5);
    			
    			java.util.List<Employee> employee = employeeMapper.findEmpById("%测试%");//这有个java多态的特点啊。 这个list返回值其实是com.github.pagehelper.Page。  然后测试过restful的接口发现,返回前端的数据确是父类List,没有Page类的属性
    			System.out.println(employee);
    			/**
    			 *            想要获取一些分页的信息可以像下面的操作:
    			 *               第一种操作:
    			 *                   Page<Object> page = PageHelper.startPage(1, 5);//设置分页,并接收返回值
    			 *                   
    			 *                   //获取分页信息。当然分页信息不止这么些,想要哪些可以到源码看
    			 *                   System.out.println("当前页码:"+page.getPageNum());
    			 *                   System.out.println("总记录数:"+page.getTotal());
    			 *                   System.out.println("每页的记录数:"+page.getPageSize());
    			 *                   System.out.println("总页码:"+page.getPages());
    			 *               
    			 *               第二种操作:
    			 *                   PageHelper.startPage(1, 5);//设置分页,可以不接收返回值
    			 *                   java.util.List<Employee> employee = employeeMapper.findEmpById("%测试%");
    			 *                   
    			 *                   //将查询结果构建一个PageInfo类实例。 第二个参数就是就是分页导航的意思,可以给你导航到当前页的前面两页,和当前页的后面两页;共5个导航;可以不设置。
    			 *                   PageInfo<Employee> info = new PageInfo<>(list, 5);
    			 *                   
    			 *                   
    			 *                   //获取分页信息。当然分页信息不止这么些,想要哪些可以到源码看
    			 *                   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());
    			 *                   
    			 *                   //这个内容需要设置new PageInfo<>(list, 5);第二个参数。  第二个参数就是就是分页导航的意思,可以给你导航到当前页的前面两页,和当前页的后面两页;共5个导航
    			 *                   System.out.println("连续显示的页码:");
    			 *                   int[] nums = info.getNavigatepageNums();
    			 *                   for (int i = 0; i < nums.length; i++) {
    			 *                   	 System.out.println(nums[i]);
    			 *                   }
    			 */               
    			
    		} finally {
    			//关闭
    			sqlSession.close();
    		}
    	}
    }
    
  7. 为了更详细的了解运行过程,可以配置一个log4j.xml文件。这样可以从它的日志信息去了解他的运行过程。

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
     
    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
     
     <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
       <param name="Encoding" value="UTF-8" />
       <layout class="org.apache.log4j.PatternLayout">
        <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m  (%F:%L) \n" />
       </layout>
     </appender>
     <logger name="java.sql">
       <level value="debug" />
     </logger>
     <logger name="org.apache.ibatis">
       <level value="info" />
     </logger>
     <root>
       <level value="debug" />
       <appender-ref ref="STDOUT" />
     </root>
    </log4j:configuration>
    

十、自定义类型处理器示例

在学习源码的时候就可以知道 Executor 在执行增、删、改、查的时候用StatementHandler, StatementHandler执行过程中, sql预编译设置参数用的是ParameterHandler,处理结果用的是ResultSetHandler;他们在设置参数的时候他是调用的是TypeHandler的setParameter(ps,i+1,value,jdbcType),处理结果调用的是 TypeHandler的getResult(re,column)

用Debug跟踪查看的时候可以看到是下面这两个类的方法

ParameterHandler参数处理器的实现类 DefaultParameterHandler的setParameter(ps,i+1,value,jdbcType)方法

ResultSetHandler结果处理器的实现类DefaultResultSetHandler的getResult(re,column)方法,

所以说呢,整个 TypeHandler 它在 MyBatis 执行增、删、改、查过程中就担任了数据库类型和JavaBean类型的映射工作。

这里找一个 StringTypeHandler 的源码看一看在这里插入图片描述

StringTypeHandler继承了BaseTypeHandler<String> 然后BaseTypeHandler 实现了TypeHandler接口。从源码里面的方法来看,其实就是操作原生jdbc的设置参数预编译,和利用原生的jdbc操作结果集。

再找一个LongTypeHandler 里面的内容也是一样的。

在 MyBatis 中继承了BaseTypeHandler<String> 并实现了TypeHandler接口的类有挺多(也就是内置的类型处理器MyBatis定义了很多)

贴图片

这里将特别介绍一下枚举类型的类型处理器

对于枚举类型 MyBatis 也有相应的处理器;EnumOrdinalTypeHandler<E extends Enum<E>> EnumTypeHandler<E extends Enum<E>> 进入他们的源码来看:

  • EnumOrdinalTypeHandler<E extends Enum<E>> 是用枚举的索引作为参数,然后设置值用于插入数据库的;结果处理也是用枚举的索引找到对应的枚举项设置值的
  • EnumTypeHandler<E extends Enum<E>> 是用枚举的name()作为参数,然后设置值用于插入数据库的;结果处理也是用枚举的name()来设置值的

MyBatis 处理枚举类型的代码示例:

  1. 编写实体类

    public class Employee {
    	
    	private Integer id;
    	private String lastName;
    	private String email;
    	private String gender;
        
        private EmpStatusVersionOne empStatusOne; //这个是自己写的枚举类
        // 写上get、set方法 根据需要写上 toString() 构造器等代码
    }
    
  2. 写个枚举类

    public enum EmpStatusVersionOne{
    	//举个例子的啊:XJ:表示休假,ZC:表示正常上班,QJ:表示请假
      	XJ,ZC,QJ
    }
    
  3. 在数据库表中创建empStatus的列,用varchar(11)类型

    alter table table_name add empStatus varchar(11);
    
  4. 编写映射文件

    <?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">
     <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
    <mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
        
    	<insert id="addEmp" parameterType="com.xxx.mybatis.entity.Employee" useGeneratedKeys="true" keyProperty="id">
            <!-- #{empStatusOne}:表示实体类中的枚举属性 -->
            insert into employee (last_name,email,gender,empStatus)values( #{lastName},#{email},#{gender},#{empStatusOne} ) 
        </insert>
    </mapper>
    
  5. 编写测试代码

    import java.io.InputStream;
    
    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 com.xxx.mybatis.customenum.EmpStatusVersionOne;
    import com.xxx.mybatis.entity.Employee;
    import com.xxx.mybatis.mapper.EmployeeMapper;
    
    public class MainTest {
    	
    	public static void main(String[] args) throws Exception {
    		String resource = "mybatis/mybatis-config.xml";
    		InputStream inputStream = Resources.getResourceAsStream(resource);
    		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
    		try{
                EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
                Employee employee = new Employee("test_enum", "enum@atguigu.com","1",EmpStatusVersionOne.ZC);
                employeeMapper.addEmp(employee);
                System.out.println("保存成功"+employee.getId());
                sqlSession.commit();
            }finally{
                sqlSession.close();
            }
    	}
    }
    

    运行测试后会发现 数据库中保存了 枚举项的名字 。在这里插入图片描述

从测试的结果可以了解到:MyBatis在处理枚举对象的时候默认保存的是枚举的名字;也就是说:mybatis在处理枚举对象的时候默认用的是 EnumTypeHandler<E extends Enum<E>> 处理器。

小知识点:

EmpStatus login = EmpStatus.LOGIN;

System.out.println(“枚举的索引:”+login.ordinal());

System.out.println(“枚举的名字:”+login.name());

这些方法是创建枚举默认就带有的。除非想办法改掉。

想要改变他这个默认的枚举处理器,让MyBatis处理枚举类型时改为EnumOrdinalTypeHandler<E extends Enum<E>>可以在全局配置文件做一下配置。

<?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>

	<typeHandlers>
          <!-- 
		      有两个属性:
		       javaType:
		       jdbcType:
		       这个例子里的意思是:如果你处理com.xxx.mybatis.customenum.EmpStatusVersionOne这种类型的时候,就用这里配置的枚举处理器;如果不写javaType属性,全部类型都用这个枚举处理器。
           -->
          <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.xxx.mybatis.customenum.EmpStatusVersionOne"/>
     </typeHandlers>

	<!-- environments表示环境的意思 -->
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<!-- 数据源 -->
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.jdbc.Driver" />
				<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
				<property name="username" value="root" />
				<property name="password" value="root" />
			</dataSource>
		</environment>
	</environments>
	
	<!-- 实体类和数据库的映射文件 -->
	<mappers>
		<!-- 多层包,在这里用斜杠表示,有的地方用点表示,需要注意。 -->
		<mapper resource="mybatis/mapper/EmployeeMapper.xml" />
	</mappers>
</configuration>

一般上面的枚举处理方式都是很少使用的,我们都是用自定义的枚举处理器

一般来说为了系统使用方便,我们在定义枚举时可能会给每一项定义一些信息;例如:

public enum EmpStatusVersionTwo {
    //举个例子的啊:XJ:表示休假,ZC:表示正常上班,QJ:表示请假
    XJ("100","休假"),ZC("200","正常上班"),QJ("300","请假");
    .....
}

在实际开发中经常都是这么来用的,我们状态码可以返回给前端,让前端做一些处理;而不是拿那些,0啊 1啊那些枚举索引。这样太low了。

在实际开发过程中一般都是希望数据库里保存信息时保存的是枚举的状态码,而不是默认的0,1这样的枚举索引或者枚举的名; 这样Mybatis的枚举类型处理器就不行了,所以我们就得要自定义类型处理器。

代码示例:

  1. 编写实体类

    public class Employee {
    	
    	private Integer id;
    	private String lastName;
    	private String email;
    	private String gender;
        
        private EmpStatusVersionTwo empStatusTwo; //这个是自己写的枚举类
        // 写上get、set方法 根据需要写上 toString() 构造器等代码
    }
    
  2. 编写枚举类

    public enum EmpStatusVersionTwo {
    	//举个例子的啊:XJ:表示休假,ZC:表示正常上班,QJ:表示请假
      	XJ("100","休假"),ZC("200","正常上班"),QJ("300","请假");
    	
      	private String code;
      	private String msg;
      	
    	private EmpStatusVersionTwo(String code, String msg) {
    		this.code = code;
    		this.msg = msg;
    	}
    
    	public String getCode() {
    		return code;
    	}
    	public void setCode(String code) {
    		this.code = code;
    	}
    	public String getMsg() {
    		return msg;
    	}
    	public void setMsg(String msg) {
    		this.msg = msg;
    	}
      	
    	public static EmpStatusVersionTwo getMsgByCode(String code) {
    		for (EmpStatusVersionTwo item : EmpStatusVersionTwo.values()) {
    			if(item.code.equals(code)) {
    				return item;
    			}
    		}
    		return null;
    	}
    }
    
  3. 在数据库表中创建empStatus的列,用varchar(11)类型,数据库有对应列了就不用创建。

    alter table table_name add empStatus varchar(11);
    
  4. 编写映射文件

    <?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">
     <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
    <mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
        
    	<insert id="addEmpTwo" parameterType="com.xxx.mybatis.entity.Employee" useGeneratedKeys="true" keyProperty="id">
            <!-- #{empStatusTwo}:表示实体类中的枚举属性 -->
            insert into employee (last_name,email,gender,empStatus)values( #{lastName},#{email},#{gender},#{empStatusTwo} )
        </insert>
    </mapper>
    
  5. 写一个自定义类型处理器的类。 实现TypeHandler接口;或者继承BaseTypeHandler 并且 实现TypeHandler接口。或者继承BaseTypeHandler

    import java.sql.CallableStatement;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    import org.apache.ibatis.type.JdbcType;
    import org.apache.ibatis.type.TypeHandler;
    
    import com.xxx.mybatis.customenum.EmpStatusVersionTwo;
    
    /**
     * 1、实现TypeHandler接口。或者继承BaseTypeHandler
     * 
     *    这里的泛型的意思就是你这个类型处理器用来处理哪个枚举的意思
     */
    public class MyEnumEmpStatusTypeHandler implements TypeHandler<EmpStatusVersionTwo> {
    
    	/**
    	 * 定义当前数据如何保存到数据库中
    	 */
    	@Override
    	public void setParameter(PreparedStatement ps, int i, EmpStatusVersionTwo parameter,JdbcType jdbcType) throws SQLException {
    			
    		// TODO Auto-generated method stub
    		System.out.println("要保存的状态码:"+parameter.getCode());
    		ps.setString(i, parameter.getCode());
    	}
    	
    	/**
    	 * 通过列名  从结果集中取出 枚举的编码,然后再通过编码找到对应的枚举项
    	 */
    	@Override
    	public EmpStatusVersionTwo getResult(ResultSet rs, String columnName) throws SQLException {
    			
    		// TODO Auto-generated method stub
    		//需要根据从数据库中拿到的枚举的状态码返回一个枚举对象
    		String code = rs.getString(columnName);
    		System.out.println("从数据库中获取的状态码:"+code);
    		EmpStatusVersionTwo status = EmpStatusVersionTwo.getMsgByCode(code);
    		return status;
    	}
    
    	/**
    	 * 通过列的下标  从结果集中取出 枚举的编码,然后再通过编码找到对应的枚举项
    	 */
    	@Override
    	public EmpStatusVersionTwo getResult(ResultSet rs, int columnIndex) throws SQLException {
    			
    		// TODO Auto-generated method stub
    		String code = rs.getString(columnIndex);
    		System.out.println("从数据库中获取的状态码:"+code);
    		EmpStatusVersionTwo status = EmpStatusVersionTwo.getMsgByCode(code);
    		return status;
    	}
    
    	/**
    	 *  从存储过程中取出 枚举的编码,然后再通过编码找到对应的枚举项
    	 */
    	@Override
    	public EmpStatusVersionTwo getResult(CallableStatement cs, int columnIndex)
    			throws SQLException {
    		// TODO Auto-generated method stub
    		String code = cs.getString(columnIndex);
    		System.out.println("从数据库中获取的状态码:"+code);
    		EmpStatusVersionTwo status = EmpStatusVersionTwo.getMsgByCode(code);
    		return status;
    	}
    
    }
    
  6. 在全局配置文件中写上自定义的类型处理器,告诉Mybatis 用自定义的 TypeHandler 来处理 EmpStatusVersionTwo 枚举

    <?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">
    <!-- 
    	官方文档说需要从一个xml中构建一个sqlSessionFactory。 当然也有不用xml创建sqlSessionFactory的方式。官网有,他就是new一些类 。
    	每一个mybatis的应用都是围绕sqlSessionFactory实例
    -->
    <configuration>
    
    	<typeHandlers>
              <!-- 
    		      有两个属性:
    		          javaType:
    		          jdbcType:
    		      这里的意思是:如果你处理com.xxx.mybatis.customenum.EmpStatusVersionTwo这种类型的时候,就用这里配置的枚举处理器;如果不写javaType属性,全部类型都用这个枚举处理器。
               -->
              <typeHandler handler="com.xxx.mybatis.typehandler.MyEnumEmpStatusTypeHandler" javaType="com.xxx.mybatis.customenum.EmpStatusVersionTwo"/>
         </typeHandlers>
    
    	<!-- environments表示环境的意思 -->
    	<environments default="development">
    		<environment id="development">
    			<transactionManager type="JDBC" />
    			<!-- 数据源 -->
    			<dataSource type="POOLED">
    				<property name="driver" value="com.mysql.jdbc.Driver" />
    				<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
    				<property name="username" value="root" />
    				<property name="password" value="root" />
    			</dataSource>
    		</environment>
    	</environments>
    	
    	<!-- 实体类和数据库的映射文件 -->
    	<mappers>
    		<!-- 多层包,在这里用斜杠表示,有的地方用点表示,需要注意。 -->
    		<mapper resource="mybatis/mapper/EmployeeMapper.xml" />
    	</mappers>
    </configuration>
    
  7. 测试查询

    import java.io.InputStream;
    
    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 com.xxx.mybatis.customenum.EmpStatusVersionOne;
    import com.xxx.mybatis.customenum.EmpStatusVersionTwo;
    import com.xxx.mybatis.entity.Employee;
    import com.xxx.mybatis.mapper.EmployeeMapper;
    
    public class MainTest {
        public static void main(String[] args) throws Exception {
            //1、根据xml文件(全局配置文件)来创建一个SqlSessionFactory    多层包,在这里用斜杠表示,有的地方用点表示,需要注意。
    		String resource = "mybatis/mybatis-config.xml";
    		InputStream inputStream = Resources.getResourceAsStream(resource);
    		
    		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    		
    		//2、通过 SqlSessionFactory 获取 SqlSession来执行 mapper.xml (映射文件) 中的sql语句
    		SqlSession sqlSession = sqlSessionFactory.openSession();
    		try {
    			
    			//3、通过sqlSession.getMapper(接口的class)  获取接口的实现对象  (mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法)
    			EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
    			System.out.println(employeeMapper.getClass());//可以看到他返回的是一个代理对象
    			
    			Employee employee = employeeMapper.findEmpById(22);
    			System.out.println(employee);
    		} finally {
    			//关闭
    			sqlSession.close();
    		}
        }
    }
    /**
     * 执行的日志记录如下:
     * class com.sun.proxy.$Proxy0
     * DEBUG 04-15 03:00:07,359 ==>  Preparing: select ......
     * DEBUG 04-15 03:00:07,404 ==> Parameters: 22(Integer) 
     * 从数据库中获取的状态码:200
     * DEBUG 04-15 03:00:07,462 <==      Total: 1
     * Employee [id=22, lastName=test_enum, email=enum@qq.com, gender=1, empStatusOne=null, empStatusTwo=ZC]
     *
     */
    

    注意

    配置自定义的类型处理器除了在全局配置文件上进行全局配置也可以在映射文件中在具体的新增、查询方法里面的某个字段单独配置类型处理的方式。

    • 新增的时候单独为某个字段配置自定义类型处理器

      <?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">
       <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
      <mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
          
      	<insert id="addEmpTwo" parameterType="com.xxx.mybatis.entity.Employee" useGeneratedKeys="true" keyProperty="id">
              <!-- #{empStatusTwo}:表示实体类中的枚举属性 -->
              insert into employee (last_name,email,gender,empStatus) values 
              ( #{lastName},#{email},#{gender},#{empStatusTwo,typeHandler=自定义类型处理器的全类名} )
          </insert>
      </mapper>
      
    • 查询的封装结果为Entity实体类对象的时候单独为某个字段配置自定义类型处理器

      <?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">
       <!--  基于接口式编程,这里的 namespace 属性值写接口的全类名,让当前这个映射文件和接口绑定  -->
      <mapper namespace="com.xxx.mybatis.mapper.EmployeeMapper">
          
          <resultMap type="com.atguigu.mybatis.bean.Employee" id="MyEmp">
              <id column="id" property="id"/>
              <result column="empStatus" property="empStatus" typeHandler="自定义类型处理器的全类名"/>
          </resultMap>
          <!-- 
              查询就得要到<resultMap>节点了。因为查询的字段不能写 #{字段}。
              如果是查询时的 where 后面的条件 可以写 #{字段} 就可以在#{}里面写自定义的类型处理器了
           -->
      	<select id="findEmpById" parameterType="java.lang.Integer" resultMap="MyEnp">
              select id,last_name as lastName,email, empStatus as empStatusTwo,gender from employee where id = #{id}
          </select>
      </mapper>
      
    • 如果在参数位置修改TypeHandler,应该保证保存数据和查询数据用的TypeHandler是一样的。这样的话就不会出现一些问题,要么就在全局注册

  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值