MyBatis

Stone大约 48 分钟

MyBatis

在 Java 中使用 JDBC 来访问数据库,实现数据的增删改查。但是在实际工作中,单纯使用 JDBC 来编写程序,对于复杂 SQL 的处理往往非常繁琐,特别是参数设置以及获取结果集,还有将 SQL 和程序代码写在一起,也不易阅读和维护。为解决上述问题,就出现了 ORM(Object Relational Mapping)框架,其中 MyBatis 是比较常用的 ORM 框架。

第一个程序

(1)在 MySQL 中创建表并插入数据

create table tbl_employee(
id int(11) primary key auto_increment,
last_name varchar(255),
gender char(1),
email varchar(255));
INSERT INTO tbl_employee (last_name, gender, email) VALUES ('tom', '0', 'tom@stone.com');

(2)创建实体类

package com.stone.mybatis.bean;

public class Employee {
	private Integer id;
	private String lastName;
	private String email;
	private String gender;
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getLastName() {
		return lastName;
	}
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public String getGender() {
		return gender;
	}
	public void setGender(String gender) {
		this.gender = gender;
	}
	@Override
	public String toString() {
		return "Employee [id=" + id + ", lastName=" + lastName + ", email=" + email + ", gender=" + gender + "]";
	}
}

(3)创建实体类接口

package com.stone.mybatis.dao;

import com.stone.mybatis.bean.Employee;

public interface EmployeeMapper {
	public Employee getEmpById(Integer id);
}

(4)导入 JAR 包

mybatis-3.4.6.jar
ant-1.9.6.jar
ant-launcher-1.9.6.jar
asm-5.2.jar
cglib-3.2.5.jar
commons-logging-1.2.jar
javassist-3.22.0-GA.jar
log4j-1.2.17.jar
log4j-api-2.3.jar
log4j-core-2.3.jar
ognl-3.1.16.jar
slf4j-api-1.7.25.jar
slf4j-log4j12-1.7.25.jar
mysql-connector-java-5.1.23-bin.jar

(5)创建日志配置文件

### 设置###
log4j.rootLogger=DEBUG,Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.logger.org.apache=INFO
log4j.logger.org.apache=INFO
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

(6)创建 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>
	<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://192.168.8.137:3306/mybatis" />
				<property name="username" value="stone" />
				<property name="password" value="stone" />
			</dataSource>
		</environment>
	</environments>
	
	<!-- 写好的 SQL 映射文件 EmployeeMapper.xml 一定要注册到全局配置文件中 mybatis-config.xml" -->
	<mappers>
		<mapper resource="EmployeeMapper.xml" />
	</mappers>
</configuration>

(7)配置 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">
<mapper namespace="com.stone.mybatis.dao.EmployeeMapper">
<!-- 
namespace:表示名称空间,指定为接口的全类名
id:唯一标识,指定为接口的方法名称
resultType:返回值类型
#{id}:从传递过来的参数中取出 id 值
 -->	
	<select id="getEmpById" resultType="com.stone.mybatis.bean.Employee">
		select id,last_name lastName,gender,email from tbl_employee where id = #{id}
	</select>
</mapper>

(8)创建测试类

package com.stone.mybatis.test;

import java.io.IOException;
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 org.junit.Test;

import com.stone.mybatis.bean.Employee;
import com.stone.mybatis.dao.EmployeeMapper;

/**
 * 1、接口式编程
 * 原生:		Dao		===>	DaoImpl
 * mybatis	Mapper	===>	xxxMapper.xml
 * 
 * 2、SqlSession 代表和数据库的一次会话,用完必须关闭。
 * 3、SqlSession 和 Connection 一样都是非线程安全,每次使用都应该去获取新的对象
 * 4、mapper 接口没有实现类,但是 mybatis 会为这个接口生成一个代理对象(将接口和 XML 进行绑定)
 * EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
 * 5、两个重要的配置文件:
 * mybatis 的全局配置文件:包含数据库的连接池信息,事物管理器信息等系统运行环境信息
 * SQL 映射文件保存了每一个 SQL 语句的映射信息。
 * 
 *
 */
public class MybatisTest {
	
	public SqlSessionFactory getSqlSessionFactory() throws IOException {
		String resource = "mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		return sqlSessionFactory;
	}
	
	/**
	 * 1、根据 XML 配置文件(全局配置文件)创建一个 SqlSessionFactory 对象
	 * 2、SQL 映射文件,配置每一个 SQL 以及封装规则
	 * 3、将 SQL 映射文件注册到全局配置文件中
	 * 4、根据全局配置文件得到 SqlSessionFactory
	 * 5、使用 SqlSessionFactory 获取到 SqlSession 对象执行增删改查,一个 SqlSession 对象就是代码和数据库的一次会话,用完关闭
	 * 6、使用 SQL 的唯一标识来告诉 Mybatis 执行那个 SQL,SQL 都是保存在 SQL 映射文件中
	 * @throws IOException
	 */
	@Test
	public void test() throws IOException {
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
		
		//7、获取 SqlSession 实例,能直接执行已经映射的 SQL 语句
		SqlSession sqlSession = sqlSessionFactory.openSession();
		try {
			Employee employee = sqlSession.selectOne("EmployeeMapper.selectEmp", 1);
			System.out.println(employee);
		} finally {
			sqlSession.close();
		}
	}

	//接口式编程
	@Test
	public void test01() throws IOException {
		//1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
		
		//2、获取 SqlSession 对象
		SqlSession openSession = sqlSessionFactory.openSession();
		
		//3、获取接口的实现类对象,会为接口自动创建一个代理对象,代理对象去执行增删改查方法
		try {
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			Employee employee = mapper.getEmpById(1);
			System.out.println(mapper.getClass());
			System.out.println(employee);
		} finally {
			openSession.close();
		}
	}
}

全局配置文件

<?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 配置文件的内容 ,了解(交由 spring 管理)
		resource:引入类路径下的资源
		url:引入网络路径或者磁盘路径下的资源
	-->
	<properties resource="db.properties"></properties>
	
	<!-- 
		2、settings 包含很多重要的设置项
		setting:用来设置每一个设置项
		name:设置项名称
		value:设置项取值
	 -->
	<settings>
		<setting name="mapUnderscoreToCamelCase" value="true"/>
		<setting name="jdbcTypeForNull" value="NULL"/>
	</settings>
	
	<!-- 3、typeAliases:别名处理器,可以为我们的 java 类型起别名,别名不区分大小写,建议在 Mapper 的 resultType 还是使用全类名 -->
	<typeAliases>
		<!-- 
			3.1、typeAlias:为某个 java 类型起别名
			type:指定要起别名的类型全类名;默认别名为类名小写
			alias:指定新的别名
		 -->
		<!-- <typeAlias type="com.stone.mybatis.bean.Employee" alias="emp"/> -->
		
		<!-- 
			3.2、package:为某个包下的所有类批量起别名 
			name:指定包名(为当前包以及下面所有子包的每一个类都起一个别名-类名小写)
		-->
		<package name="com.stone.mybatis.bean"/>
		
		<!—3.3、批量起别名的情况下,使用 @Alias 注解为某个类型指定新的别名 -->
	</typeAliases>
	
	<!-- 4、environments:可以配置多种环境,default 指定使用某种环境,可以达到快速切换 -->
	<environments default="mysql">
		<!-- 
			environment:配置一个具体的环境 ,id 表示当前环境的唯一标识
			必须有以下两个标签:
			transactionManager:事物管理器,
				type:指定事物管理器类型:JDBC(JdbcTransactionFactory)|MANAGED(ManagedTransactionFactory)
				自定义事物管理器:使用 TransactionFactory 接口,type 指定全类名
				一般交给 Spring 来进行事务管理
			dataSource:数据源;
				type:数据源类型:UNPOOLED|POOLED|JNDI
				自定义数据源:实现 DataSourceFactory 接口,type 是全类名
		-->
		<environment id="oracle">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${oracle.driverClass}" />
				<property name="url" value="${oracle.jdbcUrl}" />
				<property name="username" value="${oracle.username}" />
				<property name="password" value="${oracle.password}" />
			</dataSource>
		</environment>
		
		<environment id="mysql">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${mysql.driverClass}" />
				<property name="url" value="${mysql.jdbcUrl}" />
				<property name="username" value="${mysql.username}" />
				<property name="password" value="${mysql.password}" />
			</dataSource>
		</environment>
	</environments>
	
	<!-- 
		5、databaseIdProvider:支持多数据库厂商
		type="DB_VENDOR",VendorDatabaseIdProvider,得到数据库厂商的标识(驱动 getDatabaseProductName()),
		Mybatis 就能根据数据库厂商标识来执行不同的SQL:MySQL,Oracle,SQL Server
	-->
	<databaseIdProvider type="DB_VENDOR">
		<!-- 为不同的数据库厂商起别名 -->
		<property name="MySQL" value="mysql"/>
		<property name="Oracle" value="oracle"/>
		<property name="SQL Server" value="sqlserver"/>
	</databaseIdProvider>
	
	<!-- 写好的 SQL 映射文件 EmployeeMapper.xml 一定要注册到全局配置文件中 mybatis-config.xml" -->
	<!-- 6、将 SQL 映射注册到全局配置中 -->
	<mappers>
		<!-- 
			mapper:注册一个 SQL 映射
				1)注册配置文件:
				resource:引用类路径下的 SQL 映射文件
				url:引用网络路径或者磁盘路径
				2)注册接口:
				class:注册接口
					1、有 SQL 映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下 
					2、没有 SQL 映射文件,所有的 SQL 都是利用注解写在接口上
					推荐:
						重要复杂的 Dao 接口来写 SQL 映射文件
						不重要简单的 Dao 接口为了开发快速可以使用注解
		-->
		<!-- <mapper resource="com/stone/mybatis/mapper/EmployeeMapper.xml" /> -->
		<!-- <mapper class="com.stone.mybatis.dao.EmployeeMapperAnnotation"/> -->
		
		<!-- 批量注册 ,需要将映射文件放在 Dao 接口同一个包下-->
		<package name="com.stone.mybatis.dao"/>		
	</mappers>		
</configuration>

映射文件

在接口中创建对应的方法:

package com.stone.mybatis.dao;

import com.stone.mybatis.bean.Employee;

public interface EmployeeMapper {
	public Employee getEmpById(Integer id);
	
	public void insertEmp(Employee employee);
	
	public boolean updateEmp(Employee employee);
	
	public void deleteEmpById(Integer id);
}

