映射文件详解
1 概述
- MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。因为这个配置文件,几乎省略掉了90%的JDBC代码。
- 我们重点学习以下部分:
- CRUD操作:insert\update\delete\select
- sql片段:sql
- 结果集映射:resultMap
2 select
- 进行查询的statement,
- 常用的几个属性:
- select元素:代表查询,类似的还有update、insert、delete
- id:这个statement的唯一标示
- parameterType: 输入参数类型,可选参数,mybatis可以自动推断数据类型
- resultType:查询结果类型,如果结果是集合,请写集合内元素类型!
- resultMap:结果集映射,这个和resultType 只能存在1个,应对复杂的结果集。后面详细讲!
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterType | 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。 |
resultType | 从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。使用 resultType 或 resultMap,但不能同时使用。 |
resultMap | 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,对其有一个很好的理解的话,许多复杂映射的情形都能迎刃而解。使用 resultMap 或 resultType,但不能同时使用。 |
3 insert update delete
- insert update delete基本类似
- 比较常用的几个属性:
- id:这个statement的唯一标识
- parameterType:输入参数类型,可选参数,mybatis可以自动推断数据类型
<!--添加用户-->
<insert id="insertUser" parameterType="User">
insert into tb_user
(
id,
user_name,
password,
name,
age,
sex,
birthday,
created,
updated
)
values (
null,
#{userName},
#{password},
#{name},
#{age},
#{sex},
#{birthday},
now(),
now()
)
</insert>
<!--修改用户-->
<update id="updateUser" parameterType="User">
update tb_user
set
user_name = #{userName},
password = #{password},
name = #{name},
age = #{age},
sex = #{sex},
birthday = #{birthday},
updated = now()
where id = #{id}
</update>
<!--删除用户-->
<delete id="deleteById" parameterType="java.lang.Long">
delete from tb_user
where id = #{id}
</delete>
3.1 insert语句实现ID回填
需求: 记录这个用户 刚刚下单 买了哪些商品, 订单 和 商品 是多对多的关系
- 1 在订单表插入一条语句, 同时 还需要 获取新增的订单编号
- 2 在订单和商品的中间表插入记录: 需要 新增的订单编号 和 商品编号
3.1.1 方式一
<!--
useGeneratedKeys : 是否开启自增主键的回显功能
keyProperty: 对应javabean的属性名
keyColumn : 对应数据库的列名
-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
insert into tb_user (id,user_name,password,name,age,sex,birthday,created, updated )
values(null,#{userName},#{password},#{name},#{age},#{sex},#{birthday},now(),now() )
</insert>
@Test
public void inserUser() throws Exception {
User user = new User();
user.setuserName("zhuobotong2");
user.setPassword("123456");
user.setName("周伯通2");
user.setAge(38);
user.setSex(1);
Date birthday = new SimpleDateFormat("yyyy-MM-dd").parse("2000-9-9");
user.setBirthday(birthday);
userMapper.insertUser(user);
System.out.println(user.getId());
}
2019-10-09 13:08:41,685 [main] [cn.hanjiaxiaozhi.mapper.UserMapper.insertUser]-[DEBUG] ==> Preparing: insert into tb_user ( id, user_name, password, name, age, sex, birthday, created, updated ) values ( null, ?, ?, ?, ?, ?, ?, now(), now() )
2019-10-09 13:08:41,701 [main] [cn.hanjiaxiaozhi.mapper.UserMapper.insertUser]-[DEBUG] ==> Parameters: zhuobotong2(String), 123456(String), 周伯通2(String), 38(Integer), 1(Integer), 2000-09-09 00:00:00.0(Timestamp)
2019-10-09 13:08:41,701 [main] [cn.hanjiaxiaozhi.mapper.UserMapper.insertUser]-[DEBUG] <== Updates: 1
16
3.1.2 方式二
<insert id="insertUser">
insert into tb_user (id,user_name,password,name,age,sex,birthday,created, updated )
values(null,#{userName},#{password},#{name},#{age},#{sex},#{birthday},now(),now() )
<selectKey keyProperty="id" keyColumn="id" order="AFTER" resultType="Long">
select last_insert_id()
</selectKey>
</insert>
4 #{}的用法
- 我们发现,在Mapper.xml映射文件中,
经常使用#{属性名} 来作为SQL语句的占位符,来映射Sql需要的实际参数
- 也就是说:
#{}就是一个预编译的占位符作用
4.1 如果方法中只有一个参数
- 只有一个参数时,入参可以是以下情况:
- 1)Java的
基本类型、基本类型包装类、String类型等
- 只有一个参数情况下,#{}中写什么都无所谓,MyBatis会把实际参数直接传入这个位置,不管参数名称和类型
- 2)Java中的
POJO
,类似User这样的对象- 这种情况下,MyBatis会把User对象中的属性名和属性值以键值对形式保存到一个类似Map的结构中。
- 因此,当我们用#{属性名} 来取值时,其实就相当于根据键查找值。可以直接获取到User的属性值!
- 3)HashMap类型(很少用)
- 如果参数是一个HashMap,mybatis无序做处理,已经是map结构了。我们可以直接用 #{键的名称} 来获取到对应的值。这种方式与上面的方式2类似,因此很少使用
总结
- 简单数据类型:#{}不管写什么都可以获取值,建议用参数名称
- 对象类型:#{}可以根据参数对象中的属性名获取属性值
- Map类型:#{}可以根据Map的键来取到对应的值
4.2 如果接口中方法有多个参数
- 控制台
- 分析:
- 当有多个参数的情况下,MyBatis就会尝试把这多个参数放入一个Map中,这样才能方便我们通过#{}来取出这些值。
- 那么问题来了:MyBatis封装这些参数为Map时,键应该是什么??
- 有同学说:为什么直接用参数的名称做键呢?
- 大家一起思考一下,MyBatis底层肯定是用反射来进行的这些操作,那么反射可以获取到方法,但是是否能获取的方法的参数名称呢?
- 答案是不行,方法的参数名称是形参,是可变的。反射类Method中,并没有提供获取方法名称的功能。所以MyBatis并不知道我们传递的参数名称,只知道这些参数的值!
- 再次回到上面的问题,只有值,那么MyBatis封装Map的时候,应该以什么做键呢?
- 答案是这样的:默认情况下MyBatis会设置两种键:
- A:以从0开始的递增数字作为键,第一个参数是0,第2个参数就是1,以此类推
- B:以”param” + i 作为键,i是从1递增的数字,第一个参数键就是param1,第2个就是param2,以此类推
- 所以我们可以通过#{arg0} 或 #{param1} 来取到第一个参数,以此类推
4.2.1 解决方案1
4.2.2 解决方案2
4.2.3 解决方案3
- 结论:
如果有多个参数,请使用@Param注解来指定参数名称,代码可读性好!
5 ${}的用法
5.1 只有一个参数时
- 如果是一个POJO或Map,处理方式与#{}一样。
5.2 如果是多个参数
- 最后的结论:无论是1个参数,还是多个参数,都使用@Param注解,然后通过注解的中指定的名称取值
5.3 注意1
- ${}在取值时,并不会进行预编译,而是直接拼接SQL语句:
- 这样
无法防止SQL注入问题
5.4 注意2
- ${}取值的时候,需要自己判断参数数据类型,如果是字符串,还得自己加引号:‘${}’
- 因此,一般在SQL语句中,如果要获取参数,一般我们都会使用#{},而不是${}"
6 面试题 #{} 和 ${}的区别
- 区别:
- #是占位符, 会对SQL进行预编译,相当于?; $是做sql拼接, 有SQL注入的隐患
- #不需要关注数据类型, MyBatis自动实现数据类型转换; ${} 必须自己判断数据类型
- #是占位符, 会对SQL进行预编译,相当于?; $是做sql拼接, 有SQL注入的隐患
- 联系:
- 两者都支持@param注解, 指定参数名称, 获取参数值. 推荐这种方式
- 两者都支持@param注解, 指定参数名称, 获取参数值. 推荐这种方式
- 一般做参数传递,都会使用#{}
- 如果不是做预编译,而是做拼接sql, 会使用${}, 例如表名称的变化
7 ResultMap的高级映射(重点重点重点)
7.1 概述
- ResultMap用来定义SQL查询的结果与Java对象的映射关系。非常重要!
7.2 解决属性名和列名不一致的问题
- mybatis-config.xml: 关闭 驼峰标识
<settings>
<!--是否开启驼峰标识-->
<setting name="mapUnderscoreToCamelCase" value="false"/>
</settings>
- UserMapper.xml
<resultMap id="userResultMap" type="User">
<!--
property: javabean对应的属性
column : 数据库对应的列名
-->
<id property="id" column="id"></id>
<result property="userName" column="user_name"/>
</resultMap>
<!--
select元素:代表查询,类似的还有update、insert、delete
id:这个statement的唯一标示
parameterType:输入参数类型,可选参数,mybatis可以自动推断数据类型
resultType:查询结果类型,如果结果是集合,请写集合内元素类型!
resultMap:结果集映射,这个和resultType 只能存在1个,应对复杂的结果集。后面详细讲!
-->
<select id="queryUserById" resultMap="userResultMap">
select * from tb_user where id=${id}
</select>
- 这就是解决列名与字段名不一致问题的解决方案3,如果名称不符合驼峰规则,就必须使用这种方案。
7.2 autoMapping自动映射
- 我们刚才的配置中,只配置了id和userName字段,其它字段没有配置,映射也没有问题,为什么呢?
- 这是因为,在resultMap中,有一个属性叫做:autoMapping,如果值为true,并且列名称和字段名一致,是可以完成自动映射的。
- 默认情况下,这个autoMapping的值就是为true的。
- 那么,如果名称不一致,但是符合驼峰规则,能否自动映射呢?
- 也是可以的,但是要开启驼峰匹配:
<settings>
<!--是否开启驼峰标识-->
<setting name="mapUnderscoreToCamelCase" value="false"/>
</settings>
- 因此,一般我们配置resultMap,只要字段符合规则,我们只需要把ID配置出来就OK了。其它字段可以自动映射!
8 sql片段
-
我们经常会把SQL中比较通用的部分,提前出来,变成一个SQL片段,然后在各个SQL中都可以调用,简化书写:
-
例如,查询语句中,一般不会使用:Select * ,而是把列名一一列出,但是表的列名往往比较多,这时就可以提取出来:
<!--
定义sql片段
id: sql片段的唯一标识
-->
<sql id="userColumns">
id, user_name, password, name, age, sex, birthday, created, updated
</sql>
<!--
select元素:代表查询,类似的还有update、insert、delete
id:这个statement的唯一标示
parameterType:输入参数类型,可选参数,mybatis可以自动推断数据类型
resultType:查询结果类型,如果结果是集合,请写集合内元素类型!
resultMap:结果集映射,这个和resultType 只能存在1个,应对复杂的结果集。后面详细讲!
-->
<select id="queryUserById" resultMap="userResultMap">
select <include refid="userColumns"/>
from tb_user where id=${id}
</select>
<select id="queryUserList" resultType="User">
select <include refid="userColumns"/>
from tb_user
</select>