一.概述
映射文件指导着MyBatis如何进行数据库增删改查,有着非常重要的意义;SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):
- cache – 给定命名空间的缓存配置。
- cache-ref – 其他命名空间缓存配置的引用。
- resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
- sql – 可被其他语句引用的可重用语句块。
- insert – 映射插入语句
- update – 映射更新语句
- delete – 映射删除语句
- select – 映射查询语句
二.属性
2.1 insert,update和delete
属性 | 描述 |
---|---|
id | 命名空间中的唯一标识符,可被用来代表这条语句。 |
parameterType | 将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。 |
flushCache | 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:true(对应插入、更新和删除语句)。 |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)。 |
statementType | STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
useGeneratedKeys | (仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。 |
keyProperty | (仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认:unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 |
keyColumn | (仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 |
databaseId | 如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。 |
2.1.2 实验:获取自增主键的值
<!-- public int insertEmployee(Employee employee); -->
<!-- 让MyBatis自动将自增id赋值传入的employee对象的id属性
useGeneratedKeys:使用自动生成的主键,原生JDBC获取自增主键的方法
keyProperty:将刚才自增的id封装给哪个属性
-->
<insert id="insertEmployee" useGeneratedKeys="true" keyProperty="id">
insert into t_employee(empname,gender,email) values(#{empname},#{gender},#{email})
</insert>
2.1.3 实验:获取非自增主键的值
<insert id="insertEmployee2">
<!-- 查询主键
order="BEFORE":在核心sql语句之前先运行一个查询sql查到的id,将查到的id值赋给JavaBean的哪个属性
-->
<selectKey order="BEFORE" keyProperty="id" resultType="integer">
select max(id)+1 from t_employye
</selectKey>
insert into t_employee(id,empname,gender,email) values(#{id},#{empname},#{gender},#{email})
</insert>
selectKey元素属性描述如下:
属性 | 描述 |
---|---|
keyProperty | selectKey 语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 |
keyColumn | 匹配属性的返回结果集中的列名称。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 |
resultType | 结果的类型。MyBatis 通常可以推算出来,但是为了更加确定写上也不会有什么问题。MyBatis 允许任何简单类型用作主键的类型,包括字符串。如果希望作用于多个生成的列,则可以使用一个包含期望属性的 Object 或一个 Map。 |
order | 这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先选择主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 元素 - 这和像 Oracle 的数据库相似,在插入语句内部可能有嵌入索引调用。 |
statementType | 与前面相同,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 语句的映射类型,分别代表 PreparedStatement 和 CallableStatement 类型。 |
2.2 select
2.2.1 概述
select元素来定义查询操作,属性描述如下:
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterType | 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。 |
resultType | 从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。使用 resultType 或 resultMap,但不能同时使用。 |
resultMap | 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,对其有一个很好的理解的话,许多复杂映射的情形都能迎刃而解。使用 resultMap 或 resultType,但不能同时使用。 |
flushCache | 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false。 |
useCache | 将其设置为 true,将会导致本条语句的结果被二级缓存,默认值:对 select 元素为 true。 |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)。 |
fetchSize | 这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。默认值为 unset(依赖驱动)。 |
statementType | STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
resultSetType | FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一个,默认值为 unset (依赖驱动)。 |
databaseId | 如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。 |
resultOrdered | 这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。 |
resultSets | 这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称是逗号分隔的。 |
2.2.2 实验:查询返回List
<!-- public List<Employee> getAllEmps(); -->
<!-- resultType:如果返回的是集合,写的是集合元素里面的类型 -->
<select id="getAllEmps" resultType="com.test.bean.Employee">
select * from t_employee
</select>
2.2.3 实验:查询单条或多条封装Map
1)查询单条记录封装Map
<!-- public Map<String,Object> getEmpByIdReturnMap(Integer id); -->
<!-- 写成map是因为支持别名 -->
<!--//默认列名作为key,值作为value-->
<select id="getEmpByIdReturnMap" resultType="map">
select * from t_employee where id=#{id}
</select>
测试:
2)查询多条封装map:
EmployeeDao.xml:使用注解@MepKey
//多条
//key就是这个记录的主键,value就是这个记录封装好的对象
//把查询的记录的id的值作为key封装这个map
@MapKey(value = "id")
public Map<Integer,Object> getAllEmpReturnMap();
映射文件:
<!-- //多条
//key就是这个记录的主键,value就是这个记录封装好的对象
public Map<Integer,Object> getAllEmpReturnMap(Integer id); -->
<!-- 查询多个返回一个:集合里面写元素类型 -->
<select id="getAllEmpReturnMap" resultType="com.test.bean.Employee">
select * from t_employee
</select>
要注意的是:查询返回List和查询多条封装Map的时候,返回类型要写成集合里面的元素类型
2.3 参数
2.3.1 参数取值
实际上在MyBatis中,两种取值方式:
- #{属性名}:是参数预编译的方式,参数的位置都是?代替,参数后来都是预编译设置进去的。安全,不会有SQL注入问题。
- ${属性名}:不是参数预编译,直接和SQL语句进行拼串。不安全
要注意,SQL语句只有参数位置是支持编译的
下面以一个sql语句:
select * from t_employee where id=${id} and empname=#{empname}
这个执行的语句:
select * from t_employee where id=1 and empname=?
如果都用#取值:
select * from t_employee where id=? and empname=?
一般都是使用#{}安全,在不支持参数预编译位置要进行取值就使用${}。
2.3.2 参数传递
1)单个参数:可以接受基本类型,对象类型,集合类型的值。这种情况MyBatis可直接使用这个参数,不需要经过任何处理。取值: 基本类型:#{随便写}
2)多个参数:只要传入多个参数,MyBatis自动将这些参数封装在一个map中,封装时使用的key就是参数的索引和参数的第几个标识。使用0,1(参数的索引)或者parm1,parm2(第几个param)。相当于下面的存入过程:
Map<String,Object> map=new HashMap<>();
map.put("1",传入的值);
map.put("2",传入的值);
#{key}就是从这个map中的值
3)命名参数:为参数使用@Param注解起一个名字,MyBatis就会将这些参数封装进Map,key就是我们指定的名字
实验代码:
<!-- public Employee getEmpByIdAndName(Integer id,String empName); -->
<!-- 传递多个参数的时候,不能像传递单个参数一样,那样取值,会出现BindingException -->
<select id="getEmpByIdAndName" resultType="com.test.bean.Employee">
<!-- select * from t_employee where id=#{0} and empname=#{1} -->
select * from t_employee where id=#{id} and empname=#{empname}
</select>
2.3.2 参数处理
参数也可以指定一个特殊的数据类型:
-
javaType 通常可以从参数对象中来去确定
-
如果 null 被当作值来传递,对于所有可能为空的列,jdbcType 需要被设置
-
对于数值类型,还可以设置小数点后保留的位数:
-
mode 属性允许指定 IN,OUT 或 INOUT 参数。如果参数为 OUT 或 INOUT,参数对象属性的真实值将会被改变,就像在获取输出参数时所期望的那样
2.4 resultMap
2.4.1 场景
上面查询的Employee对象属性名与数据库中的属性名相对应,即使是loginAccount也满足驼峰命名规则。但是也有种情况,数据库表中列名和JavaBean对象的属性名不相符,就可以使用resutMap,自定义结果封装结果集。
2.4.2 属性简介
2.4.3 id 和result
这是结果映射最基本内容。id 和 result 都映射一个单独列的值到简单数据类型(字符 串,整型,双精度浮点数,日期等)的单独属性或字段。这两者之间的唯一不同是 id 表示的结果将是当比较对象实例时用到的标识属性。
属性 | 描述 |
---|---|
property | 映射到列结果的字段或属性。如果匹配的是存在的,和给定名称相同 的 JavaBeans 的属性,那么就会使用。否则 MyBatis 将会寻找给定名称 property 的字段。这两种情形你可以使用通常点式的复杂属性导航。比如,你 可以这样映射一些东西: “username” ,或者映射到一些复杂的东西: “address.street.number” 。 |
column | 从数据库中得到的列名,或者是列名的重命名标签。这也是通常和会 传递给 resultSet.getString(columnName)方法参数中相同的字符串。 |
javaType | 一个 Java 类的完全限定名,或一个类型别名(参考上面内建类型别名 的列表) 。如果你映射到一个 JavaBean,MyBatis 通常可以断定类型。 然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证所需的行为。 |
jdbcType | 在这个表格之后的所支持的 JDBC 类型列表中的类型。JDBC 类型是仅 仅需要对插入,更新和删除操作可能为空的列进行处理。这是 JDBC jdbcType 的需要,而不是 MyBatis 的。如果你直接使用 JDBC 编程,你需要指定 这个类型-但仅仅对可能为空的值。 |
typeHandler | 我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默 认的类型处理器。这个属性值是类的完全限定名或者是一个类型处理 器的实现,或者是类型别名。 |
实验代码:
<?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.test.dao.CatDao">
<!-- public Cat getCatById(Integer id); -->
<!-- resultType="com.test.bean.Cat":使用默认规则,属性列名一一对应
resultMap="":查出数据使用这个指定的规则进行封装
-->
<select id="getCatById" resultMap="mycat">
select id,cname name,cAge age,cgender gender from t_cat where id=#{id}
</select>
<!-- 自定义结果集:自己定义每一列数据和JavaBean的映射规则 -->
<!-- type="":指定为哪个JavaBean自定义规则,全类名
id="" :唯一标识,让别名在后面引用-->
<resultMap type="com.test.bean.Cat" id="mycat">
<!--id:指定主键的对应规则
column="id":指定哪一列是主键列
property="":指定JavaBean的哪个属性封装id这一列数据
-->
<id column="id" property="id"/>
<!-- 普通列 :result-->
<result column="cName" property="name"/>
<result column="cAge" property="age"/>
<result column="cgender" property="gender"/>
</resultMap>
</mapper>
2.4.4 联合查询实验一:有两个对象,Lock和Key,在Key中有Lock对象,也就是说一把钥匙对应一把锁
/*
* 钥匙
*/
public class Key {
private Integer id;
private String keyName;
private Lock lock; //当前钥匙能开哪把锁
...
}
数据库设计,数据库Key表中有外键:
(1)级联属性的方式封装查出的数据
KeyDao:
package com.test.dao;
import com.test.bean.Key;
public interface KeyDao {
//将锁和钥匙一起查出
public Key getKeyByID(Integer id);
}
KeyDao的实现:SQL映射文件,在这个文件中自定义结果集封装规则,用来封装JavaBean中另一个Bean的属性
<?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.test.dao.KeyDao">
<!-- public Key getKeyByID(Integer id); -->
<select id="getKeyByID" resultMap="mykey">
SELECT k.id ,k.keyname,k.lockid,l.lockname FROM t_key k LEFT JOIN t_lock l ON k.lockid=l.id WHERE k.id=#{id}
</select>
<!-- 自定义封装规则 ,使用级联属性封装联合查询出的结果-->
<resultMap type="com.test.bean.Key" id="mykey">
<id property="id" column="id"/>
<result property="keyName" column="keyname"/>
<result property="lock.id" column="lid"/>
<result property="lock.lockName" column="lockname"/>
</resultMap>
</mapper>
(2)使用association定义联合查询的对象的封装规则
实验代码:
<?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.test.dao.KeyDao">
<!-- public Key getKeyByID(Integer id); -->
<select id="getKeyByID" resultMap="mykey">
SELECT k.id ,k.keyname,k.lockid,l.id,l.lockname FROM t_key k LEFT JOIN t_lock l ON k.lockid=l.id WHERE k.id=#{id}
</select>
<!-- MaBatis推荐的 -->
<resultMap type="com.test.bean.Key" id="mykey">
<id property="id" column="id"/>
<result property="keyName" column="keyname"/>
<!-- 接下来的属性是一个对象,自定义这个对象的封装规则,使用association表示联合了一个对象 -->
<!-- javaType这个属性的全类名 -->
<association property="lock" javaType="com.test.bean.Lock">
<!-- 定义lock属性对应的lock对象如何封装 -->
<id property="id" column="lid"/>
<result property="lockName" column="lockname"/>
</association>
</resultMap>
</mapper>
2.4.5 联合查询实验二背景:一把锁可以有多个钥匙
public class Lock {
private Integer id;
private String lockName;
private List<Key> keys;
...
}
LockDao:
public interface LockDao {
public Lock getLockById(Integer id);
}
(1)collection定义集合类型属性的封装规则
<?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.test.dao.LockDao">
<!-- public Lock getLockById(Integer id); -->
<select id="getLockById" resultMap="mylock">
SELECT k.id kid,k.keyname,k.lockid,l.* FROM t_lock l LEFT JOIN t_key k ON l.id=k.lockid WHERE l.id=#{id}
</select>
<!-- 自定义封装规则 -->
<resultMap type="com.test.bean.Lock" id="mylock">
<id property="id" column="id"/>
<result property="lockName" column="lockname"/>
<!-- collection定义集合元素的封装
property指定哪个属性是集合属性
ofType指定集合元素的类型 -->
<collection property="keys" ofType="com.test.bean.Key">
<!-- 标签体中指定这个集合的封装规则 -->
<id property="id" column="kid"/>
<result property="keyName" column="keyname"/>
</collection>
</resultMap>
</mapper>
2.4.5 分步查询
(1)使用collection分步查询
实验背景:查锁对应的钥匙,一把锁对应多个钥匙
KeyDao实现:有一个简答的方法查询Key对象
<!-- public List<Key> getKeysByLockId(Integer id); -->
<select id="getKeysByLockId" resultType="com.test.bean.Key">
select * from t_key where lockid=#{lockid}
</select>
LockDao:一个简单的方法查询Lock,Lock对应多个Key,所以使用的List类型,自定义结果封装集的时候,使用collection,同时使用select告诉MyBatis去调用一个查询去Key对象。
<!-- public Lock getLockIdByStep(Integer id); -->
<select id="getLockIdByStep" resultMap="mylockstep">
select * from t_lock where id=#{id}
</select>
<resultMap type="com.test.bean.Lock" id="mylockstep">
<id property="id" column="id"/>
<result property="lockName" column="lockname"/>
<!-- collection指定集合类型的封装规则 -->
<collection property="keys" select="com.test.dao.KeyDao.getKeysByLockId" column="id"></collection>
</resultMap>
(2)使用select语句指定分步查询
实验背景:查钥匙对应的锁,一个钥匙对应一个锁
LockDao实现:有一个简单的方法查询Lock对象
<!-- public Lock getLockByIdSimple(Integer id); -->
<select id="getLockByIdSimple" resultType="com.test.bean.Lock">
select * from t_lock where id=#{id}
</select>
KeyDao实现:一个简单的方法查询Key,但是自定义封装结果集的时候,告诉MyBatis去调用一个查询去查询Lock对象
<!-- public Key getKeyByIdSimple(Integer id); -->
<!-- 查询key的时候也可以带上锁 -->
<!-- id keyname lockid -->
<select id="getKeyByIdSimple" resultMap="mykey01">
select * from t_key where id=#{id}
</select>
<resultMap type="com.test.bean.Key" id="mykey01">
<id property="id" column="id"/>
<result property="keyName" column="keyname"/>
<!-- 告诉MyBatis自己去调用一个查询查锁子
select="":指定一个查询sql的唯一标识,MyBatis自动调用指定的SQL将查询的lock封装进来
需要指定的id,告诉MyBatis把哪一列的值传递过去
column="":指定把哪一列的数据传递过去-->
<association property="lock" select="com.test.dao.LockDao.getLockByIdSimple" column="lockid"
fetchType="eager">
</association>
</resultMap>
参考MyBatis的官方文档,需要详细了解SQL映射文件可以看官方文档。映射文件