在映射文件中创建对应的 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">
<mapper namespace="com.stone.mybatis.dao.EmployeeMapper">
<!-- 
namespace:表示名称空间,指定为接口的全类名
id:唯一标识,指定为接口的方法名称
parameterType:参数类型,可以不传,MyBatis 会根据 TypeHandler 自动推断
resultType:返回值类型
#{id}:从传递过来的参数中取出 id 值
 -->	
 	<select id="getEmpById" resultType="com.stone.mybatis.bean.Employee">
		select * from tbl_employee where id = #{id}
	</select> 
 	
 	<select id="getEmpById" resultType="com.stone.mybatis.bean.Employee" databaseId="mysql">
		select * from tbl_employee where id = #{id}
	</select> 
	
	<select id="getEmpById" resultType="com.stone.mybatis.bean.Employee" databaseId="oracle">
		select employee_id id,last_name lastName,email from employees where employee_id=#{id}
	</select>
	
	<!-- public void insertEmp(Employee employee); -->
	<!-- parameterType:参数类型,可以省略 -->
	<!-- 
		获取自增主键的值:
			MySQL 支持自增主键,自增主键值的获取,MyBatis 也是利用 statement.getGeneratedKeys()
			useGeneratedKeys="true":使用自增主键获取主键值策略
			keyProperty:指定对应的主键属性,也就是 MyBatis 获取到主键值以后,将这个值封装给 Javabean 的哪个属性
	 -->
	<insert id="insertEmp" useGeneratedKeys="true" keyProperty="id" databaseId="mysql">
		insert into tbl_employee(last_name,gender,email)
		values(#{lastName},#{gender},#{email})
	</insert>
	
	<!-- 
		Oracle 不支持自增:Oracle 使用序列来模拟自增,每次插入的数据的主键是从序列中取值。
	 -->
	 <insert id="insertEmp" databaseId="oracle">
	 	<!-- 
	 		keyProperty:查出的主键值封装给 Javabean 的哪个属性
	 		order="BEFORE":当前 SQL 在插入 SQL 之前运行
	 		resultType:查出的数据返回值类型
	 	-->
	 	<!-- 
	 		<selectKey keyProperty="id" order="BEFORE" resultType="Integer">
	 			select emp_seq.nextval from dual
	 		</selectKey> 
	 	-->
		<!-- 插入时的主键是从序列中拿到的 -->
		<!-- 
			insert into emp(empno,ename)
			values(#{id},#{lastName}) 
		-->
		
		
		<!-- 也可以直接使用序列进行插入 -->
		insert into emp(empno,ename)
		values(emp_seq.nextval,#{lastName})
		
		<!-- 
			使用 order="AFTER",先执行插入 SQL(从序列中取值),再来获取 id 的值 
			如果一次插入多条数据,则取到的是最后一个 id 的值
		-->
		<selectKey keyProperty="id" order="AFTER" resultType="Integer">
			select emp_seq.currval from dual
		</selectKey>
	</insert>
	
	<!-- public void updateEmp(Employee employee); -->
	<update id="updateEmp">
		update tbl_employee 
		set last_name=#{lastName},gender=#{gender},email=#{email}
		where id=#{id}
	</update>
	
	<!-- public void deleteEmpById(Integer id); -->
	<delete id="deleteEmpById">
		delete from tbl_employee where id=#{id}
	</delete>
	
</mapper>

测试:

	/**
	 * 测试增删改
	 * 1、MyBatis 允许增删改直接定义以下类型返回值:Integer、Long、Boolean、void
	 * 2、需要手动提交数据
	 * sqlSessionFactory.openSession() ===> 手动提交
	 * sqlSessionFactory.openSession(true) ===> 自动提交
	 * @throws IOException
	 */
	@Test
	public void test03() throws IOException {
		// 1、获取SQLSessionFactory对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		// 2、获取SqlSession对象,不会自动提交
		SqlSession openSession = sqlSessionFactory.openSession();
		
		try {
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			
			//测试添加
			Employee employee = new Employee("jerry", "jerry@stone.com", "1");
			mapper.insertEmp(employee);
			System.out.println(employee.getId());
			
			//测试修改
			//Employee employee2 = new Employee(1, "tom", "tom@stone.com", "1");
			//boolean updateEmp = mapper.updateEmp(employee2);
			//System.out.println(updateEmp);
			
			//测试删除
			//mapper.deleteEmpById(2);
			
			//3、手动提交
			openSession.commit();
		} finally {
			openSession.close();
		}
	}

SELECT 返回 List

接口方法:

	public List<Employee> getEmpsByLastNameLike(String lastName);

对应 SQL:

	<!-- public List<Employee> getEmpsByLastNameLike(String lastName); -->
	<!-- resultType:如果返回的是一个集合,要写集合中元素的类型 -->
	<select id="getEmpsByLastNameLike" resultType="com.stone.mybatis.bean.Employee">
		select * from tbl_employee where last_name like #{lastName}
	</select>

测试:

	@Test
	public void test05() throws IOException {
		// 1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		// 2、获取 SqlSession 对象,不会自动提交
		SqlSession openSession = sqlSessionFactory.openSession();
		
		try {
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			List<Employee> empsByLastNameLike = mapper.getEmpsByLastNameLike("%e%");
			for (Employee employee : empsByLastNameLike) {
				System.out.println(employee);
			}
		} finally {
			openSession.close();
		}
	}

输出:

Employee [id=3, lastName=jerry, email=jerry@stone.com, gender=1]
Employee [id=4, lastName=jerry, email=jerry@stone.com, gender=1]

SELECT 返回 Map

接口方法:

	//返回一条记录的 map,key 就是列名,值就是对应的值
	public Map<String, Object> getEmpByIdReturnMap(Integer id);
	
	//多条记录封装一个 map:Map<Integer, Employee>:键是这条记录的主键,值是记录封装后的 Javabean
	//@MapKey("id") 告诉 MyBatis 封装这个 map 的时候使用哪个属性作为 map 的 key
	@MapKey("id")
	public Map<Integer, Employee> getEmpByLastNameLikeReturnMap(String lastName);

对应 SQL:

	<!-- public Map<String, Object> getEmpByIdReturnMap(Integer id); -->
	<select id="getEmpByIdReturnMap" resultType="map">
		select * from tbl_employee where id=#{id}
	</select>
	
	<!-- public Map<Integer, Employee> getEmpByLastNameLikeReturnMap(String lastName); -->
	<select id="getEmpByLastNameLikeReturnMap" resultType="com.stone.mybatis.bean.Employee">
		select * from tbl_employee where last_name like #{lastName}
	</select>

测试:

	@Test
	public void test06() throws IOException {
		// 1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		// 2、获取 SqlSession 对象,不会自动提交
		SqlSession openSession = sqlSessionFactory.openSession();
		
		try {
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			Map<String, Object> empByIdReturnMap = mapper.getEmpByIdReturnMap(1);
			System.out.println(empByIdReturnMap);
		} finally {
			openSession.close();
		}
	}
	//输出结果:{gender=1, last_name=tom, id=1, email=tom@stone.com}
	
	@Test
	public void test07() throws IOException {
		// 1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		// 2、获取 SqlSession 对象,不会自动提交
		SqlSession openSession = sqlSessionFactory.openSession();
		
		try {
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			Map<Integer, Employee> empByLastNameLikeReturnMap = mapper.getEmpByLastNameLikeReturnMap("%e%");
			System.out.println(empByLastNameLikeReturnMap);
		} finally {
			openSession.close();
		}
	}
	//输出结果:{3=Employee [id=3, lastName=jerry, email=jerry@stone.com, gender=1], 4=Employee [id=4, lastName=jerry, email=jerry@stone.com, gender=1]}

自定义结果映射规则

接口方法:

package com.stone.mybatis.dao;

import com.stone.mybatis.bean.Employee;

public interface EmployeeMapperPlus {
	
	public Employee getEmpById(Integer id);
	
}

对应 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">
<mapper namespace="com.stone.mybatis.dao.EmployeeMapperPlus">
	
	<!-- 
		resultMap:自定义某个 Javabean 的封装规则
		type:自定义规则的 Java 类型
		id:唯一 id,方便引用
	-->
	<resultMap type="com.stone.mybatis.bean.Employee" id="MyEmp">
		<!-- 
			id:指定主键列的封装规则 
			column:指定唯一列
			property:指定对应的 Javabean 属性
		-->
		<id column="id" property="id"/>
		
		<!-- result:定义普通列封装规则 -->
		<result column="last_name" property="lastName"/>
		
		<!-- 其他不指定的列会自动封装,推荐只要写 resultMap 就把全部的映射规则都写上 -->
		<result column="email" property="email"/>
		<result column="gender" property="gender"/>
	</resultMap>
	
	<!-- public Employee getEmpById(Integer id); -->
	<select id="getEmpById" resultMap="MyEmp">
		select * from tbl_employee where id=#{id}
	</select>
</mapper>

测试:

	@Test
	public void test08() throws IOException {
		// 1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		// 2、获取 SqlSession 对象,不会自动提交
		SqlSession openSession = sqlSessionFactory.openSession();
		
		try {
			EmployeeMapperPlus mapper = openSession.getMapper(EmployeeMapperPlus.class);
			Employee empById = mapper.getEmpById(1);
			System.out.println(empById);
		} finally {
			openSession.close();
		}
	}

输出:

Employee [id=1, lastName=tom, email=tom@stone.com, gender=1]

关联查询

级联属性封装结果

创建表和约束:

CREATE TABLE tbl_dept (
  id int(11) NOT NULL AUTO_INCREMENT,
  dept_name varchar(255) DEFAULT NULL,
  PRIMARY KEY (id)
) ;
alter table tbl_employee add column d_id int(11);
alter table tbl_employee add constraint fk_emp_dept foreign key(d_id) references tbl_dept(id);

接口方法:

package com.stone.mybatis.dao;

import com.stone.mybatis.bean.Employee;

public interface EmployeeMapperPlus {
	
	public Employee getEmpById(Integer id);
	
	public Employee getEmpAndDept(Integer id);
	
}

对应 SQL:

	<!-- 
		场景1:查询 Employee 的同时查询员工的部门
	 -->
	 <!-- 联合查询:级联属性封装结果集 -->
	 <resultMap type="com.stone.mybatis.bean.Employee" id="MyDifEmp">
	 	<id column="id" property="id"/>
	 	<result column="last_name" property="lastName"/>
	 	<result column="gender" property="gender"/>
	 	<result column="did" property="dept.id"/>
	 	<result column="dept_name" property="dept.departmentName"/>
	 </resultMap>
	<!-- public Employee getEmpAndDept(Integer id); -->
	<select id="getEmpAndDept" resultMap="MyDifEmp">
		select e.id id,e.last_name last_name,e.gender gender,
				e.d_id d_id,d.id did,d.dept_name dept_name
		from tbl_employee e,tbl_dept d
		where e.d_id=d.id and e.id=#{id}
	</select>

测试:

	@Test
	public void test09() throws IOException {
		// 1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		// 2、获取 SqlSession 对象,不会自动提交
		SqlSession openSession = sqlSessionFactory.openSession();
		
		try {
			EmployeeMapperPlus mapper = openSession.getMapper(EmployeeMapperPlus.class);
			Employee empAndDept = mapper.getEmpAndDept(1);
			System.out.println(empAndDept);
			System.out.println(empAndDept.getDept());
		} finally {
			openSession.close();
		}
	}

输出:

Employee [id=1, lastName=tom, email=null, gender=1]
Department [id=1, departmentName=开发部]

定义关联对象封装规则

关联员工表和部门表,查询员工信息及其部门名称,这里是 1 对 1 关系。

对应 SQL:

	 <!-- 使用 association 定义关联的单个对象的封装规则 -->
	 <resultMap type="com.stone.mybatis.bean.Employee" id="MyDifEmp2">
	 	<id column="id" property="id"/>
	 	<result column="last_name" property="lastName"/>
	 	<result column="gender" property="gender"/>
	 	<!-- 
	 		association:可以指定联合的 Javabean 对象
	 		property="dept":指定哪个属性是联合的对象
	 		javaType:指定这个属性对象的类型【不能省略】
	 	 -->
	 	<association property="dept" javaType="com.stone.mybatis.bean.Department">
	 		<id column="did" property="id"/>
	 		<result column="dept_name" property="departmentName"/>
	 	</association>
	 </resultMap>
	 
	<!-- public Employee getEmpAndDept(Integer id); -->
	<select id="getEmpAndDept" resultMap="MyDifEmp2">
		select e.id id,e.last_name last_name,e.gender gender,
				e.d_id d_id,d.id did,d.dept_name dept_name
		from tbl_employee e,tbl_dept d
		where e.d_id=d.id and e.id=#{id}
	</select>

分步查询

接口方法:

package com.stone.mybatis.dao;

import com.stone.mybatis.bean.Employee;

public interface EmployeeMapperPlus {
	
	public Employee getEmpById(Integer id);
	
	public Employee getEmpAndDept(Integer id);
	
	public Employee getEmpByIdStep(Integer id);
}
package com.stone.mybatis.dao;

import com.stone.mybatis.bean.Department;

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

对应 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">
<mapper namespace="com.stone.mybatis.dao.DepartmentMapper">
	
	<!-- public Department getDeptById(Integer id); -->
	<select id="getDeptById" resultType="com.stone.mybatis.bean.Department">
		select id,dept_name departmentName from tbl_dept where id=#{id}
	</select>
	
</mapper>
	<!-- 
		使用 association 进行分步查询 
		1、先按照员工 id 查询员工信息
		2、根据查询员工信息的 d_id 值去部门表查询部门信息
		3、部门设置到员工中
	-->
	<resultMap type="com.stone.mybatis.bean.Employee" id="MyEmpByStep">
		<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 属性
	 	-->
	 	<association property="dept" select="com.stone.mybatis.dao.DepartmentMapper.getDeptById" column="d_id">	
	 	</association>
	</resultMap>
	<!-- public Employee getEmpByIdStep(Integer id); -->
	<select id="getEmpByIdStep" resultMap="MyEmpByStep">
		select * from tbl_employee where id=#{id}
	</select>

测试:

	@Test
	public void test10() throws IOException {
		// 1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		// 2、获取 SqlSession 对象,不会自动提交
		SqlSession openSession = sqlSessionFactory.openSession();
		
		try {
			EmployeeMapperPlus mapper = openSession.getMapper(EmployeeMapperPlus.class);
			Employee empByIdStep = mapper.getEmpByIdStep(1);
			System.out.println(empByIdStep);
			System.out.println(empByIdStep.getDept());
		} finally {
			openSession.close();
		}
	}

输出:

==>  Preparing: select * from tbl_employee where id=? 
==> Parameters: 1(Integer)
====>  Preparing: select id,dept_name departmentName from tbl_dept where id=? 
====> Parameters: 1(Integer)
<====      Total: 1
<==      Total: 1
Employee [id=1, lastName=tom, email=tom@stone.com, gender=1]
Department [id=1, departmentName=开发部]

延迟加载

延迟加载其实就是将数据加载时机推迟,比如推迟嵌套查询的执行时机。在 MyBatis 中经常用到关联查询,但是并不是任何时候都需要立即返回关联查询结果。比如查询订单信息,并不一定需要及时返回订单对应的产品信息,查询商品分类信息并不一定要及时返回该类别下有哪些产品,这种情况一下需要一种机制,当需要查看时,再执行查询,返回需要的结果集,这种需求在 MyBatis 中可以使用延迟加载机制来实现。延迟加载可以实现先查询主表,按需实时做关联查询,返回关联表结果集,一定程度上提高了效率。

全局配置文件:

	<!-- 
		2、settings 包含很多重要的设置项
		setting:用来设置每一个设置项
		name:设置项名称
		value:设置项取值
	 -->
	<settings>
		<setting name="mapUnderscoreToCamelCase" value="true"/>
		<setting name="jdbcTypeForNull" value="NULL"/>
		<!-- 显式的指定每个需要更改的配置的值,即使是默认的。防止版本更新带来的问题 -->
		<setting name="lazyLoadingEnabled" value="true"/>
		<setting name="aggressiveLazyLoading" value="false"/>
	</settings>

测试:

	@Test
	public void test11() throws IOException {
		// 1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		// 2、获取 SqlSession 对象,不会自动提交
		SqlSession openSession = sqlSessionFactory.openSession();
		
		try {
			EmployeeMapperPlus mapper = openSession.getMapper(EmployeeMapperPlus.class);
			Employee empByIdStep = mapper.getEmpByIdStep(1);
			System.out.println(empByIdStep.getLastName());
			System.out.println(empByIdStep.getDept());
		} finally {
			openSession.close();
		}
	}

输出:

2018-12-14 10:37:27,672 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperPlus.getEmpByIdStep] - ==>  Preparing: select * from tbl_employee where id=? 
2018-12-14 10:37:27,703 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperPlus.getEmpByIdStep] - ==> Parameters: 1(Integer)
2018-12-14 10:37:27,782 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperPlus.getEmpByIdStep] - <==      Total: 1
tom
2018-12-14 10:37:27,783 [main] DEBUG [com.stone.mybatis.dao.DepartmentMapper.getDeptById] - ==>  Preparing: select id,dept_name departmentName from tbl_dept where id=? 
2018-12-14 10:37:27,783 [main] DEBUG [com.stone.mybatis.dao.DepartmentMapper.getDeptById] - ==> Parameters: 1(Integer)
2018-12-14 10:37:27,785 [main] DEBUG [com.stone.mybatis.dao.DepartmentMapper.getDeptById] - <==      Total: 1
Department [id=1, departmentName=开发部]

