1.什么是MyBatis?
MyBatis是一款优秀的持久层框架,用于简化JDBC的开发.
持久层,指的就是持久化操作的层,通常是指数据访问层.用来操作数据库.
简单来说,MyBatis是更简单完成程序和数据库交互的框架,也就是更简单操作和读取数据库的工具.
接下来我们就通过一个入门的程序,感受一下MyBatis是如何操作数据库的.
2.MyBatis入门
2.1 准备工作
创建一个SpringBoot项目,引入mybatis起步依赖和mysql的驱动包
用mysql数据库创建数据.
用navicat运行mysql脚本.
-- 创建数据库 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');在Java中创建对应的实体类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; }2.2 配置数据库连接字符串
MyBatis中药连接数据库,需要数据库相关配置.
如果是application.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=root如果是.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.Driver2.3 写持久层代码
在项目中创建持久层接口UserInfoMapper
@Mapper public interface UserInfoMapper { @Select("select * from user_info") public List<UserInfo> getAll(); }Mybatis的持久层接口规范一般都叫XxxMapper
@Mapper注解:表示是MyBatis中的Mapper接口.
程序运行时,框架会自动生成接口的实现类对象.并交给SpringIoC容器管理.
@Select注解:代表的就是Select查询,也就是注解对应方法中的具体实现内容.
2.4 单元测试
在创建出来的SpringBoot工程中,在src的test目录下,已经帮我们自动创建好了测试类,我们可以直接用这个测试类来进行测试.
运行程序,就帮我们拿到了数据库中的信息.
除此之外,我们还可以使用IDEA中自动生成测试类.右键Generate,然后选Test
然后选择要测试的方法就行
@SpringBootTest class UserInfoMapperTest { @Autowired private UserInfoMapper userInfoMapper; @Test void getAll() { System.out.println(userInfoMapper.getAll()); } }运行代码,同样可以拿到相同的结果.
3.MyBatis的基础操作
3.1 打印日志
在Mybatis中,我们可以借助日志,查看SQL语句的执行,执行传递的参数以及执行的结果.
在配置文件中配置即可.
yml的配置:
mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImplproperties配置:
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl将配置加入即可,然后重新运行程序,观察结果.就可以拿到日志信息了
这里显示了查询语句,传递参数以及类型.还有查询结果.
3.2 参数传递
需求,想要查找ID = 4的用户,对应的SQL就是select * from user_info where id = 4
但是这样的话,我们就只能查找id = 4的数据了,所以SQL语句中,不能把id写成固定的值,需要动态的数值.
解决方法,在getOne方法中,添加一个参数,将方法中的参数,传递给SQL语句.
我们使用#{}来获取方法中的参数.
添加测试用例:
运行程序:
在写SQL语句的时候,我们也可以通过@Param对参数起别名.这样在SQL语句中的参数就要和别名一样了.
3.3 增(Insert)
SQL语句:
insert into user_info (username, `password`, age, gender, phone) values ("zhaoliu","zhaoliu",19,1,"18700001234")使用#{}将SQL中的常量转换为动态的参数
@Insert("insert into user_info (username, `password`, age, gender, phone) values (#{username},#{password},#{age},#{gender},#{phone})") public int insert(UserInfo userInfo);生成测试用例,然后用userInfo对象的属性名来获取参数:
@Test void insert() { UserInfo userInfo = new UserInfo(); userInfo.setUsername("zhaoliu"); userInfo.setPassword("zhaoliu"); userInfo.setGender(2); userInfo.setAge(21); userInfo.setPhone("18612340005"); userInfoMapper.insert(userInfo); }运行代码,观察结果.显示成功
打开数据库,也可以看到成功添加这条数据.
正常情况下,Insert语句返回的是受影响的行数.
但是在有些情况下,数据插入之后,还有相关的后续操作.需要获取到新插入数据的id
如果想要拿到自增id,就需要再@Mapper接口上添加一个Option注解.
@Options(useGeneratedKeys = true, keyProperty = "id")useGeneratedKeys:这会令Mybatis使用jdbc的getGeneratedKeys方法取出由数据库内部生产的主键.
keyProperty:指定能够唯一识别对象的属性,mybatis会使用getGeneratedKeys的返回值或者insert语句的selectKey子元素设置它的值.
设置useGeneratedKey = true之后,方法返回的依然是受影响的行数,自增id会在设置上述keyProperty指定属性中.
有的人可能就会问了,这个id本来就是自增啊,要这个注解有啥用.
如果不加这个注解.就不能从对象中获取到id.把刚刚的option注解注释掉,再次运行看看结果.
这里的id就显示为null.
这个注解的作用就是在不手动给对象添加id的情况下把数据库生成的id回填给Java对象.
3.4 删(Delete)
SQL语句:
delete from user_info where id = 6把SQL中的常数换为动态参数.
生成测试用例并运行代码
3.5 改(Update)
SQL语句:
update uer_info set nsername="zhaoliu" where id=5运行代码,去数据库中观察,可以看到已经被改为wnagwu1
3.6 查(Select)
我们在上面查询的时候发现,有几个字段是没有被赋值的.在数据库中有值,但是在select中却查不到.
这是因为在自动映射查询结果的时候,mybatis会获取结果并在Java类中查找相同名字的属性,而Java类中的deleteFlag和SQL中的delete_flag名字不同.
这时候就有三种解决办法了.
1.起别名
在SQL语句中,给列起别名,让列名和Java对象属性名一致.
2.结果映射
我们可以用@Results注解创建映射关系,后续其他SQL想用这个映射关系,就可以使用@ResultMap注解
执行查询,看看结果.此时就可以拿到所有的数据.
3.开启驼峰命名
数据库一般使用_分割,而Java对象一般使用驼峰命名,这时候我们只需要在这两种命名方式之间进行映射就行,需要将mapUnderscoreToCameCase设置为true.
这样,再次运行程序,也可以正确映射到Java对象中.
4.MyBatis XML配置文件
Mybatis的开发方式有两种,一种是注解的方式,另一种使用XML的方式.
上面,我们使用的是注解来实现mybatis,接下来让我们学习使用xml来实现mybatis
4.1 配置连接字符串和MyBatis
如果使用的是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 的文件路径,在 resources/mapper 创建所有的 xml 文件 mybatis: mapper-locations: classpath:mapper/**Mapper.xmlproperties配置文件:
# 驱动类名称 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=root # 配置 mybatis xml 的文件路径,在 resources/mapper 创建所有的 xml 文件 mybatis.mapper-locations=classpath:mapper/**Mapper.xml4.2 写持久层代码
持久层代码分为两部分,方法定义和方法实现.
4.2.1 添加mapper接口
数据持久层接口定义:
@Mapper public interface UserInfoXMLMapper { public List<UserInfo> quaryAllList(); }4.2.2 添加UserInfoXMLMapper.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.mybatis.mapper.UserInfoMapper"> </mapper>创建路径在resources的mapper包下面
查询所有用户的具体实现:
将.xml文件中的namespace改为想要实现的接口路径.
然后写xml的实现.用<select></select>将select语句包起来
然后id就是要实现接口的方法名.resultType就是返回的数据类型.
单元测试:
生成单元测试用例,运行代码
就可以拿到正确结果了.
4.3 增删改查操作
4.3.1 增(Insert)
定义接口:
接口实现:
测试用例:
运行代码,成功添加数据.
如果想要返回自增id,则需要设置useGeneratedKeys和keyProperty属性
4.3.2 删(Delete)
4.3.3 改(Update)
4.3.4 查(Select)
在查询的时候,和我们使用注解相同,也会出现匹配不到内容的情况,这时候解决方法也是有三个,起别名,结果映射和开启驼峰命名
这里我们主要讲结果映射.
5.其他查询操作
5.1 多表查询
多表查询和代表查询类似,只是SQL不同.
在上面,我们创还能了一张用户表,我们再来创建一张文章表,进行多表查询.
MySQL代码:
-- 创建文章表 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 );Java实体类:
import lombok.Data; import java.util.Date; @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; }数据查询:
需求:根据uid查询作者名称等相关信息.
SQL:
select ta.id,ta.title,ta.content,ta.uid,tb.username,tb.age,tb.gender from articleinfo ta left join user_info tb on ta.uid = tb.id where ta.id = 1补充实体类:
接口定义:
生成测试用例,然后运行代码:
5.2 #{}和${}的使用
5.2.1 预编译和即时编译
当我们传递Integer参数的时候
我们将#{}替换为${},看看SQL的打印日志有什么变化.
使用#{}的时候,SQL的日志:
使用${}的时候,SQL的日志:
这个时候都能正常运行,当我们换成String类型参数,再试试
使用#{}
使用${}
当使用#{}的时候,可以正常运行程序,但是当换成${}的时候,报错了,我们可以看到在使用${}的时候,IDEA直接把admin拼接在了SQL语句的后面,没有用'',这时候程序才会报错.
我们将${}放在单引号中,再次运行程序.
这次程序就可以正常运行.
使用#{}是预编译SQL,也就是通过?占位的方式,提前对SQL进行编译,然后把参数填充到SQL语句中,#{}会根据参数类型,自动拼接' '
${}会直接进行字符串替换,一起对SQL进行编译,如果参数为字符串,需要加上' '
5.2.2 #{}和${}的区别
#{}和${}的区别就是预编译和即时编译的区别.
1.性能更高
绝大多数情况下,某一条 SQL 语句可能会被反复调用执行,或者每次执行的时候只有个别的值不同 (比如 select 的 where 子句值不同,update 的 set 子句值不同,insert 的 values 值不同).
如果每次都需要经过上面的语法解析,SQL 优化、SQL 编译等,则效率就明显不行了.
预编译 SQL, 编译一次之后会将编译后的 SQL 语句缓存起来,后面再次执行这条语句时,不会再次编译 (只是输入的参数不同), 省去了解析优化等过程,以此来提高效率
2.更安全(防止SQL注入)
SQL 注入:是通过操作输入的数据来修改事先定义好的 SQL 语句,以达到执行代码对服务器进行攻击的方法。
当我们将${}中传递的数据更改,将方法接收改为List,再次查询:
就会一次性查出所有数据,而用#{}就不会有这种风险.
使用#{}可以防止SQL注入.
5.3 排序功能
从上面的例子中,我们可以知道使用${}会有SQL注入的风险,所以我们尽量使用#{}来完成查询操作.
既然如此,那么${}存在的意义是什么呢?
在我们使用软件的时候,也会碰到排序这种功能,比如价格从高到低,销量从高到低排序等.
这种情况下,我们的SQL语句就是
@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time from user_info order by id ${sort} ")在这里我们需要使用${}而不能使用#{}.
因为在mybatis中,预编译机制会将#{}预编译成?
比如你写的是 select * from userinfo order by id #{}.
数据库就会编译成 select * from userinfo order by id ?
这样就会出现SQL语法错误.
所以#{}只能处理值,而不能处理列名或者关键字.
使用${},运行程序,可以正确排序.
使用#{}代替${},运行程序.
可以看到这边出现报错信息.
5.4 like查询
like使用#{}也会报错,同样是因为会被编译成?
所以在like查询的时候,同样需要使用$[{}来完成.
但是使用${}会出现SQL注入的问题.
这里我们就可以使用MySQL的内置函数concat()来处理.
正常使用${}.
使用MySQL内置函数concat()
6.数据库连接池
6.1 介绍
在上面mybatis的讲解中,我们使用了数据库连接池技术,避免频繁创建销毁连接.
那么什么是数据库连接池?
数据库连接池负责分配管理和释放数据库连接.他允许应用程序重复使用一个现有的数据库连接,而不是再建立一个.
没有使用数据库连接池的情况:
每次执行 SQL 语句,要先创建一个新的连接对象,然后执行 SQL 语句,SQL 语句执行完,再关闭连接对象释放资源。
这种重复的创建连接,销毁连接比较消耗资源.
使用数据库连接池的情况:
程序启动时,会在数据库连接池中创建一定数量的 Connection 对象,当客户请求数据库连接池,会从数据库连接池中获取 Connection 对象,然后执行 SQL, SQL 语句执行完,再把 Connection 归还给连接池.
优点:
1.减少了网络开销.
2.资源重用.
3.提升了系统的性能.
2.使用
常见的数据库连接池有C3P0,DBCP,Druid,Hikari.
SpringBoot用的数据库连接池就是Hikari,可以在SpringBoot的日志上看到.
如果我们想把默认的数据库连接池换成Druid,只需要引入相关依赖即可.
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-3-starter</artifactId> <version>1.2.21</version> </dependency>