目录
1. 什么是 MyBaits ?
2. 搭建 MyBaits 环境
3. 了解 MyBaits 设计模式
MyBaits 操作数据库:
前置工作:
(1)建库建表
(2) 添加实体类
(3)添加 Mapper 接口
(4)添加 UserMapper.xml
理解两种返回类型:
(1)resultType
(2)resultMap
1. 查询操作:
1.1 简单查询
1.2 条件查询
1.3 like 模糊查询
1.4 多表查询
2. 插入操作
3. 删除操作
4. 修改操作
关于 MyBaits 的知识点较深,细节较多,因此为了缓解阅读压力,其被分为两篇文章:
1.Mybatis 操作数据库的基本CRUD (本篇);
2.Mybaits 之动态SQL的五大常用标签;
1. 什么是 MyBaits ?
MyBaits 是一款功能极其强大的持久层框架,它支持自定义 SQL、存储过程以及高级映射。Mybaits 是基于 JDBC 的,但其免除了几乎所有 JDBC 以及设置参数和获取结果集的工作。MyBaits 可以通过简单的 XML或注解来配置和映射原始类型、接口和 java 对象为数据库中的记录。
简而言之:MyBaits 可以更加简洁的进行程序和数据库之间交互。
Mybaits 的学习路径:
了解什么是MyBaits → 学会添加 Mybaits 框架到 java 项目中 → MyBaits 的初始化工作(连接数据库、配置 XML等)→ 学会基本的数据库CRUD操作 → 理解动态SQL(MyBaits的关键)→ 掌握常用几种动态 SQL 标签
2. 搭建 MyBaits 环境:
2.1 创建 Spring 项目,添加 Mybaits 框架依赖:
2.2 设置 MyBaits 配置信息:
(1)先在application.properties 配置文件中连接数据库:
由于在引入依赖时专门引入了 MySQL Driver 这个数据库驱动,所以需要在这里为驱动设置类名,不过很简单,因为只要都是 MySQL,那么类名都是下面这个:
#设置 MySQL 驱动类名
spring.datasource.driver-class-name=com.mysql.cj.Driver
MyBaits 要通过 XML 配置来映射数据库数据,那么就要有对于的 XML 文件,并且在 MyBaits 配置文件中 设置 XML 文件的路径:mapper-locatins。在 resource 目录下创建好响应的 XML 文件,如果统一的文件名都叫:xxxMapper.xml,那么就在路径设置时写为:*Mapper.xml
#设置 MyBaits 的 XML 路径 = 所有类的根目录:/mybatis目录下/所有名字以 Mapper.xml 结尾的 XML 文件都是当前mybatis要映射的
mybatis.mapper-locations=classpath:/mybatis/*Mapper.xml
3. 了解 MyBaits 设计模式:
首先 Mybaits 的主要工具由两部分组成:interface + xml;
- interface:MyBaits 既然是持久层的工具,那么要符合标准分层,就一定要接受其他层注入的接口、对象等;
- xml:xml 文件中实现具体的 sql 语句;
MyBaits 将 interface 和 xml 结合、封装起来,将本来不能被注入的 interface 变成一个可以被注入的代理对象,这个过程对用户是透明度。
于是就可以实现:将数据库中的数据与对象建立映射关系,进而完成数据与对象之间的相互转换。
比如:
- 数据库表 <——> 类;
- 记录(行数据) <——> 对象;
- 字段 <——> 对象的属性;
MyBaits 操作数据库:
接下来开始真正的通过 MyBaits 去操作数据库了;
前置工作:
(1)建库建表:
准备工作:先在 MySQL 中创建一个mycnblog的数据库,再增添几张表:
-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8mb4;
-- 使用数据数据
use mycnblog;
-- 创建表[用户表]
drop table if exists userinfo;
create table userinfo(
id int primary key auto_increment,
username varchar(100) not null,
password varchar(32) not null,
photo varchar(500) default '',
createtime timestamp default current_timestamp,
updatetime timestamp default current_timestamp,
`state` int default 1
) default charset 'utf8mb4';
-- 创建文章表
drop table if exists articleinfo;
create table articleinfo(
id int primary key auto_increment,
title varchar(100) not null,
content text not null,
createtime timestamp default current_timestamp,
updatetime timestamp default current_timestamp,
uid int not null,
rcount int not null default 1,
`state` int default 1
)default charset 'utf8mb4';
-- 创建视频表
drop table if exists videoinfo;
create table videoinfo(
vid int primary key,
`title` varchar(250),
`url` varchar(1000),
createtime timestamp default current_timestamp,
updatetime timestamp default current_timestamp,
uid int
)default charset 'utf8mb4';
-- 添加一个用户信息
INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES
(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);
-- 文章添加测试数据
insert into articleinfo(title,content,uid)
values('Java','Java正文',1);
-- 添加视频
insert into videoinfo(vid,title,url,uid) values(1,'java title','http://www.baidu.com',1);
(2) 添加实体类:
要通过对象操作数据库,就得先有对于的实体类:这里先添加一个用户的实体类 USer,其与 userinfo 表对应。
根据 userinfo 表中的字段信息,设置与实体类一对一的属性:
package com.example.demo.entity;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
private Integer id;
private String username;
private String password;
private String photo;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private Integer state;
}
(3)添加 Mapper 接口:
Mapper 接口用来接收其他层注入的接口、方法、对象,通过结合 xml 来与数据库数据完成映射;Mapper 接口的创建都放在一个专门的 mapper 目录下;
此处是专门来操作 userinfo 表的,它的实体类是 User,那么就先创建一个 UserMapper 接口:
注意: MMapper 接口创建出来第一时间添加 @Mapper 注解!!!!
package com.example.demo.mapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper // 一定要加 Mapper 注解!!!!
public interface UserMapper {
}
(4)添加 UserMapper.xml :
这也正是上面所说的 MyBaits 两大工具中的第二个:MyBaits 的 xml 文件,也就是要和 Mapper 接口结合使用的 xml 文件(xml文件有很多人种,要分清楚),它正是数据持久层的具体实现。所有的这类 xml 文件的路径,都要和最开始配置的 mybatis-locations 路径一致;
首先它有它的固定格式:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybati
s.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
</mapper>
- <mapper><mapper>标签:需要指定 namespace 属性,意为命名空间,其值为 Mapper 接口的全包名.类名;
- mapper 标签内部的子标签:也即是黄色框内的<select>、<insert>、<update>等标签,用来实现具体 SQL;
- 子标签要注意的点:
- id 要和 Mapper 接口中的方法名对应;
- 返回类型根据需要填写;
理解两种返回类型:
xml 里的返回类型有两种:resultType 和 resultMap;
返回类型的作用是 MyBatis 做结果映射;
如果是查询操作,返回类型必须设置;
如果是增、删、改等操作,不强制设置返回类型;
(1)resultType:
这是使用场景最多的类型,优点是非常方便,直接从全包名定义到类名即可
(2)resultMap:
返回字典映射,有具体的使用场景:
- 字段名和程序中的实体类的属性名不同的情况,可以配置 resultMap 映射;
- 一对一和一对多关系可以使用 resultMap 映射;
演示第一种:字段名和属性名不同
数据库中的密码是 password,而 UserMapper 实体类中的属性名确实 pwd
查询结果就会报错:检测不到 pwd
借助 resultMap 设置映射关系:
<mapper namespace="com.example.demo.mapper.UserMapper">
<resultMap id="baseMap" type="com.example.demo.entity.User">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="password" property="pwd"></result>
</resultMap>
<select id="getByPwd" resultMap="com.example.demo.mapper.UserMapper.baseMap">
select * from userinfo where password=#{pwd}
</select>
</mapper>
此处运行结果就可以查出来:
(大细节)不过如果在使用 @Param() 传参时就把属性名重命名成和字段名一样的话,也就不必大动干戈设置 resultMap 映射了;
resultMap 设置映射的原理:
实际上,这个过程是以查询出结果为分界的,在接口拿到参数时,将该参数传递给 xml 里 {} 内部去匹配,接着将 SQL 和它拼接好传给数据库去实现,当数据库查询结束后要返回值给 MyBatis时,此时 resultMap 才会进行字段和属性的匹配;
第二种和第三种:一对一的表映射和一对多的表映射(了解即可,后续文章专门讲解,为了不增加不必要的篇幅)
1. 查询操作:
1.1 简单查询:
下面演示一个简单的查询功能:
(1)在 Mapper 接口中实现相关方法:
@Mapper // 一定要加 Mapper 注解!!!!
public interface UserMapper {
// 查询所有用户
List<User> getAllUser();
}
(2)在 UserMapper.xml 文件里实现具体的SQL:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybati
s.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<select id="getAllUser" resultType="com.example.demo.entity.User">
select * from userinfo
</select>
</mapper>
(3)实现 UserService 类 和 UserController 类:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public List<User> getAllUser() {
return userMapper.getAllUser();
}
}
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/getAllUser")
private List<User> getAllUSer() {
return userService.getAllUser();
}
}
启动后查看查询结果:
鉴于 SQL 的执行要介绍很多种,如果全都通过 url 的方式来访问查看就很麻烦,所以采取一劳永逸的方法:设置打印 SQL 记录,并执行单元测试;
打印 mybatis 执行的 SQL:
在 application.properties 文件中继续配置,加上下面这两句:
#打印 MyBaits 执行的 SQL
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
logging.level.com.example/demo=debug
单元测试:
在 Mapper 接口中,右键 - Generate - Test... - 勾选测试方法 - 生成测试类及测试方法 - 给测试类添加注解 @SpringBootTest - 注入 UserMapper 对象 - 实现具体的测试方法
到这里做好一切配置和简化操作,下面继续介绍增、删、改、查等操作;
1.2 条件查询:
凡是条件查询,就会涉及到条件匹配,站在用户角度就是要给方法传参,SQL去匹配参数;
动态参数参数匹配有两种方法:
- #{paramName} —— 占位符模式;
- ${paramName} —— 直接替换;
下面使用这两种匹配分别演示实例:
#{} 形式:
@Mapper // 一定要加 Mapper 注解!!!!
public interface UserMapper {
// 按照姓名查询
User getUserByName(@Param("username") String username);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybati
s.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<select id="getUserByName" resultType="com.example.demo.entity.User">
select * from userinfo where username=#{username}
</select>
</mapper>
@SpringBootTest //声明测试类是运行在 SpringBoot 环境中的
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void getUserByName() {
String username = "zhangSan";
User user = userMapper.getUserByName(username);
System.out.println(user);
}
}
${} 形式:
其他不用变,只修改 UserMapper.xml 里的 SQL 语句部分:
<select id="getUserByName" resultType="com.example.demo.entity.User">
select * from userinfo where username='${username}'
</select>
正如结果所示,因为 ${} 是直接替换的逻辑,也就是说我们在写字符串时不能忘记自己加单引号,而 #{} 是占位符的逻辑,它会自动为我们传递的参数做适应性调整;
1.3 like 模糊查询:
特殊情况:如果进行 like 模糊查询,又该用这两种的哪一种呢?
如果使用 #{} :会报错,因为它会自动给匹配到的参数加一对单引号
如果使用 ${} : 虽然可以查出来,但其实不能直接使用,为了防止 SQL 注入的危险!!!
SQL 注入:
下面这个示例,原本查询时要匹配用户名和密码都正确的,但是这个时候的password的输入很特殊,它实现了 SQL 注入,就像拿了一把本不属于这扇门的钥匙,但因为尺寸相似就打开了门,看到了里面的所有信息。
为什么查出来了?and 后面还有一个 or ,只要后面 or 的条件满足了,就全查出来了;
<<< 正确的模糊查询应该借助内置的 concat 拼接函数进行字符拼接: >>>
<select id="getUserByLike" resultType="com.example.demo.entity.User">
select * from userinfo where username like concat('%',#{username},'%')
</select>
1.4 多表查询:
为了演示多表查询,就需要有其他表的实体类,这里选择当前库中的 articleinfo 表对应的 Article 类做其实体类;
先增加 Article 实体类:
@Data
public class Article {
private int id;
private String title;
private String content;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private int uid;
private int rcount;
private int state;
}
考虑到文章表内没有作者信息,所以我们再添加一个 ArticleVO 实体类作为扩展类,继承 Article 类,也就是当查到想要的文章详情时,不仅显示文章表内的列字段,再把对应的作者也显示出来;
public class ArticleVO extends Article {
private String username; // 表示查询出来的文章是哪个作者写的
}
上面这样做也是在解耦合,试想在企业中如果某个业务进行多表查询时要互相增添的字段非常非常多,总不能去每个实体类的内部增添一遍所有需要的属性,因此使用扩展类去继承可以降低冗余度和耦合性,提高开发效率;
下面开始多表查询:
查询 mycnblog 库中的 articleinfo 表 和 userinfo 表,目标查出 文章详情描述以及其对应作者,文章详情在 articleinfo 表中,作者信息在 userinfo 表中;
首先清楚,多表查询,主表应该是 articleinfo 表,userinfo 表的 username 字段应该是外连接部分,因此这次的查询方法以及 SQL 都要在 articleinfo 表的基础上完成;
(1)新增 ArticleMapper 接口,添加多表查询方法:
@Mapper // 一定不要忘记注解
public interface ArticleMapper {
// 多表查询
ArticleVO getFromTwoTables(@Param("id") int id);
}
(2)实现 ArticleMapper.xml 中的 SQL:
<mapper namespace="com.example.demo.mapper.ArticleMapper">
<select id="getFromTwoTables" resultType="com.example.demo.entity.vo.ArticleVO">
select a.*, u.username from articleinfo a
join userinfo u on a.uid=u.id
where a.id=#{id}
</select>
</mapper>
(3)添加测试方法:
@SpringBootTest
class ArticleMapperTest {
@Autowired
private ArticleMapper articleMapper;
@Test
void getFromTwoTables() {
int id = 1;
ArticleVO articleVO = articleMapper.getFromTwoTables(id);
System.out.println("文章详情:" + articleVO);
}
}
执行结果:
如果上面的查询一系列操作都掌握了,那么对下面的增、删、改就可以信手拈来了~
2. 插入操作:
从这里开始增删改操作可能会影响数据表的内容,如果单纯测试不修改,就可以在单元测试方法上面加 @Transactional 注解,意为添加事务操作,操作完表的数据后进行回滚。
新增、删除、修改这些操作的 xml 中可以不加返回类型;
@Mapper // 一定要加 Mapper 注解!!!!
public interface UserMapper {
// 新增用户
int addUser(User user);
}
<insert id="addUser">
insert into userinfo (username,password) values(#{username}, #{password})
</insert>
@SpringBootTest //声明测试类是运行在 SpringBoot 环境中的
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void addUser() {
String username = "zhaoLiu";
String password = "666666";
User user = new User();
user.setUsername(username);
user.setPassword(password);
int ret = userMapper.addUser(user);
System.out.println("新增了 " + ret + " 行");
}
}
3. 删除操作:
@Mapper // 一定要加 Mapper 注解!!!!
public interface UserMapper {
// 删除用户
int deleteUserByName(@Param("username") String username);
}
<delete id="deleteUserByName">
delete from userinfo where username=#{username}
</delete>
@SpringBootTest //声明测试类是运行在 SpringBoot 环境中的
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void deleteUserByName() {
String username = "zhaoLiu";
int ret = userMapper.deleteUserByName(username);
System.out.println("删除了 " + ret + " 行");
}
}
4. 修改操作:
@Mapper // 一定要加 Mapper 注解!!!!
public interface UserMapper {
// 修改操作
int update(@Param("username")String username, @Param("password")String password);
}
<update id="update">
update userinfo set username=#{username}
where password=#{password}
</update>
@SpringBootTest //声明测试类是运行在 SpringBoot 环境中的
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void update() {
String username = "张三";
String password = "111111";
int ret = userMapper.update(username,password);
System.out.println("修改了 " + ret + " 行");
}
}