定义关联集合封装规则

关联员工表和部门表,查询部门及其对应的员工,这里是 1 对 N 关系。

接口方法:

package com.stone.mybatis.dao;

import com.stone.mybatis.bean.Department;

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

对应 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">
<mapper namespace="com.stone.mybatis.dao.DepartmentMapper">
	
	<!-- public Department getDeptById(Integer id); -->
	<select id="getDeptById" resultType="com.stone.mybatis.bean.Department">
		select id,dept_name departmentName from tbl_dept where id=#{id}
	</select>
	
	<!-- 嵌套结果集的方式,使用 collection 标签定义关联的集合类型的封装属性规则 -->
	<resultMap type="com.stone.mybatis.bean.Department" id="MyDept">
		<id column="did" property="id"/>
		<result column="dept_name" property="departmentName"/>
		<!-- 
			collection:定义集合类型的属性的封装规则 
			ofType:指定集合里面元素的类型
		-->
		<collection property="emps" ofType="com.stone.mybatis.bean.Employee">
			<!-- 定义这个集合中元素的封装规则 -->
			<id column="eid" property="id"/>
			<id column="last_name" property="lastName"/>
			<id column="email" property="email"/>
			<id column="gender" property="gender"/>
		</collection>
	</resultMap>
	<!-- public Department getDeptByIdPlus(Integer id); -->
	<select id="getDeptByIdPlus" resultMap="MyDept">
		select d.id did,d.dept_name dept_name,e.id eid,e.last_name last_name,e.email email,e.gender gender
		from tbl_dept d left join tbl_employee e on d.id=e.d_id where d.id=#{id}
	</select>
	
</mapper>

测试:

	@Test
	public void test12() throws IOException {
		// 1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		// 2、获取 SqlSession 对象,不会自动提交
		SqlSession openSession = sqlSessionFactory.openSession();
		
		try {
			DepartmentMapper mapper = openSession.getMapper(DepartmentMapper.class);
			Department department = mapper.getDeptByIdPlus(1);
			System.out.println(department);
			System.out.println(department.getEmps());
		} finally {
			openSession.close();
		}
	}

输出:

2018-12-14 13:17:11,604 [main] DEBUG [com.stone.mybatis.dao.DepartmentMapper.getDeptByIdPlus] - ==>  Preparing: select d.id did,d.dept_name dept_name,e.id eid,e.last_name last_name,e.email email,e.gender gender from tbl_dept d left join tbl_employee e on d.id=e.d_id where d.id=? 
2018-12-14 13:17:11,661 [main] DEBUG [com.stone.mybatis.dao.DepartmentMapper.getDeptByIdPlus] - ==> Parameters: 1(Integer)
2018-12-14 13:17:11,700 [main] DEBUG [com.stone.mybatis.dao.DepartmentMapper.getDeptByIdPlus] - <==      Total: 3
Department [id=1, departmentName=开发部]
[Employee [id=1, lastName=tom, email=tom@stone.com, gender=1], Employee [id=4, lastName=jerry, email=jerry@stone.com, gender=1], Employee [id=6, lastName=null, email=jerry3@stone.com, gender=1]]

分步查询及延迟加载

接口方法:

package com.stone.mybatis.dao;

import com.stone.mybatis.bean.Department;

public interface DepartmentMapper {
	
	public Department getDeptById(Integer id);
	
	public Department getDeptByIdPlus(Integer id);
	
	public Department getDeptByIdStep(Integer id);
}
package com.stone.mybatis.dao;

import java.util.List;

import com.stone.mybatis.bean.Employee;

public interface EmployeeMapperPlus {
	
	public Employee getEmpById(Integer id);
	
	public Employee getEmpAndDept(Integer id);
	
	public Employee getEmpByIdStep(Integer id);
	
	public List<Employee> getEmpsByDeptId(Integer deptId);
}

对应 SQL:

	<!-- public List<Employee> getEmpsByDeptId(Integer deptId); -->
	<select id="getEmpsByDeptId" resultType="com.stone.mybatis.bean.Employee">
		select * from tbl_employee where d_id=#{deptId}
	</select>
	<!-- collection:分步查询 -->
	<resultMap type="com.stone.mybatis.bean.Department" id="MyDeptStep">
		<id column="id" property="id"/>
		<result column="dept_name" property="departmentName"/>
			<!-- 
				扩展:collection 中多列的值传递过去:将多列的值封装 map 传递
				column="{key1=column1,key2=column2}" 
				fetchType="lazy":表示使用延迟加载
					lazy:延迟
					eager:立即
			-->
		<collection property="emps" select="com.stone.mybatis.dao.EmployeeMapperPlus.getEmpsByDeptId" column="{deptId=id}" fetchType="lazy"></collection>
	</resultMap>
	<!-- public Department getDeptByIdStep(Integer id); -->
	<select id="getDeptByIdStep" resultMap="MyDeptStep">
		select id,dept_name from tbl_dept where id=#{id}
	</select>

测试:

	@Test
	public void test13() throws IOException {
		// 1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		// 2、获取 SqlSession 对象,不会自动提交
		SqlSession openSession = sqlSessionFactory.openSession();
		
		try {
			DepartmentMapper mapper = openSession.getMapper(DepartmentMapper.class);
			Department department = mapper.getDeptByIdStep(1);
			System.out.println(department.getDepartmentName());
			System.out.println(department.getEmps());
		} finally {
			openSession.close();
		}
	}

输出:

2018-12-14 14:03:22,262 [main] DEBUG [com.stone.mybatis.dao.DepartmentMapper.getDeptByIdStep] - ==>  Preparing: select id,dept_name from tbl_dept where id=? 
2018-12-14 14:03:22,290 [main] DEBUG [com.stone.mybatis.dao.DepartmentMapper.getDeptByIdStep] - ==> Parameters: 1(Integer)
2018-12-14 14:03:22,351 [main] DEBUG [com.stone.mybatis.dao.DepartmentMapper.getDeptByIdStep] - <==      Total: 1
开发部
2018-12-14 14:03:22,352 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperPlus.getEmpsByDeptId] - ==>  Preparing: select * from tbl_employee where d_id=? 
2018-12-14 14:03:22,353 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperPlus.getEmpsByDeptId] - ==> Parameters: 1(Integer)
2018-12-14 14:03:22,356 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperPlus.getEmpsByDeptId] - <==      Total: 3
[Employee [id=1, lastName=tom, email=tom@stone.com, gender=1], Employee [id=4, lastName=jerry, email=jerry@stone.com, gender=1], Employee [id=6, lastName=null, email=jerry3@stone.com, gender=1]]

鉴别器

对应 SQL:

	<!-- 
		<discriminator javaType=""></discriminator> 
		鉴别器:MyBatis 可以使用 discriminator 判断某列的值,然后根据某列的值改变封装行为
		封装 Employee:
			如果查出来的是女生,就把部门信息查询出来,否则不查询
			如果查出来的是男生,把 last_name 这一列的值赋值给 email
	-->
	<resultMap type="com.stone.mybatis.bean.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类型
	 	-->
	 	<discriminator javaType="string" column="gender">
	 		<!-- 女生 -->
	 		<!-- resultType:指定封装的结果类型 -->
	 		<case value="0" resultType="com.stone.mybatis.bean.Employee">
	 			<association property="dept" select="com.stone.mybatis.dao.DepartmentMapper.getDeptById" column="d_id">	
	 			</association>
	 		</case>
	 		<!-- 男生,把last_name这一列的值赋值给email -->
	 		<case value="1" resultType="com.stone.mybatis.bean.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="getEmpByIdStep" resultMap="MyEmpDis">
		select * from tbl_employee where id=#{id}
	</select>

测试:

	@Test
	public void test11() throws IOException {
		// 1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		// 2、获取 SqlSession 对象,不会自动提交
		SqlSession openSession = sqlSessionFactory.openSession();
		
		try {
			EmployeeMapperPlus mapper = openSession.getMapper(EmployeeMapperPlus.class);
			Employee empByIdStep = mapper.getEmpByIdStep(1);
			System.out.println(empByIdStep);
			System.out.println(empByIdStep.getDept());
			empByIdStep = mapper.getEmpByIdStep(3);
			System.out.println(empByIdStep);
			System.out.println(empByIdStep.getDept());
		} finally {
			openSession.close();
		}
	}

输出:

2018-12-14 14:28:57,869 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperPlus.getEmpByIdStep] - ==>  Preparing: select * from tbl_employee where id=? 
2018-12-14 14:28:57,901 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperPlus.getEmpByIdStep] - ==> Parameters: 1(Integer)
2018-12-14 14:28:57,967 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperPlus.getEmpByIdStep] - <==      Total: 1
2018-12-14 14:28:57,968 [main] DEBUG [com.stone.mybatis.dao.DepartmentMapper.getDeptById] - ==>  Preparing: select id,dept_name departmentName from tbl_dept where id=? 
2018-12-14 14:28:57,968 [main] DEBUG [com.stone.mybatis.dao.DepartmentMapper.getDeptById] - ==> Parameters: 1(Integer)
2018-12-14 14:28:57,971 [main] DEBUG [com.stone.mybatis.dao.DepartmentMapper.getDeptById] - <==      Total: 1
Employee [id=1, lastName=tom, email=tom@stone.com, gender=0]
Department [id=1, departmentName=开发部]
2018-12-14 14:28:57,972 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperPlus.getEmpByIdStep] - ==>  Preparing: select * from tbl_employee where id=? 
2018-12-14 14:28:57,972 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperPlus.getEmpByIdStep] - ==> Parameters: 3(Integer)
2018-12-14 14:28:57,985 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperPlus.getEmpByIdStep] - <==      Total: 1
Employee [id=3, lastName=jerry, email=jerry, gender=1]
null

