5.Mybatis 入门
5.1 快速入门程序
5.1.1 准备工作
创建 springboot 工程
pom.xml 中的依赖如下:
<dependencies> <!-- mybatis起步依赖 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.0</version> </dependency> <!-- mysql驱动包依赖 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <!-- spring单元测试 (集成了junit) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
数据准备
创建用户表 User,并创建对应的实体类 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');
实体类的属性名与表中的字段名一一对应。
public class User { private Integer id; //id(主键) private String name; //姓名 private Short age; //年龄 private Short gender; //性别 private String phone; //手机号 //省略GET, SET方法 }
5.1.2 配置 Mybatis
连接数据库的四大参数:MySQL 驱动类,登录名,密码和数据库连接字符串。
在 springboot 项目中,可以编写 application.properties 文件,配置数据库连接信息。我们要连接数据库,就需要配置数据库连接的基本信息,包括:driver-class-name、url 、username,password。
#驱动类名称 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #数据库连接的url spring.datasource.url=jdbc:mysql://localhost:3306/db_mybatis #连接数据库的用户名 spring.datasource.username=root #连接数据库的密码 spring.datasource.password=1234
5.1.3 编写 SQL 语句
在创建出来的 springboot 工程中,在引导类所在包下,再创建一个包 mapper。在 mapper 包下创建一个接口 UserMapper,这是一个持久层接口(Mybatis 的持久层接口规范一般都叫 XXXMapper)。
import com.ayanokouji.pojo.User; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.List; @Mapper public interface UserMapper { // 查询所有用户信息 @Select("select id, name, age, gender, phone from user") public List<User> list(); }
@Mapper
注解:表示是 mybatis 的 Mapper 接口。程序运行时,框架会自动生成接口的实现类对象(代理对象),并交给 Spring 的 IOC 容器管理。
@Select
注解:代表的就是 select 查询,用于书写 select 查询语句。
5.1.4 单元测试
在创建出来的 SpringBoot 工程中,在 src 下的 test 目录下,已经自动帮我们创建好了测试类 ,并且在测试类上已经添加了注解 @SpringBootTest
,代表该测试类已经与 SpringBoot 整合。
该测试类在运行时,会自动通过引导类加载 Spring 的环境(IOC 容器)。我们要测试那个 bean 对象,就可以直接通过 @Autowired
注解直接将其注入进行,然后就可以测试了。
@SpringBootTest class MybaitsQuickstartApplicationTests { @Autowired private UserMapper userMapper; @Test public void testList(){ List<User> users = userMapper.list(); for (User user : users) { System.out.println(user); } } }
运行结果如下:
User{id=1, name='白眉鹰王', age=55, gender=1, phone='18800000000'} User{id=2, name='金毛狮王', age=45, gender=1, phone='18800000001'} User{id=3, name='青翼蝠王', age=38, gender=1, phone='18800000002'} User{id=4, name='紫衫龙王', age=42, gender=2, phone='18800000003'} User{id=5, name='光明左使', age=37, gender=1, phone='18800000004'} User{id=6, name='光明右使', age=48, gender=1, phone='18800000005'}
5.2 数据库连接池
数据库连接池是个容器,负责分配、管理数据库连接:
- 在程序启动时,会在数据库连接池中,创建一定数量的 Connection 对象
允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个
- 客户端在执行 SQL 时,先从连接池获取一个 Connection 对象,然后再执行 SQL 语句,SQL 语句执行完之后,释放 Connection 时就会把 Connection 随想归还给连接池(Connection 对象可以复用)
释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏
- 客户端获取到 Connection 对象了,但是 Connection 对象并没有去访问数据库(处于空闲),数据库连接池发现 Connection 兑现的空闲时间》连接池中预设的最大空闲时间,此时数据库连接池就会自动释放掉这个连接对象
数据库连接池的好处:
- 资源重用
- 提升系统响应速度
- 避免数据库连接遗漏
常见的数据库连接池:
- C3P0
- DBCP
- Druid
- Hikari(Springboot 默认)
现在使用更多的是:Hikari(追光者)、Druid(德鲁伊)。
如果我们想把默认的数据库连接池切换为 Druid 数据库连接池,只需要完成以下两步操作即可:
-
在 pom.xml 文件中引入依赖
com.alibaba druid-spring-boot-starter 1.2.8 -
在 application.properties 中引入数据库连接配置
方式 1:
spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.druid.url=jdbc:mysql://localhost:3306/db_mybatis spring.datasource.druid.username=root spring.datasource.druid.password=1234 方式 2:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/db_mybatis spring.datasource.username=root spring.datasource.password=1234
5.3 lombok
Lombok 是一个实用的 Java 类库,可以通过简单的注解来简化和消除一些必须有但显得很臃肿的 Java 代码,可以使得实体类的编写更加简洁。
注解 | 作用 |
---|---|
@Getter/@Setter | 为所有的属性提供 get/set 方法 |
@ToString | 会给类自动生成易阅读的 toString 方法 |
@EqualsAndHashCode | 根据类所拥有的非静态字段自动重写 equals 方法和 hashCode 方法 |
@Data | 提供了更综合的生成代码功能(@Getter + @Setter + @ToString + @EqualsAndHashCode) |
@NoArgsConstructor | 为实体类生成无参的构造器方法 |
@AllArgsConstructor | 为实体类生成除了 static 修饰的字段之外带有各参数的构造器方法。 |
第一步,在 pom.xml 中引入依赖
<!-- 在springboot的父工程中,已经集成了lombok并指定了版本号,故当前引入依赖时不需要指定version --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
第二步,在实体类上添加注解
@Data //getter方法、setter方法、toString方法、hashCode方法、equals方法 @NoArgsConstructor //无参构造 @AllArgsConstructor//全参构造 public class User { private Integer id; //id(主键) private String name; //姓名 private Short age; //年龄 private Short gender; //性别 private String phone; //手机号 }
6. Mybatis
6.1 准备工作
实施前的准备工作:
- 准备数据库表
- 创建一个新的 springboot 工程,选择引入对应的起步依赖(mybatis、mysql 驱动、lombok)
- application.properties 中引入数据库连接信息
- 创建对应的实体类 Emp(实体类属性采用驼峰命名)
- 准备 Mapper 接口 EmpMapper
准备数据库表
-- 部门管理 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 工程,选择引入对应的起步依赖:
application.properties 引入数据库连接信息
#驱动类名称 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #数据库连接的url spring.datasource.url=jdbc:mysql://localhost:3306/db_mybatis #连接数据库的用户名 spring.datasource.username=root #连接数据库的密码 spring.datasource.password=1234
创建对应的实体类 Emp
@Data @AllArgsConstructor @NoArgsConstructor public class Emp { private Integer id; private String username; private String password; private String name; private Short gender; private String image; private Short job; private LocalDate entrydate; //LocalDate类型对应数据表中的date类型 private Integer deptId; private LocalDateTime createTime;//LocalDateTime类型对应数据表中的datetime类型 private LocalDateTime updateTime; }
准备 Mapper 接口
/*@Mapper注解:表示当前接口为mybatis中的Mapper接口 程序运行时会自动创建接口的实现类对象(代理对象),并交给Spring的IOC容器管理 */ @Mapper public interface EmpMapper { }
完成上述操作后,项目工程结构目录如下
6.2 功能实现
6.2.1 删除
6.2.1.1 删除功能
功能:根据主键删除数据
SQL 语句:
-- 删除id=17的数据 delete from emp where id = 17;
接口方法:
@Mapper public interface EmpMapper { /* * 根据id删除数据 * */ @Delete("delete from emp where id=#{id}")//使用#{key}方式获取方法中的参数值 public void delete(Integer id); }
Delete
注解:用于编写 delete 操作的 SQL 语句。如果 mapper 接口方法形参只有一个普通类型的参数,#{...} 里面的属性名可以随便写,但是建议保持名字一致。
测试:
@SpringBootTest class MybatisMdApplicationTests { @Autowired private EmpMapper empMapper; @Test public void testDel(){ // 调用删除方法 empMapper.delete(16); } }
6.2.1.2 日志输入
在 Mybatis 当中我们可以借助日志,查看 sql 语句的执行、执行传递的参数以及执行结果。具体操作如下:
- 打开 application.properties 文件
- 开启 mybatis 的日志,并指定输出到控制台
# 指定mybatis输出日志的位置,输出到控制台 mybatis.configuration.logImpl=org.apache.ibatis.logging.stdout.StdOutImpl
开启日志之后,我们再次运行单元测试,可以看到在控制台中,输出了以下的 SQL 语句信息:
但是我们发现输出的 SQL 语句:delete from emp where id = ?,我们输入的参数 16 并没有在后面拼接,id 的值是使用?进行占位。那这种 SQL 语句我们称为预编译 SQL。
6.2.1.3 预编译 SQL
SQL 注入
SQL 注入:是通过操作输入的数据来修改事先定义好的 SQL 语句,以达到执行代码对服务器进行攻击的方法。
用户在页面提交数据的时候人为的添加一些特殊字符,使得 sql 语句的结构发生了变化,最终可以在没有用户名或者密码的情况下进行登录。
参数占位符
在 Mybatis 中提供的参数占位符有两种:${...} 和 #{...}。
#{...}:
- 执行 SQL 时,会将 #{…} 替换为?,生成预编译 SQL,会自动设置参数值
- 使用时机:参数传递,都使用 #{…}
${...}:
- 拼接 SQL。直接将参数拼接在 SQL 语句中,存在 SQL 注入问题
- 使用时机:如果对表名、列表进行动态设置时使用
6.2.2 新增
6.2.2.1 基本新增
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);
测试类:
@SpringBootTest class MybatisMdApplicationTests { @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,1)); emp.setCreateTime(LocalDateTime.now()); emp.setUpdateTime(LocalDateTime.now()); emp.setDeptId(1); //调用添加方法 empMapper.insert(emp); } }
日志输出:
6.2.2.2 主键返回
概念:在数据添加成功后,需要获取插入数据库数据的主键。
那要如何实现在插入数据之后返回所插入行的主键值呢?
默认情况下,执行插入操作时,是不会主键值返回的。如果我们想要拿到主键值,需要在 Mapper 接口中的方法上添加一个 Options
注解,并在注解中指定属性 useGeneratedKeys=true 和 keyProperty="实体类属性名"。
主键返回代码实现:
@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);
测试代码:
@SpringBootTest class MybatisMdApplicationTests { @Autowired private EmpMapper empMapper; @Test public void testInsert(){ Emp emp = new Emp(); emp.setUsername("jack"); 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); System.out.println(emp.getId()); } }
6.2.3 更新
SQL 语句:
update emp set username = 'linghushaoxia', name = '令狐少侠', gender = 1 , image = '1.jpg' , job = 2, entrydate = '2012-01-01', dept_id = 2, update_time = '2022-10-01 12:12:12' where id = 18;
接口方法:
/** * 根据id修改员工信息 * @param 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);
测试类:
@SpringBootTest class MybatisMdApplicationTests { @Autowired private EmpMapper empMapper; @Test public void testUpdate(){ //要修改的员工信息 Emp emp = new Emp(); emp.setId(23); emp.setUsername("songdaxia"); emp.setPassword(null); emp.setName("老宋"); emp.setImage("2.jpg"); emp.setGender((short)1); emp.setJob((short)2); emp.setEntrydate(LocalDate.of(2012,1,1)); emp.setCreateTime(null); emp.setUpdateTime(LocalDateTime.now()); emp.setDeptId(2); //调用方法,修改员工数据 empMapper.update(emp); } }
6.2.4 查询
6.2.4.1 根据 ID 查询
SQL 语句:
select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp;
接口方法:
@Mapper public interface EmpMapper { @Select("select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp where id=#{id}") public Emp getById(Integer id); }
测试类:
@SpringBootTest class MybatisMdApplicationTests { @Autowired private EmpMapper empMapper; @Test public void testGetById(){ Emp emp = empMapper.getById(1); System.out.println(emp); } }
执行结果:
可以看到 deptId, createTime 和 updateTime 字段为空,没有数值。
6.2.4.2 数据封装
出现上述问题的原因是,实体类名和数据库字段名不一致。实体类属性名和数据库表查询返回的字段名一致,mybatis 会自动封装。如果实体类属性名和数据库表查询返回的字段名不一致,不能自动封装。
怎么解决这个问题呢?有以下三种方案:
- 起别名
- 结果映射
- 开启驼峰命名
起别名:在 SQL 语句中,对不一样的列名起别名,别名与实体类属性名一致
@Select("select id, username, password, name, gender, image, job, entrydate, " + "dept_id AS deptId, create_time AS createTime, update_time AS updateTime " + "from emp " + "where id=#{id}") public Emp getById(Integer id);
手动结果映射:通过 @Results
和 @Result
进行手动结果映射
@Results({@Result(column = "dept_id", property = "deptId"), @Result(column = "create_time", property = "createTime"), @Result(column = "update_time", property = "updateTime")}) @Select("select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp where id=#{id}") public Emp getById(Integer id);
开启驼峰命名(推荐):如果字段名与属性名符合驼峰命名规则,mybatis 会自动通过驼峰命名规则映射。
驼峰命名规则:abc_xyz => abcXyz
表中字段名:abc_xyz
类中属性名:abcXyz
# 在application.properties中添加: mybatis.configuration.map-underscore-to-camel-case=true
6.2.4.3 条件查询
任务要求:
- 姓名:要求支持模糊匹配
- 性别:要求精确匹配
- 入职时间:要求进行范围查询
- 根据最后修改时间进行降序排序
SQL 语句:
select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time 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 list(@Param("name") String name, @Param("gender")Short gender, @Param("begin")LocalDate begin, @Param("end")LocalDate end); 测试类:
@SpringBootTest class MybatisMdApplicationTests { @Autowired private EmpMapper empMapper; @Test public void testList(){ LocalDate begin = LocalDate.of(2010, 1, 1); LocalDate end = LocalDate.of(2020, 1, 1); Short gender = (short) 1; String name = "张"; List list = empMapper.list(name, gender, begin, end); System.out.println(list); } } 模糊查询使用 ${...} 进行字符串拼接,这种方式呢,由于是字符串拼接,并不是预编译的形式,所以效率不高、且存在 sql 注入风险。
-
方式二(解决 SQL 注入风险)
使用 MySQL 提供的字符串拼接函数:concat ('%' , ' 关键字 ' , '%')
@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 list(@Param("name") String name, @Param("gender")Short gender, @Param("begin")LocalDate begin, @Param("end")LocalDate end);
6.3 Mybatis 的 XML 配置文件
6.3.1 XML 配置文件规范
上述案例中我们都是通过注解的方式来实现一些简单的功能,如果需要实现复杂的 SQL 功能,建议使用 XML 来配置映射语句,也就是将 SQL 语句写在 XML 配置文件中。
在 Mybatis 中使用 XML 映射文件方式开发,需要符合一定的规范:
- XML 映射文件的名称与 Mapper 接口名称一致,并且将 XML 映射文件和 Mapper 接口防止在相同包下(同包同名)
- XML 映射文件的 namespace 属性为 Mapper 接口全限定名一致
- XML 映射文件中 sql 语句 id 与 Mapper 接口中的方法名一致,并保持返回类型一致。
首先是,第一点同包同名:
然后是第二点、第三点:
\