# 4. MyBatis 映射文件
# 4.1 insert
# 4.1.1 示例
<insert id="addEmp" parameterType="com.hedon.mybatis.bean.Employee">
insert into tbl_employee(last_name,gender,email) values (#{lastName},#{gender},#{email})
</insert>
# 4.1.2 获取主键自增的值
<insert>
标签中有 2 个属性:
- useGeneratedKeys:默认为 false,设置为 true 的话就可以取到自增的主键;
- keyProperty:指明这个自增得到的主键要放在哪个属性上
<insert id="addEmp" parameterType="com.hedon.mybatis.bean.Employee"
useGeneratedKeys="true"
keyProperty="id">
insert into tbl_employee(last_name,gender,email) values (#{lastName},#{gender},#{email})
</insert>
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee employee = new Employee();
employee.setLastName("AAAAABC");
employee.setGender("男");
employee.setEmail("!234");
System.out.println("添加前 ID:"+employee.getId()); //添加前 ID:null
mapper.addEmp(employee);
System.out.println("添加后 ID:"+employee.getId()); //添加后 ID:10
# 补充
原生 JDBC 的 java.sql.Statement 中有 getGeneratedKeys() 方法可以获取。
# 4.2 delete
<delete id="deleteEmp" parameterType="java.lang.Integer">
delete from tbl_employee where id = #{id}
</delete>
# 4.3 update
<update id="updateEmp" parameterType="com.hedon.mybatis.bean.Employee">
update tbl_employee set last_name = #{lastName}, gender=#{gender},email=#{email} where id = #{id}
</update>
# △ 补充:insert、update、delete 标签的相关属性:
# 4.4 select
# 4.4.1 返回单个对象
- resultType:即为返回对象的类型。
Employee getEmployeeById(Integer id);
<select id="getEmployeeById" resultType="com.hedon.mybatis.bean.Employee">
select * from tbl_employee where id = #{id}
</select>
# 4.4.2 返回集合
- resultType:是 List
的 T 的类型,MyBatis 会自动为我们将对象封装到集合中。
List<Employee> getEmps();
<select id="getEmps" resultType="com.hedon.mybatis.bean.Employee">
select * from tbl_employee
</select>
# 4.4.3 返回一条记录的 Map
- resultType:map。
- MyBatis 会为我们自动封装,key 就是列名,value 就是属性的值。
Map<String,Object> getEmployeeMapById(Integer id);
<select id="getEmployeeMapById" resultType="map">
select * from tbl_employee where id = #{id}
</select>
结果:
{gender=1, last_name=hedon, id=1, email=171725713@qq.com}
# 4.4.4 返回多条记录的 Map
- resultType:这个时候 resultType 还是写 Employee 的全限定类名。
这个时候问题就来了,Map 中的 value 已经解决了,那 key 是什么呢?这里就需要用到 @MapKey
来指定用 Employee 中的哪一个属性来作为 key。
@MapKey("id")
Map<String,Employee> getEmployeesMap();
<select id="getEmployeesMap" resultType="com.hedon.mybatis.bean.Employee">
select * from tbl_employee
</select>
结果:
{1=Employee{id=1, lastName='hedon', gender=1, email='171725713@qq.com'},
9=Employee{id=9, lastName='AAAAABC', gender=?, email='!234'},
10=Employee{id=10, lastName='AAAAABC', gender=?, email='!234'}}
# 4.5 参数处理
# 4.5.1 单个参数
单个参数的时候 MyBatis 不会做特殊处理,直接 #{随意值}
就可以取出参数的值。
# 4.5.2 多个参数
# 4.5.2.1 错误示例
<select id="getEmpByIdAndName" resultType="com.hedon.mybatis.bean.Employee">
select * from tbl_employee where id = #{id} and last_name = #{lastName}
</select>
- 报错信息:
Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [arg1, arg0, param1, param2]
原因分析:
面对多个参数的时候,MyBatis 会做特殊处理。它会将多个参数封装成一个 map。这个 map 有以下规则:
- key:param1,param2,……,paramN 或者 arg0,arg1,arg2,……argn
- value:参数值
# 4.5.2.2 正确样例
<select id="getEmpByIdAndName" resultType="com.hedon.mybatis.bean.Employee">
select * from tbl_employee where id = #{arg0} and last_name = #{arg1}
</select>
# 4.5.3 命名参数
所谓命名参数也就是自己来指定上述 map 的 key,而不用 MyBatis 的默认规则。
首先要在 Mapper 接口类方法传参中加入 @Param 注解:
public interface EmployeeMapper {
Employee getEmpByIdAndName(@Param("id") Integer id, @Param("lastName") String lastName);
}
这样就可以直接指定 key 了:
<select id="getEmpByIdAndName" resultType="com.hedon.mybatis.bean.Employee">
select * from tbl_employee where id = #{id} and last_name = #{lastName}
</select>
# 4.5.4 POJO
如果是传对象的话,可以直接用 #{属性名}
来取出对象中对应的属性值。
public class Employee {
private Integer id;
private String lastName;
private String gender;
private String email;
}
<update id="updateEmp" parameterType="com.hedon.mybatis.bean.Employee">
update tbl_employee set last_name = #{lastName}, gender=#{gender},email=#{email} where id = #{id}
</update>
一个思考题:下面的传参该如何取值(根据 id 和 lastName 来查询)?
Employee getEmp(Integer id, Employee employee);
因为都没有加 @Param,所以会被 MyBatis 自动封装到 Map 中,且 key 为 param1,param2,……,paramn。但是因为 Employee 是一个对象,所以我们要取出 employee 里面的 lastName 的话,只需要用 “.” 就可以了。综上,如下:
select * from tbl_employee where id = #{param1} and last_name = #{param2.lastName}
# 4.5.5 Map
如果没有现成的 POJO,那么我们可以传 map,然后用 #{key}
取出 map 中的值。
<select id="getEmpByMap" resultType="com.hedon.mybatis.bean.Employee">
select * from tbl_employee where id = #{id} and last_name = #{lastName}
</select>
测试:
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("id",1);
map.put("lastName","hedon");
Employee empByMap = mapper.getEmpByMap(map);
System.out.println(empByMap); //Employee{id=1, lastName='hedon', gender=1, email='171725713@qq.com'}
# 4.5.6 DTO
每次都要封装一个 map 的话就太麻烦了,我们可以直接封装成一个类,这个类我们一般称为 DTO(Data Transfer Object),是专门用来做数据传输的,原理如上,不赘述。
# 4.5.7 集合类
集合类的话 MyBatis 也会进行特殊处理,也是把传入的 list 或 array 存入 map 中,但是这个时候 key 就有特殊规定了:
参数 | key |
---|---|
Connection | connection |
List | list |
Array | array |
如下:
# 接口方法
Employee getEmployeesByIds(List<\Integer> ids)
# Mapper 映射文件
我们现在要取出 ids 中的第一个值
select * from tbl_employee where id = #{list[0]}
# 4.5.8 传参封装 Map 的源码解读
待补。
# 4.5.9 # 和 $ 的区别
最大的区别就在于 填充
的方式不同。我们可以直接举个例子来看看它输出的 SQL 语句的不同:
<select id="getEmpByMap" resultType="com.hedon.mybatis.bean.Employee">
select * from tbl_employee where id = ${id} and last_name = #{lastName}
</select>
以上我们采用 $ 来取出 id 值,用 # 来取出 lastName 的值。运行 getEmpByMap() 方法,查看 SQL 语句:
我们可以清楚的看到:
- $ 取出来的值会直接放到 SQL 语句中,也就是传统的拼接方式。
- # 取出来的值不会直接放到 SQL 语句中,SQL 语句会在要填充值的地方用 ? 来占位,然后再把 # 取出来的值赋值进去。
# SQL 注入攻击
$ 取出来的值直接放到 SQL 语句中,也就是下面这种方式
method(String id){
String sql = "select * from tbl_employee where id = '" + id + "'";
}
如果 id = “' or 1=1 or id='”
那么拼接起来就是 select * from tbl_employee where id = '' or 1=1 or id=''
里面有一个 1=1,又用的是 or,所以会取出所有的 employee 的信息,这样就会造成数据泄露。
# 什么时候用 $ ?
在`分表查询`的时候,如下按照年份进行分表,那么我们就需要这么查询:
select * from ${year}_salary where xxx;
数据库表名是不能用 #{} 来取的,又因为原生的 JDBC 也不支持占位符的方式,所以我们都只能用拼接的方式来进行分表查询。
# 4.5.10 # 取值时指定参数相关规则
在用 # 进行取值的时候,还可以规定参数的一些规则,如:
- javaType
- jdbcType
- mode:存储过程
- numericScale
- resultMap
- typeHandler
- jdbcTypeName
- expression(未来准备支持的功能)
# 重点记录一下 jdbcType
在我们数据为 null 的时候,有些数据库是不能识别 MyBatis 对 null 的默认处理的。比如 Oracle,它会报错。
因为 MyBatis 默认会将 null 的数据设置为 JdbcType.OTHER 类型的,这个类型 Orcle 不能识别,而 MySQL 可以识别。
这里有两种解决途径:
① 主动指定 null 类型的处理,如:#{email,jdbcType=NULL}
② 在 MyBatis 全局配置文件中设置:<setting name="jdbcTypeForNull" value="NULL"></setting>
# 4.6 属性映射
# 4.6.1 自动映射
# 4.6.1.1 autoMappingBehavior
在 MyBatis 的全局配置的 setting
中有一个属性 autoMappingBehavior
,它的值是 PARTIAL
,默认开启自动映射的功能。唯一的要求是列名和 javaBean 属性名一致。
如果我们将 autoMappingBehavior
设置为 null
,就会关闭自动映射。
# 4.6.1.2 mapUnderscoreToCamelCase
在前面的基础上,如果我们的 POJO 属性命名符合驼峰命名法
,那我们也可以开启自动驼峰命名规则映射功能,即将 mapUnderscoreToCamelCase
设置为 true
。
# 4.6.2 自定义 ResultMap,实现自定义结果集映射
# 4.6.2.1 单个类,普通属性
- resultMap:自定义某个 javaBean 的封装规则
- type:自定义规则的 Java 类型
- id:resultMap 唯一的 id 索引
- column:指定哪一列(数据库中的)
- property:指定哪一个属性(javaBean 中的)
<resultMap id="map" type="com.hedon.mybatis.bean.Employee">
<!--指定 id 的话 MyBatis 底层会有优化处理-->
<id column="id" property="id"/>
<!--定义普通列封装规则-->
<result column="last_name" property="lastName"/>
<!--如果其他的列不写,则按默认规则封装,建议写全-->
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</resultMap>
<select id="getEmpById" resultMap="map">
select * from tbl_employee where id = #{id}
</select>
# 4.6.2.2 类中的属性是个对象 —— 级联查询
法一:用 association 表示级联查询
- property:Employee 类中的属性值
- javaType:property 是哪种类型的(不可省略)
<resultMap id="mapWithDept" type="com.hedon.mybatis.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<association property="department" javaType="com.hedon.mybatis.bean.Department">
<id column="id" property="id"/>
<result column="dept_name" property="departmentName"/>
</association>
</resultMap>
<select id="getEmpWithDept" resultMap="mapWithDept">
select e.id,e.last_name,e.email,e.gender,e.d_id,d.dept_name from tbl_employee e, tbl_dept d where e.d_id = d.id and e.id = #{id}
</select>
法二:用 “.” 表示级联查询
<resultMap id="mapWithDept" type="com.hedon.mybatis.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<result column="d_id" property="department.id"/>
<result column="dept_name" property="department.departmentName"/>
</resultMap>
<select id="getEmpWithDept" resultMap="mapWithDept">
select e.id,e.last_name,e.email,e.gender,e.d_id,d.dept_name from tbl_employee e, tbl_dept d where e.d_id = d.id and e.id = #{id}
</select>
# 4.6.2.3 分步查询
我们还可以先查询 Employee,得到 dId,再用 dId 去查询 Department 的信息:
<resultMap id="mapByStep" type="com.hedon.mybatis.bean.Employee">
<!--第一步:按照员工Id查询出员工信息-->
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<!--第二步:按照部门ID取查询部门信息
select:表明当前这个属性是根据哪个方法来查询得到的
column:传给另外一个方法的参数
-->
<association property="department"
select="com.hedon.mybatis.dao.DepartmentMapper.getDeptById"
column="d_id">
</association>
</resultMap>
<select id="getEmployeeWithDeptByStep" resultMap="mapByStep">
select * from tbl_employee where id = #{id}
</select>
# 4.2.6.4 延迟加载
我们每次在查询 Employee 的时候,都把 Department 也一次性查询出来。其实我们可以在需要部门信息的时候再去查询,也就是所谓的延迟加载。
为了实现上述目的,我们需要在分步查询的基础上加上 2 个配置。来到 MyBatis 全局配置文件上:
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启懒加载支持,默认为 false -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- true:属性一次性加载; false:按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
测试:
当我们要拿到部门信息的时候,才会继续去查询部门信息:
# 4.2.6.5 一对多
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Department {
private Integer id;
private String departmentName;
private List<Employee> employees;
}
如上,一个部门对应多个员工,我们现在希望在查询部门的时候将属于这个部门的员工也一并查询出来。
Department getDeptWithEmpsById(Integer id);
<resultMap id="deptWithEmpsMap" type="com.hedon.mybatis.bean.Department">
<id column="id" property="id"/>
<result column="dept_name" property="departmentName"/>
<!-- 集合的话就用 collection, ofType 指定集合里面的类型 -->
<collection property="employees" ofType="com.hedon.mybatis.bean.Employee">
<!--在 collection 中定义集合中元素的属性的封装规则-->
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</collection>
</resultMap>
<select id="getDeptWithEmpsById" resultMap="deptWithEmpsMap">
select d.id, d.dept_name, e.id, e.last_name, e.gender, e.email
from tbl_dept d
left join tbl_employee e
on d.id = e.d_id
where d.id = #{id}
</select>
# 4.2.6.6 一对多 —— 分步查询
第一步:查询部门信息
第二步:根据部门ID去查询员工信息
Department getDeptWithEmpsByIdByStep(Integer id);
<resultMap id="byStepMap" type="com.hedon.mybatis.bean.Department">
<id column="id" property="id"/>
<result column="dept_name" property="departmentName"/>
<!--第2步:根据部门ID查询员工信息-->
<collection property="employees"
select="com.hedon.mybatis.dao.EmployeeMapperPlus.getEmpsByDeptId"
column="id"/>
</resultMap>
<!--第1步:查询部分信息-->
<select id="getDeptWithEmpsByIdByStep" resultMap="byStepMap">
select d.id,d.dept_name from tbl_dept d where id = #{id}
</select>
这里需要在 EmployeeMapperPlus 中加一个 getEmpsByDeptId 方法:
List<Employee> getEmpsByDeptId(Integer id);
<select id="getEmpsByDeptId" resultType="com.hedon.mybatis.bean.Employee">
select * from tbl_employee where d_id = #{id}
</select>
这样就完成一对多的分步查询了。
# 4.2.6.7 一对多 —— 延迟加载
在前面在 MyBatis 全局配置文件 mybatis-config.xml
中设置了懒加载的基础上,一对多也会自动懒加载:
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启懒加载支持 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- true:属性一次性加载; false:按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
# 4.2.6.8 分步查询 —— 传递多列
如果在分步查询的时候需要传多个值,那就需要将多个值封装到 map
里面来传递,如:
coloum="{key1=column1,key2=column2}"
# 4.7 鉴别器
MyBatis 可以使用 discriminator
鉴别器来判断某列的值,然后根据某列的值改变封装行为。
Employee getEmpWithDiscriminator(Integer id);
<!--
实现功能:
① 如果查出的是女生(gender=0),就把部门信息查出来,否则不查询;
② 如果查出的是男生(gender=1),就把 last_name 这一列的值赋值给 email
-->
<resultMap id="MyEmpWithDiscriminator" type="com.hedon.mybatis.bean.Employee">
<id column="id" property="id"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<result column="last_name" property="lastName"/>
<!--鉴别器
column: 指定判定的列名
javaType: 列值对应的类型
-->
<discriminator javaType="String" column="gender">
<!--女生-->
<case value="0" resultType="com.hedon.mybatis.bean.Employee">
<!--如果是女生,就查询部门信息-->
<association property="department"
select="com.hedon.mybatis.dao.DepartmentMapper.getDeptById"
column="d_id">
</association>
</case>
<!--男生-->
<case value="1" resultType="com.hedon.mybatis.bean.Employee">
<id column="id" property="id"/>
<result column="gender" property="gender"/>
<!--不查部门信息,并把 last_name 的值赋值给 email-->
<result column="last_name" property="email"/>
<result column="last_name" property="lastName"/>
</case>
</discriminator>
</resultMap>
<select id="getEmpWithDiscriminator" resultMap="MyEmpWithDiscriminator">
select * from tbl_employee where id = #{id}
</select>
查询一个男生:
@Test
public void test5() throws IOException{
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = sqlSessionFactory.openSession();
try{
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee emp = mapper.getEmpWithDiscriminator(1);
System.out.println(emp);
}finally {
sqlSession.close();
}
in.close();
}
查询一个女生:
Employee emp = mapper.getEmpWithDiscriminator(10);