动态 SQL

使用 MyBatis 可以大大简化动态 SQL 的开发,无需在程序中根据不同条件拼接 SQL 语句。

if

使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。

接口方法:

package com.stone.mybatis.dao;

import java.util.List;

import com.stone.mybatis.bean.Employee;

public interface EmployeeMapperDynamicSQL {
	
	public List<Employee> getEmpsByConditionIf(Employee employee);
}

对应 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">
<mapper namespace="com.stone.mybatis.dao.EmployeeMapperDynamicSQL">
	
	<!-- 查询员工,要求,携带了哪个字段查询条件就带上这个字段的值 -->
	<!-- public List<Employee> getEmpsByConditionIf(Employee employee); -->
	<select id="getEmpsByConditionIf" resultType="com.stone.mybatis.bean.Employee">
		select * from tbl_employee
		<!-- 
			查询的时候如果某些条件没带,可能 SQL 拼接会有问题 
			解决方法:
				1、给 where 后面加上 1=1,以后的条件都加上 and
				2、MyBatis 使用 where 标签来将所有的查询条件包括在内
				Mybatis 就会将 where 标签中拼接的 SQL 多出来的 and 或者 or 去掉
		-->
		<where>
			<!-- test:判定表达式(OGNL),从参数中取值进行判断,遇到特殊符号应该使用转义字符 -->
			<if test="id!=null">
				id=#{id}
			</if>
			<if test="lastName!=null and lastName!=''">
				and last_name like #{lastName}
			</if>
			<if test="email!=null and email.trim()!=''">
				and email=#{email}
			</if>
			<!-- OGNL 会进行字符串与数字的转换判断 -->
			<if test="gender==0 or gender==1">
				and gender=#{gender}
			</if>
		</where>
	</select>

</mapper>

测试:

	@Test
	public void test14() throws IOException {
		// 1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		// 2、获取 SqlSession 对象,不会自动提交
		SqlSession openSession = sqlSessionFactory.openSession();
		
		try {
			EmployeeMapperDynamicSQL mapper = openSession.getMapper(EmployeeMapperDynamicSQL.class);
			Employee employee = new Employee(null, "%e%", "jerry@stone.com", null);
			List<Employee> emps = mapper.getEmpsByConditionIf(employee);
			for (Employee employee2 : emps) {
				System.out.println(employee2);
			}
		} finally {
			openSession.close();
		}
	}

输出:

2018-12-14 15:38:56,989 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.getEmpsByConditionIf] - ==>  Preparing: select * from tbl_employee WHERE last_name like ? and email=? 
2018-12-14 15:38:57,020 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.getEmpsByConditionIf] - ==> Parameters: %e%(String), jerry@stone.com(String)
2018-12-14 15:38:57,041 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.getEmpsByConditionIf] - <==      Total: 2
Employee [id=3, lastName=jerry, email=jerry@stone.com, gender=1]
Employee [id=4, lastName=jerry, email=jerry@stone.com, gender=1]

trim

接口方法:

package com.stone.mybatis.dao;

import java.util.List;

import com.stone.mybatis.bean.Employee;

public interface EmployeeMapperDynamicSQL {
	
	public List<Employee> getEmpsByConditionIf(Employee employee);
	
	public List<Employee> getEmpsByConditionTrim(Employee employee);

}

对应 SQL:

	<!-- public List<Employee> getEmpsByConditionTrim(Employee employee); -->
	<select id="getEmpsByConditionTrim" resultType="com.stone.mybatis.bean.Employee">
		select * from tbl_employee
		<!-- 
			trim:自定义字符串的截取规则,解决后面多出的 and 或者 or,where 标签不能解决 
			prefix="":前缀,整个 trim 标签体拼接后加一个前缀
			prefixOverrides="":前缀覆盖,去掉整个字符串前面多余的字符
			suffix="":后缀,整个 trim 标签体拼接后加一个后缀
			suffixOverrides="":后缀覆盖,去掉整个字符串后面多余的字符
		-->
		<trim prefix="where" suffixOverrides="and">
			<if test="id!=null">
				id=#{id} and
			</if>
			<if test="lastName!=null and lastName!=''">
				last_name like #{lastName} and
			</if>
			<if test="email!=null and email.trim()!=''">
				email=#{email} and
			</if>
			<!-- OGNL会进行字符串与数字的转换判断 -->
			<if test="gender==0 or gender==1">
				gender=#{gender}
			</if>
		</trim>
	</select>

测试:

		@Test
		public void test15() throws IOException {
			// 1、获取 SQLSessionFactory 对象
			SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

			// 2、获取 SqlSession 对象,不会自动提交
			SqlSession openSession = sqlSessionFactory.openSession();
			
			try {
				EmployeeMapperDynamicSQL mapper = openSession.getMapper(EmployeeMapperDynamicSQL.class);
				Employee employee = new Employee(null, "%e%", "jerry@stone.com", null);
				List<Employee> emps = mapper.getEmpsByConditionTrim(employee);
				for (Employee employee2 : emps) {
					System.out.println(employee2);
				}
			} finally {
				openSession.close();
			}
		}

输出:

2018-12-14 15:53:37,339 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.getEmpsByConditionTrim] - ==>  Preparing: select * from tbl_employee where last_name like ? and email=? 
2018-12-14 15:53:37,367 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.getEmpsByConditionTrim] - ==> Parameters: %e%(String), jerry@stone.com(String)
2018-12-14 15:53:37,389 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.getEmpsByConditionTrim] - <==      Total: 2
Employee [id=3, lastName=jerry, email=jerry@stone.com, gender=1]
Employee [id=4, lastName=jerry, email=jerry@stone.com, gender=1]

choose

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

接口方法:

package com.stone.mybatis.dao;

import java.util.List;

import com.stone.mybatis.bean.Employee;

public interface EmployeeMapperDynamicSQL {
	
	public List<Employee> getEmpsByConditionIf(Employee employee);
	
	public List<Employee> getEmpsByConditionTrim(Employee employee);
	
	public List<Employee> getEmpsByConditionChoose(Employee employee);

}

对应 SQL:

	<!-- 如果带了 id 就用 id 查,如果带了 lastName 就用 lastName 查,只会进入其中一个 -->
	<!-- public List<Employee> getEmpsByConditionChoose(Employee employee); -->
	<select id="getEmpsByConditionChoose" resultType="com.stone.mybatis.bean.Employee">
		select * from tbl_employee
		<where>
			<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>

测试:

		@Test
		public void test16() throws IOException {
			// 1、获取 SQLSessionFactory 对象
			SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

			// 2、获取 SqlSession 对象,不会自动提交
			SqlSession openSession = sqlSessionFactory.openSession();
			
			try {
				EmployeeMapperDynamicSQL mapper = openSession.getMapper(EmployeeMapperDynamicSQL.class);
				Employee employee = new Employee(3, "%e%", "jerry@stone.com", null);
				List<Employee> emps = mapper.getEmpsByConditionChoose(employee);
				for (Employee employee2 : emps) {
					System.out.println(employee2);
				}
			} finally {
				openSession.close();
			}
		}

输出:

2018-12-14 16:14:41,174 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.getEmpsByConditionChoose] - ==>  Preparing: select * from tbl_employee WHERE id=? 
2018-12-14 16:14:41,203 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.getEmpsByConditionChoose] - ==> Parameters: 3(Integer)
2018-12-14 16:14:41,224 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.getEmpsByConditionChoose] - <==      Total: 1
Employee [id=3, lastName=jerry, email=jerry@stone.com, gender=1]

set

接口方法:

package com.stone.mybatis.dao;

import java.util.List;

import com.stone.mybatis.bean.Employee;

public interface EmployeeMapperDynamicSQL {
	
	public List<Employee> getEmpsByConditionIf(Employee employee);
	
	public List<Employee> getEmpsByConditionTrim(Employee employee);
	
	public List<Employee> getEmpsByConditionChoose(Employee employee);
	
	public void updateEmp(Employee employee);
}

对应 SQL:

	<!-- set 标签用于更新 -->
	<!-- public void updateEmp(Employee employee); -->
	<update id="updateEmp">
		update tbl_employee
		<set>
		<!-- <trim prefix="set" suffixOverrides=","></trim> -->
			<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}
	</update>

测试:

	@Test
	public void test17() throws IOException {
		// 1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		// 2、获取 SqlSession 对象,不会自动提交
		SqlSession openSession = sqlSessionFactory.openSession();

		try {
			EmployeeMapperDynamicSQL mapper = openSession.getMapper(EmployeeMapperDynamicSQL.class);
			Employee employee = new Employee(1, "Admin", null, null);
			mapper.updateEmp(employee);
			openSession.commit();
		} finally {
			openSession.close();
		}
	}

输出:

2018-12-14 16:30:33,224 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.updateEmp] - ==>  Preparing: update tbl_employee SET last_name=? where id=? 
2018-12-14 16:30:33,253 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.updateEmp] - ==> Parameters: Admin(String), 1(Integer)
2018-12-14 16:30:33,258 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.updateEmp] - <==    Updates: 1

foreach

接口方法:

package com.stone.mybatis.dao;

import java.util.List;

import org.apache.ibatis.annotations.Param;

import com.stone.mybatis.bean.Employee;

public interface EmployeeMapperDynamicSQL {
	
	public List<Employee> getEmpsByConditionIf(Employee employee);
	
	public List<Employee> getEmpsByConditionTrim(Employee employee);
	
	public List<Employee> getEmpsByConditionChoose(Employee employee);
	
	public void updateEmp(Employee employee);
	
	public List<Employee> getEmpsByConditionForeach(@Param("ids")List<Integer> ids);
}

对应 SQL:

	<!-- public List<Employee> getEmpsByConditionForeach(List<Integer> ids); -->
	<select id="getEmpsByConditionForeach" resultType="com.stone.mybatis.bean.Employee">
		select * from tbl_employee where id in 
		<!-- 
			collection:指定要遍历的集合 
				list 类型的参数会特殊处理封装在 map 里,map 的 key 就叫 list
			item:将当前遍历出的元素赋值给指定的变量
			separator:每个元素之间的分隔符
			open:遍历出所有结果拼接一个开始的字符
			close:遍历出所有结果拼接一个结束的字符
			index:
				遍历 list 的时候表示的是索引,item 就是当前值,
				遍历 map 的时候表示的是 map 的 key,item 就是 map 的值
			#{变量名}就能取出变量的值,也就是当前遍历出的元素
		-->
		<foreach collection="ids" item="item_id" separator="," open="(" close=")">
			#{item_id}
		</foreach>
	</select>

测试:

	@Test
	public void test18() throws IOException {
		// 1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		// 2、获取 SqlSession 对象,不会自动提交
		SqlSession openSession = sqlSessionFactory.openSession();

		try {
			EmployeeMapperDynamicSQL mapper = openSession.getMapper(EmployeeMapperDynamicSQL.class);
			List<Employee> empsByConditionForeach = mapper.getEmpsByConditionForeach(Arrays.asList(1,3));
			for (Employee employee : empsByConditionForeach) {
				System.out.println(employee);
			}
			
		} finally {
			openSession.close();
		}
	}

输出:

2018-12-14 17:01:07,852 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.getEmpsByConditionForeach] - ==>  Preparing: select * from tbl_employee where id in ( ? , ? ) 
2018-12-14 17:01:07,882 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.getEmpsByConditionForeach] - ==> Parameters: 1(Integer), 3(Integer)
2018-12-14 17:01:07,902 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.getEmpsByConditionForeach] - <==      Total: 2
Employee [id=1, lastName=Admin, email=tom@stone.com, gender=0]
Employee [id=3, lastName=jerry, email=jerry@stone.com, gender=1]

MySQL foreach

接口方法:

package com.stone.mybatis.dao;

import java.util.List;

import org.apache.ibatis.annotations.Param;

import com.stone.mybatis.bean.Employee;

public interface EmployeeMapperDynamicSQL {
	
	public List<Employee> getEmpsByConditionIf(Employee employee);
	
