🔥 本文由 程序喵正在路上 原创,CSDN首发!
💖 系列专栏:JavaWeb开发
🌠 首发时间:2024年3月7日
🦋 欢迎关注🖱点赞👍收藏🌟留言🐾
目录
Mybatis介绍
什么是Mybatis
-
MyBatis是一款优秀的持久层框架,用于简化JDBC的开发
-
MyBatis本是 Apache的一个开源项目iBatis,2010年这个项目由apache迁移到了google code,并且改名为MyBatis,2013年11月迁移到Github
Mybatis入门
快速入门
案例:使用Mybatis查询所有用户数据
- 准备工作(创建springboot工程、数据库表user、实体类User)
- 引入Mybatis的相关依赖,配置Mybatis(数据库连接信息)
- 编写SQL语句(注解/XML)
打开我们之前创建的工程 web_project,选择 File → \rightarrow → New → \rightarrow → Module → \rightarrow → Spring Initializr,和之前一样填写一下内容:
勾选Mybatis的相关依赖:
然后我们将模块中不需要的部分删掉,保持工程的简洁,剩下这两个即可:
对 pom.xml 中一些依赖的说明:
新建数据库 mybatis,新建 console mybatis,复制下列语句并执行,创建表user:
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');
在刚刚创建的模块的包下创建一个包 pojo 专门用来存放实体类,在 pojo 下创建实体类 User,根据表 User 先写出它的属性,然后快捷生成构造方法、get 和 set 方法以及 toString() 方法:
配置Mybatis(数据库连接信息),复制以下内容到 application.properties 中:
#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
#连接数据库的用户名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=1234
记得将用户名和密码改成你自己的
然后,创建一个包 mapper,在包下创建一个接口 UserMappe,编写SQL语句r:
import com.xixi.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper // 在运行时,会自动生成该接口的实现类对象(代理对象),并且将该对象交给IOC容器管理
public interface UserMapper {
// 查询全部用户信息
@Select("select * from user")
public List<User> list();
}
接下来,我们来测试一下:
找到工程自带的测试类,编写测试代码:
import com.xixi.mapper.UserMapper;
import com.xixi.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.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);
});
}
}
其中 @Autowired
注解为依赖注入方面的内容,如果不懂可以点击 这篇文章 观看最后的分层解耦部分内容
最后,右键执行 testListUser 方法,显示出用户信息即为成功:
配置SQL提示
默认在mybatis中编写SQL语句是不识别的。可以做如下配置,选中SQL语句右键按下图操作:
然后我们发现SQL语句颜色变了,但是user报红了:
-
产生原因:Idea和数据库没有建立连接,识别不到表信息
-
解决方式:在Idea中配置MySQL数据库连接
JDBC介绍
JDBC: (Java DataBase Connectivity),就是使用Java语言操作关系型数据库的一套API。
本质
- sun公司官方定义的一套操作所有关系型数据库的规范,即接口
- 各个数据库厂商去实现这套接口,提供数据库驱动jar包
- 我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类
Mybatis VS JDBC
数据库连接池
- 数据库连接池是个容器,负责分配、管理数据库连接 (Connection)
- 它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个
- 释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏
优势
- 资源重用
- 提升系统响应速度
- 避免数据库连接遗漏
数据库连接池
标准接口:DataSource
- 官方(sun)提供的数据库连接池接口,由第三方组织实现此接口。
- 功能:获取连接
常见产品:
Druid(德鲁伊)
- Druid连接池是阿里巴巴开源的数据库连接池项目
- 功能强大,性能优秀,是Java语言最好的数据库连接池之一
切换Druid数据库连接池
在 pom.xml 中添加依赖:
application.properties 中的内容写成这样也可以:
lombok
在前面写的实体类 User 中,我们快捷生成了一大堆方法,虽然是快捷生成的,但是当类的属性多了之后,还是很麻烦的,下面我们介绍一个工具包 lombok
lombok
Lombok是一个实用的Java类库,能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,并可以自动化生成日志变量,简化java开发、提高效率。
lombok 拥有以下注解:
lombok使用方法
-
引入依赖,在 pom.xml 中加入 lombok 的依赖
<!-- lombok工具包--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
-
修改 User 类
-
测试,能够查询出数据即为成功
注意事项
- Lombok会在编译时,自动生成对应的java代码。我们使用lombok时,还需要安装一个lombok的插件 (idea自带)。
Mybatis基础增删改查
根据页面原型及需求,完成员工管理的需求开发。
准备
- 准备数据库表 emp
- 创建一个新的springboot工程,选择引入对应的起步依赖(mybatis、mysql驱动、lombok)
- application.properties中引入数据库连接信息
- 创建对应的实体类 Emp(实体类属性采用驼峰命名)
- 准备Mapper接口 EmpMapper
-
复制下列语句到 mybatis 中并执行
-- 部门管理 create table dept ( id int unsigned primary key auto_increment comment '主键ID', name varchar(10) not null unique comment '部门名称', create_time datetime not null comment '创建时间', update_time datetime not null comment '修改时间' ) comment '部门表'; insert into dept (id, name, create_time, update_time) values (1, '学工部', now(), now()), (2, '教研部', now(), now()), (3, '咨询部', now(), now()), (4, '就业部', now(), now()), (5, '人事部', now(), now()); -- 员工管理 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());
-
创建一个新的springboot工程,选择引入对应的起步依赖(mybatis、mysql驱动、lombok)
-
application.properties中引入数据库连接信息
#驱动类名称 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #数据库连接的url spring.datasource.url=jdbc:mysql://localhost:3306/mybatis #连接数据库的用户名 spring.datasource.username=root #连接数据库的密码 spring.datasource.password=1234
-
创建对应的实体类 Emp(实体类属性采用驼峰命名),准备Mapper接口 EmpMapper
删除
根据主键删除
-
SQL语句:
delete from emp where id = 17;
-
接口方法:
@Delete("delete from emp where id = #{id}") public void delete(Integer id);
记得刷新一下,不然是找不到 emp 表的:
写完后,我们来测试一下是否能够成功删除,在测试类中编写代码测试:
运行后,发现 17 号已经不在表中,说明已经被删除了
注意事项
- 如果mapper接口方法形参只有一个普通类型的参数,#{…} 里面的属性名可以随便写,如:#{id}、#{value}。
- 但是建议两者保持一致,增强可读性
日志输出
-
可以在application.properties中添加配置,打开mybatis的日志,并指定输出到控制台。
#指定mybatis输出日志的位置,输出控制台 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
再次运行,我们发现运行结果多了一些东西,我们称之为预编译SQL
预编译SQL的优势
- 性能更高
- 更安全 (可以防止SQL注入)
SQL注入
- SQL注入是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。
参数占位符
插入
实现新增员工的操作:
-
SQL语句:
insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) values ('songyuanqiao', '宋远桥', 1, '1.jpg', 2, '2012-10-09', 2, '2022-10-01 10:00:00', '2022-10-01 10:00:00');
-
接口方法:
//新增员工 @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);
-
测试方法:
@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, 1)); emp.setCreateTime(LocalDateTime.now()); emp.setUpdateTime(LocalDateTime.now()); emp.setDeptId(1); //执行新增员工信息操作 empMapper.insert(emp); }
新增(主键返回)
- 描述:在数据添加成功后,需要获取插入数据库数据的主键。如:添加套餐数据时,还需要维护套餐菜品关系表数据。
如果我们在刚刚插入员工数据后,想获取它的ID,应该怎么办?
如果直接获取,是行不通的。我们先将Tom这个员工删除,然后在 testInsert() 方法中添加一行输出语句,来看看结果:
我们发现获取不到ID。那该如何实现呢?
在接口方法前添加主键返回的注解:
//新增员工
@Options(useGeneratedKeys = true, keyProperty = "id") //会自动将生成的主键值,赋值给emp对象的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);
更新
-
接口方法
//更新员工信息 @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);
测试:
查询
查询(根据ID查询)
-
接口方法
//根据ID查询 @Select("select * from emp where id = #{id}") public Emp getById(Integer id);
-
测试方法:
//根据ID查询员工 @Test public void testGetById() { Emp emp = empMapper.getById(16); System.out.println(emp); }
数据封装
- 实体类属性名和数据库表查询返回的字段名一致,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
进行手动结果映射。//方案二:通过@Results,@Result注解手动映射封装 @Results({ @Result(column = "dept_id", property = "deptId"), @Result(column = "create_time", property = "createTime"), @Result(column = "update_time", property = "updateTime") }) @Select("select * from emp where id = #{id}") public Emp getById(Integer id);
-
方案三:开启驼峰命名:如果字段名与属性名符合驼峰命名规则,mybatis会自动通过驼峰命名规则映射。
在 application.properties 中添加:
#开启驼峰命名自动映射,即从数据库字段名 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;
-
接口方法
//根据条件查询员工 //方法一:不推荐 @Select("select * from emp where name like '%${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); //方法二:推荐 @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);
-
测试:
//根据条件查询员工 @Test public void testList() { List<Emp> empList = empMapper.list("张", (short) 1, LocalDate.of(2000, 1, 1), LocalDate.of(2020, 1, 1)); System.out.println(empList); }
参数名说明
Mybatis XML映射文件
前面我们讲解了用注解的方式来编写SQL语句,下面我们来介绍用XML映射文件的方式
规范
- XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同包同名)。
- XML映射文件的namespace属性为Mapper接口全限定名一致。
- XML映射文件中sql语句的id与Mapper 接口中的方法名一致,并保持返回类型一致。
右键 resources → \rightarrow → New → \rightarrow → Directory:
然后在这个包下新建 EmpMapper.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">
接着在后面写SQL语句:
<!-- namespace属性与Mapper接口全限定名一致-->
<mapper namespace="com.xixi.mapper.EmpMapper">
<!-- sql语句的id与Mapper接口中的方法名一致,并保持返回类型一致-->
<select id="list" resultType="com.xixi.pojo.Emp">
select *
from emp
where name like concat('%', #{name}, '%')
and gender = #{gender}
and entrydate between #{begin} and #{end}
order by update_time desc
</select>
</mapper>
返回类型可以按下图操作复制:
然后我们再将接口方法的注解删掉,剩下方法即可:
接着,进行测试:
MybatisX
-
MybatisX 是一款基于 IDEA 的快速开发Mybatis的插件,为效率而生
-
安装方法:
安装成功后,我们会发现页面多了几个小鸟的标志,点击一下接口方法前面的标志,发现页面会自动跳转到其对应的 XML 映射文件:
选择注解好还是XML好
使用Mybatis的注解,主要是来完成一些简单的增删改查功能。如果需要实现复杂的SQL功能,建议使用XML来配置映射语句。
Mybatis动态SQL
随着用户的输入或外部条件的变化而变化的SQL语句,我们称为动态SQL。
在前面写的这个SQL语句中,如果我们在传递参数的时候只传递过来 name,gender 或者 begin 和 end,也就是不给全条件,那么我们是查询不到数据的,这个时候我们就需要动态SQL的帮助了
<if>
<if>
:用于判断条件是否成立。使用test属性进行条件判断,如果条件为true,则拼接SQL。<where>
:where 元素只会在子元素有内容的情况下才插入where子句。而且会自动去除子句的开头的AND或OR。
改进:
<!-- namespace属性与Mapper接口全限定名一致-->
<mapper namespace="com.xixi.mapper.EmpMapper">
<!-- sql语句的id与Mapper接口中的方法名一致,并保持返回类型一致-->
<select id="list" resultType="com.xixi.pojo.Emp">
select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time
from emp
<where>
<if test="name != null">
name like concat('%', #{name}, '%')
</if>
<if test="gender != null">
and gender = #{gender}
</if>
<if test="begin != null and end != null">
and entrydate between #{begin} and #{end}
</if>
</where>
order by update_time desc
</select>
</mapper>
我们只传入一个性别,进行测试:
<if>案例
完善更新员工功能,修改为动态更新员工数据信息
需求
- 动态更新员工信息,如果更新时传递有值,则更新;如果更新时没有传递值,则不更新。
实现
<set>
:动态地在行首插入 SET 关键字,并会删掉额外的逗号。(用在update语句中)
将接口方法的注解删掉,在 EmpMapper.xml 添加 XML 映射文件:
<update id="update">
update emp
<set>
<if test="username != null">username = #{username},</if>
<if test="name != null">name = #{name},</if>
<if test="gender != null">gender = #{gender},</if>
<if test="image != null">image = #{image},</if>
<if test="job != null">job = #{job},</if>
<if test="entrydate != null">entrydate = #{entrydate},</if>
<if test="deptId != null">dept_id = #{deptId},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
</set>
where id = #{id}
</update>
将测试方法随便修改一下进行测试:
更新成功:
<foreach>
需求:批量删除员工
-
SQL语句
delete from emp where id in (1,2,3);
-
接口方法
//批量删除 public void deleteByIds(List<Integer> ids);
-
XML映射文件
<delete id="deleteByIds"> delete from emp where id in <foreach collection="ids" item="id" separator="," open="(" close=")"> #{id} </foreach> </delete>
-
测试方法
//批量删除员工 @Test public void testDeleteByIds() { List<Integer> ids = Arrays.asList(13, 14, 15); empMapper.deleteByIds(ids); }
<foreach>
属性:
- collection:集合名称
- item:集合遍历出来的元素/项
- separator:每一次遍历使用的分隔符
- open:遍历开始前拼接的片段
- close:遍历结束后拼接的片段
<sql>&<include>
<sql>
:定义可重用的 SQL 片段<include>
:通过属性refid,指定包含的sql片段
简单来说,<sql>
用来保存SQL语句中经常用到的片段,而<include>
则是用来将<sql>
中的SQL片段引入,这样可以减小代码篇幅,同时方便我们对SQL语句进行修改
接下来我们对前面的这段SQL语句进行改造,将前面的部分拿出来:
<!-- namespace属性与Mapper接口全限定名一致-->
<mapper namespace="com.xixi.mapper.EmpMapper">
<!-- sql语句的id与Mapper接口中的方法名一致,并保持返回类型一致-->
<select id="list" resultType="com.xixi.pojo.Emp">
select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time
from emp
<where>
<if test="name != null">
name like concat('%', #{name}, '%')
</if>
<if test="gender != null">
and gender = #{gender}
</if>
<if test="begin != null and end != null">
and entrydate between #{begin} and #{end}
</if>
</where>
order by update_time desc
</select>
</mapper>
改造后:
<sql id="commonSelect">
select id,
username,
password,
name,
gender,
image,
job,
entrydate,
dept_id,
create_time,
update_time
from emp
</sql>
<!-- sql语句的id与Mapper接口中的方法名一致,并保持返回类型一致-->
<select id="list" resultType="com.xixi.pojo.Emp">
<include refid="commonSelect"/>
<where>
<if test="name != null">
name like concat('%', #{name}, '%')
</if>
<if test="gender != null">
and gender = #{gender}
</if>
<if test="begin != null and end != null">
and entrydate between #{begin} and #{end}
</if>
</where>
order by update_time desc
</select>
测试一下,成功: