文章目录
- MyBatis入门
- 定义
- 依赖
- 数据准备
- 数据库
- 实体类
- 企业开发规范
- 数据库配置
- 基础实现与测试
- Mapper层
- 测试
- MyBatis日志
- 增删改查
- 增--Insert
- 增--insert
- @Param
- 插入成功返回ID--@Options
- 删--delete
- 改--update
- 查--select
- 起别名
- 结果映射
- 开启驼峰命名
- 参数传递
- #{} 和 ${}区别
- 案例
- 区别
- 预编译SQL和即时SQL
- #{}的效率更高
- #{}更安全(防止sql注入)
- like查询
- 传递多个参数
- xml实现
- 简单插入示例--insert
- 简单删除示例--delete
- 简单修改示例--delete
- 简单查询示例--delete
- 多表查询
- 准备工作
- 动态SQL
- < if >和< trim >
- < where >
- < set >
- < foreach >
- < include >
MyBatis入门
定义
MyBatis是⼀款优秀的持久层框架,⽤于简化JDBC的开发。
依赖
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.4</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency>
数据准备
数据库
-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;-- 使用数据数据
USE mybatis_test;-- 创建表[用户表]
DROP TABLE IF EXISTS userinfo;
CREATE TABLE `userinfo` (`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,`username` VARCHAR ( 127 ) NOT NULL,`password` VARCHAR ( 127 ) NOT NULL,`age` TINYINT ( 4 ) NOT NULL,`gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2`在这里插入代码片`-女 0-默认',`phone` VARCHAR ( 15 ) DEFAULT NULL,`delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now(),PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;-- 添加用户信息
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );
实体类
对应上述实现出来的数据库表,实现对应的实体类 UserInfo 。
@Data
public class UserInfo {private Integer id;private String username;private String password;private Integer age;private Integer gender;private String phone;private Integer deleteFlag;private Date createTime;private Date updateTime;
}
企业开发规范
- 表名:字段名使用小写字母或数字,单词之间以下划线分割,尽量避免出现数字开头或者两个下划线,中间只出现数字。
- 表必备三字段:id(主键)、create_time(创建时间)、 update_time(更新时间),有同等含义的字段即可,字段名不做强制要求。
- 在表查询中,避免使用 * 作为查询的字段列表。
数据库配置
这里展示 application.yml 配置文件。
spring:application:name: mybatis-demodatasource:url: jdbc:mysql://localhost:3306/mybatis_test?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver
基础实现与测试
Mapper层
Mybatis的持久层接口规范一般叫 XxxMapper。
@Mapper注解:表示是MyBatis中的Mapper接口,程序运行时,框架会自动生成接口的实现类对象(代理对象),并给交Spring的IoC容器管理。
@Select注解:代表的就是select查询,也就是注解对应⽅法的具体实现内容。
@Mapper
public interface UserInfoMapper {@Select("select * from userinfo")List<UserInfo> getUserInfoAll();
}
测试
IDEA可以自动生成测试类,在需要测试的Mapper接口中,右键->Generate ->Test,setUp/@Before方法会出现在执行该测试的前面,tearDown/@After会出现在执行该测试的后面,一定要将@SpringBootTest注解到该类,以及通过@Autowired注入Mapper对象。
@SpringBootTest
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userInfoMapper;@BeforeEachvoid setUp() {System.out.println("before each");}@Testvoid getUserInfoAll() {System.out.println(userInfoMapper.getUserInfoAll());}@AfterEachvoid tearDown() {System.out.println("after each");}
MyBatis日志
在Mybatis中可以借助日志查看到sql语句的执行、执行传递的参数以及执行结果,在配置文件中进行配置即可,配置内容如下:
mybatis:configuration: # 配置打印 MyBatis 执行的 SQLlog-impl: org.apache.ibatis.logging.stdout.StdOutImpl
增删改查
增–Insert
//Mapper类@Insert("insert into userinfo (username, password, age, gender) " +"values (#{username},#{password},#{age},#{gender})")Integer insert(UserInfo userinfo);//测试类@Testvoid insert() {UserInfo userinfo = new UserInfo();userinfo.setUsername("laojun");userinfo.setPassword("123456");userinfo.setAge(18);userinfo.setGender(1);System.out.println(userInfoMapper.insert(userinfo));}
增–insert
//Mapper类@Insert("insert into userinfo (username, password, age, gender) " +"values (#{username},#{password},#{age},#{gender})")Integer insert(UserInfo userinfo);//测试类@Testvoid insert() {UserInfo userinfo = new UserInfo();userinfo.setUsername("laojun");userinfo.setPassword("123456");userinfo.setAge(18);userinfo.setGender(1);System.out.println(userInfoMapper.insert(userinfo));}
@Param
如果设置了 @Param 属性,#{…}对象就只能是一个整体需要使用 参数.属性 来获取。
@Insert("insert into userinfo (username, password, age, gender) " +"values (#{userinfo.username},#{userinfo.password},#{userinfo.age},#{userinfo.gender})")Integer insert1(@Param("userinfo") UserInfo userinfo);
插入成功返回ID–@Options
返回主键
如果想要拿到 ID ,需要在Mapper接口的方法上添加一个Options的注解。
keyProperty:指定能够唯一识别对象的属性。
//Mapper类@Options(useGeneratedKeys = true, keyProperty = "id")@Insert("insert into userinfo (username, password, age, gender) " +"values (#{userinfo.username},#{userinfo.password},#{userinfo.age},#{userinfo.gender})")Integer insert1(@Param("userinfo") UserInfo userinfo);//测试类@Testvoid insert1() {UserInfo userinfo = new UserInfo();userinfo.setUsername("laojun2");userinfo.setPassword("1234562");userinfo.setAge(12);userinfo.setGender(2);System.out.println(userInfoMapper.insert1(userinfo));System.out.println("id: "+userinfo.getId());}
删–delete
//Mapper类@Delete("delete from userinfo where id = #{id}")Integer delete(Integer id);//测试类@Testvoid delete() {System.out.println(userInfoMapper.delete(6));}
改–update
//Mapper类@Update("update userinfo set password = #{password} where id = #{id}")Integer update(UserInfo userInfo);//测试类@Testvoid update() {UserInfo userinfo = new UserInfo();userinfo.setId(1);userinfo.setPassword("123456");System.out.println(userInfoMapper.update(userinfo));}
查–select
//Mapper类@Select("select * from userinfo")List<UserInfo> queryAllUser();//测试类@Testvoid queryAllUser() {System.out.println(userInfoMapper.queryAllUser());}
从结果中可以看出,实体类与数据库字段名不同的结果为null,无法接收到相应的数据。
原因:
当自动映射查询结果时,MvBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性。
实体类的属性名与数据库字段名不同。(deleteFlag–delete_flag、createTime–create_time、updateTime–update_time)
解决办法:
起别名
结果映射
开启驼峰命名
起别名
//Mapper类@Select("select id, username, password, age, gender, phone, " +"delete_flag as deleteFlag, create_time as createTime, update_time as updateTime " +"from `userinfo`")List<UserInfo> selectUserInfos();//测试类@Testvoid selectUserInfos() {System.out.println(userInfoMapper.selectUserInfos());}
结果映射
//Mapper类@Results(id ="resultMap" , value = {@Result(column = "delete_flag", property = "deleteFlag"),@Result(column = "create_time", property = "createTime"),@Result(column = "update_time", property = "updateTime")})@Select("select * from userinfo")List<UserInfo> selectUserInfo2();//测试类@Testvoid selectUserInfo2() {System.out.println(userInfoMapper.selectUserInfo2());}
开启驼峰命名
通常数据库列使用蛇形命名法进行命名(下划线分割各个单词),而 java 属性一般遵循驼峰命名法约定,为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase 设置为 true,进行如下配置即可。
//Mapper类@Select("select * from userinfo")List<UserInfo> queryAllUser();//测试类@Testvoid queryAllUser() {System.out.println(userInfoMapper.queryAllUser());}
mybatis:configuration:map-underscore-to-camel-case: true
参数传递
将方法中的参数,传给SQL语句,使用 #{}或${}的方式获取方法中的参数。
#{} 和 ${}区别
案例
#{}:
@Select("select * from userinfo where username = #{username}")List<UserInfo> getUserInfoByUsername(String username);@Testvoid getUserInfoByUsername() {System.out.println(userInfoMapper.getUserInfoByUsername("admin"));}
${}:
@Select("select * from userinfo where username = ${username}")List<UserInfo> getUserInfoByUsername1(String username);@Testvoid testGetUserInfoByUsername1() {System.out.println(userInfoMapper.getUserInfoByUsername1("admin"));}
报错
修改:
@Select("select * from userinfo where username = '${username}'")List<UserInfo> getUserInfoByUsername1(String username);@Testvoid testGetUserInfoByUsername1() {System.out.println(userInfoMapper.getUserInfoByUsername1("admin"));}
区别
预编译SQL和即时SQL
#{}:使用的是预编译SQL,通过?占位的方式,提前对SQL进行编译,然后把参数填充到SQL语句中,# 会根据参数类型,自动拼接引号" “。
${}:直接进行字符替换,一起对SQL进行编译,如果参数为字符串,需要加上引号” "。
#{}的效率更高
绝大多数情况下,某一条 SQL 语句可能会被反复调用执行,或者每次执行的时候只有个别的值不同,如果每次都需要经过 语法解析-SQL优化-SQL编译等,则效率就明显不行了。
预编译SQL,编译一次之后会将编译后的SQL语句缓存起来,后面再次执行这条语句时,不会再次编译(只是输入的参数不同),省去了解析优化等过程,以此来提高效率。
#{}更安全(防止sql注入)
因为${}会直接进行字符替换,一起对SQL进行编译,很有可能造成sql注入。
SQL注入:通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。
' or 1='1
,这就是一个经典的sql注入语句。
#会根据参数类型,如果参数类型为Sting,自动拼接引号" “,当不想要自动拼接引号” "时,选择使用${},就一定要考虑SQL注入。
示例(排序):
@Select("select * from userinfo order by id ${sort}")public List<Userinfo> queryAllUser2(String sort);
此时${sort}拼接的是desc或者asc,但是这种其实也就两种情况,为了安全起见,最好还是将其写死,不需要参数引用,写为两个方法最好不过。
like查询
@Select("select * from userinfo where username like '#${key}%'")List<UserInfo> queryAllUser3(String key);@Select("select * from userinfo where username like '%${key}%'")List<UserInfo> queryAllUser3(String key);
#{}会SQL错误,但是${}存在SQL注入的问题,所以不能直接使用 ${}解决办法,使用 mysql 的内置函数 concat()来处理。
@Select("select * from userinfo where username like concat('%',#{username},'%')")List<UserInfo> queryAllUser4(String key);
传递多个参数
1.如果SQL语句中只有一个变量,变量名不需要与参数名对应。
2.如果SQL语句中有多个变量,变量名必需与参数名对应。
3.会自动生成对应参数比如param1自动对应函数的第一个参数,也可以通过 @Param,设置参数的别名,如果使用 @Param 设置别名, #{…}里面的属性名必须和@Param 设置的一样。
xml实现
简单插入示例–insert
首先,配置yml文件,将xml文件的路径配置在yml文件中。
mybatis:mapper-locations: classpath:mybatis/**Mapper.xml
其次,创建UserInfoXmlMapper接口,作为Mapper层,此时注意该Mapper接口所在的文件的路径。
package com.example.mybatis.mapper;@Mapper
public interface UserInfoXmlMapper {Integer insert(UserInfo userInfo);
}
随后,xml的路径下创建xxMapper.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.mybatis.mapper.UserInfoXmlMapper"><insert id="insert">insert into userinfo (username, password, age, gender)values (#{username}, #{password}, #{age}, #{gender})</insert>
</mapper>
最后,进行测试。
@Testvoid insert() {UserInfo userinfo = new UserInfo();userinfo.setUsername("laojun2");userinfo.setPassword("1234562");userinfo.setAge(12);userinfo.setGender(2);System.out.println(userInfoXmlMapper.insert(userinfo));}
简单删除示例–delete
Integer deleteById(Integer id);@Testvoid deleteById() {System.out.println(userInfoXmlMapper.deleteById(14));}
<delete id="deleteById">delete from userinfo where id = #{id}</delete>
简单修改示例–delete
Integer updateById(UserInfo userInfo);@Testvoid updateById() {UserInfo userinfo = new UserInfo();userinfo.setId(1);userinfo.setPassword("admin");System.out.println(userInfoXmlMapper.updateById(userinfo));}
<update id="updateById">update userinfo set password = #{password} where id = #{id}</update>
简单查询示例–delete
List<UserInfo> selectAllUserInfo();@Testvoid selectAllUserInfo() {System.out.println(userInfoXmlMapper.selectAllUserInfo());}
这里需要注意的是resultType指定返回的类型。
<select id="selectAllUserInfo" resultType="com.example.mybatis.model.UserInfo">select * from userinfo</select>
同时,还需要注意的是,此时我的配置文件中配置了自动驼峰转换。
多表查询
多表查询和单表查询类似,只是SOL不同而已。
准备工作
上面建了一张用户表,这里再建一张文章表,进行多表关联查询。
-- 创建⽂章表
DROP TABLE IF EXISTS articleinfo;CREATE TABLE articleinfo (id INT PRIMARY KEY auto_increment,title VARCHAR ( 100 ) NOT NULL,content TEXT NOT NULL,uid INT NOT NULL,delete_flag TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',create_time DATETIME DEFAULT now(),update_time DATETIME DEFAULT now()
) DEFAULT charset 'utf8mb4';INSERT INTO articleinfo ( title, content, uid ) VALUES ( 'Java', 'Java正⽂', 1);
对应实体类:
@Data
public class ArticleInfo {private Integer id;private String title;private String content;private Integer uid;private Integer deleteFlag;private Date createTime;private Date updateTime;
}
原始sql语句:根据uid查询作者的名称等相关信息
SELECTta.id,ta.title,ta.content,ta.uid,tb.username,tb.age,tb.gender
FROMarticleinfo taLEFT JOIN userinfo tb ON ta.uid = tb.id
WHEREta.id =1
补充实体类:
@Data
public class ArticleInfo {private Integer id;private String title;private String content;private Integer uid;private Integer deleteFlag;private Date createTime;private Date updateTime;//⽤⼾相关信息private String username;private Integer age;private Integer gender;
}
Mapper接口:
@Mapper
public interface ArticleInfoMapper {@Select("SELECT ta.id,ta.title,ta.content,ta.uid,tb.username,tb.age,tb.gender " +"FROM articleinfo ta LEFT JOIN userinfo tb ON ta.uid = tb.id " +"WHERE ta.id = #{id}")ArticleInfo queryUserByUid(Integer id);
}
其对应的xml未改一下就行。
动态SQL
< if >和< trim >
是存在非必填字段问题的解决方法。简单来说,< if >标签就是一个判断语句,test 里是要进行的判断,如果为true,则会填充标签里的内容。< trim >标签可以修饰< if >标签填充的不合法SQL语句。
< trim >标签属性:
prefix:表示整个语句块,以prefix的值作为前缀
suffix:表示整个语句块,以suffix的值作为后缀
prefixOverrides:表示整个语句块要去除掉的前缀
suffixOverrides:表示整个语句块要去除掉的后缀
-
使用< trim >标签动态生成SQL语句:
第一个< trim>处理列名部分
第二个< trim>处理值部分
自动去除末尾多余的逗号 -
使用< if>条件判断:
只有当字段不为null时(如username!=null)才会包含该字段
gender字段比较特殊,只检查是否存在(不为空)而不判断null -
最终生成的SQL会根据传入参数动态变化,只包含非空的字段,避免插入null值。
<insert id="insert">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">gender</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">#{gender}</if></trim></insert>
如果只传了username和password,生成的SQL会是:
insert into userinfo(username, password) values(#{username}, #{password})
< where >
如果我们想根据 id,password 来对表进行删除操作,仅使用< if>标签和< trim>标签:
<delete id="delete">delete from userinfo<trim prefix="where" prefixOverrides="and"><if test="id!=null">id = #{id}</if><if test="password!=null">and password = #{password}</if></trim></delete>
面对条件判断使用< where>标签可以化简,使用< where>标签对上述代码化简:
<delete id="delete1">delete from userinfo<where><if test="id!=null">id = #{id}</if><if test="password!=null">and password = #{password}</if></where></delete>
注意:
- < where>只会在子元素有内容的情况下才插入where子句
- 会自动去除子句的开头的AND或 OR
< set >
根据传入的用户对象属性来更新用户数据,可以使用标签来指定动态内容,与< where>标签的使用类似
根据传入的用户 id 属性,修改其他不为 null 的属性:
<update id="update">update userinfo<set><if test="username!=null">username=#{username}</if></set>where id=#{id}</update>
< set> :动态的在SQL语句中插⼊set关键字,并会删掉额外的逗号
< foreach >
对集合进行遍历时可以使用该标签。
- collection:绑定方法参数中的集合,如 List,Set,Map或数组对象
- item:遍历时的每一个对象
- open:语句块开头的字符串
- close:语句块结束的字符串
- separator:每次遍历之间间隔的字符串
根据多个id,删除用户数据
<delete id="delete4">delete from userinfowhere id in<foreach collection="ids" open="(" close=")" separator="," item="id" >#{id}</foreach></delete>
< include >
在xml映射文件中配置的SQL,有时可能会存在很多重复的片段,此时就会存在很多冗余的代码。可以对重复的代码片段进行抽取,将其通过< sql >标签封装到一个SQL片段,然后再通过< include >标签进行引用。
< sql >:定义可重用的SQL片段
< include >:通过属性refid,指定包含的SQL片段
<sql id="baseColumns">id, username, age, gender
</sql><select id="selectAllUser" resultType="com.example.myBatis.model.UserInfo">SELECT<include refid="baseColumns"/>FROM userinfo
</select>