	public List<Employee> getEmpsByConditionTrim(Employee employee);
	
	public List<Employee> getEmpsByConditionChoose(Employee employee);
	
	public void updateEmp(Employee employee);
	
	public List<Employee> getEmpsByConditionForeach(@Param("ids")List<Integer> ids);
	
	public void addEmps(@Param("emps")List<Employee> emps);
}

对应 SQL:

	<!-- 批量保存 -->
	<!-- public void addEmps(@Param("emps")List<Employee> emps); -->
	<!-- MySQL 下批量保存,可以 foreach 遍历,MySQL支持 values(),(),() 语法 -->
	<insert id="addEmps">
		insert into tbl_employee(last_name,email,gender,d_id) 
		values
		<foreach collection="emps" item="emp" separator=",">
			(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
		</foreach>
	</insert>
	
	<!-- 这种方式需要数据库连接属性 allowMultiQueries=true;这种分号分隔多个 SQL 可以用于其他的批量操作 -->
	<!-- <insert id="addEmps">
		<foreach collection="emps" item="emp" separator=";">
			insert into tbl_employee(last_name,email,gender,d_id) 
			values(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
		</foreach>
	</insert> -->

测试:

	@Test
	public void test19() throws IOException {
		// 1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		// 2、获取 SqlSession 对象,不会自动提交
		SqlSession openSession = sqlSessionFactory.openSession();

		try {
			EmployeeMapperDynamicSQL mapper = openSession.getMapper(EmployeeMapperDynamicSQL.class);
			List<Employee> emps = new ArrayList<>();
			emps.add(new Employee(null, "smith", "smith@stone.com", "1", new Department(1)));
			emps.add(new Employee(null, "allen", "allen@stone.com", "0", new Department(1)));
			mapper.addEmps(emps);
			openSession.commit();
		} finally {
			openSession.close();
		}
	}

输出:

2018-12-14 17:33:09,263 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.addEmps] - ==>  Preparing: insert into tbl_employee(last_name,email,gender,d_id) values (?,?,?,?) , (?,?,?,?) 
2018-12-14 17:33:09,292 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.addEmps] - ==> Parameters: smith(String), smith@stone.com(String), 1(String), 1(Integer), allen(String), allen@stone.com(String), 0(String), 1(Integer)
2018-12-14 17:33:09,294 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.addEmps] - <==    Updates: 2

Oracle foreach

对应 SQL:

	<!-- 
		Oracle 数据库批量保存 
		1、不支持 values(),(),()
		2、Oracle 支持的批量方式
			2.1、多个 insert 放在 begin ... end 里面
				begin
				  insert into emp(empno,ename) values(emp_seq.nextval,'test01');
				  insert into emp(empno,ename) values(emp_seq.nextval,'test02');
				end;
			2.2、利用中间表
				insert into emp(empno,ename)
					select emp_seq.nextval,ename from(
						select 'test_a_01' ename from dual
						union
						select 'test_a_02' ename from dual
						union
						select 'test_a_03' ename from dual
				)
	-->
	<insert id="addEmps" databaseId="oracle">
		<!-- Oracle 第 1 种批量方式 -->
		<!-- <foreach collection="emps" item="emp" open="begin" close="end;">
			insert into emp(empno,ename) values(emp_seq.nextval,#{emp.lastName});
		</foreach> -->
		
		<!-- Oracle 第 2 种批量方式 -->
		insert into emp(empno,ename)
			select emp_seq.nextval,ename from(
				<foreach collection="emps" item="emp" separator="union">
					select #{emp.lastName} ename from dual
				</foreach>
			)
	</insert>

测试:

	@Test
	public void test19() throws IOException {
		// 1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		// 2、获取 SqlSession 对象,不会自动提交
		SqlSession openSession = sqlSessionFactory.openSession();

		try {
			EmployeeMapperDynamicSQL mapper = openSession.getMapper(EmployeeMapperDynamicSQL.class);
			List<Employee> emps = new ArrayList<>();
			emps.add(new Employee(null, "smith", "smith@stone.com", "1", new Department(1)));
			emps.add(new Employee(null, "allen", "allen@stone.com", "0", new Department(1)));
			mapper.addEmps(emps);
			openSession.commit();
		} finally {
			openSession.close();
		}

输出:

2018-12-17 11:15:16,293 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.addEmps] - ==>  Preparing: insert into emp(empno,ename) select emp_seq.nextval,ename from( select ? ename from dual union select ? ename from dual ) 
2018-12-17 11:15:16,666 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.addEmps] - ==> Parameters: smith(String), allen(String)
2018-12-17 11:15:16,689 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.addEmps] - <==    Updates: 2

内置参数

接口方法:

package com.stone.mybatis.dao;

import java.util.List;

import org.apache.ibatis.annotations.Param;

import com.stone.mybatis.bean.Employee;

public interface EmployeeMapperDynamicSQL {
	
	public List<Employee> getEmpsByConditionIf(Employee employee);
	
	public List<Employee> getEmpsByConditionTrim(Employee employee);
	
	public List<Employee> getEmpsByConditionChoose(Employee employee);
	
	public void updateEmp(Employee employee);
	
	public List<Employee> getEmpsByConditionForeach(@Param("ids")List<Integer> ids);
	
	public void addEmps(@Param("emps")List<Employee> emps);
	
	public List<Employee> getEmpsTestInnerParameter(Employee employee);
}

对应 SQL:

	<!-- 
		2 个内置参数:
		不只是方法传递过来的参数可以被用来判断,取值等,MyBatis 默认还有 2 个内置参数:
		_parameter:代表整个参数
			单个参数:_parameter 就是这个参数
			多个参数:参数会被封装为一个 map;_parameter 就是代表这个 map
		_databaseId:如果配置了 databaseIdProvider 标签:
			_databaseId:就是代表当前数据库别名
	 -->
	 <!-- public List<Employee> getEmpsTestInnerParameter(Employee employee); -->
	 <select id="getEmpsTestInnerParameter" resultType="com.stone.mybatis.bean.Employee">
	 	<if test="_databaseId=='mysql'">
	 		select * from tbl_employee
	 		<if test="_parameter!=null">
	 			where last_name=#{_parameter.lastName}
	 		</if>
	 	</if>
	 	<if test="_databaseId=='oracle'">
	 		select empno id,ename last_name,gender,email from emp
	 		<if test="_parameter!=null">
	 			where ename=#{_parameter.lastName}
	 		</if>
	 	</if>
	 </select>

测试:

	@Test
	public void test20() throws IOException {
		// 1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		// 2、获取 SqlSession 对象,不会自动提交
		SqlSession openSession = sqlSessionFactory.openSession();

		try {
			EmployeeMapperDynamicSQL mapper = openSession.getMapper(EmployeeMapperDynamicSQL.class);
			List<Employee> list = mapper.getEmpsTestInnerParameter(new Employee());
			for (Employee employee : list) {
				System.out.println(employee);
			}
		} finally {
			openSession.close();
		}
	}

输出:

2018-12-17 11:50:25,812 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.getEmpsTestInnerParameter] - ==>  Preparing: select empno id,ename last_name,gender,email from emp where ename=? 
2018-12-17 11:50:26,012 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.getEmpsTestInnerParameter] - ==> Parameters: null
2018-12-17 11:50:26,197 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.getEmpsTestInnerParameter] - <==      Total: 0

SQL 绑定

对应 SQL:

	 <select id="getEmpsTestInnerParameter" resultType="com.stone.mybatis.bean.Employee">
	 	<!-- bind:可以将 OGNL 表达式的值绑定到一个变量中,方便后来引用这个变量的值 -->
	 	<bind name="_lastName" value="'%'+lastName+'%'"/>
	 	<if test="_databaseId=='mysql'">
	 		select * from tbl_employee
	 		<if test="_parameter!=null">
	 			where last_name like #{_lastName}
	 		</if>
	 	</if>
	 	<if test="_databaseId=='oracle'">
	 		select empno id,ename last_name,gender,email from emp
	 		<if test="_parameter!=null">
	 			where ename=#{_parameter.lastName}
	 		</if>
	 	</if>
	 </select>

测试:

	@Test
	public void test21() throws IOException {
		// 1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		// 2、获取 SqlSession 对象,不会自动提交
		SqlSession openSession = sqlSessionFactory.openSession();

		try {
			EmployeeMapperDynamicSQL mapper = openSession.getMapper(EmployeeMapperDynamicSQL.class);
			Employee employee2 = new Employee();
			employee2.setLastName("e");
			List<Employee> list = mapper.getEmpsTestInnerParameter(employee2);
			for (Employee employee : list) {
				System.out.println(employee);
			}
		} finally {
			openSession.close();
		}
	}

输出:

2018-12-17 13:18:07,078 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.getEmpsTestInnerParameter] - ==>  Preparing: select * from tbl_employee where last_name like ? 
2018-12-17 13:18:07,106 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.getEmpsTestInnerParameter] - ==> Parameters: %e%(String)
2018-12-17 13:18:07,137 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapperDynamicSQL.getEmpsTestInnerParameter] - <==      Total: 4
Employee [id=3, lastName=jerry, email=jerry@stone.com, gender=1]
Employee [id=4, lastName=jerry, email=jerry@stone.com, gender=1]
Employee [id=10, lastName=allen, email=allen@stone.com, gender=0]
Employee [id=12, lastName=allen, email=allen@stone.com, gender=0]

对于模糊查询来说,还是建议将查询条件写到代码中。

SQL 片段

