MyBatis 用法详解
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的工作,使得开发者可以专注于编写 SQL 语句本身。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 对象。
使用 MyBatis 需要导入依赖(MyBatis 和 MySQL(数据库都行)),和配置 yml 文件。导入对应的依赖比较简单,所以下面只给出 yml 文件的配置。
yml 文件配置如下。
下面用中文填写(除了注释)的都是要根据自己的情况来填写的。
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/对应数据库名称?characterEncoding=utf8&useSSL=false
username: root
password: 对应数据库的密码(如果是纯数字记得加上单引号)
driver-class-name: com.mysql.cj.jdbc.Driver
mvc:
favicon:
enable: false
profiles: #多平台配置
active: dev
# 设置 Mybatis 的 xml 保存路径
mybatis:
mapper-locations: classpath:mapper/*Mapper.xml
configuration: # 配置打印 MyBatis 执行的 SQL
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true #自动驼峰转换
MyBatis 有两种实现方式。(1)注解(2)xml 文件实现。
一、普通 SQL
1.1 注解实现:
注解实现,其实就是在对应的注解里面写上 SQL 语句即可。
1.1.1 参数传递:
需求:查找 id=4 的用户,对应的 SQL 就是:select * from userinfo where id=4。
@Select("select username, `password`, age, gender, phone from userinfo where id = 4")
UserInfo queryById();
但是这样的话,只能查找 id=4 的数据,所以 SQL 语句中的 id 值不能写成固定数值,需要变为动态的数值。
解决方法:在 queryById 方法中添加一个参数(id),将方法中的参数,传给 SQL 语句。
使用#{}的方式获取方法中的参数。注意:#{}里面的名称需要和方法参数名称一致(如果方法参数是一个对象,那么#{}里面的值,要和对象的属性名称一样。)
@Select("select username, `password`, age, gender, phone from userinfo where id= #{id} ")
UserInfo queryById(Integer id);
如果 mapper 接口方法形参只有一个普通类型的参数,#{…}里面的属性名可以随便写,如:#{id}、# {value}。建议和参数名保持一致。
添加测试用例:
@Test
void queryById() {
UserInfo userInfo = userInfoMapper.queryById(4);
System.out.println(userInfo);
}
运行结果:
也可以通过 @Param ,设置参数的别名,如果使用 @Param 设置别名,#{…}里面的属性名必须和 @Param 设置的一样。
@Select("select username, `password`, age, gender, phone from userinfo where id = #{userid} ")
UserInfo queryById(@Param("userid") Integer id);
如果参数是对象,设置了 @Param 属性,#{…} 需要使用 别名.属性 来获取。
//插入数据
@Insert("insert into userinfo(username,`password`,age,gender,phone) " +
"values(#{gobeyye.username},#{gobeyye.password}" +
",#{gobeyye.age},#{gobeyye.gender},#{gobeyye.phone})")
Integer insertUserInfo1(@Param("gobeyye") UserInfo userInfo);
1.1.2 增(@Insert):
SQL 语句:
insert into userinfo (username, `password`, age, gender, phone) values
("zhaoliu","zhaoliu",19,1,"18700001234")
把 SQL 中的常量替换为动态的参数:
Mapper 接口的方法:
@Insert("insert into userinfo (username, `password`, age, gender, phone) values (#{username},#{password},#{age},#{gender},#{phone})")
Integer insert(UserInfo userInfo);
直接使用 UserInfo 对象的属性名来获取参数。
测试代码:
@Test
void insertUesrInfo1() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("zhaoliu");
userInfo.setPassword("zhaoliu");
userInfo.setAge(19);
userInfo.setGender(1);
userInfo.setPhone("18700001234");
userInfoMapper.insertUesrInfo1(userInfo);
}
运行结果:下面这个运行结果,需要导入日志配置。
返回主键:
如果想要拿到自增 id(主键一般默认是 id),需要在 Mapper 接口的方法上添加一个 Options 的注解。
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into userinfo (username, age, gender, phone) values (#{userinfo.username},#{userinfo.age},#{userinfo.gender},#{userinfo.phone})")
Integer insert(@Param("userinfo") UserInfo userInfo);
-
useGeneratedKeys:这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
-
keyProperty:指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素,设置它的值,默认值:未设置(unset)。
注意:设置 useGeneratedKeys = true 之后,方法返回值依然是受影响的行数,自增 id 会设置在上述 keyProperty 指定的属性中。
1.1.3 删(@Delete):
SQL 语句:
问号是占位符的意思,方便下面写代码,不是 SQL 语法支持的。
delete from userinfo where id = ?
Mapper 接口的方法:
@Delete("delete from userinfo where id = #{id}")
void delete(Integer id);
1.1.4 改(@Update):
SQL 语句:
update userinfo set username="zhaoliu" where id=5
Mapper 接口的方法:
@Update("update userinfo set username=#{username} where id=#{id}")
void update(UserInfo userInfo);
1.1.5 查(@Select):
SQL 语句:
@Select("select id, username, `password`, age, gender, phone, delete_flag,
create_time, update_time from userinfo")
Mapper 接口的方法:
@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from userinfo")
List<UserInfo> queryAllUser();
查询结果:
从运行结果上可以看到,我们 SQL 语句中,查询了 delete_flag,create_time,update_time 但是这几个属性却没有赋值。
原因分析:
当自动映射查询结果时,MyBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性(忽略大小写)。这意味着如果发现了 ID 列和 id 属性,MyBatis 会将列 ID 的值赋给 id 属性。
观察下图:
我们可以发现,这是因为字段名和属性名称不一致导致的(命名规则不同导致的)。
解决方法:
-
起别名
-
结果映射
-
开启驼峰命名
1.1.5.1 起别名:
在 SQL 语句中,给列名起别名,保持别名和实体类属性名一样。
@Select("select id, username, `password`, age, gender, phone," +
" delete_flag as deleteFlag, create_time as createTime, update_time as updateTime from userinfo")
public List<UserInfo> queryAllUser();
1.1.5.2 结果映射:
@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from userinfo")
@Results({
@Result(column = "delete_flag", property = "deleteFlag"),
@Result(column = "create_time", property = "createTime"),
@Result(column = "update_time", property = "updateTime")
})
List<UserInfo> queryAllUser2();
如果其他 SQL,也希望可以复用这个映射关系,可以给这个 Results 定义一个名称。
@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from userinfo")
@Results(id="resultMap",value = {
@Result(column = "delete_flag", property = "deleteFlag"),
@Result(column = "create_time", property = "createTime"),
@Result(column = "update_time", property = "updateTime")
})
List<UserInfo> queryAllUser2();
@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time " +
"from userinfo where id= #{userid} ")
@ResultMap(value = "resultMap")
UserInfo queryById(@Param("userid") Integer id);
使用 id 属性给该 Results 定义别名,使用 @ResultMap 注解来复用 Results。
1.1.5.3 开启驼峰命名(推荐):
通常数据库列使用蛇形命名法进行命名(下划线分割各个单词),而 Java 属性一般遵循驼峰命名法约定。为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase 设置为 true(设置 application.properties
或者 application.yml
文件)。
mybatis:
configuration:
# 日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 自动驼峰转换
map-underscore-to-camel-case: true
这时 Java 代码不做任何处理。字段就能全部正确赋值。
1.2 MyBatis XML 配置文件实现:
1.2.1 添加 Mapper.xml
首先根据 yml 文件中配置的 mapper 路径创建对应的 xml 文件。
创建完成后,向里面填入 MyBatis 的固定 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="com.example.demo.mapper.UserInfoXMlMapper">
<select id="queryAllUser" resultType="com.example.demo.model.UserInfo">
select username,`password`, age, gender, phone from userinfo
</select>
</mapper>
以下是对以上标签的说明:
<mapper>
标签:需要指定 namespace 属性,标识命名空间,值为 mapper 接口的全限定名,包括全包名.类名。<select>
查询标签:是用来执行数据库的查询操作的。select 中的属性解释如下:
- id :是和 Interface(接口)中定义的方法名称一样的,表示对接口的具体实现方法。
- resultType:返回的数据类型。
其实 xml 增删改查和注解实现增删改查,其实差不多,无非就是一个使用注解,另一个使用标签。
1.2.2 增:
UserInfoMapper 接口:
Integer deleteUser(Integer id);
UserInfoMapper.xml 实现:
<insert id="insertUser">
insert into userinfo (username, `password`, age, gender, phone) values (#{username}, #{password}, #{age},#{gender},#{phone})
</insert>
测试代码:
@Test
void insertUser() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("gobeyye");
userInfo.setPassword("123456");
userInfo.setAge(18);
userInfo.setGender(1);
userInfo.setPhone("12345678");
userInfoXMLMapper.insertUser(userInfo);
}
效果如下:
- 返回自增 id:
接口定义不变,Mapper.xml 实现设置 useGeneratedKeys 和 keyProperty 属性。代码如下:
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into userinfo (username, `password`, age, gender, phone)
values (#{username}, #{password}, #{age},#{gender},#{phone})
</insert>
1.2.3 删:
UserInfoMapper 接口:
Integer deleteUser(Integer id);
UserInfoMapper.xml 实现:
<delete id="deleteUser">
delete from userinfo where id = #{id}
</delete>
1.2.4 改:
UserInfoMapper 接口:
Integer updateUser(UserInfo userInfo);
UserInfoMapper.xml 实现:
<update id="updateUser">
update userinfo set username=#{username} where id=#{id}
</update>
1.2.5 查:
UserInfoMapper 接口:
List<UserInfo> queryAllUser();
UserInfoMapper.xml 实现:
<select id="queryAllUser" resultType="com.example.demo.model.UserInfo">
select id, username,`password`, age, gender, phone, delete_flag, create_time, update_time from userinfo
</select>
运行结果:
结果显示:deleteFlag,createTime,updateTime 也没有进行赋值。
解决办法和注解类似:
-
起别名
-
结果映射
-
开启驼峰命名
其中 1 和 3 方法和注解一样,下面重点讲如何使用 xml 写结果映射。
Mapper.xml:
<resultMap id="BaseMap" type="com.gobeyye.mybatis.moder.UserInfo">
<id column="id" property="id"></id>
<result column="delete_flag" property="deleteFlag"></result>
<result column="create_time" property="createTime"></result>
<result column="update_time" property="updateTime"></result>
</resultMap>
<select id="queryAllUser" resultMap="BaseMap">
select id, username,`password`, age, gender, phone, delete_flag, create_time, update_time from userinfo
</select>
多表查询和单表查询类似,只是 SQL 不同而已,不再多说。
二、#{} 和 ${} 的区别:
MyBatis 参数赋值有两种方式,文章前面使用了 #{} 进行赋值,接下来我们看下二者的区别。
#{} 和 ${} 的区别就是预编译SQL
和即时SQL
的区别。
了解 SQL 语句的执行流程,有助于理解下面的区别。
SQL 语句的执行流程:
-
SQL 语法校验和解析
-
SQL 优化
-
SQL 执行
-
#{} 性能更高:
绝大多数情况下,某一条 SQL 语句可能会被反复调用执行,或者每次执行的时候只有个别的值不同(比如 select 的 where 子句值不同,update 的 set 子句值不同,insert 的 values 值不同)。如果每次都需要经过上面的语法解析,SQL 优化、SQL 编译等,则效率就明显不行了。
预编译 SQL,编译一次之后会将编译后的 SQL 语句缓存起来,后面再次执行这条语句时,不会再次编译 (只是输入的参数不同),省去了解析优化等过程,以此来提高效率。
- #{} 更安全(防止 SQL 注入,最重要的一点):
SQL 注入:是通过操作输入的数据来修改事先定义好的 SQL 语句,以达到执行代码对服务器进行攻击的方法。
#{} 内部的值,只会被识别成参数,而 ${} 内部的值,既可以识别成参数也可以被识别成 SQL 语句。
- 特殊场景只能使用 ${}:
在排序功能中,我们可以选择升序或者降序。
Mapper 实现:
@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time " +
"from userinfo order by id ${sort} ")
List<UserInfo> queryAllUserBySort(String sort);
部分结果如下:
如果将 ${} 换成 #{},就会出现如下结果,并报错:
这是因为 #{} 会根据参数类型判断,是否拼接引号''
。如果参数类型为 String,就会加上引号。
当使用 #{sort} 查询时,asc 前后自动给加了引号,导致 sql 错误。
除此之外,还有表名作为参数时,也只能使用 ${},但是一定要注意 SQL 注入问题。
综上,能使用 #{} 的场景就使用 #{},实在不行再使用 ${},但是一定要注意 SQL 注入问题。
三、动态 SQL:
动态 SQL 是 Mybatis 的强大特性之一,能够完成不同条件下不同的 sql 拼接。
可以参考官方文档:https://mybatis.net.cn/dynamic-sql.html
由于动态 SQL 会复杂一点,所以一般将动态 SQL 写在 xml 文件中(使用注解也行)。下面就都使用 xml 文件的形式,注解就不再演示。
3.1 <if>
标签:
接口定义:
Integer insertUserByCondition(UserInfo userInfo);
Mapper.xml 实现:
<insert id="insertUserByCondition">
INSERT INTO userinfo (
username, `password`, age,
<if test="gender != null">
gender,
</if>
phone)
VALUES (#{username},#{password}, #{age},
<if test="gender != null">
#{gender},
</if>
#{phone})
</insert>
测试代码如下:
@Test
void insertUserByCondition() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("gobeyye");
userInfo.setPassword("123123");
userInfo.setAge(18);
userInfo.setPhone("999999");
userInfoXMLMapper.insertUserByCondition(userInfo);
}
效果如下:可以看到 gender 由于属性没有值,所以该字段被去除了。
这里有的友友可能会有疑问:把全部字段都加上,不进行赋值的字段,参数传为 null 不就行了?
答:不行,因为 MySQL 中,有的字段如果没有赋值,会有默认值(可以自己设定),如果传值为 null,就会把默认值覆盖掉,这是我们不希望看到的,所以只能使用 if 标签。
3.2 <trim>
标签:
之前的插入用户功能,只是有一个 gender 字段可能是选填项,如果有多个字段,一般考虑使用标签结合标签,对多个字段都采取动态生成的方式。
<trim>
标签中有如下属性:
-
prefix:表示整个语句块,以 prefix 的值作为前缀。
-
suffix:表示整个语句块,以 suffix 的值作为后缀。
-
prefixOverrides:表示整个语句块要去除掉的前缀。
-
suffixOverrides:表示整个语句块要去除掉的后缀。
调整 Mapper.xml 的插入语句为:
<insert id="insertUserByCondition">
insert into userinfo
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username !=null">
username,
</if>
<if test="password !=null">
`password`,
</if>
<if test="age != null">
age,
</if>
<if test="gender != null">
gender,
</if>
<if test="phone != null">
phone,
</if>
</trim>
values
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username !=null">
#{username},
</if>
<if test="password !=null">
#{password},
</if>
<if test="age != null">
#{age},
</if>
<if test="gender != null">
#{gender},
</if>
<if test="phone != null">
#{phone}
</if>
</trim>
</insert>
在上面的这段代码中,就可以使用 <trim>
标签起到解决,多余后缀,
的问题。
3.3 <where>
标签:
where 标签解决的是,如果所有条件都加上了 if 标签,且都没有进行传值,这时 where 就会多出来,这时 SQL 语法就出错了,使用 where 标签就可以很好的解决上面的问题。
<where>
只会在子元素有内容的情况下才插入 where 字句,而且会自动去除字句开头的 AND 或 OR。
<select id="queryByCondition" resultType="com.example.demo.model.UserInfo">
select id, username, age, gender, phone, delete_flag, create_time,
update_time
from userinfo
<where>
<if test="age != null">
and age = #{age}
</if>
<if test="gender != null">
and gender = #{gender}
</if>
<if test="deleteFlag != null">
and delete_flag = #{deleteFlag}
</if>
</where>
</select>
3.4 <set>
标签:
<set>
:动态的在 SQL 语句中插入 set 关键字,并会删掉额外的逗号。(用于 update 语句中)。
Mapper.xml:
<update id="updateUserByCondition">
update userinfo
<set>
<if test="username != null">
username = #{username},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="deleteFlag != null">
delete_flag = #{deleteFlag},
</if>
</set>
where id = #{id}
</update>
3.5 <foreach>
标签:
对集合进行遍历时可以使用该标签。标签有如下属性:
-
collection:绑定方法参数中的集合,如 List,Set,Map 或数组对象。
-
item:遍历时的每一个对象。
-
open:语句块开头的字符串。
-
close:语句块结束的字符串。
-
separator:每次遍历之间间隔的字符串。
使用演示:
接口方法:
void deleteByIds(List<Integer> ids);
Mapper.xml:
<delete id="deleteByIds">
delete from userinfo
where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
3.6 <include>
标签:
在 xml 映射文件中配置的 SQL,有时可能会存在很多重复的片段,此时就会存在很多冗余的代码。
我们可以对重复的代码片段进行抽取,将其通过标签封装到一个 SQL 片段,然后再通过标签进行引用。
<sql>
:定义可重用的 SQL 片段。<include>
:通过属性 refid,指定包含的 SQL 片段。
例如 SQL片段:
<sql id="allColumn">
id, username, age, gender, phone, delete_flag, create_time, update_time
</sql>
通过 include 标签进行引用。操作如下:
<select id="queryById" resultType="com.example.demo.model.UserInfo">
select
<include refid="allColumn"></include>
from userinfo where id= #{id}
</select>
结语:
其实写博客不仅仅是为了教大家,同时这也有利于我巩固知识点,和做一个学习的总结,由于作者水平有限,对文章有任何问题还请指出,非常感谢。如果大家有所收获的话,还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。
更多推荐
所有评论(0)