Web后端开发_03
MyBatis框架
什么是MyBatis?
- MyBatis是一款优秀的持久层框架,用于简化JDBC的开发。
- MyBatis本是 Apache的一个开源项目iBatis,2010年这个项目由apache迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
- 官网:https://mybatis.org/mybatis-3/zh/index.html
MyBatis入门
快速入门
使用MyBatis查询所有用户数据
- 准备工作(创建springboot工程、数据库表user、实体类User)
- 引入Mybatis相关依赖,配置Mybatis(数据库连接信息)
- 编写SQL语句(注解/XML)
数据准备
create table user
(
id int unsigned primary key auto_increment comment 'ID',
name varchar(100) comment '姓名',
age tinyint unsigned comment '年龄',
gender tinyint unsigned comment '性别, 1:男, 2:女',
phone varchar(11) comment '手机号'
) comment '用户表';
insert into user(id, name, age, gender, phone)
VALUES (null, '白眉鹰王', 55, '1', '18800000000');
insert into user(id, name, age, gender, phone)
VALUES (null, '金毛狮王', 45, '1', '18800000001');
insert into user(id, name, age, gender, phone)
VALUES (null, '青翼蝠王', 38, '1', '18800000002');
insert into user(id, name, age, gender, phone)
VALUES (null, '紫衫龙王', 42, '2', '18800000003');
insert into user(id, name, age, gender, phone)
VALUES (null, '光明左使', 37, '1', '18800000004');
insert into user(id, name, age, gender, phone)
VALUES (null, '光明右使', 48, '1', '18800000005');
创建springboot项目
选择依赖,MyBatis Framework,MySQL Driver
配置数据库连接信息
#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mybatis
#连接数据库的用户名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=123456
编写SQL语句
@Mapper //在运行时,会自动生成该接口的实现类对象(代理对象),并且将该对象交给IOC容器管理
public interface UserMapper {
//查询全部的用户信息
@Select("select * from user")
public List<User> list();
}
单元测试
@SpringBootTest //springboot整合单元测试的注解
class SpringbootMybatisQuickstartApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void testListUser() {
List<User> userList = userMapper.list();
userList.stream().forEach(user -> {
System.out.println(user);
});
}
}
测试结果
- 默认在MyBatis中编写SQL语句是不识别的。可以做如下配置
配置SQL提示
- 产生原因:IDEA和数据库没有建立连接,不识别表信息、
- 解决方式:在IDEA中配置MySQL数据库连接
JDBC介绍
- JDBC(Java DataBase Connectivity),就是使用Java语言操作关系型数据库的一套API。
例如
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
本质
- sun公司官方定义的一套操作所有关系型数据库的规范,即接口。
- 各个数据库厂商去实现这套接口,提供数据库驱动jar包。
- 我们可以直接使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
JDBC缺点
MyBatis优势
数据库连接池
数据库连接池
- 数据库连接池是个容器,负责分配、管理数据库连接(Connection)
- 它允许应用程序重复使用一个现有的数据库连接,再不是在建立一个
- 释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏
优势
- 资源复用
- 提升系统响应速度
- 避免数据库连接遗漏
-
标准接口:DataSource
- 官方提供的数据库连接池接口,由第三方组织实现此接口。
- 功能:获取连接
Connection getConnection() throws SQLException;
-
常见产品:
-
Druid(德鲁伊)
- Druid连接池是阿里巴巴开源数据库连接池项目
- 功能强大,性能优秀,是Java语言最好的数据库连接池之一
-
切换Druid数据库连接池
官方地址:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.8</version> </dependency>
lombok
- Lombok是一个实用的Java类库,能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,并可以自动化生成日志变量,简化Java开发、提高效率。
引入lombok依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
示例
@Data
@NoArgsConstructor//无参构造
@AllArgsConstructor//有参构造
public class User {
private Integer id;
private String name;
private Short age;
private Short gender;
private String phone;
}
注意事项
- Lombok会在编译时,自动生成对应的java代码。使用lombok时,还需要安装一个lombok的插件(IDEA自带)。
MyBatis基础增删改查
需求说明
根据资料中提供的《tlias智能学习辅助系统》页面原型及需求,完成员工管理的需求开发。
准备
准备工作
- 准备数据库emp
- 创建一个新的springboot工程,选择引入对应的起步依赖(mybatis、mysql驱动、lombok)
application.properties
中引入数据库连接信息- 创建对应的实体类Emp(实体类属性采用驼峰命名法)
- 准备Mapper接口EmpMapper
准备数据库emp
-- 员工管理
create table emp
(
id int unsigned primary key auto_increment comment 'ID',
username varchar(20) not null unique comment '用户名',
password varchar(32) default '123456' comment '密码',
name varchar(10) not null comment '姓名',
gender tinyint unsigned not null comment '性别, 说明: 1 男, 2 女',
image varchar(300) comment '图像',
job tinyint unsigned comment '职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师',
entrydate date comment '入职时间',
dept_id int unsigned comment '部门ID',
create_time datetime not null comment '创建时间',
update_time datetime not null comment '修改时间'
) comment '员工表';
INSERT INTO emp
(id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time)
VALUES (1, 'jinyong', '123456', '金庸', 1, '1.jpg', 4, '2000-01-01', 2, now(), now()),
(2, 'zhangwuji', '123456', '张无忌', 1, '2.jpg', 2, '2015-01-01', 2, now(), now()),
(3, 'yangxiao', '123456', '杨逍', 1, '3.jpg', 2, '2008-05-01', 2, now(), now()),
(4, 'weiyixiao', '123456', '韦一笑', 1, '4.jpg', 2, '2007-01-01', 2, now(), now()),
(5, 'changyuchun', '123456', '常遇春', 1, '5.jpg', 2, '2012-12-05', 2, now(), now()),
(6, 'xiaozhao', '123456', '小昭', 2, '6.jpg', 3, '2013-09-05', 1, now(), now()),
(7, 'jixiaofu', '123456', '纪晓芙', 2, '7.jpg', 1, '2005-08-01', 1, now(), now()),
(8, 'zhouzhiruo', '123456', '周芷若', 2, '8.jpg', 1, '2014-11-09', 1, now(), now()),
(9, 'dingminjun', '123456', '丁敏君', 2, '9.jpg', 1, '2011-03-11', 1, now(), now()),
(10, 'zhaomin', '123456', '赵敏', 2, '10.jpg', 1, '2013-09-05', 1, now(), now()),
(11, 'luzhangke', '123456', '鹿杖客', 1, '11.jpg', 5, '2007-02-01', 3, now(), now()),
(12, 'hebiweng', '123456', '鹤笔翁', 1, '12.jpg', 5, '2008-08-18', 3, now(), now()),
(13, 'fangdongbai', '123456', '方东白', 1, '13.jpg', 5, '2012-11-01', 3, now(), now()),
(14, 'zhangsanfeng', '123456', '张三丰', 1, '14.jpg', 2, '2002-08-01', 2, now(), now()),
(15, 'yulianzhou', '123456', '俞莲舟', 1, '15.jpg', 2, '2011-05-01', 2, now(), now()),
(16, 'songyuanqiao', '123456', '宋远桥', 1, '16.jpg', 2, '2010-01-01', 2, now(), now()),
(17, 'chenyouliang', '123456', '陈友谅', 1, '17.jpg', NULL, '2015-03-21', NULL, now(), now());
删除
根据主键删除
- SQL语句:
delete from emp where id = 17;
-
接口方法:
@Delete("delete from emp where id = #{id}") public void delete(Integer id);
注意事项:
如果mapper接口方法形参只有一个普通类型的参数,
#{...}
里面的属性名可以随便编写,如#{id}
、#{value}
。
日志输出
-
可以在
application.properties
中,打开mybatis的日志,并指定输出到控制台。#指定mybatis输出日志的位置,输出控制台 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
预编译SQL
优势
- 性能更高
- 更安全(防止SQL注入)
SQL注入
- SQL注入是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。
参数占位符
#{...}
- 执行SQL时,会将
#{...}
替换为?
,生成预编译SQL,会自动设置参数值- 使用时机:参数传递时,都用
#{...}
${...}
- 拼接SQL。直接将参数拼接在SQL语句中,存在SQL注入问题
- 使用时机:如果对表名、列表进行动态设置时使用
插入(新增)
新增
-
SQL语句:
insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) values ('Tom', '汤姆', 1, '1.jpg', 1, '2000-05-05', 1, now(), now());
-
接口方法:
@Mapper public interface EmpMapper { @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time)" + "values (#{username}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})") public void insert(Emp emp); }
-
测试数据
@SpringBootTest class SpringbootMybatisCrudApplicationTests { @Autowired private EmpMapper empMapper; @Test public void testInsert() { //构造员工对象 Emp emp = new Emp(); emp.setUsername("Tom"); emp.setName("汤姆"); emp.setImage("1.jpg"); emp.setGender((short) 1); emp.setJob((short) 1); emp.setEntrydate(LocalDate.of(2000, 1, 4)); emp.setCreateTime(LocalDateTime.now()); emp.setUpdateTime(LocalDateTime.now()); emp.setDeptId(1); //执行新增员工信息操作 empMapper.insert(emp); } }
新增(主键返回)
- 描述:在数据添加成功后,需要获取插入数据库数据的主键。如:添加套餐数据时,还需要维护套餐菜品关系表数据。
-
实现:
@Mapper public interface EmpMapper { @Options(useGeneratedKeys = true,keyProperty = "id") @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time)" + "values (#{username}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})") public void insert(Emp emp); }
@Options(useGeneratedKeys = true,keyProperty = "id")
会自动将生成的主键值,赋值给emp对象的id属性
更新
-
SQL语句(根据ID更新员工信息)
update emp set username='zhaomin', name='赵敏', gender='2', image='5.jpg', job='2', entrydate='2008-12-18', dept_id='1', update_time='2023-11-25 12:38:20' where id = 18;
-
接口方法
@Mapper public interface EmpMapper { @Options(useGeneratedKeys = true,keyProperty = "id") @Update("update emp set username = #{username}, name = #{name}, gender = #{gender}, image = #{image}, job = #{job}," + " entrydate = #{entrydate}, dept_id = #{deptId}, update_time = #{updateTime} where id = #{id} ") public void update(Emp emp); }
-
测试数据更新
@SpringBootTest class SpringbootMybatisCrudApplicationTests { @Autowired private EmpMapper empMapper; @Test public void testUpdate() { //构造员工对象 Emp emp = new Emp(); emp.setId(18); emp.setUsername("Tom5"); emp.setName("汤姆5"); emp.setImage("1.jpg"); emp.setGender((short) 1); emp.setJob((short) 1); emp.setEntrydate(LocalDate.of(2002, 1, 9)); emp.setUpdateTime(LocalDateTime.now()); emp.setDeptId(1); //执行更新员工信息操作 empMapper.update(emp); } }
查询
根据ID查询
用于页面的回显展示
-
SQL语句
select * from emp where id = 2;
-
接口方法
@Mapper public interface EmpMapper { @Options(useGeneratedKeys = true,keyProperty = "id") @Select("select * from emp where id = #{id}") public Emp getByID(Integer id); }
-
测试查询
@SpringBootTest class SpringbootMybatisCrudApplicationTests { @Autowired private EmpMapper empMapper; @Test public void testGetById() { Emp emp = empMapper.getByID(5); System.out.println(emp); } }
-
查询结果
Emp(id=5, username=changyuchun, password=123456, name=常遇春, gender=1, image=5.jpg, job=2, entrydate=2012-12-05, deptId=null, createTime=null, updateTime=null)
数据封装
- 实体类属性名和数据库表查询返回的字段名一致,mybatis会自动封装
- 如果实体类属性名和数据库表查询返回的字段名不一致,mybatis不会自动封装
解决方案:
-
起别名:在SQL语句中,对不一样的列名起别名,别名和实体类属性名一样
@Select("select id, username, password, name, gender, image, job," + " entrydate, dept_id deptId, create_time createTime, update_time updateTime" + " from emp where id = #{id}") public Emp getByID(Integer id);
-
手动映射结果:通过
@Results
及@Result
进行手动结果映射@Select("select * from emp where id = #{id}") @Results({ @Result(column = "dept_id", property = "deptId"), @Result(column = "create_time", property = "createTime"), @Result(column = "update_time", property = "updateTime") }) public Emp getByID(Integer id);
-
开启驼峰命名:如果字段名与属性名符合驼峰命名规则,mybatis会自动通过驼峰命名规则映射
#开启驼峰命名自动映射,即从数据库字段名a_column映射到java属性名aColumn. mybatis.configuration.map-underscore-to-camel-case=true
条件查询
-
SQL语句
-- 模糊查询 select * from emp where name like '%张%' and gender = 1 and entrydate between '2010-01-01' and '2020-01-01' order by update_time desc ; -- 使用concat() select * from emp where name like concat('%', '张', '%') and gender = 1 and entrydate between '2010-01-01' and '2020-01-01' order by update_time desc ;
-
接口方法
1、以下方法性能低、不安全、存在SQL注入问题
@Select("select * from emp where name like concat('%${name}%') and gender = #{gender} and " + "entrydate between #{begin} and #{end} order by update_time desc ") public List<Emp> list(@Param("name") String name, @Param("gender") Short gender, @Param("begin") LocalDate begin, @Param("end") LocalDate end);
2、推荐使用下列方法,
@Select("select * from emp where name like concat('%',#{name},'%') and gender = #{gender} and " + "entrydate between #{begin} and #{end} order by update_time desc ") public List<Emp> list(@Param("name") String name, @Param("gender") Short gender, @Param("begin") LocalDate begin, @Param("end") LocalDate end);
参数名说明
-
在springboot2.x版本
@Select("select * from emp where name like concat('%',#{name},'%') and gender = #{gender} and " + "entrydate between #{begin} and #{end} order by update_time desc ") public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);
-
在springboot1.x版本/单独使用mybatis
@Select("select * from emp where name like concat('%',#{name},'%') and gender = #{gender} and " + "entrydate between #{begin} and #{end} order by update_time desc ") public List<Emp> list(@Param("name") String name, @Param("gender") Short gender, @Param("begin") LocalDate begin, @Param("end") LocalDate end);
XML映射文件
规范
- XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下**(同包同名)**
- XML映射文件的namespace属性为Mapper接口全限定名一致
- XML映射文件中SQL语句的id与Mapper接口中的方法名一致,并保持返回类型一致
-
MybatisX是一款基于IDEA的快速开发Mybatis的插件,为效率而生。
-
安装:
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
选择何种方式来配置映射,以及认为是否应该要统一映射语句定义的形式,完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换。
使用Mybatis的注解,主要是来完成一些简单的增删改查功能。如果需要实现复杂的SQL功能,建议使用XML来配置映射语句
官方说明:https://mybatis.net.cn/getting-started.html
MyBatis动态SQL
动态SQL
随着用户的输入或外部条件的变化而变化的SQL语句,称为动态SQL。
<if>
-
<if>:
用于判断条件是否成立。使用test属性进行条件判断,如果条件为true,则拼接SQL。<if test="name != null"> name like concat('%',#{name},'%') </if>
<where></where>
用来替换where
关键字,并且可以去除多余的and
或or
案例:完善更新员工功能,修改为动态更新员工数据信息
需求
- 动态更新员工信息,如果更新时传递有值,则更新;如果更新时没有传递值,则不更新。
解决方案
- 动态SQL
<set></set>
用来替换set
关键字,并且会去除字段之后多余的,
(用在update语句中)
小结
1、<if>
-
用于判断条件是否成立,如果条件为true,则拼接SQL
-
形式:
<if test="name != null">...</if>
2、<where>
- where元素只会在子元素有内容的情况下才插入where子句,而且会自动去除子句的开头的AND或OR
3、<set>
- 动态地在行首插入SET关键字,并会删除掉额外的逗号。(用在update语句中)
<foreach>
主要用于批量操作中
完成批量删除操作
报错解决
Caused by: org.apache.ibatis.binding.BindingException: Parameter 'ids' not found. Available parameters are [arg0, collection, list]
解决方案(参考参数名说明)
EmpMapper.java
中的配置文件添加@Param("ids")
public void deleteByIds(@Param("ids") List<Integer> ids);
-
SQL语句
delete from emp where id in (18, 19, 20);
-
接口方法
//批量删除员工 public void deleteByIds(@Param("ids") List<Integer> ids);
-
XML映射文件
<!--批量删除员工(18, 19, 20)--> <!-- collection 遍历的集合 item 遍历出来的元素 separator 分隔符 open 遍历开始前拼接的SQL片段 close 遍历开始后拼接的SQL片段 --> <delete id="deleteByIds"> delete from emp where id in <foreach collection="ids" item="id" separator="," open="(" close=")"> #{id} </foreach> </delete>
<sql>
和<include>
问题分析
<sql>
:定义可重用的SQL片段,即负责公共SQL片段的抽取<include>
:通过属性refid
,指定包含的SQL片段,即负责对抽取的SQL片段进行引用
m emp where id in (18, 19, 20);
- 接口方法
```java
//批量删除员工
public void deleteByIds(@Param("ids") List<Integer> ids);
-
XML映射文件
<!--批量删除员工(18, 19, 20)--> <!-- collection 遍历的集合 item 遍历出来的元素 separator 分隔符 open 遍历开始前拼接的SQL片段 close 遍历开始后拼接的SQL片段 --> <delete id="deleteByIds"> delete from emp where id in <foreach collection="ids" item="id" separator="," open="(" close=")"> #{id} </foreach> </delete>
<sql>
和<include>
问题分析
[外链图片转存中…(img-T6ISOjWb-1700927686726)]
<sql>
:定义可重用的SQL片段,即负责公共SQL片段的抽取<include>
:通过属性refid
,指定包含的SQL片段,即负责对抽取的SQL片段进行引用
[外链图片转存中…(img-f7SPmrEh-1700927686726)]