对应 SQL:

	<!-- 
		sql 标签:抽取可重用的 SQL 片段,方便后面引用
		1、经常将要查询的列名,或者插入用的列名抽取出来方便引用
		2、include 标签来引用已经抽取的 SQL
		3、include 还可以自定义一些 property,sql 标签内部就能使用自定义的属性,取值的正确方式为:${prop}
	-->
	<sql id="insertColumn">
		<if test="_databaseId=='mysql'">
			last_name,email,gender,d_id,${testColumn}
		</if>
		<if test="_databaseId=='oracle'">
			ename,email,gender,deptno
		</if>
	</sql>
	<!-- <insert id="addEmps">
		insert into tbl_employee(
		引用外部定义的 SQL
		<include refid="insertColumn">
			<property name="testColumn" value="abc"/>
		</include>
		)
		values
		<foreach collection="emps" item="emp" separator=",">
			(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
		</foreach>
	</insert> -->

缓存

MyBatis 系统中默认定义了两级缓存:一级缓存和二级缓存。

  • 默认情况下,只有一级缓存(SqlSession 级别的缓存,也称为本地缓存)开启。
  • 二级缓存需要手动开启和配置,是基于 namespace 级别的缓存。
  • 为了提高扩展性,MyBatis 定义了缓存接口 Cache。可以通过实现 Cache 接口来自定义二级缓存。

缓存的使用顺序:

  • 先二级缓存
  • 再一级缓存
  • 最后数据库

一级缓存

测试:

	/**
	 * 两级缓存:
	 * 一级缓存(本地缓存):SqlSession 级别的缓存,一级缓存一直开启的。SqlSession 级别的一个 map
	 *   与数据库同一次会话期间查询到的数据会放在本地缓存中。
	 *   以后如果需要获取相同的数据,直接从缓存中获取,没必要再去查询数据库。
	 *   一级缓存失效情况(没有使用到当前的一级缓存情况,效果就是还需要再向数据库发出查询)
	 *   	1、SqlSession 不同。
	 *   	2、SqlSession 相同,查询条件不同。
	 *   	3、SqlSession 相同,两次查询之间执行了增删改。
	 *   	4、SqlSession 相同,手动清除了一级缓存
	 *   	
	 *   
	 * 二级缓存(全局缓存):	
	 * @throws IOException 
	 */
	@Test
	public void testFirstLevelCache() throws IOException {
		SqlSession openSession = getSqlSessionFactory().openSession();
		try {
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			Employee employee = mapper.getEmpById(1);
			System.out.println(employee);
			//openSession.clearCache();
			Employee employee2 = mapper.getEmpById(1);
			System.out.println(employee2);
			System.out.println(employee==employee2);
		} finally {
			openSession.close();
		}
	}

二级缓存

POJO:

package com.stone.mybatis.bean;

public class Employee implements Serializable {
	private static final long serialVersionUID = 1L;
	private Integer id;
	private String lastName;
	private String email;
	private String gender;
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getLastName() {
		return lastName;
	}
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public String getGender() {
		return gender;
	}
	public void setGender(String gender) {
		this.gender = gender;
	}
	@Override
	public String toString() {
		return "Employee [id=" + id + ", lastName=" + lastName + ", email=" + email + ", gender=" + gender + "]";
	}
}

全局配置:

	<settings>
		<setting name="mapUnderscoreToCamelCase" value="true"/>
		<setting name="jdbcTypeForNull" value="NULL"/>
		<!-- 显式的指定每个需要更改的配置的值,即使是默认的。防止版本更新带来的问题 -->
		<setting name="cacheEnabled" value="true"/>
		<setting name="lazyLoadingEnabled" value="true"/>
		<setting name="aggressiveLazyLoading" value="false"/>
	</settings>

Mapper 配置:

	<!-- 
 		eviction:缓存的回收策略,默认是 LRU
 		flushInterval:缓存刷新间隔,多长时间清空一次,默认不清空,设置一个毫秒值
 		readOnly:是否只读,
 			true:只读,MyBatis 认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。为了加快获取速度,直接就会将数据在缓存中的引用交给用户,不安全,速度快
 			false:非只读,默认。MyBatis 认为获取的数据可能会被修改,会利用序列化&反序列化的技术克隆一份新的数据给用户,安全,速度慢
 		size:缓存存放多少元素
 		type:指定自定义缓存的全类名,实现Cache接口即可
 	 -->
 	<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache>

测试:

	/**
	 * 两级缓存:
	 * 一级缓存(本地缓存):SqlSession 级别的缓存,一级缓存一直开启的。SqlSession 级别的一个 map
	 *   与数据库同一次会话期间查询到的数据会放在本地缓存中。
	 *   以后如果需要获取相同的数据,直接从缓存中获取,没必要再去查询数据库。
	 *   一级缓存失效情况(没有使用到当前的一级缓存情况,效果就是还需要再向数据库发出查询)
	 *   	1、SqlSession 不同。
	 *   	2、SqlSession 相同,查询条件不同。
	 *   	3、SqlSession 相同,两次查询之间执行了增删改。
	 *   	4、SqlSession 相同,手动清除了一级缓存
	 *   	
	 *   
	 * 二级缓存(全局缓存):基于 namespace 级别的缓存,一个 namespace 对应一个二级缓存。
	 * 工作机制:
	 * 	1、一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中。
	 * 	2、如果会话关闭,一级缓存中的数据会被保存到二级缓存中。新的会话查询信息就可以参照二级缓存。
	 * 	3、不同 namespace 查询的数据会放在自己对应的缓存中(map)
	 * 	4、数据会从二级缓存中获取
	 * 	5、查出的数据都会被默认先放在一级缓存中,只有会话提交或者关闭后,一级缓存中的数据才会转移到二级缓存中
	 * 
	 * 使用步骤:
	 * 	1、开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>
	 * 	2、在 mapper.xml 中配置使用二级缓存:<cache></cache>
	 * 	3、POJO 需要实现序列化接口
	 * 
	 * 和缓存有关的设置/属性:
	 * 	1、cacheEnabled=true。如果为 false,则关闭二级缓存,一级缓存不受影响
	 * 	2、每个 select 标签都有 useCache="true" 属性,如果为 false,则不使用二级缓存,一级缓存不受影响
	 * 	3、每个增删改标签都有 flushCache="true" 属性,表示增删改执行完成后就会清空一级缓存和二级缓存
	 * 	4、每个 select 标签都有 flushCache="false",如果为 true,每次查询之后都会清空缓存
	 *	5、openSession.clearCache() 只是清除当前 session 的一级缓存,二级缓存不受影响
	 *	6、localCacheScope:本地缓存作用域,默认为 session,当前会话的所有数据保存在会话缓存中。如果设置为 statement,则禁用一级缓存
	 * 	
	 * @throws IOException 
	 */
	@Test
	public void testFirstLevelCache() throws IOException {
		SqlSession openSession = getSqlSessionFactory().openSession();
		try {
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			Employee employee = mapper.getEmpById(1);
			System.out.println(employee);
			//openSession.clearCache();
			Employee employee2 = mapper.getEmpById(1);
			System.out.println(employee2);
			System.out.println(employee==employee2);
		} finally {
			openSession.close();
		}
	}
	
	@Test
	public void testSecondLevelCache() throws IOException {
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
		SqlSession openSession = sqlSessionFactory.openSession();
		SqlSession openSession2 = sqlSessionFactory.openSession();
		try {
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
			
			Employee employee = mapper.getEmpById(1);
			System.out.println(employee);
			openSession.close();
			
			//第二次查询是从二级缓存中拿到的数据,并没有发送新的 SQL
			Employee employee2 = mapper2.getEmpById(1);
			System.out.println(employee2);
			openSession2.close();
			
		} finally {
			
		}
	}

输出:

2018-12-18 13:29:28,198 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper] - Cache Hit Ratio [com.stone.mybatis.dao.EmployeeMapper]: 0.0
2018-12-18 13:29:28,207 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.getEmpById] - ==>  Preparing: select * from tbl_employee where id = ? 
2018-12-18 13:29:28,234 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.getEmpById] - ==> Parameters: 1(Integer)
2018-12-18 13:29:28,256 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.getEmpById] - <==      Total: 1
Employee [id=1, lastName=Admin, email=tom@stone.com, gender=0]
2018-12-18 13:29:28,328 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper] - Cache Hit Ratio [com.stone.mybatis.dao.EmployeeMapper]: 0.5
Employee [id=1, lastName=Admin, email=tom@stone.com, gender=0]

整合 Spring

导入 JAR 包

创建 Dynamic Web Project,在 https://github.com/mybatis/spring/releases,http://tomcat.apache.org/download-taglibs.cgi#Standard-1.2.5open in new window 下载整合包,将所有 JAR 包拷贝到 /WebContent/WEB-INF/lib 目录下。

ant-1.9.6.jar
ant-launcher-1.9.6.jar
aopalliance-1.0.jar
asm-5.2.jar
aspectjweaver-1.9.2.jar
c3p0-0.9.5.2.jar
cglib-2.2.2.jar
cglib-3.2.5.jar
commons-logging-1.2.jar
ehcache-3.6.1.jar
ehcache-core-2.6.8.jar
javassist-3.22.0-GA.jar
log4j-1.2.17.jar
log4j-api-2.3.jar
log4j-core-2.3.jar
mchange-commons-java-0.2.11.jar
mybatis-3.4.6.jar
mybatis-ehcache-1.0.3.jar
mybatis-spring-1.3.2.jar
mysql-connector-java-5.1.23-bin.jar
ognl-3.1.16.jar
slf4j-api-1.7.25.jar
slf4j-log4j12-1.7.25.jar
spring-aop-4.3.2.RELEASE.jar
spring-aspects-4.3.2.RELEASE.jar
spring-beans-4.3.2.RELEASE.jar
spring-context-4.3.2.RELEASE.jar
spring-context-support-4.3.2.RELEASE.jar
spring-core-4.3.2.RELEASE.jar
spring-data-commons-1.13.3.RELEASE.jar
spring-data-redis-1.8.3.RELEASE.jar
spring-expression-4.3.2.RELEASE.jar
spring-instrument-4.3.2.RELEASE.jar
spring-instrument-tomcat-4.3.2.RELEASE.jar
spring-jdbc-4.3.2.RELEASE.jar
spring-jms-4.3.2.RELEASE.jar
spring-messaging-4.3.2.RELEASE.jar
spring-orm-4.3.2.RELEASE.jar
spring-oxm-4.3.2.RELEASE.jar
spring-test-4.3.2.RELEASE.jar
spring-tx-4.3.2.RELEASE.jar
spring-web-4.3.2.RELEASE.jar
spring-webmvc-4.3.2.RELEASE.jar
spring-webmvc-portlet-4.3.2.RELEASE.jar
spring-websocket-4.3.2.RELEASE.jar
taglibs-standard-compat-1.2.5.jar
taglibs-standard-impl-1.2.5.jar
taglibs-standard-jstlel-1.2.5.jar
taglibs-standard-spec-1.2.5.jar

配置 db.properties

mysql.username=stone
mysql.password=stone
mysql.driverClass=com.mysql.jdbc.Driver
mysql.jdbcUrl=jdbc:mysql://192.168.8.137:3306/mybatis

oracle.username=hr
oracle.password=hr
oracle.driverClass=oracle.jdbc.driver.OracleDriver
oracle.jdbcUrl=jdbc:oracle:thin:@192.168.247.133:1521/rac

配置 log4j.properties

log4j.rootLogger=DEBUG,Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.logger.org.apache=INFO
log4j.logger.org.apache=INFO
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

创建 Bean

package com.stone.mybatis.bean;

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;
	private Department dept;
	public Employee() {
		super();
	}
	public Employee(String lastName, String email, String gender) {
		super();
		this.lastName = lastName;
		this.email = email;
		this.gender = gender;
	}
	public Employee(Integer id, String lastName, String email, String gender) {
		super();
		this.id = id;
		this.lastName = lastName;
		this.email = email;
		this.gender = gender;
	}
	public Employee(Integer id, String lastName, String email, String gender, Department dept) {
		super();
		this.id = id;
		this.lastName = lastName;
		this.email = email;
		this.gender = gender;
		this.dept = dept;
	}
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getLastName() {
		return lastName;
	}
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public String getGender() {
		return gender;
	}
	public void setGender(String gender) {
		this.gender = gender;
	}
	@Override
	public String toString() {
		return "Employee [id=" + id + ", lastName=" + lastName + ", email=" + email + ", gender=" + gender + "]";
	}
	public Department getDept() {
		return dept;
	}
	public void setDept(Department dept) {
		this.dept = dept;
	}
}

创建 Dao

package com.stone.mybatis.dao;

import java.util.List;

import com.stone.mybatis.bean.Employee;

public interface EmployeeMapper {

	public Employee getEmpById(Integer id);
	
	public List<Employee> getEmps();

}

配置 mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.stone.mybatis.dao.EmployeeMapper">
	
	<!-- public Employee getEmpById(Integer id);	 -->
 	<select id="getEmpById" resultType="com.stone.mybatis.bean.Employee">
		select * from tbl_employee where id = #{id}
	</select> 
	
	<!-- public List<Employee> getEmps(); -->
	<select id="getEmps" resultType="com.stone.mybatis.bean.Employee">
		select * from tbl_employee
	</select>
	
</mapper>

配置 mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

	<settings>
		<setting name="mapUnderscoreToCamelCase" value="true"/>
		<setting name="jdbcTypeForNull" value="NULL"/>
		<!-- 显式的指定每个需要更改的配置的值,即使是默认的。防止版本更新带来的问题 -->
		<setting name="cacheEnabled" value="true"/>
		<setting name="lazyLoadingEnabled" value="true"/>
		<setting name="aggressiveLazyLoading" value="false"/>
	</settings>

	<databaseIdProvider type="DB_VENDOR">
		<!-- 为不同的数据库厂商起别名 -->
		<property name="MySQL" value="mysql"/>
		<property name="Oracle" value="oracle"/>
		<property name="SQL Server" value="sqlserver"/>
	</databaseIdProvider>

</configuration>

配置 web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
  <display-name>Mybatis_06_ssm</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  
  <!-- Spring 配置:needed for ContextLoaderListener -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:applicationContext.xml</param-value>
	</context-param>

	<!-- Bootstraps the root web application context before servlet initialization -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	
	<!-- SpringMVC 配置 -->
	<!-- The front controller of this Spring Web application, responsible for handling all application requests -->
	<servlet>
		<servlet-name>spring</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<!-- Map all requests to the DispatcherServlet for handling -->
	<servlet-mapping>
		<servlet-name>spring</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	  
</web-app>

配置 spring-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

	<!-- SpringMVC 只是控制网站跳转逻辑 -->
	<!-- 只扫描控制器 -->
	<context:component-scan base-package="com.stone.mybatis" use-default-filters="false">
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
	</context:component-scan>
	
	<!-- 视图解析器 -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/pages/"></property>
		<property name="suffix" value=".jsp"></property>
	</bean>
	
	<mvc:annotation-driven></mvc:annotation-driven>
	<mvc:default-servlet-handler/>
	
</beans>

