02-1-映射文件配置
1.入参
1.1.parameterType(了解)
CRUD标签都有一个属性parameterType,底层的statement通过它指定接收的参数类型。入参数据有以下几种类型:HashMap,基本数据类型(包装类),实体类;
设置传入这条语句的参数类的完全限定名或别名。
这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数类型。
说明:
在mybatis中入参的数据类型分为2大类:
- 基本数据类型:int,string,long,Date等;
- 复杂数据类型:类(pojo)和Map;
说明:如果传递参数是数组或者集合,底层都会封装到Map集合中。<br />【示例】
public interface UserMapper {
//根据id查询
User findById(Integer id);
}
【基本类型数据】
<!--根据id查询-->
<!--parameterType="int" 表示sql语句参数id的类型,int是Integer的别名.MyBatis 可以通过类型处理器(TypeHandler) 根据接口中的方法User queryById(Integer id)参数类型推断出具体传入语句的参数类型。-->
<select id="findById" resultType="user" parameterType="int">
select * from user where id = #{id}
</select>
【pojo类型】
<insert id="savetUser" parameterType="User">
INSERT INTO user(...) values(#{userName},...);
</insert>
底层原理:
User类:user_name属性,自动生成setter或者getter方法时,getUserName
当我们向xml传入#{user_Name}---->getUser_Name--->利用反射根据方法名称获取方法对象--->报反射异常
综上:
1)传入的时pojo的话,传入xml的变量名称与pojo类下属性名称要一致;
2)pojo中属性名称定义要尽量遵循驼峰命名,或者在自动生成getter或者setter方法时,自己手动纠正;
说明:对于parameterType属性可以不书写,那么MyBatis 就会通过类型处理器(TypeHandler) 根据接口中的方法User queryById(Integer id)参数类型推断出具体传入语句的参数类型。
1.2.自增主键回填(了解)
需求:新增一条数据成功后,将这条数据的主键封装到实体类中,并查看主键的值。
方式1:使用insert标签的子标签selectKey+last_insert_id()函数实现实现
| 属性 | 说明 | | —- | —- |
| keyColumn | 主键在表中对应的列名 |
| keyProperty | 主键在实体类中对应的属性名 |
| resultType | 主键的数据类型 |
| order | BEFORE:会首先选择主键,设置 keyProperty 然后执行插入语句 AFTER: 在添加语句后执行查询主键的语句 |
测试代码:
1)接口
/**
* 添加用户
* 返回值时影响的行数
* @param user
* @return
*/
Integer addUserAndGetFkId(User user);
2)映射文件:
<!--
selectKey:表示查询主键字段的标签 keyColumn:表示表中字段名称,一般指主键名称
keyProperty="id":表示pojo类中对应主键的属性名称
order="AFTER":表示在操作之前或者之后获取主键值
<selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
select last_insert_id()
</selectKey>
-->
<insert id="addUserAndGetFkId">
insert into user values(null,#{username},#{birthday},#{sex},#{address})
<selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
select last_insert_id()
</selectKey>
</insert>
3)测试
@Test
public void test13(){
UserMapper userMapper = MybatisUtil.getMapper(UserMapper.class);
User user = new User();
user.setUsername("唐僧2");
user.setSex("男");
Date date = Date.valueOf("2020-12-15");
user.setBirthday(date);
user.setAddress("长安");
Integer count = userMapper.addUserAndGetFkId(user);
System.out.println(user.getId());
//mybatis默认事务手动提交
MybatisUtil.commit();
MybatisUtil.close();
}
4)效果
方式2:使用insert标签的属性useGeneratedKeys,keyProperty,keyColumn实现
参数说明:
| 属性 | 说明 | | —- | —- |
| useGeneratedKeys | true 获取自动生成的主键,相当于select last_insert_id() |
| keyColumn | 表中主键的列名 |
| keyProperty | 实体类中主键的属性名 |
说明:直接在insert标签中增加属性的方式,只适合于支持自动增长主键类型的数据库,比如MySQL或SQL Server;
测试代码:
接口:
/**
* 添加用户
* 返回值时影响的行数
* @param user
* @return
*/
Integer addUserAndGetFkId2(User user);
映射文件:
<!--
useGeneratedKeys="true"表示开启获取主键id的功能
keyColumn="id":表示指定表中主键字段名称
keyProperty="id":表示指定pojo类中主键对应的属性名称
-->
<insert id="addUserAndGetFkId2" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
insert into user values(null,#{username},#{birthday},#{sex},#{address})
</insert>
测试:
@Test
public void test14(){
UserMapper userMapper = MybatisUtil.getMapper(UserMapper.class);
User user = new User();
user.setUsername("唐僧3");
user.setSex("男");
Date date = Date.valueOf("2020-12-15");
user.setBirthday(date);
user.setAddress("长安");
Integer count = userMapper.addUserAndGetFkId2(user);
System.out.println(user.getId());
//mybatis默认事务手动提交
MybatisUtil.commit();
MybatisUtil.close();
}
效果:
说明:使用方式2的话,数据库必须支持主键自增;
1.3.Mybatis入参是单参和多参(掌握)
1)单个参数
单个参数:接口方法传入一个参数
【接口传参】
User queryById(Integer id);
【接收参数】
1、通过#{参数名}接收
<!--根据id查询-->
<select id="queryById" resultType="User" parameterType="int">
select *,user_name AS userName from user where id = #{id}
</select>
2、通过#{任意变量名}接收
<!--根据id查询-->
<select id="queryById" resultType="User" parameterType="int">
select *,user_name AS userName from user where id = #{abc}
</select>
【结论】
如果接口传入的时单个参数,可以在xml中使用任意变量取接收,但是不建议乱写,最好见名知意;
2) 多个参数
需求:根据用户名和性别查询用户
2.1 接口传参
/**
* 需求:根据用户名和性别查询用户
* @param name
* @param sex
* @return
*/
List<User> findUsersByUserNameAndSex(String name,String sex);
2.2 UserMapper.xml
<select id="findUsersByUserNameAndSex" resultType="user">
select * from user where user_name=#{name} and sex=#{sex}
</select>
2.3 测试类
@Test
public void test15(){
UserMapper mapper = MybatisUtil.getMapper(UserMapper.class);
List<User> users = mapper.findUsersByUserNameAndSex("孙悟空", "男");
System.out.println(users);
MybatisUtil.close();
}
2.3 结果
此时会报参数绑定异常
解决方案:
方式1、使用参数索引获取:arg0,arg1(了解,不推荐)
<select id="findUsersByUserNameAndSex" resultType="user">
select * from user where user_name=#{arg0} and sex=#{arg1}
</select>
说明: 接口参数顺序要与arg0与arg1顺序一致;
方式2:使用参数位置获取:param1,param2(了解,不推荐)
<select id="findUsersByUserNameAndSex" resultType="user">
select * from user where user_name=#{param1} and sex=#{param2}
</select>
方式3:使用命名参数获取,明确指定传入参数的名称:(掌握)
步骤一:在接口中传入参数时通过@Param指定参数名称
/**
* 需求:根据用户名和性别查询用户
* @param name
* @param sex
* @return
*/
List<User> findUsersByUserNameAndSex(@Param("name") String name,@Param("sex") String sex);
步骤二:在接收参数时,通过指定的名称获取参数值;
<select id="findUsersByUserNameAndSex" resultType="user">
select * from user where user_name=#{name} and sex=#{sex}
</select>
测试:
小结:
1)入参是单个参数(单个参数是单值类型)
在xml中可以任意变量取接收,但是建议名称见名知意;
2)多个参数
建议使用方式3通过注解方式给参数取别名,然后再xml中使用这个别名
eg:
List<User> findUsersByUserNameAndSex(@Param("name") String name,@Param("sex") String sex);
那么在xml中:
<select id="findUsersByUserNameAndSex" resultType="user">
select * from user where user_name=#{name} and sex=#{sex}
</select>
1.4 入参类型是Pojo和Map
1)pojo参数【掌握】
说明:接口方法传入pojo类型的数据时,mybatis底层直接使用pojo封装数据。 sql语句中 #{username}取值==》到pojo中调用 getUsername(){}
测试代码:
接口:
/**
* 插入功能
* @param user
*/
void saveAndGetkey2(User user);
映射文件:
<insert id="saveAndGetkey2" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
insert into user values(null,#{username},#{age},#{birthday},#{sex},#{address})
</insert>
测试:
@Test
public void testAdd3(){
User user = new User();
user.setAddress("北京");
user.setUsername("于彪");
user.setBirthday(new Date());
mapper.saveAndGetkey2(user);
//直接获取返回的主键id值
System.out.println(user.getId());
}
小结:
使用pojo的话,xml中参数一定名称要与pojo中的属性名称一致,否则报错(binding exception);
2)HashMap参数
需求:模拟用户登录,登录方法参数是Map集合,泛型都是String类型分别表示用户名和性别。
注意事项:参数map中的key值是与SQL语句中 #{} 的取值名称一致。
代码实现:
2.1 接口:
/**
* 需求:模拟用户登录,登录方法参数是Map集合,泛型都是String类型分别表示用户名和性别。
* @return
*/
User loginByNameAndSex(Map map);
2.2 映射文件:
<select id="loginByNameAndSex" resultType="user" parameterType="map">
select * from user where user_name=#{name} and sex=#{sex}
</select>
2.3 测试:
@Test
public void test16(){
UserMapper mapper = MybatisUtil.getMapper(UserMapper.class);
HashMap<String, String> map = new HashMap<>();
//map中的key要与xml映射文件下的参数名称要一致
map.put("name","孙悟空");
map.put("sex","男");
User user = mapper.loginByNameAndSex(map);
System.out.println(user);
MybatisUtil.close();
}
小结:
1)入参类型pojo注意事项?
xml映射文件下的参数名称要与pojo类中属性名称一致;
2)入参是map注意事项?
xml映射文件下的参数名称要与map中key的名称一致;
2.参数值的获取
2.1.#{}和${}两种获取参数方式TODO
参数值的获取指的是statement获取接口方法中传入的参数。<br /> 获取参数,有两种方式:**#{}**和**${}**;<br /> 以根据id查询为例测试#{}和${}区别:
使用#{}接收参数:
select * from user where id=? //预编译处理,防止sql注入
使用${}接收参数:
select * from user where id=1 //参数值直接拼接到sql中,会有sql注入的风险
1).#{}取值
使用#{}的sql是进行预编译的,可以防止sql注入;
2).${}
取值
注意:${id} 获取id值时,必须使用命名参数取值@param:
补充:如果是取单个值,也可使用${value}获取
1)映射文件
<select id="findById2" resultType="user" parameterType="int">
select * from user where id=${id}
</select>
2)接口
/**
* 根据id查询用户信息 测试${}
* @param id
* @return
*/
User findById2(@Param("id") Integer id);
3)测试
@Test
public void test12(){
UserMapper mapper = MybatisUtils.getMapper(UserMapper.class);
User user = mapper.findById2(1);
System.out.println(user);
MybatisUtils.close();
}
小结:
面试:#{}和${}取值有什么区别?
#{}
sql进行预编译处理,防止sql注入;
${}
参数与sql直接拼接,有sql注入的风险;
2.2.${}应用场景和注意事项(了解)
1)${}取值的应用场景
在一些特殊的应用场景中,需要对SQL语句部分(不是参数)进行拼接,这个时候就必须使用${}来进行拼接,不能使用#{}.例如:
1、企业开发中随着数据量的增大,往往会将数据表按照年份进行分表,如:2017_user,2018_user....,对这些表进行查询就需要动态把年份传入进来,而年份是表名的一部分,并不是参数,JDBC无法对其预编译,所以只能使用${}进行拼接:
SELECT * FROM ${year}_user;
2、根据表名查询数据总记录数:
SELECT COUNT(*) FROM user
SELECT COUNT(*) FROM order
SELECT COUNT(*) FROM ${tableName}
简言之:如果需要设置到SQL中的不是查询的条件,只能使用${}拼接;
示例:
需求:根据输入的表名统计指定表下的总记录数;
1)接口
/**
* 需求:根据输入的表名统计指定表下的总记录数;
*/
Integer countByTableName(@Param("tableName") String tableName);
2)映射文件
<select id="countByTableName" resultType="integer">
select count(*) from ${tableName}
</select>
3)测试类
@Test
public void test18(){
UserMapper mapper = MybatisUtil.getMapper(UserMapper.class);
Integer count = mapper.countByTableName("user");
System.out.println(count);
MybatisUtil.close();
}
4)效果:
小结:
${}使用方式及场景?
使用方式:
1.接口中@param给参数取别名 2.在xml中使用注解的别名${注解别名}
场景:
一切非条件查询的参数拼接可以使用${}
2)${}取值注意事项 (了解)
【 ${}获取单个值】
${}
获取单个值时,最好是通过命名参数的形式获取。如果不指定参数的,也可以使用${value}来获取传入的单个值;
传入参数:没有指定参数名称
User selectUserById(Integer id);
获取参数通过${value}获取
<select id="selectUserById" resultType="user">
select * from user where id = ${value}
</select>
【${}获取配置文件中的值】
有时候,我们如果非要使用$来接收参数,将login修改如下:
<!--根据用户名和性别查询-->
<select id="queryByUserNameAndSex" resultType="User">
SELECT * FROM user WHERE user_name = '${jdbc.user}' AND sex = #{sex}
</select>
说明:上述sql语句中:SELECT FROM user WHERE user_name = *’${username}’ AND sex = #{sex}
对于 ‘{}获取数据的方式直接将获取的数据拼接到字符串上,并不会加引号,如果获取的值是数值型,没有问题,但是如果是字符类型就会有问题,所以需要加上引号进行拼接。
使用${}注意事项:
1.使用${变量}方式获取变量值时,不要与全局的properties下定义的参数名称冲突,否则数据注入错误;
2.使用${变量}传入字符串类型时,需要自己维护字符串的上引号;
3.1 简单结果集映射TODO
在使用原生的JDBC操作时,对于结果集ResultSet,需要手动处理。<br /> mybatis框架提供了resultType和resultMap来对结果集进行封装。<br />**注意:只要一个方法有返回值需要处理,那么 resultType和resultMap必须有一个**
1.resultType
从sql语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。可以使用 resultType 或 resultMap,但不能同时使用。
1.1 返回值是基本类型
例如 int ,string ===>resultType=”书写对应的基本类型别名或者全名即可”
测试:findNameById:
1)基本类型 int short double ... 别名: _基本类型名称
2)包装类 类 String ArrayList .... 别名:类名首字母小写
3)自定义类 扫包取别名 类首字母小写(大写也可)
1.2 返回值为一个pojo(User)对象时
测试:findById:
1.3 返回值为一个List时
当返回值为List集合时,resultType需要设置成集合中存储的具体的pojo数据类型:<br /> 测试:findAllUsers:
3.2 Map类型结果映射
【1】返回一条数据,封装到map中
需求:查询id是1的数据,将查询的结果封装到Map
1)定义接口
/**
* 需求:查询id是1的数据,将查询的结果封装到Map<String,Object>中
* @param id
* @return
*/
Map<String,Object> findMapById(@Param("id") Integer id);
2)定义映射文件配置
<select id="findMapById" resultType="map">
select id,user_name as userName,address from user where id=#{id}
</select>
3)测试:
@Test
public void test19(){
UserMapper mapper = MybatisUtil.getMapper(UserMapper.class);
Map<String, Object> map = mapper.findMapById(1);
System.out.println(map);
MybatisUtil.close();
}
4)效果
【2】返回多条数据,封装到map中
需求:查询数据表所有的数据封装到Map<Integer,User>集合中<br /> 要求: Key值为一条记录的主键,Value值为pojo的对象.<br />说明:需要在接口的方法上使用注解@MapKey指定数据表中哪一列作为Map集合的key,否则mybatis不知道具体哪个列作为Map集合的key.<br />1)映射文件:
<select id="findAllToMap" resultType="map">
select id,user_name as name,birthday,sex,address from user
</select>
2)接口:
/**
* 获取所有用户,其中key为id值,value为user对象
* @return
*/
@MapKey("id")
Map<Integer,User> findAllToMap();
3)测试:
@Test
public void test16(){
UserMapper mapper = MybatisUtils.getMapper(UserMapper.class);
Map map = mapper.findAllToMap();
System.out.println(map);
MybatisUtils.close();
}
小结:
1)map接收单条记录
map中的key就是查询的表的字段名称,如果使用as区别名,那么可以就是对应的别名的名称;
2)map接收多条记录
1)需要指定作为key的字段,一般是主键字段 @MapKey("指定字段名称")
2)指定每一条记录封装的对象类型;
5.resultMap映射(掌握)
1.正常开发中,数据库字段名称与Pojo类属性名称不一致时,一般通过驼峰映射或者As关键字取别名可以搞定,但是很多场景下,对于复杂的orm映射,上述的2种方式就不能适用了;
2.ResultMap是mybatis中最重要最强大的元素,使用ResultMap可以解决复杂映射问题:
1. POJO属性名和表结构字段名不一致的问题(有些情况下也不是标准的驼峰格式,比如id和userId)
2. 完成高级查询,比如说,一对一、一对多、多对多。
【需求】
使用resultMap完成结果集的封装(resultSet===》JavaBean)
【实现步骤】
手动配置实体类属性和表字段映射关系的步骤如下:
1、 配置自定义结果集<resultMap>
2、 配置id映射
3、 配置其他普通属性的映射
步骤一:将驼峰匹配注释掉
一旦注释掉驼峰匹配,那么再通过findAll查询的结果中,用户名就无法封装了,此时我们可以尝试使用ResultMap来解决这个问题。
<settings>
<!--作用:表:user_name 类:userName/username 自动映射-->
<setting name="mapUnderscoreToCamelCase" value="false"/>
</settings>
步骤二:配置resultMap
resultMap标签的作用:自定义结果集,自行设置结果集的封装方式
id属性:resultMap标签的唯一标识,不能重复,一般是用来被引用的
type属性:结果集的封装类型
autoMapping属性:操作单表时,不配置默认为true,如果pojo对象中的属性名称和表中字段名称相同,则自动映射。
在映射文件中自定义结果集类型:
<!--type="user" 表示结果集的封装类型是user-->
<resultMap id="userResultMap" type="user" autoMapping="true">
<!--配置主键映射关系-->
<id column="id" property="id"></id>
<!--配置用户名的映射关系 column 表示数据表列 property表示pojo的属性-->
<result column="user_name" property="name"></result>
</resultMap>
步骤三:修改查询语句的statement
在查询语句的select标签中通过resultMap属性可以引用自定义结果集作为数据的封装方式
<!--自定义映射规则-->
<!--
autoMapping="true":表示如果字段名称与pojo属性名称一致,可以省略不写
-->
<resultMap id="userMap" type="user" autoMapping="true">
<!--主键字段映射-->
<id column="id" property="id"/>
<!--非主键字段-->
<result column="user_name" property="username"/>
</resultMap>
<select id="findByIdUseResutMap" resultMap="userMap">
select * from user where id=#{id}
</select>
定义接口:
/**
* 使用resultMap标签自定义映射规则
* @param id
* @return
*/
User findByIdUseResutMap(@Param("id") Integer id);
测试代码:
@Test
public void test21(){
UserMapper mapper = MybatisUtil.getMapper(UserMapper.class);
User user = mapper.findByIdUseResutMap(1);
System.out.println(user);
MybatisUtil.close();
}
注意:测试完记得将驼峰命名的配置重新开启,因为其他的测试方法还要用。
PPT演示:
resultMap小结
1.resultMap标签的作用?
1)提高了代码的复用性;
2)结果集映射的;(将查询的结果映射到pojo类下)
2.resultMap有哪些属性和子标签?
<resultMap id="唯一标识" type="映射的pojo类" autoMapping="true">
<!--主键字段映射-->
<id column="表中主键字段" property="pojo类中主键对应的属性名称"/>
<!--普通字段映射-->
<result column="表中非主键字段" property="pojo类中非主键对应的属性名称"/>
</resultMap>
6.SQL片段(了解)
6.1.引用当前文件中的SQL片段
sql标签可以定义一个sql片段,在需要使用该sql片段的地方,通过<include refid="sql片段id"/>标签来使用。
【1】定义SQL片段
<sql id="userCommonSql">
id,user_name,birthday,sex,address
</sql>
注意:SQL片段必须设置id属性;
【2】使用SQL片段
在SQL语句中通过`<include>`标签引入SQL片段;
<select id="findByIdUseResutMap" resultMap="userMap">
select <include refid="userCommonSql"/> from user where id=#{id}
</select>
6.2 引入独立文件中的SQL片段
很多时候同一个sql片段,可能在多个映射文件中都有使用,如果每一个映射文件都编写一个相同的sql就比较麻烦,因此可以将通用的sql片段都定义在一个专门存放sql片段的映射文件中,然后由其他映射文件引用它即可。<br />
如下,在src目录下新增CommonSQL.xml文件:
【1】新建SQL片段文件
复制一份映射文件,将SQL片段写入即可<br />**【CommonSQL.xml】**
<?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="CommonSql">
<sql id="userSql">
id,user_name,birthday,sex,address
</sql>
<sql id="empSql">
eid,ename,age
</sql>
</mapper>
【2】在mybatis核心配置文件mybatis-config.xml引入SQL片段文件
定义好sql片段的映射文件之后,接下来就该使用它了,首先应该把该映射文件引入到mybatis的全局配置文件中(mybatis-config.xml):
<mappers>
<mapper resource="CommonSql.xml"/>
<package name="com.heima.mapper"/>
</mappers>
【3】引用SQL片段
最后在需要使用该sql片段的地方通过include标签的refId属性引用该sql片段:`<include refId=”名称空间.sql片段的id” />`<br />
在UserMapper.xml的映射文件中,进一步改造根据用户名查询用户信息
<select id="findUserById2" resultMap="userMap" parameterType="int">
select <include refid="CommonSql.userSql"/> from user where id = ${id}
</select>