4、MyBatis 映射文件
映射文件指导着 MyBatis 如何进行数据库增删改查,有着非常重要的意义;
-
cache –命名空间的二级缓存配置
-
cache-ref –其他命名空间缓存配置的引用。
-
resultMap–自定义结果集映射
-
parameterMap –已废弃!老式风格的参数映射
-
sql –抽取可重用语句块。
-
insert –映射插入语句
-
update –映射更新语句
-
delete –映射删除语句
-
select –映射查询语句
4.1 insert、update、delete元素
(1)mybatis 允许增删改直接定义以下类型返回值:Integer、Long、Boolean、void
(2)需要手动提交数据
- sqlSessionFactory.openSession();===>手动提交
- sqlSessionFactory.openSession(true);===>自动提交
EmployeeMapper.java
public interface EmployeeMapper {
public Employee getEmpById(Integer id);
public Long addEmp(Employee employee);
public boolean updateEmp(Employee employee);
public void deleteEmpById(Integer id);
}
EmployeeMapper.xml
<insert id="addEmp" parameterType="com.mycode.mybatis.bean.Employee"
useGeneratedKeys="true" keyProperty="id">
insert into tbl_employee(last_name,email,gender)
values(#{lastName},#{email},#{gender})
</insert>
<!-- public void updateEmp(Employee employee); -->
<update id="updateEmp">
update tbl_employee set last_name=#{lastName},email=#{email},gender=#{gender}
where id=#{id}
</update>
<!-- public void deleteEmpById(Integer id); -->
<delete id="deleteEmpById">
delete from tbl_employee where id=#{id}
</delete>
@Test
public void test03() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 1、获取到的 SqlSession 不会自动提交数据
try(SqlSession openSession = sqlSessionFactory.openSession()) {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
// 测试添加
Employee employee = new Employee(null, "Jerry", "[email protected]", "1");
mapper.addEmp(employee);
System.out.println(employee.getId());
//测试修改
// Employee employee = new Employee(1, "Tom", "[email protected]", "0");
// boolean updateEmp = mapper.updateEmp(employee);
// System.out.println(updateEmp);
// 测试删除
// mapper.deleteEmpById(2);
// 2、手动提交数据
openSession.commit();
}
}
(3)主键生成方式
① 若数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),则可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置到目标属性上。
mysql 支持自增主键,自增主键值的获取,mybatis 也是利用 statement.getGeneratedKeys()
- useGeneratedKeys=“true”:使用自增主键获取主键值策略
- keyProperty:指定对应的主键属性,也就是 mybatis 获取到主键值以后,将这个值封装给 javaBean 的哪个属性
② 而对于不支持自增型主键的数据库(例如 Oracle,Oracle 使用序列来模拟自增;每次插入的数据的主键是从序列中拿到的值),则可以使用 selectKey 子元素:selectKey 元素将会首先运行,id 会被设置,然后插入语句会被调用
<insert id="addEmp" databaseId="oracle">
<!--
keyProperty:查出的主键值封装给 javaBean 的哪个属性
order="BEFORE":当前 sql 在插入 sql 之前运行
AFTER:当前 sql 在插入 sql 之后运行
resultType:查出的数据的返回值类型
BEFORE 运行顺序:
先运行 selectKey 查询 id 的 sql;查出 id 值封装给 javaBean 的 id 属性
再运行插入的 sql;就可以取出 id 属性对应的值
AFTER 运行顺序:
先运行插入的 sql(从序列中取出新值作为 id);
再运行 selectKey 查询 id 的 sql;
-->
<selectKey keyProperty="id" order="BEFORE" resultType="Integer">
<!-- 编写查询主键的 sql 语句 -->
<!-- BEFORE -->
select EMPLOYEES_SEQ.nextval from dual
<!-- AFTER
select EMPLOYEES_SEQ.currval from dual -->
</selectKey>
<!-- 插入时的主键是从序列中拿到的 -->
<!-- BEFORE:-->
insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL)
values(#{id},#{lastName},#{email})
<!-- AFTER:
insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL)
values(employee_seq.nextval,#{lastName},#{email}) -->
</insert>
- selectKey
4.2 参数(Parameters)传递
(1)单个参数
可以接受基本类型,对象类型,集合类型的值。这种情况 MyBatis 可直接使用这个参数,不需要经过任何处理。
#{参数名}:取出参数。
(2)多个参数
任意多个参数,都会被 MyBatis 重新包装成一个 Map 传入。Map 的 key 是 param1,param2,arg0,arg1…,值就是参数的值。
异常:
org.apache.ibatis.binding.BindingException:
Parameter ‘id’ not found.
Available parameters are [arg1, arg0, param1, param2]
操作:
- 方法:public Employee getEmpByIdAndLastName(Integer id,String lastName);
- 取值:#{id},#{lastName}
(3)命名参数
为参数使用 @Param 起一个名字,MyBatis 就会将这些参数封装进 map 中,key 就是我们自己指定的名字
明确指定封装参数时 map 的 key ,@Param(“id”),多个参数会被封装成一个 map
- key:使用 @Param 注解指定的值
- value:参数值
- #{指定的key}取出对应的参数值
public Employee getEmpByIdAndLastName(@Param("id") Integer id,@Param("lastName") String lastName);
<select id="getEmpByIdAndLastName" resultType="com.mycode.mybatis.bean.Employee">
select * from tbl_employee where id=#{id} and last_name=#{lastName}
</select>
(4)POJO
如果多个参数正好是我们业务逻辑的数据模型,就可以直接传入 POJO
#{属性名}:取出传入的 pojo 的属性值
(5)Map
如果多个参数不是业务模型中的数据,没有对应的 pojo,不经常使用,为了方便,我们也可以传入 map
#{key}:取出 map 中对应的值
(6)TO
如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个 TO(Transfer Object) 数据传输对象
思考:
-
public Employee getEmp(@Param(“id”)Integer id,String lastName);
取值:id => #{id/param1} lastName => #{param2} -
public Employee getEmp(Integer id,@Param(“e”)Employee emp);
取值:id => #{param1} lastName => #{param2.lastName/e.lastName}
特别注意:如果是 Collection(List,Set)类型或者是数组,也会特殊处理。
也是把传入的 list 或者数组封装在 map 中。
-
key:Collection(collection),如果是 List 还可以使用这个 key(list)、数组(array)
-
例:public Employee getEmpById(List<Integer> ids);
取值:取出第一个 id 的值:#{list[0]}
4.3 结合源码分析 mybatis 怎么处理参数
(@Param("id") Integer id,@Param("lastName") String lastName)
ParaNameResolver 解析参数封装成 map
(1)names:{0=id, 1=lastName} 构造器的时候就确定好了
确定流程:
① 获取每个标了 param 注解的参数的 @Param 值:id,lastName,赋值给name
② 每次解析一个参数给 map 中保存信息:(key:参数索引,value:name的值)
name的值:
- 标注了 param 注解:注解的值
- 没有标注:
- 全局配置:useActualParamName(jdk1.8):name=参数名
- name=map.size(); 相当于当前元素的索引
{0=id, 1=lastName, 2=2}
args [1,"Tom","hello"]
public Object getNamedParams(Object[] args) {
int paramCount = this.names.size();
// 1、参数为 null 直接返回
if (args != null && paramCount != 0) {
// 2、如果只有一个元素,并且没有 Param 注解;args[0]:单个参数直接返回
if (!this.hasParamAnnotation && paramCount == 1) {
Object value = args[(Integer)this.names.firstKey()];
return wrapToMapIfCollection(value, this.useActualParamName ? (String)this.names.get(0) : null);
// 3、多个元素或者有 Param 标注
} else {
Map<String, Object> param = new ParamMap();
int i = 0;
// 4、遍历 names 集合;{0=id, 1=lastName, 2=2}
for(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) {
Entry<Integer, String> entry = (Entry)var5.next();
// names 集合的 value 作为 key;names 集合的 key 又作为取值的参考 args[0]:args [1,"Tom","hello"]
// eg: {id=args[0]:1,lastName=args[1]:Tom,2=args[2]:hello}
param.put(entry.getValue(), args[(Integer)entry.getKey()]);
// 额外的将每一个参数也保存到 map 中,使用新的 key:param1...paramN
// 效果:有 Param 注解可以#{指定的key},或者#{param1}
String genericParamName = "param" + (i + 1);
if (!this.names.containsValue(genericParamName)) {
param.put(genericParamName, args[(Integer)entry.getKey()]);
}
}
return param;
}
} else {
return null;
}
}
4.4 参数值的获取
#{}:可以获取 map 中的值或者 pojo 对象属性的值;
${}:可以获取 map 中的值或者 pojo 对象属性的值;
select * from tbl_employee where id=${id} and last_name=#{lastName}
Preparing: select * from tbl_employee where id=1 and last_name=?
区别:
- #{}:是以预编译的形式,将参数设置到 sql 语句中;PreparedStatement;防止 sql 注入
- ${}:取出的值直接拼装在 sql 语句中;会有安全问题
大多情况下,我们取参数的值都应该去使用#{}
原生 jdbc 不支持占位符的地方,我们就可以使用 ${} 进行取值,比如分表、排序…:按照年份分表拆分
select * from ${year}_salary where xxx;
select * from tbl_employee order by ${f_name} ${order}
#{}:更丰富的用法:
规定参数的一些规则:
javaType、jdbcType、mode(存储过程)、numericScale(保留几位小数)、
resultMap(封装的结果类型)、typeHandler(处理数据的类型处理器)、
jdbcTypeName、expression(未来准备支持的功能)
javaType 通常可以从参数对象中来去确定
如果null 被当作值来传递,对于所有可能为空的列,jdbcType 需要被设置
对于数值类型,还可以设置小数点后保留的位数:
mode 属性允许指定IN,OUT 或INOUT 参数。如果参数为OUT 或INOUT,参数对象属性的真实值将会被改变,就像在获取输出参数时所期望的那样。
-
jdbcType 通常需要在某种特定的条件下被设置:
在我们数据为 null 的时候,有些数据库可能不能识别 mybatis 对 null 的默认处理。比如 Oracle(报错) -
JdbcType OTHER:无效的类型,因为 mybatis 对所有的 null 都映射的是原生 Jdbc 的 OTHER 类型,oracle 不能正确处理
-
由于全局配置中:jdbcTypeForNull=OTHER; oracle 不支持,两种解决办法
1、#{email,jdbcType=OTHER}
2、jdbcTypeForNull=NULL
<setting name=“jdbcTypeForNull” value=“NULL”/>
4.5 select 元素
Select元素来定义查询操作。
-
Id:唯一标识符。
–用来引用这条语句,需要和接口的方法名一致 -
parameterType:参数类型。
–可以不传,MyBatis 会根据 TypeHandler 自动推断 -
resultType:返回值类型。
–别名或者全类名,如果返回的是集合,定义集合中元素的类型。不能和resultMap同时使用
① 返回集合public List<Employee> getEmpsByLastNameLike(String lastName);
<!-- resultType:如果返回的是一个集合,要写集合中元素的类型-->
<select id="getEmpsByLastNameLike" resultType="com.mycode.mybatis.bean.Employee">
select * from tbl_employee where last_name like #{lastName}
</select>
② 返回一条记录的 map,key 就是列名,值就是对应的值public Map<String, Object> getEmpByIdReturnMap(Integer id);
<select id="getEmpByIdReturnMap" resultType="map">
select * from tbl_employee where id=#{id}
</select>
③ 多条记录封装成一个 map,Map<Integer, Employee>:键是这条记录的主键。值是记录封装后的 javaBean
// 告诉 mybatis 封装这个 map 的时候使用哪个属性作为 map 的 key
@MapKey("lastName")
public Map<String,Employee> getEmpByLastNameLikeReturnMap(String lastName);
<select id="getEmpByLastNameLikeReturnMap" resultType="com.mycode.mybatis.bean.Employee">
select * from tbl_employee where last_name like #{lastName}
</select>
4.6 自动映射
(1)全局 setting 设置
-
autoMappingBehavior 默认是 PARTIAL,开启自动映射的功能。唯一的要求是列名和 javaBean 属性名一致
-
如果 autoMappingBehavior 设置为 null 则会取消自动映射
-
数据库字段命名规范,POJO 属性符合驼峰命名法,如A_COLUMN–>aColumn,我们可以开启自动驼峰命名规则映射功能,mapUnderscoreToCamelCase=true。
(2)自定义 resultMap,实现高级结果集映射
resultMap
-
constructor:类在实例化时, 用来注入结果到构造方法中
- idArg:ID 参数; 标记结果作为 ID 可以帮助提高整体效能
- arg:注入到构造方法的一个普通结果
-
id:一个ID 结果; 标记结果作为 ID 可以帮助提高整体效能
-
result:注入到字段或 JavaBean 属性的普通结果
-
association:一个复杂的类型关联;许多结果将包成这种类型
嵌入结果映射,结果映射自身的关联,或者参考一个 -
collection:复杂类型的集
- 嵌入结果映射,结果映射自身的集,或者参考一个
-
discriminator:使用结果值来决定使用哪个结果映射
- case:基于某些值的结果映射
- 嵌入结果映射–这种情形结果也映射它本身,因此可以包含很多相同的元素,或者它可以参照一个外部的结果映射。
① id & result
- id 和 result 映射一个单独列的值到简单数据类型(字符串,整型,双精度浮点数,日期等)的属性或字段。
② association
- 复杂对象映射
- POJO 中的属性可能会是一个对象
- 我们可以使用联合查询,并以级联属性的方式封装对象。
<resultMap id="MyDifEmp" type="com.mycode.mybatis.bean.Employee">
<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>
- 使用 association 标签定义对象的封装规则
association 嵌套结果集
<resultMap id="MyDifEmp2" type="com.mycode.mybatis.bean.Employee">
<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.mycode.mybatis.bean.Department">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
</association>
</resultMap>
association 分段查询
<!-- 使用 association 进行分步查询:
1、先按照员工 id 查询员工信息
2、根据查询员工信息中的 d_id 值去部门表查出部门信息
3、部门设置到员工中
-->
<resultMap id="MyEmpByStep" type="com.mycode.mybatis.bean.Employee">
<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.mycode.mybatis.dao.DepartmentMapper.getDeptById"
column="d_id">
</association>
</resultMap>
association 分段查询&延迟加载
- 开启延迟加载和属性按需加载
使用延迟加载(懒加载)(按需加载)
部门信息在我们使用的时候再去查询
分段查询的基础之上加上两个配置:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
- 旧版本的MyBatis需要额外的支持包
–asm-3.3.1.jar
–cglib-2.2.2.jar
③ Collection
- Collection 集合类型&嵌套结果集
<!-- 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>
<!-- 嵌套结果集的方式,使用 collection 标签定义关联的集合类型的属性封装规则 -->
<resultMap id="MyDept" type="com.mycode.mybatis.bean.Department">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
<!--
collection 定义关联集合类型的属性的封装规则
ofType:指定集合里面元素的类型
-->
<collection property="emps" ofType="com.mycode.mybatis.bean.Employee">
<!-- 定义这个集合中元素的封装规则 -->
<id column="eid" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</collection>
</resultMap>
- Collection 分步查询&延迟加载
<!-- public Department getDeptByIdStep(Integer id); -->
<select id="getDeptByIdStep" resultMap="MyDeptStep">
select id,dept_name from tbl_dept where id=#{id}
</select>
<resultMap id="MyDeptStep" type="com.mycode.mybatis.bean.Department">
<id column="id" property="id"/>
<result column="dept_name" property="departmentName"/>
<collection property="emps"
select="com.mycode.mybatis.dao.EmployeeMapperPlus.getEmpsByDeptId"
column="id">
</collection>
</resultMap>
扩展:多列值封装 map 传递
- 分步查询的时候通过 column 指定,将对应的列的数据传递过去,我们有时需要传递多列数据。
- 使用 {key1=column1,key2=column2…} 的形式
<resultMap id="MyDeptStep" type="com.mycode.mybatis.bean.Department">
<id column="id" property="id"/>
<result column="dept_name" property="departmentName"/>
<collection property="emps"
select="com.mycode.mybatis.dao.EmployeeMapperPlus.getEmpsByDeptId"
column="{deptId=id}" fetchType="eager">
</collection>
</resultMap>
- association 或者 collection 标签的 fetchType=eager/lazy 可以覆盖全局的延迟加载策略,指定立即加载(eager)或者延迟加载(lazy)
④ discriminator
- mybatis 可以使用 discriminator 判断某列的值,然后根据某列的值改变封装行为
- column:指定判断的列名
- javaType:列值对应的 java 类型
<!--
封装 Employee:
如果查出的是女生:就把部门信息查询出来,否则不查询
如果是男生,把 last_name 这一列的值赋值给 email
-->
<resultMap id="MyEmpDis" type="com.mycode.mybatis.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<discriminator javaType="string" column="gender">
<!-- 女生 resultType:指定封装的结果类型,不能缺少。resultMap -->
<case value="0" resultType="com.mycode.mybatis.bean.Employee">
<association property="dept"
select="com.mycode.mybatis.dao.DepartmentMapper.getDeptById"
column="d_id">
</association>
</case>
<!-- 男生 如果是男生,把 last_name 这一列的值赋值给 email -->
<case value="1" resultType="com.mycode.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>