一、MyBatis基本介绍
1.1 MyBatis历史
- MyBatis是Apache的一个开源项目iBatis,2010年6月这个项目由Apache Software Foundation 迁移到了Google Code,随着开发团队转投Google Code旗下, iBatis3.x 正式更名为MyBatis ,代码于2013年11月迁移到Github
- iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架
- iBatis提供的持久层框架包括SQL Maps和Data Access Objects(DAO)
1.2 MyBatis简介
- MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
- MyBatis 避免了几乎所有的JDBC代码和手动设置参数以及获取结果集
- MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的pojo映射成数据库中的记录
- Mybatis 是一个半自动的ORM(Object Relation Mapping)框架
二、MyBatis环境搭建(Mapper接口开发)
2.1 MyBatis下载
通过MyBatis网址,进入下载MyBatis包
2.2 MyBatis包的准备
log4j.jar
mybatis-3.5.6.jar
mysql-connector-java-5.1.37-bin.jar
2.3 导入log4j.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT"
class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />
</layout>
</appender>
<logger name="java.sql">
<level value="debug" />
</logger>
<logger name="org.apache.ibatis">
<level value="info" />
</logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>
2.4 创建核心配置文件
2.4.1 创建核心配置文件mybatis-config.xml
1. 创建jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm
jdbc.username=root
jdbc.password=123
2. 创建核心配置文件mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 1.引入资源文件jdbc.properties -->
<properties resource="jdbc.properties" />
<!-- 2.配置环境,默认使用mysql数据库 -->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
<!-- 3.引入映射文件 -->
<mappers>
<mapper resource="UserMapper.xml" />
</mappers>
</configuration>
2.4.2 核心配置文件的标签简介
在核心配置文件中,有一系列的标签,这里就来解释一下重要的几个标签
1. <properties>
标签——引入资源文件
a. 属性resource:在类路径下访问资源文件
b. 属性url:在网络路径或磁盘路径下访问资源文件
2. <settings>
标签——重要的调整设置,它们会改变 MyBatis 的运行时行为
<settings>
<!-- 将下划线映射成驼峰 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 是否查询所有数据 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
3. <typeAliases>
标签——别名设置
a. 内部标签<typeAlias>:为单个的java类型取别名
type:指定全限定名
alias:指定别名,默认类名即为别名
b. 内部标签<package>:批量取别名,为指定包下面的类取默认的别名
<typeAliases>
<!-- <typeAlias type="com.hkd.bean.Emp" alias="e"/> -->
<package name="com.hkd.bean"/>
</typeAliases>
4. <environments>
标签——设置连接数据库的环境
a. 属性default:设置默认使用的数据库环境,mybatis支持配置多个环境
b. 内部标签<environment>:设置某个具体的数据库环境
id:数据库环境的唯一标识
c. 内部标签<transactionManager>:事务管理
type:
取值为JDBC,则使用JDBC原生的事务管理方式,即提交和回滚都需要手动提交
取值还可以为MANAGED,了解即可
d. 内部标签<dataSource>:数据源
type:
UNPOOLED:不适用连接池
POOLED:使用连接池
JNDI:从应用服务器中获连接
5. <mappers>
标签——引入映射文件
a. 内部标签<mapper>:引入单个sql映射文件
b. 内部标签<package>:Mapper接口与sql映射文件必须同名同位置
2.5 创建数据库表和实体类
1. 创建数据库表
表名叫User,字段如下图
2. 创建实体类User
package com.hkd.bean;
public class User {
private Integer uid;
private String username;
private String password;
private Integer age;
private String sex;
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "User [uid=" + uid + ", username=" + username + ", password=" + password + ", age=" + age + ", sex="
+ sex + "]";
}
}
2.6 创建mapper接口,实现两个绑定
1. 创建mapper接口
package com.hkd.mapper;
import com.hkd.bean.User;
public interface UserMapper {
User getUserByUid(String uid);
}
2. 创建映射文件XxxMapper.xml,并实现两个绑定
- 第一个绑定:接口全限定名要和映射文件的 namespace 保持一致
- 第二个绑定:接口中方法名和SQL语句的 id 保持一致
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 绑定一:接口和映射文件的namespace绑定,体现在namespace的值是接口的全限定名 -->
<mapper namespace="com.hkd.mapper.UserMapper">
<!-- 绑定二:接口中的方法名和映射文件绑定,体现在sql语句的id是方法名 -->
<!--
<select>:定义查询语句
id:设置sql语句的唯一标识
resultType:结果类型,即实体类的全限定名
-->
<select id="getUserByUid" resultType="com.hkd.bean.User">
select * from user where uid = #{id}
</select>
</mapper>
注意:mapper标签的namespace命名空间:
- 随便写:不使用Mapper接口开发,就随便写
- 不能随便写:使用Mapper接口开发,不能随便写,必须写成接口的全限定名
2.7 进行测试(注意步骤)
- 读取配置文件,创建输入流对象
- 通过第一步,构建SqlSessionFactory对象
- 通过SqlSessionFactory对象,创建SqlSession
- 通过getMapper()获取接口的动态代理实现类
- 关闭SqlSession
package com.hkd.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.hkd.bean.User;
import com.hkd.mapper.UserMapper;
public class TestMybatis {
@Test
public void test() throws IOException {
//1. 读取配置文件,创建输入流对象
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//2. 通过第一步,构建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3. 通过SqlSessionFactory对象,创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//4. getMapper():会通过动态代理动态生成UserMapper的代理实现类
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserByUid("1");
System.out.println(user);
//5. 关闭SqlSession
sqlSession.close();
}
}
测试结果:
三、Mybatis映射文件
3.1 <select>元素及4种查询方式演示
<select>元素用于映射查询语句,示例如下:
<select id="getEmpByEid" resultType="com.hkd.bean.Emp">
select eid,ename,age,sex from emp where eid = #{eid}
</select>
- id属性:接口中方法名和SQL语句的 id 保持一致
- parameterType属性:传递参数的类型。一般不写,因为MyBatis会自动匹配类型
- resultType属性:结果集的封装类型
1. 创建Emp实体类,并创建对应数据库
package com.hkd.bean;
public class Emp {
private Integer eid;
private String ename;
private Integer age;
private String sex;
public Integer getEid() {
return eid;
}
public void setEid(Integer eid) {
this.eid = eid;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Emp [eid=" + eid + ", ename=" + ename + ", age=" + age + ", sex=" + sex + "]";
}
public Emp(Integer eid, String ename, Integer age, String sex) {
super();
this.eid = eid;
this.ename = ename;
this.age = age;
this.sex = sex;
}
public Emp() {
super();
// TODO Auto-generated constructor stub
}
}
2. 创建EmpMapper接口,写四种查询方式的方法
注意第四种方式,查询多条数据保存到map中,需要使用@MapKey注解指定谁是map中的key
package com.hkd.mapper;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.MapKey;
import com.hkd.bean.Emp;
public interface EmpMapper {
// 4种查询的方式:
//1. 根据eid查询一个员工信息【查询单条数据】
Emp getEmpByEid(String eid);
//2. 查询所有员工信息【查询多条数据返回对一个对象集合】
List<Emp> getAllEmp();
// 3. 以map集合获取一个员工信息【查询单条数据返回一个map集合】
Map<String, Object> getEmpMapByEid(String eid);
// 4. 以map集合获取所有员信息【查询多条数据,返回一个map集合】
@MapKey("eid") //指定使用对象的哪个属性作为map中的key
Map<String, Object> getAllEmpMap();
}
3. 编写EmpMapper.xml映射文件
注意 resultType 的写法
<?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.hkd.mapper.EmpMapper">
<!-- 查询 -->
<!-- 1. Emp getEmpByEid(String eid); -->
<select id="getEmpByEid" resultType="com.hkd.bean.Emp">
select eid,ename,age,sex from emp where eid = #{eid}
</select>
<!-- 2. List<Emp> getAllEmp(); -->
<!-- resultType是结果集的封装类型,而最后保存的List集合是在结果集之后,故这里应该是Emp类型 -->
<select id="getAllEmp" resultType="com.hkd.bean.Emp">
select eid,ename,age,sex from emp
</select>
<!-- 3. Map<String, Object> getEmpMapByEid(String eid); -->
<select id="getEmpMapByEid" resultType="java.util.HashMap">
select eid,ename,age,sex from emp where eid = #{eid}
</select>
<!-- 4. Map<String, Object> getAllEmpMap(); -->
<select id="getAllEmpMap" resultType="com.hkd.bean.Emp">
select eid,ename,age,sex from emp
</select>
</mapper>
4. 测试4中查询方式
package com.hkd.test;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import com.hkd.mapper.EmpMapper;
public class TestCRUD {
@Test
public void testCRUD() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
// SqlSession sqlSession = sqlSessionFactory.openSession();//需要手动处理事务
SqlSession sqlSession = sqlSessionFactory.openSession(true);//自动处理事务
EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
// 测试查询的4种方式:
// 1. 测试:根据eid获取员工信息
Emp emp = empMapper.getEmpByEid("3");
System.out.println(emp);
//2. 测试:获取所有的员工信息
List<Emp> list = empMapper.getAllEmp();
System.out.println(list);
//3. 测试:根据eid获取员工信息并返回map集合
Map<String, Object> empMapByEid = empMapper.getEmpMapByEid("1");
System.out.println(empMapByEid);
//4. 测试:查询所有员工信息并返回map集合
Map<String, Object> allEmpMap = empMapper.getAllEmpMap();
System.out.println(allEmpMap);
}
}
3.2 <insert>元素及获取自动生成的主键
<insert>元素用于映射插入语句,示例如下:
<insert id="addEmp" useGeneratedKeys="true" keyProperty="eid">
insert into emp values(null,#{ename},#{age},#{sex})
</insert>
- id属性:接口中方法名和SQL语句的 id 保持一致
- useGeneratedKeys属性:可以使用自动生成的主键
- keyProperty属性:将自动生成的主键赋值给传递过来的参数的哪一个属性
- 最后会返回一个表示插入记录数的整数
- 单元测试:返回结果是新增主键已赋值给了Emp对象的eid属性
// 测试:添加员工信息
Emp emp = new Emp(null,"www",23,"Woman");
empMapper.addEmp(emp);
sqlSession.commit(); //提交事务
System.out.println(emp.getEid());
3.3 <update>元素和<delete>元素
分别用于映射更新和删除语句,示例如下:
<update id="updateEmp">
update emp set ename = #{ename}, age = #{age}, sex =#{sex} where eid = #{eid}
</update>
<delete id="deleteEmp">
delete from emp where eid = #{eid}
</delete>
- 会返回一个影响记录条数的整数
四、MyBatis参数的传递和获取
4.1 参数传递的两种方式
4.1.1 ${ }
- 相当于Statement,必须使用字符串拼接的方式操作SQL,一定要之注意引号问题
- 程序看到的是:
insert into emp values(null, admin, 23, 男)
4.1.2 #{ }
- 相当于PreparedStatement,可以使用通配符操作SQL,因为在String赋值时,会自动加上单引号,因此不需要考虑单引号问题
- 程序看到的是:
insert into emp values(null, ?, ?, ?)
注意:建议使用#{ }
方式,在特殊情况下使用${ }
,例如模糊查询和批量删除
4.2 不同类型参数${}和#{}的不同取值方式
1. 传递参数为单个String类型或基本数据类型和其包装类
- #{ }:可以以任意的名字获取参数值
- ${ }:只能写
${value}
或${_parameter}
来获取
2. 传递参数为JavaBean
- #{ }:通过属性名直接获取属性值
- ${ }:通过属性名直接获取属性值,但要注意单引号问题
3. 传递多个参数时,MyBatis会默认将这些参数放在map集合中
两种方式:
- 键为 0,1,2,3,…,N-1,以参数为值
- 键为 param1,param2,param3,…,paramN,以参数为值
- #{ }:#{0}, #{1}, #{2}…或者 #{param1}, #{param2}, #{param3}…
- ${ }:${param1}, ${param2},${param3}…,但要注意单引号问题
4. 传递Map参数
- #{ }:通过键的名字直接获取值
- ${ }:通过键的名字直接获取值,但要注意单引号问题
5. 命名参数
- 可以通过
@Param("key")
的方式为map集合指定键的名字 - 例如:
Emp getEmpByEidAndEname(@Param("eid") String eid, @Param("ename") String ename)
- #{ }和${ }都可以通过键的名字直接获取值,但要注意${ }的单引号问题
6. 传递参数为List或Array类型,MyBatis会将List或Array放在map中
List以list为键,Array以array为键
五、MyBatis的关联映射
5.1 多对一自定义映射
5.1.1 数据库表和实体类以及接口的准备
1. 创建Emp员工表和Dept部门表
2. 分别创建Emp实体类和Dept实体类
请自行创建。
3. 创建EmpDeptMapper接口,接口中有方法
方法:查询员工,并查询员工所在的部门
package com.hkd.mapper;
import java.util.List;
import com.hkd.bean.Emp;
public interface EmpDeptMapper {
// 查询员工,并查询员工所在的部门
List<Emp> getAllEmpDept();
}
5.1.2 多对一自定义映射的三种方法
<resultMap>
:自定义映射,处理复杂的表关系<id>
:封装主键列
* 属性column:设置结果集中的列名
* 属性property:封装的对象的属性名<result>
:封装普通列
* 属性column:设置结果集中的列名
* 属性property:封装的对象的属性名
5.1.2.1 方法一——id&result(一般不用)
(接5.1.1)4. 创建EmpDeptMapper.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.hkd.mapper.EmpDeptMapper">
<!-- 自定义映射 -->
<resultMap type="com.hkd.bean.Emp" id="empMap">
<id column="eid" property="eid"/>
<result column="ename" property="ename"/>
<result column="age" property="age"/>
<result column="sex" property="sex"/>
<!-- 支持级联属性操作 -->
<result column="did" property="dept.did"/>
<result column="dname" property="dept.dname"/>
</resultMap>
<!-- List<Emp> getAllEmpDept(); -->
<select id="getAllEmpDept" resultMap="empMap">
SELECT e.eid,e.ename,e.age,e.sex,e.did,d.dname FROM emp e LEFT JOIN dept d ON e.did = d.did
</select>
</mapper>
5.1.2.2 方法二——<association>标签
<association>
标签也是写在resultMap标签中,通过association能帮我们创建一个javaType中所写类型的对象- 运行原理(以下面的例子为例来讲解):
- 通过association 标签创建了一个com.hkd.bean.Dept类的对象;
- 这个对象有两个属性,分别是did和dname,体现在<id>、<result>的property属性值是did、dname;
- 现在要给这个对象赋值。已知column属性值是查询语句查出来的值,就把查询出的did赋值给com.hkd.bean.Dept类的did属性,dname赋值给dnam属性;
- 创建对象后,赋完值以后,再把对象赋值给association的property属性值dept
(接5.1.1)4. 创建EmpDeptMapper.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.hkd.mapper.EmpDeptMapper">
<resultMap type="com.hkd.bean.Emp" id="empMap">
<id column="eid" property="eid"/>
<result column="ename" property="ename"/>
<result column="age" property="age"/>
<result column="sex" property="sex"/>
<association property="dept" javaType="com.hkd.bean.Dept">
<id column="did" property="did"/>
<result column="dname" property="dname"/>
</association>
</resultMap>
<!-- List<Emp> getAllEmpDept(); -->
<select id="getAllEmpDept" resultMap="empMap">
SELECT e.eid,e.ename,e.age,e.sex,e.did,d.dname FROM emp e LEFT JOIN dept d ON e.did = d.did
</select>
</mapper>
5.1.2.3 方法三——分步查询
分步查询步骤:
- 要查员工emp的所有属性包括dept,就可以分为两步查询;
- 第一步是先根据eid查询一条数据emp,这时其dept属性是部门表的did字段值1、2、3…
- 第二步就是将上面查到的部门属性dept的1、2等等的值,用这个值去dept表中查询对应的dname值
- 这就是分步查询
(接5.1.1)4. 创建DeptMapper接口
package com.hkd.mapper;
import com.hkd.bean.Dept;
public interface DeptMapper {
Dept getDeptByDid(String did);
}
5. 创建DeptMapper.xml文件
这个文件的作用是第二步的查询dept的功能
<?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.hkd.mapper.DeptMapper">
<!-- Dept getDeptByDid(String did); -->
<select id="getDeptByDid" resultType="com.hkd.bean.Dept">
select did,dname from dept where did = #{did}
</select>
</mapper>
6. 创建EmpDeptMapper.xml映射文件
这里要使用到
<association>
标签的属性:
- select属性:分步查询的sql的id,即接口的全限定名.方法名或者namespace.SQL的id
- column属性:分步查询条件,注意此条件必须是从数据库查询的字段
- 下面例子column属性传递的是一个参数did,如果需要传递多个参数,则需要传递一个map集合,语法如下: {k1=v1, k2=v2…}
<?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.hkd.mapper.EmpDeptMapper">
<!-- 分布查询 -->
<resultMap type="com.hkd.bean.Emp" id="empMapStep">
<id column="eid" property="eid"/>
<result column="ename" property="ename"/>
<result column="age" property="age"/>
<result column="sex" property="sex"/>
<!-- select是指DeptMapper接口的全限定名.方法名-->
<!-- column是指第一步emp查到的did,根据这个did作为参数传递给getDeptByDid方法去查询dept的值 -->
<association property="dept" select="com.hkd.mapper.DeptMapper.getDeptByDid" column="did" />
</resultMap>
<!-- Emp getEmpStep(String eid); -->
<select id="getEmpStep" resultMap="empMapStep">
select eid,ename,age,sex,did from emp where eid = #{eid}
</select>
</mapper>
5.1.2.4 分步查询的延迟加载
- 在分步查询的基础上,可以使用延迟加载来提升查询的效率
- 有时候结果只需要分步查询第一步查询出来的结果,不需要第二步的结果,这时候开启延迟加载,就能仅进行第一步,不进行第二步
- 只需要在核心配置文件的
<settings>
中进行如下的配置
<settings>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 是否查询所有数据 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
5.2 一对多自定义映射
5.2.1 数据库表和实体类以及接口的准备
- 数据库表和实体类与5.1.1相似,现在我们只需要知道什么是一对多以及怎样处理一对多的映射即可
- 现在要求查询一个部门的所有员工,显然这是一个一对多的问题
- 首先要给Dept实体类中添加Emp对象类型的属性,添加set/get和toString方法
5.2.2 一对多自定义映射的两种方法
5.2.2.1 方法一——<collection>标签
- <collection>标签也是写在resultMap里的标签
- 专门用来处理一对多和多对多关系的标签
- ofType:指集合中的类型,不需要指定javaType
<?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.hkd.mapper.EmpDeptMapper">
<!-- 一对多 -->
<resultMap type="com.hkd.bean.Dept" id="deptMap">
<id column="did" property="did" />
<result column="dname" property="dname"/>
<collection property="emps" ofType="com.hkd.bean.Emp">
<id column="eid" property="eid"/>
<result column="ename" property="ename"/>
<result column="age" property="age"/>
<result column="sex" property="sex"/>
</collection>
</resultMap>
<!-- Dept getDeptEmpsByDid(String did); -->
<select id="getDeptEmpsByDid" resultMap="deptMap">
select d.did,d.dname,e.eid,e.ename,e.age,e.sex from dept d left join emp e on d.did = e.did where d.did = #{did}
</select>
</mapper>
5.2.2.2 方法二——分步查询
跟多对一的分步查询一样,也需要两步走,这里不再详述
这里要使用到
<collection>
标签的属性:
- select属性:分步查询的sql的id,即接口的全限定名.方法名或者namespace.SQL的id
- column属性:分步查询条件,注意此条件必须是从数据库查询的字段
- 下面例子column属性传递的是一个参数did,如果需要传递多个参数,则需要传递一个map集合,语法如下: {k1=v1, k2=v2…}
<!-- 分布查询 -->
<!-- Dept getOnlyDeptByDid(String did); -->
<select id="getOnlyDeptByDid" resultMap="deptMapStep">
select did,dname from dept where did = #{did}
</select>
<resultMap type="com.hkd.bean.Dept" id="deptMapStep">
<id column="did" property="did" />
<result column="dname" property="dname"/>
<collection property="emps" select="com.hkd.mapper.EmpDeptMapper.getEmpListByDid" column="did"/>
</resultMap>
<!-- List<Emp> getEmpListByDid(String did); -->
<select id="getEmpListByDid" resultType="com.hkd.bean.Emp">
select eid,ename,age,sex from emp where did = #{did}
</select>
5.3 拓展:association或collection的fetchType属性
- 在<association> 和<collection>标签中都可以设置fetchType,指定本次查询是否要使用延迟加载
- 默认为 fetchType=“lazy” ,如果本次的查询不想使用延迟加载,则可设置为fetchType=“eager”
- fetchType可以灵活的设置查询是否需要使用延迟加载,而不需要因为某个查询不想使用延迟加载将全局的延迟加载设置关闭
六、动态SQL
6.1 <if>元素
- 用于完成简单的判断,类似于Java中的if语句
<if test=''>
:通过test表达式,进行条件判断,进而拼接sql语句- 当第一个条件不成立,第二个条件成立的时候,为了避免sql语句多出and关键字,就在where之后添加一个恒成立条件1=1
<select id="getEmpListByMore" resultType="com.hkd.bean.Emp">
select eid,ename,age,sex,did from emp where 1=1
<if test="eid != null">
and eid = #{eid}
</if>
<if test="ename != null and ename != ''">
and ename = #{ename}
</if>
<if test="age != null and age != ''">
and age = #{age}
</if>
<if test="sex == 'Man' or sex == 'Woman'">
and sex = #{sex}
</if>
</select>
</mapper>
6.2 <where>元素
- 我们知道,if语句为了使sql语句正确,在where后面增加了一个恒成立条件1=1
- 现在不想使用这个恒成立条件,就可以用<where>标签将if语句包裹住
<select id="getEmpListByMore" resultType="com.hkd.bean.Emp">
select eid,ename,age,sex,did from emp
<where>
<if test="eid != null">
and eid = #{eid}
</if>
<if test="ename != null and ename != ''">
and ename = #{ename}
</if>
<if test="age != null and age != ''">
and age = #{age}
</if>
<if test="sex == 'Man' or sex == 'Woman'">
and sex = #{sex}
</if>
</where>
</select>
6.3 <trim>元素
<trim>
标签是用来截取和拼接字符串的- 属性prefix——在操作的sql语句之前加入某些内容
- 属性suffix——在操作的sql语句之后加入某些内容
- 属性prefixOverrides——在操作的sql语句前删除某些内容
- 属性suffixOverrides——在操作的sql语句后删除某些内容
<select id="getEmpListByMore" resultType="com.hkd.bean.Emp">
select eid,ename,age,sex,did from emp
<!--既要去掉and又要去掉or,这时需要写成and|or -->
<trim prefix="where" suffixOverrides="and|or">
<if test="eid != null">
and eid = #{eid}
</if>
<if test="ename != null and ename != ''">
and ename = #{ename}
</if>
<if test="age != null and age != ''">
and age = #{age} or
</if>
<if test="sex == 'Man' or sex == 'Woman'">
and sex = #{sex}
</if>
</trim>
</select>
6.4 <set>元素
<set>
主要是用于解决修改操作中SQL语句中可能多出逗号的问题- 这个标签使可以用<trim>标签代替的,所以使用频率不多
6.5 <choose>、<when>和<otherwise>元素
<choose>
标签主要是用于分支判断,类似于java中的if-else if…else语句,只会满足所有分支中的一个
<!-- List<Emp> getEmpListByChoose(Emp emp); -->
<select id="getEmpListByChoose" resultType="com.hkd.bean.Emp">
select eid,ename,age,sex from emp
where
<choose>
<when test="eid != null">
eid = #{eid}
</when>
<when test="ename != null and ename != ''">
ename = #{ename}
</when>
<when test="age != null and age != ''">
age = #{age}
</when>
<otherwise>
sex = #{sex}
</otherwise>
</choose>
</select>
</mapper>
6.6 <sql>元素
- sql 标签是用于抽取可重用的sql片段
- 将相同的、使用频繁的SQL片段抽取出来单独定义,方便多次引用
<!--设置某个sql片段 -->
<sql id="aaa">
.....
</sql>
<!-- 访问某个sql片段 -->
<include refid="aaa"/>
6.7 <foreach>元素
<foreach>
主要是用于循环迭代- 属性:
collection
:指定要遍历的集合或数组 - 属性:
item
:设置别名 - 属性:
open
:设置循环体的开始内容 - 属性:
close
:设置循环体的结束内容 - 属性:
separator
:设置每一次循环之间的分隔符 - 属性:
index
:若遍历的使list集合,index代表下标;若遍历map集合,index代表键
6.8 批量删除的两种方式
6.8.1 拼接字符串方式
1. mapper.xml映射文件
<!-- void deleteMoreEmp(String eids);-->
<delete id="deleteMoreEmp">
<!-- 这里使用${value}的写法 -->
delete from emp where eid in (${value})
</delete>
2. 测试
@Test
public void testDeleteMore() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
//拼接字符串
String eids = "12,13,14";
mapper.deleteMoreEmp(eids);
}
6.8.2 <foreach>标签方式
1. mapper.xml映射文件
<!-- void deleteMoreByList(List<Integer> eids); -->
<delete id="deleteMoreByList">
delete from emp where eid in
<foreach collection="list" item="eid" separator="," open="(" close=")">
#{eid}
</foreach>
</delete>
2. 测试
@Test
public void testDeleteMore() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
//foreach方式批量删除
List<Integer> eids = new ArrayList<Integer>();
eids.add(12);
eids.add(13);
eids.add(14);
mapper.deleteMoreByList(eids);
七、MyBatis缓存机制
7.1 缓存机制简介
- MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制,可以极大的提升查询效率
- MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启;二级缓存需要手动开启和配置,他是基于namespace级别的缓存
- 为了提高扩展性,MyBatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存
7.2 一级缓存的使用
- 一级缓存(local cache),即本地缓存,作用域默认为sqlSession。当 Session flush 或 close 后,该 Session 中的所有 Cache 将被清空
- 本地缓存不能被关闭,但可以调用 clearCache() 来清空本地缓存, 或者改变缓存的作用域
- 在mybatis3.1之后, 可以配置本地缓存的作用域
- 一级缓存的工作机制:同一次会话期间只要查询过的数据都会保存在当前SqlSession的一个Map中,key: hashCode+查询的SqlId+编写的sql查询语句+参数
7.3 一级缓存失效的几种情况
- 不同的SqlSession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两次查询期间执行了任何一次增删改操作
- 同一个SqlSession两次查询期间手动清空了缓存
7.4 二级缓存
- 二级缓存(second level cache),全局作用域缓存
- 二级缓存默认不开启,需要手动配置
- MyBatis提供二级缓存的接口以及实现,缓存实现要求POJO实现Serializable接口
- 二级缓存在 SqlSession 关闭或提交之后才会生效