MyBatis从入门到进阶
目录
- MyBatis入门
- 1、创建项目、数据准备
- 2、数据库配置
- 3、编写持久层代码
- 单元测试
- 打印日志
- 基本操作
- 查询数据
- 插入数据
- 删除数据
- 更新数据
- MyBatis - xml
- 插入数据
- 更新数据
- 删除数据
- 查询数据
- #{}与${}
- SQL注入
- 排序
- like查询
- MyBatis进阶
- if标签
- trim标签
- where标签
- set标签
- foreach标签
- sql标签和include标签
MyBatis入门
MyBatis 是一个优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的工作,通过使用简单的 XML 或注解来配置和映射原生信息,使开发者可以专注于编写 SQL 语句。
基本步骤:创建项目和准备数据、数据库配置、编写持久层代码
1、创建项目、数据准备
创建过程和SpringBoot项目一模一样,需要注意的是,下面两个一定选上
在数据库中创建对应的数据库和表:
-- 创建数据库
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;
-- 使用数据数据
USE mybatis_test;
-- 创建表[用户表]
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (
`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() ON UPDATE now(),
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
-- 添加用户信息
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );
对应Java中的实体类
@Data
@RestController
@RequestMapping
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 createDate;
private Date updateDate;
}
2、数据库配置
在yml文件中,添加如下的配置:
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
username: root
password: '111111'
driver-class-name: com.mysql.cj.jdbc.Driver
如果是properties格式的,配置如下:
#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mybatis_test?
characterEncoding=utf8&useSSL=false
#连接数据库的⽤⼾名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=111111
3、编写持久层代码
根据规范,一般是Controller层调用Service层接口,Service层调用Dao层接口:
在MyBatis中持久层又叫Mapper
Controller层:
@RestController
@RequestMapping("/user")
public class UserController {
//注入UserService
@Autowired
private UserService userService;
@RequestMapping("/selectUserList")
public List<UserInfo> selectUserList(){
return userService.selectUserList();
}
}
Service层:
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
public List<UserInfo> selectUserList() {
return userInfoMapper.selectAll();
}
}
Dao层:
MyBatis持久层规范一般叫xxxMapper
@Mapper
public interface UserInfoMapper {
@Select("select * from user_info")
List<UserInfo> selectAll();
}
@Mapper注解:表示的是MyBatis中的Mapper接口
@Select注解:代表数据库的select操作
单元测试
如果按照:Controller层调用Service层接口,Service层调用Dao层接口这个规范来进行代码测试,未免有些麻烦,因为我们编写完代码之后,往往只是测试单独的某个功能。我们可以使用SpringBoot帮我们创建好的Test测试类来进行测试
在test目录中创建测试类,需要给类加上类注解@SpringBootTest
注解,才能从Spring中获取Bean,给方法加上@Test
注解,可以执行对应的方法。例如:
@SpringBootTest
class MyBatisDemoTests {
@Test
void TestFunc() {
}
}
IDEA自动生成测试类:右键–>Generate–>Test
打印日志
yml配置如下:
# 配置日志
mybatis:
configuration: # 配置打印 MyBatis⽇志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
properties配置如下:
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
配置了上述信息后,会把sql语句以及执行的结果以日志的形式打印出来
基本操作
查询数据
使用@Select注解
1、通过参数传递
@Mapper
public interface UserInfoMapper {
//通过参数传递
@Select("select * from user_info where id=#{id}")
UserInfo selectAll(Integer id);
}
注意:select操作可能返回多条数据,要使用List接收,如果只返回一条数据,使用对象接收即可
测试代码:
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void selectAll() {
//使用foreach遍历,类似于for循环
userInfoMapper.selectAll().forEach(x -> System.out.println(x));
List<UserInfo> userInfos = userInfoMapper.selectAll();
/*
等价于下面代码
for (UserInfo user:userInfos) {
System.out.println(user);
}*/
}
}
如果参数只有一个,参数名可以不对应,(多个参数必须对应)例如:
@Mapper
public interface UserInfoMapper {
@Select("select * from user_info where id=#{abc}")
UserInfo selectById(Integer id);
}
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void selectById() {
System.out.println(userInfoMapper.selectById(1));
}
}
2、多个参数
MyBatis会根据参数名自动匹配
@Mapper
public interface UserInfoMapper {
@Select("select * from user_info where id=#{id} and age =#{age}")
UserInfo selectFunc(Integer age,Integer id);
}
3、参数重命名
MyBatis会给参数取名字,第一个参数取名为param1,第二个参数取名为param2…以此类推
@Mapper
public interface UserInfoMapper {
@Select("select * from user_info where id=#{param1} and age =#{param2}")
UserInfo selectFunc(Integer id,Integer age);
}
我们也可以自己重命名:
@Mapper
public interface UserInfoMapper {
@Select("select * from user_info where id=#{id1} and age =#{age1}")
UserInfo selectFunc(@Param("id1") Integer id,@Param("age1")Integer age);
}
最推荐的写法如下:
参数和注解的名称一致
@Mapper
public interface UserInfoMapper {
@Select("select * from user_info where id=#{id} and age =#{age}")
UserInfo selectFunc(@Param("id") Integer id,@Param("age")Integer age);
}
4、数据库的字段命名规范是统一小写字母,单词之间使用下划线分割,而java的规范是属性名称统一小驼峰。但是MyBatis是通过名称来映射的,如何让数据库字段和Java属性名映射起来?
方法1:使用@Results注解映射
@Mapper
public interface UserInfoMapper {
//下划线命名法转换为驼峰命名法
@Results(id = "map", value = {
@Result(column = "delete_flag", property = "deleteFlag"),
@Result(column = "create_time", property = "createTime"),
@Result(column = "update_time", property = "updateTime")
})//建立映射
@Select("select * from user_info")
List<UserInfo> selectAll2();
@ResultMap("map")//注解复用
@Select("select * from user_info")
List<UserInfo> selectAll3();
}
方法2:通过配置实现,只需要添加下面的配置信息即可
mybatis:
configuration:
map-underscore-to-camel-case: true # 配置驼峰自动转换
插入数据
使用@Insert注解,获取参数直接使用对象的属性名
@Mapper
public interface UserInfoMapper {
@Insert("insert into user_info(username,`password`,age,gender) values (#{username},#{password},#{age},#{gender})")
Integer insertUserInfo(UserInfo userInfo);
}
测试代码:
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void insertUserInfo() {
UserInfo userInfo = new UserInfo();
userInfo.setId(11);
userInfo.setAge(18);
userInfo.setGender(1);
userInfo.setUsername("z111");
userInfo.setPassword("1234");
userInfo.setPhone("1234567");
userInfoMapper.insertUserInfo(userInfo);
}
}
使用@Param注解,可以给对象重命名,重命名之后,需要通过点号获取
@Mapper
public interface UserInfoMapper {
//对象重命名
@Insert("insert into user_info(username,`password`,age,gender) values (#{Info.username},#{Info.password},#{Info.age},#{Info.gender})")
Integer insertUserInfo2(@Param("Info") UserInfo userInfo);//返回值是影响的行数
}
测试代码:
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void insertUserInfo2() {
UserInfo userInfo = new UserInfo();
userInfo.setId(11);
userInfo.setAge(18);
userInfo.setGender(1);
userInfo.setUsername("z222");
userInfo.setPassword("1234");
userInfo.setPhone("1234567");
Integer i = userInfoMapper.insertUserInfo2(userInfo);
System.out.println(i);
}
}
使用@Options注解,获取主键:
@Mapper
public interface UserInfoMapper {
@Options(useGeneratedKeys = true, keyProperty = "id")//
@Insert("insert into user_info(username,`password`,age,gender) values (#{Info.username},#{Info.password},#{Info.age},#{Info.gender})")
Integer insertUserInfo3(@Param("Info") UserInfo userInfo);//返回值:影响的行数
}
测试代码:
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void insertUserInfo3() {
UserInfo userInfo = new UserInfo();
userInfo.setId(10);
userInfo.setAge(18);
userInfo.setGender(0);
userInfo.setUsername("abcd");
userInfo.setPassword("13579");
userInfo.setPhone("1234567");
Integer i = userInfoMapper.insertUserInfo3(userInfo);
System.out.println(i);
}
}
删除数据
使用@Delete注解:
@Mapper
public interface UserInfoMapper {
@Delete("delete from user_info where id = #{id}")
Integer delete(Integer id);//返回值:影响的行数
}
测试代码:
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void delete() {
userInfoMapper.delete(9);
}
}
更新数据
使用@Update注解
@Mapper
public interface UserInfoMapper {
@Update("update user_info set password = #{password} where id =#{id}")
Integer update(Integer id, String password);
}
测试代码:
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void update() {
userInfoMapper.update(1, "6666666");
}
}
参数为对象:
@Mapper
public interface UserInfoMapper {
@Update("update user_info set password = #{password} where id =#{id}")
Integer update2(UserInfo userInfo);
}
测试代码:
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void update2() {
UserInfo userInfo = new UserInfo();
userInfo.setId(8);
userInfo.setPassword("00000");
userInfoMapper.update2(userInfo);
}
}
MyBatis - xml
上述的基本操作都是基于注解的方式进行的,下面我们介绍使用xml的方式进行增删查改等基本操作
数据库配置信息还是一样
# 配置数据库信息
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
username: root
password: "111111"
driver-class-name: com.mysql.cj.jdbc.Driver
配置MyBatis xml文件路径
mybatis:
mapper-locations: classpath:mapper/**Mapper.xml
表示在mapper路径下的,文件名以Mapper结束的xml文件,classpath指的是resources这个目录,例如:
插件安装:
安装好插件之后,可以实现接口和xml文件之间跳转,甚至可以帮我们根据接口中的方法自动生成对应xml的语句
定义接口:
@Mapper
public interface UserInfoXmlMapper {
//查询
List<UserInfo> queryAllUser();
}
对应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.wpre.mybatisdemo2.mapper.UserInfoXmlMapper">
<select id="queryAllUser" resultType="com.wpre.mybatisdemo2.model.UserInfo">
select *
from user_info
</select>
</mapper>
resultType只有查询操作需要写,其他操作不需要
插入数据
使用insert标签,标签内写sql语句
<insert id="insertUser">
insert into user_info(username, `password`, age, gender)
values (#{username}, #{password}, #{age}, #{gender})
</insert>
对应的接口:
Integer insertUser(UserInfo userInfo);
测试类:
@Test
void insertUser() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("wpre");
userInfo.setPassword("wpre");
userInfo.setAge(18);
userInfo.setGender(1);
userInfoXmlMapper.insertUser(userInfo);
}
参数重命名:
Integer insertUser2(@Param("uf") UserInfo userInfo);
<insert id="insertUser2">
insert into user_info(username, `password`, age, gender)
values (#{uf.username}, #{uf.password}, #{uf.age}, #{uf.gender})
</insert>
重命名之后,需要通过点号获取
测试类:
@Test
void insertUser2() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("wpre");
userInfo.setPassword("wpre");
userInfo.setAge(18);
userInfo.setGender(1);
userInfoXmlMapper.insertUser2(userInfo);
}
返回自增id
Integer insertUser(UserInfo userInfo);
<insert id="insertUser3" useGeneratedKeys="true">
insert into user_info(username, `password`, age, gender)
values (#{uf.username}, #{uf.password}, #{uf.age}, #{uf.gender})
</insert>
测试类:
@Test
void insertUser() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("wpre");
userInfo.setPassword("wpre");
userInfo.setAge(18);
userInfo.setGender(1);
Integer result = userInfoXmlMapper.insertUser3(userInfo);
System.out.println("影响行数"+result+"id:"+userInfo.getId());
}
更新数据
使用update标签
Integer updateUser(Integer id,String password);
<update id="updateUser">
update user_info
set password = #{password}
where id = #{id}
</update>
测试类:
@Test
void updateUser() {
userInfoXmlMapper.updateUser(1, "12321");
}
删除数据
Integer deleteUserById(Integer id);
<delete id="deleteUserById">
delete
from user_info
where id = #{id}
</delete>
测试类:
@Test
void deleteUserById() {
userInfoXmlMapper.deleteUserById(1);
}
查询数据
List<UserInfo> queryAllUser();
<select id="queryAllUser" resultType="com.wpre.mybatisdemo2.model.UserInfo">
select *
from user_info
</select>
测试类:
@Test
void queryAllUser() {
userInfoXmlMapper.queryAllUser();
}
xml同样会出现数据库字段名和Java中的属性名不匹配的问题
解决办法:
1、通过sql语句中的as取别名
例如
select delete_flag as deleteFlag,create_time as createTime,
from user_info;
2、建立映射关系
<resultMap id="BaseMap" type="com.wpre.mybatisdemo2.model.UserInfo">
<id property="id" column="id"></id>
<result property="deleteFlag" column="delete_flag"></result>
<result property="createDate" column="create_date"></result>
<result property="updateDate" column="update_flag"></result>
</resultMap>
如果使用这种方式映射,建议把所有的字段都写上
使用映射来进行查询:
<select id="queryAllUser2" resultMap="BaseMap">
select id, username, delete_flag, create_time, update_time
from user_info
</select>
3、配置驼峰自动转换
mybatis.configuration.map-underscore-to-camel-case=true
#{}与${}
MyBatis的参数赋值有两种方式,一种是使用#符号,一种是使用$符号
#{}:
${}:
#{}是预编译SQL, 是即时 S Q L , {}是即时SQL, 是即时SQL,{}是直接替换参数。一般情况下,能使用#尽量使用#。
SQL注入
SQL注入(SQL Injection)是一种常见的数据库攻击手段,它允许攻击者插入或“注入”一个或多个SQL语句到原本的查询中,欺骗数据库服务器执行非预期的命令。这种攻击利用了应用程序在构建SQL查询时对用户输入数据的不当处理。
例如:
select * from user_info where username = '';drop table user_info;--'
@Select("select * from user_info where username=${username}")
UserInfo selectById(String username);
如果是即时SQL,传递的参数如果是';drop table user_info;--
这样一条SQL就变成两条SQL了,在不知不觉中你的表就已经被删除了
排序
结论:排序只能使用${}
//排序
@Select("select * from user_info order by id #{order};")
List<UserInfo> selectUsernameByOrder(String order);
测试类
@Test
void selectUsernameByOrder() {
userInfoXmlMapper.selectUsernameByOrder("desc");
}
运行结果
使用的是#{}进行参数赋值,参数是String类型,此时默认加上单引号,所以我们必须使用${}进行参数赋值。
like查询
结论:使用${}
//like查询
@Select("select from user_info where username like '%${name}%' ")
List<UserInfo> selectByLike(String name);
模糊查询+CONCAT可以避免SQL注入问题
//like查询
@Select("select * from user_info where username like CONCAT('%','#{name}','%') ")
List<UserInfo> selectByLike(String name);
MyBatis进阶
if标签
Integer insertUserByCondition(UserInfo userInfo);
<insert id="insertUserByCondition">
insert into user_info(username, `password`, age, <if test="gender!=null">gender</if>)
values (#{username}, #{password}, #{age}, <if test="gender!=null">#{gender}</if>)
</insert>
测试类:
@Test
void insertUserByCondition() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("zhangsan");
userInfo.setPassword("123");
userInfo.setAge(18);
userInfoXmlMapper.insertUserByCondition(userInfo);
}
表示的含义是:检查参数gender是否为null,如果参数gender为null,gender就不会被包含在这条SQL语句中;如果参数gender不为null,gender就会被包含在这条SQL语句中。上述测试类中并没有设置gender,gender为空,所以SQL语句为
insert into user_info(username, `password`, age,)
values (username, `password`, age,)
可以发现,上述的SQL语句是有语法错误的,age的后面多了一个逗号,何解?使用trim标签
trim标签
trim标签有4个属性,分别是:
prefix:增加前缀
suffix:增加后缀
prefixOverrides:去除前缀
suffixOverrides:去除后缀
使用方法:if标签嵌套在trim标签中
<trim >
<if test="">
</if>
</trim>
例如:
<insert id="insertUserByCondition">
insert into user_info
<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>
</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>
</trim>
</insert>
如果逗号写在前面,则需要使用prefixOverrides
where标签
where标签的作用:
1、能够去除多余的and
2、当有查询条件时,生成where关键字
3、没有查询条件时,不生成where关键字
例如:
<select id="selectByCondition" resultType="com.wpre.mybatisdemo2.model.UserInfo">
select *
from user_info
<where>
<if test="age!=null">
age = #{age} and
</if>
<if test="deleteFlag!=null">
delete_flag = #{deleteFlag}
</if>
</where>
</select>
set标签
set标签的作用:
1、添加set关键字
2、去除末尾的逗号
<update id="updateByCondition">
update user_info
<set>
<if test="username!=null">
username=#{username},
</if>
<if test="password!=null">
password=#{password},
</if>
<if test="gender!=null">
gender=#{gender},
</if>
</set>
<where>
id = #{id}
</where>
</update>
foreach标签
foreach标签处理的是如下SQL语句
delete from user_info where id in (10,11,12)
forecah属性如下:
1、collection:遍历的集合
2、separator:分隔符
3、item:元素的名称
4、open:以xx为开始
5、close:以xx为结束
例如:
Integer foreachDelete(List<Integer> ids);
<update id="foreachDelete">
delete from user_info where id in
<foreach collection="ids" separator="," item="id" open="(" close=")">
#{id}
</foreach>
</update>
测试类:
@Test
void foreachDelete() {
userInfoXmlMapper.foreachDelete(List.of(10, 11, 12));
}
sql标签和include标签
sql标签可以把重复冗余的代码片段进行抽取,封装成一个SQL片段,通过使用include标签进行引用
例如:
<sql id="queryAllUser">
id,username,age,gender,phone,delete_flag,create_time,update_time
</sql>
<select id="queryAllUser" resultType="com.wpre.mybatisdemo2.model.UserInfo">
select
<include refid="queryAllUser">
</include>
from user_info
</select>
本文结束,感谢阅读~