配置 applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
	xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">

	<!-- Spring 希望管理所有的业务逻辑 -->
	<context:component-scan base-package="com.stone.mybatis">
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
	</context:component-scan>
	
	<!-- 引入数据库的配置文件 -->
	<context:property-placeholder location="classpath:db.properties"/>
	
	<!-- Spring 用来控制业务逻辑,数据源,事物控制,aop -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="jdbcUrl" value="${mysql.jdbcUrl}"></property>
		<property name="driverClass" value="${mysql.driverClass}"></property>
		<property name="user" value="${mysql.username}"></property>
		<property name="password" value="${mysql.password}"></property>
	</bean>
	
	<!-- Spring 事物管理 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- 开启基于注解的事物 -->
	<tx:annotation-driven transaction-manager="transactionManager"/>
	
	<!-- 
		整合Mybatis 
		目的:
			1、Spring 管理所有组件,mapper 的实现类:Service==>Dao @Autowired 自动注入 mapper
			2、Spring 用来管理事物:Spring 声明式事物
	-->
	<!-- 创建出 sqlSessionFactory 对象 -->
	<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource"></property>
		<!-- configLocation 指定全局配置文件的位置 -->
		<property name="configLocation" value="classpath:mybatis-config.xml"></property>
		<!-- mapperLocations 指定 mapper 文件的位置 -->
		<property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"></property>
	</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>

	<!-- 
		扫描所有的 mapper 接口的实现,让这些 mapper 能够自动注入 
		base-package 指定 mapper 接口的包名
	-->
	<mybatis-spring:scan base-package="com.stone.mybatis.dao"/>

</beans>

创建 Service

package com.stone.mybatis.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.stone.mybatis.bean.Employee;
import com.stone.mybatis.dao.EmployeeMapper;

@Service
public class EmployeeService {	
	@Autowired
	private EmployeeMapper employeeMapper;
	
	public List<Employee> getEmps(){
		return employeeMapper.getEmps();
	}
}

创建 Controller

package com.stone.mybatis.controller;

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.stone.mybatis.bean.Employee;
import com.stone.mybatis.service.EmployeeService;

@Controller
public class EmployeeController {
	
	@Autowired
	EmployeeService employeeService;
	
	@RequestMapping("/getemps")
	public String emps(Map<String, Object> map) {
		List<Employee> emps = employeeService.getEmps();
		map.put("allEmps", emps);
		return "list";
	}
}

创建 index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a href="getemps">查询所有员工</a>
</body>
</html>

创建 list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>员工列表</title>
</head>
<body>
	<table>
			<tr>
				<td>id</td>
				<td>lastName</td>
				<td>gender</td>
				<td>email</td>
			</tr>
		<c:forEach items="${allEmps}" var="emp">
			<tr>
				<td>${emp.id }</td>
				<td>${emp.lastName }</td>
				<td>${emp.gender }</td>
				<td>${emp.email }</td>
			</tr>
		</c:forEach>
	</table>
</body>
</html>

MyBatis Generator

使用 MyBatis Generator 可以根据数据库表结构生成对应的 Javabean,Dao 接口以及 mapper 配置文件。

创建 generatorConfig.xml 配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
	<!-- MyBatis3Simple:生成简单版本 -->
	<!-- MyBatis3:生成动态 SQL -->
	<context id="MySQLTables" targetRuntime="MyBatis3">
		<!-- jdbc 连接 -->
		<jdbcConnection connectionURL="jdbc:mysql://192.168.8.137:3306/mybatis" driverClass="com.mysql.jdbc.Driver" password="stone" userId="stone" />
		
		<!-- Javabean 生成策略 -->
		<javaModelGenerator targetPackage="com.stone.mybatis.bean" targetProject="mybatis_07_mbg\src">
			<property name="enableSubPackages" value="true"/>
			<property name="trimStrings" value="true"/>
		</javaModelGenerator>
		
		<!-- 生成 mapper.xml 文件 -->
		<sqlMapGenerator targetPackage="com.stone.mybatis.dao" targetProject="mybatis_07_mbg\conf">
			<property name="enableSubPackages" value="true"/>
		</sqlMapGenerator>
		
		<!-- 生成 mapper.xml 对应 client,也就是 mapper 接口 -->
		<javaClientGenerator targetPackage="com.stone.mybatis.dao" targetProject="mybatis_07_mbg\src" type="XMLMAPPER">
			<property name="enableSubPackages" value="true"/>
		</javaClientGenerator>
		
		<!-- 指定要逆向分析哪些表,根据表创建 Javabean -->	
		<table tableName="tbl_dept" domainObjectName="Department"></table>
		<table tableName="tbl_employee" domainObjectName="Employee"></table>	
	</context>
</generatorConfiguration>

在开发工具中安装 MyBatis Generator 插件,右键点击 generatorConfig.xml,点击 Run As,选择 Run MyBatis Generator,生成 Javabean,Dao 接口以及 mapper 配置文件。

测试:

	@Test
	public void testMyBatis3Simple() throws IOException {
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
		SqlSession openSession = sqlSessionFactory.openSession();
		try {
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			List<Employee> selectAll = mapper.selectByExample(null);
			for (Employee employee : selectAll) {
				System.out.println(employee.getId());
			}
		} finally {
			openSession.close();
		}
	}
	
	
	@Test
	public void testMyBatis3() throws IOException {
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
		SqlSession openSession = sqlSessionFactory.openSession();
		try {
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			//xxxExample 就是封装查询条件
			//1.查询所有
			List<Employee> emps = mapper.selectByExample(null);
			for (Employee employee : emps) {
				System.out.println(employee.getId());
			}
			//2.查询员工名字中有 e 字母的,和员工性别是 1 的,或者 email 中有字母 e
			//封装员工查询条件的 employeeExample
			EmployeeExample employeeExample = new EmployeeExample();
			//创建一个 Criteria,这个 Criteria 就是拼装查询条件的
			Criteria criteria = employeeExample.createCriteria();
			criteria.andLastNameLike("%e%");
			criteria.andGenderEqualTo("1");

			Criteria criteria2 = employeeExample.createCriteria();
			criteria2.andEmailLike("%e%");
			employeeExample.or(criteria2);

			List<Employee> selectByExample = mapper.selectByExample(employeeExample);
			for (Employee employee : selectByExample) {
				System.out.println(employee.getId());
			}
		} finally {
			openSession.close();
		}
	}

输出:

2018-12-19 13:15:22,371 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.selectByExample] - ==>  Preparing: select id, last_name, gender, email, d_id from tbl_employee 
2018-12-19 13:15:22,436 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.selectByExample] - ==> Parameters: 
2018-12-19 13:15:22,487 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.selectByExample] - <==      Total: 11
1
3
4
5
6
7
8
9
10
11
12
2018-12-19 13:15:22,509 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.selectByExample] - ==>  Preparing: select id, last_name, gender, email, d_id from tbl_employee WHERE ( last_name like ? and gender = ? ) or( email like ? ) 
2018-12-19 13:15:22,510 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.selectByExample] - ==> Parameters: %e%(String), 1(String), %e%(String)
2018-12-19 13:15:22,516 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.selectByExample] - <==      Total: 11
1
3
4
5
6
7
8
9
10
11
12

分页插件

https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.mdopen in new window 下载 JAR 包。

pagehelper-5.1.8.jar
jsqlparser-1.3.jar

或者使用 Maven 引入依赖。

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>最新版本</version>
</dependency>

接口方法:

package com.stone.mybatis.dao;

import java.util.List;

import com.stone.mybatis.bean.Employee;

public interface EmployeeMapper {
	public Employee getEmpById(Integer id);
	public List<Employee> getEmps();
}

对应 SQL:

	<!-- public List<Employee> getEmps(); -->
	<select id="getEmps" resultType="com.stone.mybatis.bean.Employee">
		select id,last_name lastName,gender,email from tbl_employee
	</select>

测试:

	@Test
	public void test02() throws IOException {
		//1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
		
		//2、获取 SqlSession 对象
		SqlSession openSession = sqlSessionFactory.openSession();
		
		//3、获取接口的实现类对象,会为接口自动创建一个代理对象,代理对象去执行增删改查方法
		try {
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			Page<Object> page = PageHelper.startPage(1, 2);
			List<Employee> emps = mapper.getEmps();
			for (Employee employee : emps) {
				System.out.println(employee);
			}
			System.out.println("当前页码:" + page.getPageNum());
			System.out.println("总记录数:" + page.getTotal());
			System.out.println("每页的记录数:" + page.getPageSize());
			System.out.println("总页码:" + page.getPages());
			
			//传入要连续显示多少页
			PageInfo<Employee> pageInfo = new PageInfo<>(emps, 5);
			System.out.println("当前页码:" + pageInfo.getPageNum());
			System.out.println("总记录数:" + pageInfo.getTotal());
			System.out.println("每页的记录数:" + pageInfo.getPageSize());
			System.out.println("总页码:" + pageInfo.getPages());
			System.out.println("是否第一页:" + pageInfo.isIsFirstPage());
			System.out.println("是否最后一页:" + pageInfo.isIsLastPage());
			System.out.println("连续显示的页码:");
			int[] nums = pageInfo.getNavigatepageNums();
			for (int i : nums) {
				System.out.println(i);
			}
		} finally {
			openSession.close();
		}
	}

输出:

2018-12-19 15:58:23,277 [main] DEBUG [SQL_CACHE] - Cache Hit Ratio [SQL_CACHE]: 0.0
2018-12-19 15:58:23,321 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.getEmps_COUNT] - ==>  Preparing: SELECT count(0) FROM tbl_employee 
2018-12-19 15:58:23,352 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.getEmps_COUNT] - ==> Parameters: 
2018-12-19 15:58:23,392 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.getEmps_COUNT] - <==      Total: 1
2018-12-19 15:58:23,395 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.getEmps] - ==>  Preparing: select id,last_name lastName,gender,email from tbl_employee LIMIT ? 
2018-12-19 15:58:23,396 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.getEmps] - ==> Parameters: 2(Integer)
2018-12-19 15:58:23,407 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.getEmps] - <==      Total: 2
Employee [id=1, lastName=Admin, email=tom@stone.com, gender=0]
Employee [id=3, lastName=jerry, email=jerry@stone.com, gender=1]
当前页码:1
总记录数:11
每页的记录数:2
总页码:6
当前页码:1
总记录数:11
每页的记录数:2
总页码:6
是否第一页:true
是否最后一页:false
连续显示的页码:
1
2
3
4
5

批量 SqlSession

接口方法:

package com.stone.mybatis.dao;

import java.util.List;

import com.stone.mybatis.bean.Employee;

public interface EmployeeMapper {
	public Employee getEmpById(Integer id);
	public List<Employee> getEmps();
	public Long addEmp(Employee employee);
}

对应 SQL:

	<!-- public Long addEmp(Employee employee); -->
	<insert id="addEmp">
		insert into tbl_employee(last_name,gender,email)
		values(#{lastName},#{gender},#{email})
	</insert>

测试:

	@Test
	public void testBatch() throws IOException {
		// 1、获取 SQLSessionFactory 对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		// 2、获取可以执行批量操作的 SqlSession 对象
		SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
		long start = System.currentTimeMillis();
		try {
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			for (int i = 0; i < 100; i++) {
				mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0, 5), "b", "1"));
			}
			openSession.commit();
			long end = System.currentTimeMillis();
			//批量:预编译 SQL1次 ==> 设置参数100次 ==> 执行1次
			//Parameters: 112b7(String), 1(String), b(String)
			//非批量:预编译 SQL100次 ==> 设置参数100次==> 执行100次
			System.out.println("执行时间:" + (end-start));
		} finally {
			openSession.close();
		}
	}

输出:

2018-12-19 17:01:44,708 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.addEmp] - ==>  Preparing: insert into tbl_employee(last_name,gender,email) values(?,?,?) 
2018-12-19 17:01:44,740 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.addEmp] - ==> Parameters: 71dfb(String), 1(String), b(String)
2018-12-19 17:01:44,741 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.addEmp] - ==> Parameters: 3eb75(String), 1(String), b(String)
2018-12-19 17:01:44,741 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.addEmp] - ==> Parameters: 602bf(String), 1(String), b(String)
2018-12-19 17:01:44,741 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.addEmp] - ==> Parameters: 700b2(String), 1(String), b(String)
2018-12-19 17:01:44,742 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.addEmp] - ==> Parameters: cac5d(String), 1(String), b(String)
2018-12-19 17:01:44,743 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.addEmp] - ==> Parameters: 198df(String), 1(String), b(String)
2018-12-19 17:01:44,743 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.addEmp] - ==> Parameters: eeb07(String), 1(String), b(String)
2018-12-19 17:01:44,744 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.addEmp] - ==> Parameters: eb4da(String), 1(String), b(String)
2018-12-19 17:01:44,744 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.addEmp] - ==> Parameters: f0d38(String), 1(String), b(String)
2018-12-19 17:01:44,744 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.addEmp] - ==> Parameters: 52db1(String), 1(String), b(String)
执行时间:825

