一、创建Mybatis的项目
Mybatis 是⼀个持久层框架, 具体的数据存储和数据操作还是在MySQL中操作的, 所以需要添加MySQL驱动
1.添加依赖
或者 手动添加依赖
<!--Mybatis 依赖包-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>
<!--mysql驱动包-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
项⽬⼯程创建完成后,⾃动在pom.xml⽂件中,导⼊Mybatis依赖和MySQL驱动依赖
版本会随着SpringBoot 版本发⽣变化
SpringBoot 3.X对⽤MyBatis版本为3.X
对应关系参考:Introduction – mybatis-spring-boot-autoconfigure
2.配置常见的数据库信息
这是常见关于Mybatis的配置信息,大家可以自取
csdn暂时还不支持yml文件,这是 yml 文件:
spring:
datasource: # 配置数据库名
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
username: root #数据库用户
password: root #密码
driver-class-name: com.mysql.cj.jdbc.Driver
# 设置 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 #自动驼峰转换
前提是有这样的一个数据库
那么这里提供一个数据库:
-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;
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' );
-- 创建文章表
DROP TABLE IF EXISTS article_info;
CREATE TABLE article_info (
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 article_info ( title, content, uid ) VALUES ( 'Java', 'Java正文', 1 );
这样一个表
3.写对应的对象
一般写在model文件夹下
@Data
public class UserInfo {
private Integer id;
private String username;
private String password;
private Integer age;
private Integer gender;
private String phone;
//数据库用下划线连接单词,java直接用小驼峰
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
4.写持久层代码
一般写在mapper文件夹下,或者Dao
@Mapper
public interface UserInfoMapper {
//查询所有用户的信息
@Select("select * from user_info")
List<UserInfo> selectAll();
}
5.单元测试
在对应的mapper接口下,右击
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void selectAll() {
userInfoMapper.selectAll().forEach(x -> System.out.println(x));
//等同于
// List<UserInfo> userInfos = userInfoMapper.selectAll();
// for (UserInfo userInfo : userInfos) {
// System.out.println(userInfo);
// }
}
}
结果:
二、Mybatis的基本操作
1.日志打印
2.传递单个参数
通过 #{……} 传递 参数
@Mapper
public interface UserInfoMapper {
// 只有一个参数的时候这里写什么名字无所谓
@Select("select * from user_info where id = #{id}")
UserInfo selectUserById(Integer id);
}
单元测试:
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void selectUserById() {
System.out.println(userInfoMapper.selectUserById(2));
}
}
结果:
3.传递多个参数
3种方法
方法1:
标签 和 方法 中的名字一样
方法2:
它给方法的每个形参取了别名,例如第一个param1 第二个 param2
方法3:
使用@Param("……"),和 标签中的 #{……}对应
@Mapper
public interface UserInfoMapper {
// @Select("select * from user_info where id = #{id} and gender = #{gender}") 方法1 推荐
// @Select("select * from user_info where id = #{param2} and gender = #{param1}") 方法2 不推荐
// @Select("select * from user_info where id = #{id2} and gender = #{gender2}") 错误
//
@Select("select * from user_info where id = #{id2} and gender = #{gender2}") // 方法3 推荐
List<UserInfo> selectUserByIdAndGender(@Param("id2") Integer id,@Param("gender2") Integer gender);
}
单元测试:
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void selectUserByIdAndGender() {
List<UserInfo> userInfos = userInfoMapper.selectUserByIdAndGender(3, 1);
System.out.println(userInfos);
}
}
结果:
返回的参数可以是对象,也可以是集合类:
当我们知道数据库中对应的参数只有一个时,可以用类接收,但是最好用集合,万一他人增加了符号相同条件的数据,一个类就装不下。
4.查(Select)
查询之前已经都写到了,就不再写了。
发现这样的一个问题:
数据库的规范是单词之间用下划线分割,java的变量规范是小驼峰命名。这就导致了属性对应不上导致为null
解决办法,我先讲最推荐的:
4.1 开启驼峰命名
在yml或者properties文件中设置:
# 设置 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 #自动驼峰转换
4.2 起别名
@Mapper
public interface UserInfoMapper {
@Select("select id, username, password, age, gender, phone, delete_flag as deleteFlag, " +
"create_time as createTime, update_time as updateTime from user_info")
List<UserInfo> selectAll2();
}
即便是不用起别名也不建议用 select * , 就应该用到哪一列写哪一列,即便是所有的列都需要也这么写,因为更加规范。
4.3 结构映射
4.3.1 @Results 和 @Result
@Mapper
public interface UserInfoMapper {
// @Select("select id, username, password, age, gender, phone, delete_flag as deleteFlag, " +
// "create_time as createTime, update_time as updateTime from user_info")
@Results({
@Result(column = "delete_flag", property = "deleteFlag"),
@Result(column = "create_time", property = "createTime"),
@Result(column = "update_time", property = "updateTime")
})
@Select("select id, username, password, age, gender, phone, delete_flag, create_time, update_time from user_info")
List<UserInfo> selectAll2();
}
这样比起别名麻烦呀,那么真实用法是这样:
4.3.2 @ResultMap
5.增(Insert)
5.1传递对象
方法一:
@Mapper
public interface UserInfoMapper {
@Insert("insert into user_info (username, `password`, age, gender) values (#{username},#{password},#{age},#{gender})")
Integer insertUser(UserInfo userInfo);
}
Mapper层
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void insertUser() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("张三");
userInfo.setPassword("123445");
userInfo.setAge(19);
userInfo.setGender(0);
userInfoMapper.insertUser(userInfo);
}
}
成功:
方法二:
@Mapper
public interface UserInfoMapper {
@Insert("insert into user_info (username, `password`, age, gender)" +
"values (#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender})")
Integer insertUserByParam(@Param("userInfo") UserInfo userInfo);
}
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void insertUserByParam() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("李四");
userInfo.setPassword("jaba213");
userInfo.setAge(19);
userInfo.setGender(0);
userInfoMapper.insertUser(userInfo);
}
}
6.删(Delete)
删除的时候一般使用id删除
假设我们现在是pdd的员工,我们下单了一个商品但是还没有付钱,那么此时我们就需要拿到这个订单的id,如果在10分钟内不付钱,我们就删除这个订单。
那么我们就需要在插入之后拿到id,可以用这个注解:
6.1 @Options
@Mapper
public interface UserInfoMapper {
@Delete("delete from user_info where id = #{id}")
Integer delete(Integer id);
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into user_info (username, `password`, age, gender)" +
"values (#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender})")
Integer insertUserByParam(@Param("userInfo") UserInfo userInfo);
}
单元测试:
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void insertUserByParam() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("wangmaz");
userInfo.setPassword("54231");
userInfo.setAge(19);
userInfo.setGender(0);
//返回影响的行数
Integer result = userInfoMapper.insertUserByParam(userInfo);
// 通过getId()获取
System.out.println("执行结果" + result + " ,id : " + userInfo.getId());
}
结果:
那么拿到数据你想用这个id干什么自己处理就好了
普通的删除:
@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() {
// 删除id为11的数据
userInfoMapper.delete(11);
}
}
运行前:
运行后:
7.改(Update)
@Mapper
public interface UserInfoMapper {
@Update("update user_info set password = #{password} where id = #{id}")
Integer update(Integer id, String password);
}
@Mapper
public interface UserInfoMapper {
@Update("update user_info set password = #{password} where id = #{id}")
Integer update(Integer id, String password);
}
修改前:
修改后 :
三、报错信息
看到这样的报错“密码错误”,直接去看配置信息
数据库返回结构太多,方法定义的返回结果不匹配
标签参数和方法参数不匹配
四、Mybatis XML配置文件
💡
Mybatis的开发有两种⽅式:
1.注解
2.XML
💡
MyBatis XML的⽅式需要以下两步:
1. 配置数据库连接字符串和MyBatis
2. 写持久层代码
4.1配置连接字符串和MyBatis
spring:
datasource: # 配置数据库名
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
username: root #数据库用户
password: root #密码
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis: # 设置 Mybatis 的 xml 保存路径
mapper-locations: classpath:mapper/*Mapper.xml
configuration: # 配置打印 MyBatis 执行的 SQL
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true #自动驼峰转换
4.2 写持久层代码
💡
持久层代码分两部分
1. ⽅法定义 Interface
2. ⽅法实现: XXX.xml
4.2.1 添加mapper 接口 和 XML文件
添加接口:
package com.Li.mybatis.demo.mapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserInfoXMLMapper {
//写接口
}
添加XML文件:
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.Li.mybatis.demo.mapper.UserInfoXMLMapper">
</mapper>
4.2.3 文件路径的存放位置
XML文件路径是配置了的需要和配置信息一样:
mybatis: # 设置 Mybatis 的 xml 保存路径
mapper-locations: classpath:mapper/*Mapper.xml
文件的存放位置:
4.2.4 如何使用
mapper:
@Mapper
public interface UserInfoXMLMapper {
List<UserInfo> selectAll();
}
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.Li.mybatis.demo.mapper.UserInfoXMLMapper">
<!-- 对应的方法名 返回的类型路劲 -->
<select id="selectAll" resultType="com.Li.mybatis.demo.model.UserInfo">
select * from user_info;
</select>
</mapper>
💡
以下是对以上标签的说明:
- <mapper> 标签:需要指定 namespace 属性,表⽰命名空间,值为 mapper 接⼝的全限定名,包括全包名.类名。
- <select> 查询标签:是⽤来执⾏数据库的查询操作的:
- id :是和 Interface (接⼝)中定义的⽅法名称⼀样的,表⽰对接⼝的具体实现⽅法。
- resultType :是返回的数据类型,也就是开头我们定义的实体类。
单元测试:
@SpringBootTest
class UserInfoXMLMapperTest {
@Autowired
UserInfoXMLMapper userInfoXMLMapper;
@Test
void selectAll() {
userInfoXMLMapper.selectAll().forEach(x -> System.out.println(x));
}
}
结果:
那么这种方法也纯在数据库名和java中名字不对应的情况。
4.3 查(Select)
解决办法和注解类似:
4.3.1 起别名
<?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.Li.mybatis.demo.mapper.UserInfoXMLMapper">
<!-- 对应的方法名 返回的类型路劲 -->
<select id="selectAll" resultType="com.Li.mybatis.demo.model.UserInfo">
select id, username, password, age, gender, phone,
delete_flag as deleteFlag,
create_time as createTime,
update_time as updateTime
from user_info;
</select>
</mapper>
在xml文件中改sql语句就可以了:
4.3.2 结果映射
<mapper namespace="com.Li.mybatis.demo.mapper.UserInfoXMLMapper">
<resultMap id="BaseMap" type="com.Li.mybatis.demo.model.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>
<!-- 注意讲这里的属性改为resultMap,并且指定id -->
<select id="selectAll" resultMap="BaseMap">
select id, username, password, age, gender, phone,
delete_flag,
create_time,
update_time
from user_info;
</select>
</mapper>
结果:
如果你又写了别名又写了映射,它就分不清哪个起作用,最后都没有作用,而且还不会报错:
4.3.3 开启驼峰命明
mybatis:
configuration:
map-underscore-to-camel-case: true #自动驼峰转换
<?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.Li.mybatis.demo.mapper.UserInfoXMLMapper">
<!-- 对应的方法名 返回的类型路劲 -->
<select id="selectAll" resultType="com.Li.mybatis.demo.model.UserInfo">
select id, username, password, age, gender, phone,
delete_flag,
create_time,
update_time
from user_info;
</select>
</mapper>
结果:
4.4 增(Insert)
方法一
写接口:
@Mapper
public interface UserInfoXMLMapper {
Integer insert(UserInfo userInfo);
}
写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.Li.mybatis.demo.mapper.UserInfoXMLMapper">
<insert id="insert">
insert into user_info (username, password, age, gender)
value (#{username},#{password},#{age},#{gender})
</insert>
</mapper>
单元测试:
@SpringBootTest
class UserInfoXMLMapperTest {
@Autowired
UserInfoXMLMapper userInfoXMLMapper;
@Test
void insert() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("张三");
userInfo.setPassword("12314");
userInfo.setAge(18);
userInfo.setGender(0);
userInfoXMLMapper.insert(userInfo);
}
}
结果:
插入依然
<?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.Li.mybatis.demo.mapper.UserInfoXMLMapper">
<insert id="insert2">
insert into user_info (username, password, age, gender)
value (#{userInfo.username},#{userInfo.password},
#{userInfo.age},#{userInfo.gender})
</insert>
</mapper>
方法二:
写mapper:
@Mapper
public interface UserInfoXMLMapper {
Integer insert2(@Param("userInfo") UserInfo userInfo);
}
写XML文件:
<mapper namespace="com.Li.mybatis.demo.mapper.UserInfoXMLMapper">
<insert id="insert2">
insert into user_info (username, password, age, gender)
value (#{userInfo.username},#{userInfo.password},
#{userInfo.age},#{userInfo.gender})
</insert>
</mapper>
结果:
拿取插上数据的指定值:
<?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.Li.mybatis.demo.mapper.UserInfoXMLMapper">
<!-- 拿取插入数据的id值 -->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into user_info (username, password, age, gender)
value (#{username},#{password},#{age},#{gender})
</insert>
</mapper>
测试用例:
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void insertUserByParam() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("wangmaz");
userInfo.setPassword("54231");
userInfo.setAge(19);
userInfo.setGender(0);
//返回影响的行数
Integer result = userInfoMapper.insertUserByParam(userInfo);
// 通过getId()获取
System.out.println("执行结果" + result + " ,id : " + userInfo.getId());
}
}
结果:
4.5 删(Delete)
写mapper
@Mapper
public interface UserInfoXMLMapper {
Integer deleteById(Integer id);
}
写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.Li.mybatis.demo.mapper.UserInfoXMLMapper">
<delete id="deleteById">
delete from user_info where id = #{id}
</delete>
</mapper>
测试用例:
@SpringBootTest
class UserInfoXMLMapperTest {
@Autowired
UserInfoXMLMapper userInfoXMLMapper;
@Test
void deleteById() {
userInfoXMLMapper.deleteById(15);
}
}
删除前:
删除后:
4.6 改(UPdate)
写mapper:
@Mapper
public interface UserInfoXMLMapper {
Integer updateById(Integer id,String username);
}
写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.Li.mybatis.demo.mapper.UserInfoXMLMapper">
<update id="updateById">
update user_info set username = #{username} where id = #{id}
</update>
</mapper>
单元测试:
@SpringBootTest
class UserInfoXMLMapperTest {
@Autowired
UserInfoXMLMapper userInfoXMLMapper;
@Test
void updateById() {
userInfoXMLMapper.updateById(14,"王五");
}
}
执行前:
执行后:
五、其他查询操作
5.1 多表查询
正在使用的表结构:
新增这样的一张表:
那么我们现在要求显示article_info的所有字段,加上user_info的password和phone字段:
在mapper中写对应的sql语句:
@Mapper
public interface ArticleInfoMapper {
@Select("select ta.id,ta.title,ta.content,ta.uid,ta.delete_flag,ta.create_time,ta.update_time," +
"tb.`password`,tb.phone from article_info as ta " +
"left join user_info as tb on ta.id = tb.id where ta.id = #{id}")
List<ArticleInfo> selectArticleInfoAndUserInfo(Integer id);
}
💡
因为增加了两条不属于article_info表的信息,我们对应接收的类不存在这样的信息,应该怎么办?
java中并没有表的概念,只有对象的概念,只要我们接收的对象可以接收到所有字段就可以了,那么我们只需要在对象中添加对应的属性。
@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 password;
private String phone;
}
单元测试:
@SpringBootTest
class ArticleInfoMapperTest {
@Autowired
private ArticleInfoMapper articleInfoMapper;
@Test
void selectArticleInfoAndUserInfo() {
List<ArticleInfo> articleInfos = articleInfoMapper.selectArticleInfoAndUserInfo(1);
articleInfos.forEach(x -> System.out.println(x));
}
}
结果:
不建议使用多表查询
5.2 #{} 和 ${}
#{}和${}都可以用于赋值,那么它们肯定是有差异的,接下来我们通过案例分析
插入Integer数据
#{}->
@Mapper
public interface UserInfoMapper {
@Select("select id, username, password, age, gender, phone, delete_flag, " +
"create_time, update_time from user_info where id = #{id}")
UserInfo selectUserById(Integer id);
}
${}->
@Mapper
public interface UserInfoMapper {
@Select("select id, username, password, age, gender, phone, delete_flag, " +
"create_time, update_time from user_info where id = ${id}")
UserInfo selectUserById(Integer id);
}
$是参数直接替换
再来看看赋值为插入String的数据:
#{}->
@Mapper
public interface UserInfoMapper {
@Select("select id, username, password, age, gender, phone, delete_flag, " +
"create_time, update_time from user_info where username = #{name}")
List<UserInfo> selectByName(String name);
}
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void selectByName() {
List<UserInfo> userInfo = userInfoMapper.selectByName("admin");
System.out.println(userInfo);
}
}
结果:
${}->
@Mapper
public interface UserInfoMapper {
@Select("select id, username, password, age, gender, phone, delete_flag, " +
"create_time, update_time from user_info where username = ${name}")
List<UserInfo> selectByName(String name);
}
那么我们把admin加上单引号就可以了,那么也发现$比较麻烦
结果:
那么如果是直接替换,会出现一个非常严重的问题!
5.2.1 sql注入问题
那么我输入这样的一个字符串 《'or 1 = '1》
结果:
由于1 = '1' 恒为真,就拿到了所有用户的信息,那不就完了嘛,还有更加严重的sql注入
所以在使用$的时候一定要考虑到sql注入的问题,那么我改为#{}还会出现sql注入问题吗?
结果:
使用#就不会存在sql注入的问题
5.2.2性能更高
绝⼤多数情况下, 某⼀条 SQL 语句可能会被反复调⽤执⾏, 或者每次执⾏的时候只有个别的值不同(⽐
如 select 的 where ⼦句值不同, update 的 set ⼦句值不同, insert 的 values 值不同). 如果每次都需要
经过上⾯的语法解析, SQL优化、SQL编译等,则效率就明显不⾏了.
预编译SQL,编译⼀次之后会将编译后的SQL语句缓存起来,后⾯再次执⾏这条语句时,不会再次编译
(只是输⼊的参数不同), 省去了解析优化等过程, 以此来提⾼效率
5.3 排序功能
那么我们来写代码:
使用#{}的情况:
@Mapper
public interface UserInfoMapper {
//用参数控制是降序还是升序
@Select("SELECT * FROM user_info ORDER BY id #{order}")
List<UserInfo> selectByOrder(String order);
}
单元测试:
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void selectByOrder() {
userInfoMapper.selectByOrder("desc");
}
}
结果:
它说我有sql语句错误,那么仔细看,它给desc加上了单引号,也就是这样:
💡
#{}在参数为String类型的时候会自动加上单引号,那么我们在使用这种关键字的时候就不适合使用#{}了,这个适合使用${}直接替换就非常合适了
我们说在使用${}的适合要考虑SQL注入的问题,那么这个也是纯在问题的:
那么这个时候我们就不允许用户输入,让用户选择,就避免了sql注入问题
5.4 like 查询
实现这样的语句
@Mapper
public interface UserInfoMapper {
@Select("SELECT * FROM user_info WHERE username LIKE '%#{src}%'")
List<UserInfo> selectByLike(String src);
}
单元测试:
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void selectByLike() {
userInfoMapper.selectByLike("wang");
}
}
结果:
仍然是sql语句问题
之前说过#{}参数为String的时候会自动加上单引号,那么就变成了这样:
使用${}结果:
为了防止sql注入,可以使⽤ mysql 的内置函数 concat() 来处理,实现代码如下:
concat()可以拼接多个字符串,你可以根据自己的需求写多个,具体实现:
@Mapper
public interface UserInfoMapper {
@Select("SELECT * FROM user_info WHERE username LIKE concat('%',#{src},'%')")
List<UserInfo> selectByLike(String src);
}
结果:
这个时候刚好就可以使用#{},反正它会自己加单引号
六、数据库连接池
在上⾯Mybatis的讲解中, 我们使⽤了数据库连接池技术, 避免频繁的创建连接, 销毁连接
下⾯我们来了解下数据库连接池
数据库连接池负责分配、管理和释放数据库连接,它允许应⽤程序重复使⽤⼀个现有的数据库连接,
⽽不是再重新建⽴⼀个.
💡
没有使⽤数据库连接池的情况: 每次执⾏SQL语句, 要先创建⼀个新的连接对象, 然后执⾏SQL语句, SQL语句执⾏完, 再关闭连接对象释放资源. 这种重复的创建连接, 销毁连接⽐较消耗资源
使⽤数据库连接池的情况: 程序启动时, 会在数据库连接池中创建⼀定数量的Connection对象, 当客⼾请求数据库连接池, 会从数据库连接池中获取Connection对象, 然后执⾏SQL, SQL语句执⾏完, 再把Connection归还给连接池.
优点:
- 减少了⽹络开销
- 资源重
- 提升了系统的性能
常⻅的数据库连接池:
- C3P0
- DBCP
- Druid
- Hikari
⽬前⽐较流⾏的是 Hikari, Druid
1.Hikari : SpringBoot默认使⽤的数据库连接池
从这里也可以看出来
2.Druid
中文命:德鲁伊
如果我们想把默认的数据库连接池切换为Druid数据库连接池, 只需要引⼊相关依赖即可
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.21</version>
</dependency>
如果SpringBoot版本为2.X, 使⽤druid-spring-boot-starter 依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
七、 总结
7.1 MySQL 开发企业规范
1.表名, 字段名使⽤⼩写字⺟或数字, 单词之间以下划线分割. 尽量避免出现数字开头或者两个下划线中间只出现数字. 数据库字段名的修改代价很⼤, 所以字段名称需要慎重考虑。
MySQL 在 Windows 下不区分⼤⼩写, 但在 Linux 下默认是区分⼤⼩写. 因此, 数据库名, 表名, 字
段名都不允许出现任何⼤写字⺟, 避免节外⽣枝
正例: aliyun_admin, rdc_config, level3_name
反例: AliyunAdmin, rdcConfig, level_3_name
2.表必备三字段: id, create_time, update_time
id 必为主键, 类型为 bigint unsigned, 单表时⾃增, 步⻓为 1
create_time, update_time 的类型均为 datetime 类型, create_time表⽰创建时间,
update_time表⽰更新时间
有同等含义的字段即可, 字段名不做强制要求
3.在表查询中, 避免使⽤ * 作为查询的字段列表, 标明需要哪些字段(课堂上给⼤家演⽰除外).
- 增加查询分析器解析成本
- 增减字段容易与 resultMap 配置不⼀致
- ⽆⽤字段增加⽹络消耗, 尤其是 text 类型的字段
7.2 #{} 和${} 区别
1. #{}:预编译处理, ${}:字符直接替换
2. #{} 可以防⽌SQL注⼊, ${}存在SQL注⼊的⻛险,使用是需要考虑sql注入防范
3. 但是⼀些场景, #{} 不能完成, ⽐如 排序功能, 表名, 字段名作为参数时, 这些情况需要使⽤${}
4. 模糊查询虽然${}可以完成, 但因为存在SQL注⼊的问题,所以通常使⽤mysql内置函数concat来完成