MyBatis映射文件
目录
¶XML 映射器
MyBatis 的真正强大在于它的语句映射,SQL 映射文件只有很少的几个顶级元素(根据应被定义的顺序列出):
标签 | 含义 |
---|---|
cache | 该命名空间的缓存配置 |
cache-ref | 引用其他命令空间的缓存配置 |
resultMap | 描述如何从数据库结果集中加载对象,时最复杂也是最强大的元素 |
sql | 可被其它语句引用的可重用语句块 |
insert | 映射插入语句 |
update | 映射更新语句 |
delete | 映射删除语句 |
select | 映射查询语句 |
¶<select>
多数应用都是查询密集型,所以先从查询开始
🎶 MyBatis 的基本原则之一是:在每个插入、更新或删除操作之间,通常会执行多个查询操作
¶常见配置
1 | <select id="selectPerson" parameterType="int" resultType="hashmap"> |
✨语句特点
- 这个语句名是
selectPerson
- 接受一个
int(Integer)
类型的参数 - 返回一个
HashMap
类型的对象,其中键是列名,值便是结果行中的对应值 - 参数使用
#{}
进行获取
¶底层设计
以上的查询语句,其实就是告诉 MyBatis 创建一个预处理语句(PreparedStatement)参数
1 | String selectPerson = "SELECT * FROM PERSON WHERE ID=?"; |
¶属性
select 元素可以配置很多属性来配置每条语句的行为细节
1 | <select |
属性 | 描述 |
---|---|
resultType | 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。 |
¶insert
,update
&delete
数据变更语句 insert,update 和 delete 的实现非常接近:
1 | <insert |
¶获取自增主键的值
mysql 支持自增主键,自增主键值的获取,mybatis 利用statement.getGeneratedKeys()
useGenerateKeys="true"
使用自增主键获取主键值策略
keyProperty
指定对应的主键属性,也就是 mybatis 获取到主键值以后,将这个值封装给 javabean 的那个属性
属性 | 描述 |
---|---|
databaseId | 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。 |
1 | <!-- public void addEmp(Employee employee); --> |
¶获取非自增主键的值
Oracle 不支持自增使的数据库,使用 selectKey 子元素
✨selectKey 元素将会首先运行, id 会被设置, 然后插入语句会被调用
属性 | 描述 |
---|---|
keyProperty | 仅适用于 insert 和 update,指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset )。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
order | “BEFORE”:当前 sql 在插入 sql 之前运行 “AFTER”:当前 sql 在插入 sql 之后运行 |
🎶BEFORE 与 AFTER 的运行顺序
BEFORE
:- 先运行 selectKey 查询 id 的 sql;查出 id 值封装给 javaBean 的 id 属性
- 再运行插入的 sql;就可以取出 id 属性对应的值
AFTER
- 先运行插入的 sql(从序列中取出新值作为 id);
- 再运行 selectKey 查询 id 的 sql;
1 | <insert id="addEmp" databaseId="oracle"> |
¶参数处理
DAO 和 MyBatis Mapper 之间参数传递的处理
¶单个参数
可以接受基本类型, 对象类型。 这种情况 MyBatis 可直接使用这个参数, 不需要经过任何处理
1 | #{参数名/任意名} |
¶多个参数
任意多个参数, 都会被 MyBatis 重新包装成一个 Map 传入
✨Map 的 key 是 param1,param2, 或者 0, 1…, 值就是参数的值
1 | key: param1 .. paramN |
¶命名参数
🎶明确指定封装参数时 map 的 key @Param("id")
1 | key:使用@Param注解指定的值 |
¶POJO
如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入 POJO
1 | #{属性名}:取出传入的pojo的属性值 |
¶Map
如果多个参数不是业务模型中的数据,没有对应的 pojo,不经常使用,为了方便,我们也可以传入 map
1 | #{key}:取出map中对应的值 |
¶TO
如果多个参数不是业务模型中的数据,但是经常要使用,推荐编写一个 TO(Transfer Object)数据传输对象
1 | Page{ |
¶Demo
1 | public Employee getEmp(; Integer id,String lastName) |
🎶如果是Collection(List,Set)
类型或者数组,也会特殊处理,也是把传入的list
或者数组封装在map
中
类型 | key |
---|---|
Collection | collection |
List | list |
数组 | array |
1 | public Employee getEmpById(List<Integer> ids); |
🎶参数多时会封装 map,为了不混乱,我们可以使用@Param
来指定封装时使用的key
对于大多数简单的使用场景,不需要使用复杂的参数
1 | <select id="selectUsers" resultType="User"> |
根据参数类型(parameterType
)会被自动设置为int
,这个参数可以随意命名
原始类型或简单数据类型(比如Integer
和String
)因为没有其他属性,会用它们的值来作为参数,然而,如果传入一个复杂的对象,行为就会不一样了
1 | <insert id="insertUser" parameterType="User"> |
如果 User 类型的参数对象传递到了语句中,会查找 id、username 和 password 属性,然后将它们的值传入预处理语句的参数中
参数也可以指定一个特殊的数据类型
1 | #{property,javaType=int,jdbcType=NUMERIC} |
几乎总是可以根据参数对象的类型确定 javaType,除非该对象是一个 HashMap
。这个时候,你需要显式指定 javaType
来确保正确的类型处理器(TypeHandler
)被使用。
🎶JDBC 要求,如果一个列允许使用 null 值,并且会使用值为 null 的参数,就必须要指定 JDBC 类型(jdbcType)
要更进一步地自定义类型处理方式,可以指定一个特殊的类型处理器类(或别名)
1 | #{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler} |
对于数值类型,还可以设置 numericScale
指定小数点后保留的位数
1 | #{height,javaType=double,jdbcType=NUMERIC,numericScale=2} |
👴参数的配置好像越来越繁琐了,但实际上,很少需要如此繁琐的配置
mode 属性允许你指定 IN
,OUT
或 INOUT
参数。如果参数的 mode
为 OUT
或 INOUT
,将会修改参数对象的属性值,以便作为输出参数返回。 如果 mode
为 OUT
(或 INOUT
),而且 jdbcType
为 CURSOR
(也就是 Oracle 的 REFCURSOR),你必须指定一个 resultMap
引用来将结果集 ResultMap
映射到参数的类型上。要注意这里的 javaType
属性是可选的,如果留空并且 jdbcType 是 CURSOR
,它会被自动地被设为 ResultMap
1 | #{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap} |
🎶大多时候,你只须简单指定属性名,顶多要为可能为空的列指定 jdbcType
,其他的事情交给 MyBatis 自己去推断就行了
1 | #{firstName} |
¶字符串替换
表达式 | 含义 |
---|---|
#{} | 可以获取 map 中的值或者 pojo 对象属性的值; 是以预编译的形式,将参数设置到 sql 语句中;使用 PreparedStatement ;防止 sql 注入 |
${} | 可以获取 map 中的值或者 pojo 对象属性的值; 取出的值直接拼接在 sql 语句中;存在安全问题 |
原生 jdbc 不支持占位符的地方我们就可以使用${}
进行取值,如:分表、排序
当 SQL 语句中的元数据(如表名或列名)是动态生成的时候,字符串替换将会非常有用。 举个例子,如果你想 select
一个表任意一列的数据时,不需要这样写:
1 |
|
而是可以只写这样一个方法:
1 |
|
其中 ${column}
会被直接替换,而 #{value}
会使用 ?
预处理。 这样,就能完成同样的任务:
1 | User userOfId1 = userMapper.findByColumn("id", 1L); |
这种方式也同样适用于替换表名的情况
🎶用这种方式接受用户的输入,并用作语句参数是不安全的,会导致潜在的 SQL 注入攻击,因此,要么不允许用户输入这些字段,要么自行转义并检验这些参数
👴大多数情况下,我们取参数的值都应该是使用#{}
¶结果映射
ResultMap 的设计思想是:对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之前的关系就可以了
如果返回的是一个集合,要写集合中元素的类型
1 |
|
@Mapkey
告诉 mybatis 封装这个 map 的时候使用那个属性作为 map 的 key
返回一条记录的 map,key 是列名,值是对应的值
1 | public Map<String, Object> getEmpByIdReturnMap(Integer id); |
¶<resultMap>
自定义结果集映射规则,实现高级结果集映射
标签 | 含义 |
---|---|
<id> | 定义主键列封装规则,底层有优化 |
<result> | 定义普通列封装规则 |
1 | <resultMap type="com.atguigu.mybatis.bean.Employee" id="MySimpleEmp"> |
👴其他不指定的列会自动封装;只要写 resultMap 就把全部的映射规则都写上
各个属性含义信息
属性 | 含义 |
---|---|
property | 映射到 Java Bean 的属性 |
column | 数据表的列命 |
javaType | 一个 Java 类的完全限定名,或一个类型别名。如果映射到一个 Java Bean,MyBatis 通常可以断定类型 |
jdbcType | JDBC 类型是仅仅需要对插入,更新和删除操作可能为空的列进行处理 |
typeHandler | 类型处理器,使用这个属性,可以覆盖默认的类型处理器,这个属性值是类的完全限定名或者是一个类型处理器的实现,或者是类型别名 |
¶场景一:联合查询
查询 Employee 的同时查询员工对应的部门
1 | <resultMap type="com.atguigu.mybatis.bean.Employee" id="MyDifEmp"> |
使用<association>
定义关联的单个对象的封装规则,association 指定联合的 javaBean 对象
1 | <!--property指定那个属性是联合对象,javaType:指定这个属性对象的类型--> |
¶场景一:分步查询
使用 association 进行分布查询
- 先按照员工 id 查询员工信息
- 根据查询员工信息中的 d_id 值去部门表查出部门信息
- 部门信息设置到员工中
1 | <result column="gender" property="gender"/> |
分段查询的基础之上加上两个配置,可以实现延迟加载(懒加载、按需加载)
1 | <settings> |
¶场景二:联合查询
查询部门的时候将部门对应的所有员工信息也查询出来
1 | public class Department { |
1 |
|
1 | <!--嵌套结果集的方式,使用collection标签定义关联的集合类型的属性封装规则 --> |
¶场景二:分步查询
1 | <resultMap type="com.atguigu.mybatis.bean.Department" id="MyDeptStep"> |
📖多列的值传递过去:将多列的值封装 map 传递;column=“{key1=column1,key2=column2}”
fetchType=“lazy”:表示使用延迟加载;- lazy:延迟- eager:立即
¶discriminator(鉴别器)
mybatis 可以使用 discriminator 判断某列的值,然后根据某列的值改变封装行为
1 | <resultMap type="com.atguigu.mybatis.bean.Employee" id="MyEmpDis"> |