存储过程

Oracle数据库创建存储过程:

create or replace procedure hello_test(p_start in int,
                                       p_end   in int,
                                       p_count out int,
                                       p_emps  out sys_refcursor) as
begin
  select count(*) into p_count from emp;
  open p_emps for
    select *
      from (select rownum rn, e.* from emp e where rownum <= p_end)
     where rn >= p_start;
end hello_test;
/

封装存储过程的参数:

package com.stone.mybatis.bean;

import java.util.List;

public class OraclePage {
	private int start;
	private int end;
	private int count;
	private List<Employee> emps;
	public int getStart() {
		return start;
	}
	public void setStart(int start) {
		this.start = start;
	}
	public int getEnd() {
		return end;
	}
	public void setEnd(int end) {
		this.end = end;
	}
	public int getCount() {
		return count;
	}
	public void setCount(int count) {
		this.count = count;
	}
	public List<Employee> getEmps() {
		return emps;
	}
	public void setEmps(List<Employee> emps) {
		this.emps = emps;
	}
}

接口方法:

package com.stone.mybatis.dao;

import java.util.List;

import com.stone.mybatis.bean.Employee;
import com.stone.mybatis.bean.OraclePage;

public interface EmployeeMapper {
	public Employee getEmpById(Integer id);
	public List<Employee> getEmps();
	public Long addEmp(Employee employee);
	public void getPageByProcedure(OraclePage page);
}

全局配置:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	
	<properties resource="db.properties"></properties>
	
	<plugins>
		<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
	</plugins>
	
	<!-- 4、environments:可以配置多种环境,default 指定使用某种环境,可以达到快速切换 -->
	<environments default="oracle">
		<!-- 
			environment:配置一个具体的环境 ,id 表示当前环境的唯一标识
			必须有以下两个标签:
			transactionManager:事物管理器,
				type:指定事物管理器类型:JDBC(JdbcTransactionFactory)|MANAGED(ManagedTransactionFactory)
				自定义事物管理器:使用 TransactionFactory 接口,type 指定全类名
				一般交给 Spring 来进行事务管理
			dataSource:数据源;
				type:数据源类型:UNPOOLED|POOLED|JNDI
				自定义数据源:实现 DataSourceFactory 接口,type 是全类名
		-->
		<environment id="oracle">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${oracle.driverClass}" />
				<property name="url" value="${oracle.jdbcUrl}" />
				<property name="username" value="${oracle.username}" />
				<property name="password" value="${oracle.password}" />
			</dataSource>
		</environment>
		
		<environment id="mysql">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${mysql.driverClass}" />
				<property name="url" value="${mysql.jdbcUrl}" />
				<property name="username" value="${mysql.username}" />
				<property name="password" value="${mysql.password}" />
			</dataSource>
		</environment>
	</environments>
	
	<databaseIdProvider type="DB_VENDOR">
		<!-- 为不同的数据库厂商起别名 -->
		<property name="MySQL" value="mysql"/>
		<property name="Oracle" value="oracle"/>
		<property name="SQL Server" value="sqlserver"/>
	</databaseIdProvider>
	
	<!-- 写好的 SQL 映射文件 EmployeeMapper.xml 一定要注册到全局配置文件中 mybatis-config.xml" -->
	<mappers>
		<mapper resource="EmployeeMapper.xml" />
	</mappers>
		
</configuration>

对应 SQL:

	<!-- public void getPageByProcedure(); -->
	<!-- 
		使用 select 标签定义调用存储过程
		statementType="CALLABLE":表示要调用存储过程
	-->
	<select id="getPageByProcedure" statementType="CALLABLE" databaseId="oracle">
		{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.stone.mybatis.bean.Employee" id="PageEmp">
		<id column="empno" property="id"/>
		<result column="ename" property="lastName"/>
		<result column="gender" property="gender"/>
		<result column="email" property="email"/>
		<result column="deptno" property="deptno"/>
	</resultMap>

测试:

	/**
	 * 存储过程来实现 Oracle 分页
	 * @throws IOException 
	 */
	@Test
	public void testProcedure() throws IOException {
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
		SqlSession openSession = sqlSessionFactory.openSession();
		try {
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			OraclePage page = new OraclePage();
			page.setStart(1);
			page.setEnd(5);
			mapper.getPageByProcedure(page);
			System.out.println("总记录数:" + page.getCount());
			System.out.println("查出的数据数:" + page.getEmps().size());
			System.out.println("查出的数据:" + page.getEmps());
		} finally {
			openSession.close();
		}
	}

输出:

2018-12-20 09:50:58,314 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.getPageByProcedure] - ==>  Preparing: {call hello_test( ?, ?, ?, ? )} 
2018-12-20 09:50:58,569 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.getPageByProcedure] - ==> Parameters: 1(Integer), 5(Integer)
总记录数:51
查出的数据:5
查出的数据:[Employee [id=7369, lastName=SMITH, email=null, gender=1], Employee [id=7499, lastName=ALLEN, email=null, gender=1], Employee [id=7521, lastName=WARD, email=null, gender=1], Employee [id=7566, lastName=JONES, email=null, gender=1], Employee [id=7654, lastName=MARTIN, email=null, gender=1]]

枚举类型

默认处理

枚举类:

package com.stone.mybatis.bean;

public enum EmpStatus {
	LOGIN,LOGOUT,REMOVE
}

实体类:

package com.stone.mybatis.bean;

public class Employee {
	private Integer id;
	private String lastName;
	private String email;
	private String gender;
	private String deptno;
	//员工状态
	private EmpStatus empStatus = EmpStatus.LOGOUT;
	public Employee() {
		super();
	}
	public Employee(String lastName, String email, String gender) {
		super();
		this.lastName = lastName;
		this.email = email;
		this.gender = gender;
	}
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getLastName() {
		return lastName;
	}
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public String getGender() {
		return gender;
	}
	public void setGender(String gender) {
		this.gender = gender;
	}
	public String getDeptno() {
		return deptno;
	}
	public void setDeptno(String deptno) {
		this.deptno = deptno;
	}
	public EmpStatus getEmpStatus() {
		return empStatus;
	}
	public void setEmpStatus(EmpStatus empStatus) {
		this.empStatus = empStatus;
	}
	@Override
	public String toString() {
		return "Employee [id=" + id + ", lastName=" + lastName + ", email=" + email + ", gender=" + gender + "]";
	}
}

全局配置:

	<!-- 声明使用 EnumOrdinalTypeHandler 来处理枚举类,插入到数据库中的是枚举的索引值 -->
	<typeHandlers>
		<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.stone.mybatis.bean.EmpStatus"/>
	</typeHandlers>

对应 SQL:

	<!-- public Long addEmp(Employee employee); -->
	<insert id="addEmp" useGeneratedKeys="true" keyProperty="id">
		insert into tbl_employee(last_name,gender,email,empStatus)
		values(#{lastName},#{gender},#{email},#{empStatus})
	</insert>

测试:

	@Test
	public void testEnumUse() {
		EmpStatus login = EmpStatus.LOGIN;
		System.out.println("枚举的索引:" + login.ordinal());
		System.out.println("枚举的名字:" + login.name());
	}
	
	/**
	 * MyBatis 在处理枚举对象的时候默认保存的是枚举的名字,使用 EnumTypeHandler
	 * 改变使用:EnumOrdinalTypeHandler,插入枚举的索引值
	 * @throws IOException
	 */
	@Test
	public void testEnum() throws IOException {
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
		SqlSession openSession = sqlSessionFactory.openSession();
		try {
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			Employee employee = new Employee("test_enum", "enum@stone.com", "1");
			mapper.addEmp(employee);
			System.out.println("保存成功" + employee.getId());
			openSession.commit();
		} finally {
			openSession.close();
		}
	}

输出:

2018-12-20 10:50:44,261 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.addEmp] - ==>  Preparing: insert into tbl_employee(last_name,gender,email,empStatus) values(?,?,?,?) 
2018-12-20 10:50:44,289 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.addEmp] - ==> Parameters: test_enum(String), 1(String), enum@stone.com(String), 1(Integer)
2018-12-20 10:50:44,293 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.addEmp] - <==    Updates: 1
保存成功10356

自定义类型处理器

枚举类:

package com.stone.mybatis.bean;

/**
 * 希望数据库保存的是 100,200 这些状态码,而不是默认的 0,1 索引
 */
public enum EmpStatus {
	LOGIN(100,"用户登录"),LOGOUT(200,"用户登出"),REMOVE(300,"用户不存在");
	private Integer code;
	private String msg;
	private EmpStatus(Integer code, String msg) {
		this.code = code;
		this.msg = msg;
	}
	public Integer getCode() {
		return code;
	}
	public void setCode(Integer code) {
		this.code = code;
	}
	public String getMsg() {
		return msg;
	}
	public void setMsg(String msg) {
		this.msg = msg;
	}
	
	//按照状态码返回枚举对象
	public static EmpStatus getEmpStatusByCode(Integer code) {
		switch (code) {
		case 100:
			return LOGIN;
		case 200:
			return LOGOUT;
		case 300:
			return REMOVE;
		default:
			return LOGOUT;
		}
	}
}

全局配置:

	<!-- 声明使用 EnumOrdinalTypeHandler 来处理枚举类,插入到数据库中的是枚举的索引值 -->
	<typeHandlers>
		<!-- 1、配置自定义的 TypeHandler -->
		<typeHandler handler="com.stone.mybatis.typehandler.MyEnumEmpStatusTypeHandler" javaType="com.stone.mybatis.bean.EmpStatus"/>
		<!-- 
			2、也可以在处理某个字段的时候告诉 MyBatis 用什么类型的 TypeHandler
				保存:#{empStatus,typeHandler=xxx} 
				查询:<resultMap type="com.stone.mybatis.bean.Employee" id="MyEmp">
						<id column="id" property="id"/>
						<result column="empStatus" property="empStatus" typeHandler="com.stone.mybatis.typehandler.MyEnumEmpStatusTypeHandler"/>
					</resultMap>
				注意:如果在参数位置修改 TypeHandler,应该保证保存数据和查询数据用的 TypeHandler 是一样的
		-->
		
	</typeHandlers>

对应 SQL:

	<resultMap type="com.stone.mybatis.bean.Employee" id="MyEmp">
		<id column="id" property="id"/>
		<result column="empStatus" property="empStatus" typeHandler="com.stone.mybatis.typehandler.MyEnumEmpStatusTypeHandler"/>
	</resultMap>
	<select id="getEmpById" resultType="com.stone.mybatis.bean.Employee">
		select id,last_name lastName,gender,email,empStatus from tbl_employee where id = #{id}
	</select>

测试:

	@Test
	public void testEnumUse() {
		EmpStatus login = EmpStatus.LOGIN;
		System.out.println("枚举的索引:" + login.ordinal());
		System.out.println("枚举的名字:" + login.name());
		
		System.out.println("枚举的状态码:" + login.getCode());
		System.out.println("枚举的提示信息:" + login.getMsg());
	}
	
	/**
	 * MyBatis 在处理枚举对象的时候默认保存的是枚举的名字,使用 EnumTypeHandler
	 * 改变使用:EnumOrdinalTypeHandler,插入枚举的索引值
	 * @throws IOException
	 */
	@Test
	public void testEnum() throws IOException {
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
		SqlSession openSession = sqlSessionFactory.openSession();
		try {
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			Employee employee = new Employee("test_enum", "enum@stone.com", "1");
			mapper.addEmp(employee);
			System.out.println("保存成功" + employee.getId());
			openSession.commit();
		} finally {
			openSession.close();
		}
	}

输出:

2018-12-20 11:23:29,862 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.addEmp] - ==>  Preparing: insert into tbl_employee(last_name,gender,email,empStatus) values(?,?,?,?) 
2018-12-20 11:23:29,891 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.addEmp] - ==> Parameters: test_enum(String), 1(String), enum@stone.com(String), 200(String)
2018-12-20 11:23:29,894 [main] DEBUG [com.stone.mybatis.dao.EmployeeMapper.addEmp] - <==    Updates: 1
保存成功10359
上次编辑于:
贡献者: stonebox