likes
comments
collection
share

11. MyBatis(一)

作者站长头像
站长
· 阅读数 4

1. MyBatis是什么?

MyBatis官方简介:MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录

简单来说 MyBatis 是基于JDBC的框架,是数据库交互的工具,可以更简单使用XML或注解来实现数据库的CRUD操作。

2. 为什么选择MyBatis

对于后端开发来说,程序是由两个重要的部分组成的:后端程序和数据库

而这两个部分的通许,就是要依靠数据库连接工具,在学习Java Web的时候一定都使用过JDBC吧,既然都已经有这玩意了,为啥还要学习MyBatis呢?

这是因为JDBC中有太多繁琐且冗余的操作了,回顾一下JDBC的操作流程:

  1. 通过DataSource配置数据源
  2. 通过DataSource获取数据库连接Connection
  3. 编写带?的预编译SQL语句
  4. 通过Connection及SQL创建操作命令对象Statement
  5. 替换占位符:使用Statement的setxxx()方法替换预编译SQL的占位符
  6. 使用Statement的executeQuery()方法执行SQL语句,
  7. 查询操作:返回的结果集ResultSet 更新操作:返回修改的条数
  8. 如果是查询操作的话还需要处理结果集:将ResultSet映射到POJO
  9. 释放资源,如:Connection,Statement,ResultSet这些对象

这些功能性的操作十分冗余,每执行一个新的SQL语句,都需要经历上述步骤,才能从数据库中拿到数据,这就是我们要学习MyBatis的原因。

3. MyBatis快速入门

3.1 了解MyBatis

3.1.1 ORM 框架

MyBatis也是一个ORM(Object Relational Mapping)框架,即为对象关系映射,在面向对象编程语言中,将关系型数据库中的数据与对象建立起映射关系,进而自动的完成对象和数据表之间的转换。

数据库与对象的映射关系为:

  • 数据库表(table)--> 类(class)
  • 记录(record)--> 对象(object)
  • 字段(field) --> 对象的属性(attribute)

3.1.2 MyBatis 的组成

MyBatis组成:

  1. 接口(当前类的所有需要操作数据库的方法声明)
  2. xxx.xml(它和接口一一对应,是接口的实现,SQL语句都放在这里面)

Controller、Service、Mapper的关系图:

11. MyBatis(一)

在学习MyBatis之前需要先建库建表,使用下面的SQL脚本创建数据库和表用于与后端交互:

-- 创建数据库
drop database if exists mybatis_test;
create database mybatis_test DEFAULT CHARACTER SET utf8mb4;

-- 使用数据库
use mybatis_test;

-- 创建用户表
drop table if exists userinfo;
create table userinfo(
    id int primary key auto_increment,
    username varchar(50) not null,
    password varchar(32) not null,
    photo varchar(500) default '',
    createtime datetime default now(),
    updatetime datetime default now(),
    `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 datetime default now(),
    updatetime datetime default now(),
    uid int not null,
    rcount int not null default 1,
    `state` int default 1
) default charset 'utf8mb4';

-- 插入一条用户数据
insert into userinfo (username, password) values ('zhangsan', '123');

-- 插入两条文章数据
insert into articleinfo (title, content, uid) values ('Spring Boot入门', 'Hello Spring Boot!', 1);
insert into articleinfo (title, content, uid) values ('MyBatis入门', 'Hello MyBatis!', 1);

3.2 配置 MyBatis 开发环境

在starter中引入MySQL Driver和MyBatis Framework的依赖

11. MyBatis(一)

创建项目后打开pom.xml文件就可以看到引入了下面两个依赖:

<!-- MySQL Driver -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>
<!-- MyBatis Framework -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>

mysql-connector-j是MySQL厂商根据JDBC标准提供的后端连接MySQL数据库的实现,我们称之为MySQL驱动,是后端与MySQL数据库的桥梁,这个依赖想必大家在使用JDBC的时候就已经引入过。

MyBatis Framework就是MyBatis框架的依赖啦,它是基于JDBC实现的持久层框架,相当于给JDBC分装了一个外壳,供开发者更方便地操作数据库。

3.3 配置数据源和MyBatis

3.3.1 配置数据源

当你在Spring Boot项目中引入了MyBatis依赖后,就必须进行下面的数据源配置,这也算是一个Spring Boot的约定。

数据源的配置包括以下信息:

  • url
  • username
  • password
  • driverClassName

因此我们需要在application.yml中进行以下配置:

# 数据源配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mybatis_test?characterEncoding=utf8&useSSL=false
    username: root
    password: 【你的数据库密码】
    driver-class-name: com.mysql.cj.jdbc.Driver

在原生的JDBC中是通过代码来实现数据源的配置和连接池的建立,因为MysqlDataSource类已经内置了MySQL的驱动器(Driver),因此还需要配置URL、User、Password,下面就是一个JDBC获取DateSource的代码实例:

public class DBUtil {
    private static volatile DataSource dataSource = null;

    private static DataSource getDataSource() {
        //懒汉模式线程安全问题
        if (dataSource == null) {
            synchronized (DBUtil.class) {
                if (dataSource == null) {
                    dataSource = new MysqlDataSource();
                    ((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/blog_system?characterEncoding=utf8&useSSL=false");
                    ((MysqlDataSource)dataSource).setUser("root");
                    ((MysqlDataSource)dataSource).setPassword("sj135433");
                }
            }
        }
        return dataSource;
    }
}

前面提到过,JDBC必须通过DataSource获取连接ConnectionMyBatis虽消除了JDBC中与业务无关的功能代码,但在Spring Boot项目启动的时候,MyBatis就会根据配置文件中的数据源信息建立连接池,以便后续使用SQL操作数据库,如果在配置文件中没有数据源的相关信息,就无法启动项目,并且在日志中会看到下面信息:

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class

注意事项:com.mysql.jdbc.Driver是MySQL 5.1及之前版本的驱动,而对于MySQL 5.1之后的版本,应该使用com.mysql.cj.jdbc.Driver

3.3.2 配置MyBatis映射目录

在MyBatis中SQL的编写与映射是在xml文件中实现的,因此需要去配置MyBatis的mapper的存放路径:

# 下面这些内容是为了让MyBatis映射
mybatis:
  mapper-locations: classpath:mapper/**Mapper.xml

在resources目录下创建一个mapper子目录,后续有关MyBatis的xml配置文件都命名为**Mapper.xml

11. MyBatis(一)

3.4 第一个MyBatis程序,实现用户信息的查询功能

目前用户表和文章表中有如下数据,后续的操作都是针对这两个表的:

11. MyBatis(一)

我们的第一个MyBatis程序就是:使用MyBatis框架把所有用户表中的数据查询出来。

3.4.1 编写实体类

首先我们编写一个普通的实体类,用于MyBatis做userinfo表的映射。

注意事项:ORM框架默认情况下我们需保证类属性名称和userinfo表的字段名一致,之后会讲如何实现不同名字的映射。

@Data
public class UserInfo {
    private int id;
    private String username;
    private String password;
    private String photo;
    private Timestamp createtime;
    private Timestamp updatetime;
    private int state;
}

3.4.2 编写接口 —— @Mapper

必须使用org.apache.ibatis.annotations包下的@Mapper注解

@Mapper
public interface UserMapper {
    public List<UserInfo> getAll();
}

3.4.3 在xml中编写SQL

1. 创建UserMapper.xml

对于xml文件的命名只要满足配置文件中约定的以Mapper.xml为结尾就行,其他没有要求,但是为了更好的展现映射关系,我将xml的命名与接口一致。

11. MyBatis(一)

2. 与UserMapper接口建立关系

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的值,以声明该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.chenshu.mybatis_demo.mapper.UserMapper">

</mapper>

3. 在mapper标签中实现接口的getAll()方法

所谓实现接口getAll()方法,其实就是编写它对应的SQL,这些SQL都放在mapper标签中

由于我们要实现查询功能,就需要使用select SQL,因此我们使用select标签, 该select标签需要设置两个属性,id对应实现的方法名,resultType对应用于映射的实现类,需使用全限定类名:

<select id="getAll" resultType="com.chenshu.mybatis_demo.model.UserInfo">

</select>

然后在select标签内写SQL语句(推荐自己在终端中运行一下确定自己编写的SQL没错,再复制进去),此时我们的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.chenshu.mybatis_demo.mapper.UserMapper">
    <select id="getAll" resultType="com.chenshu.mybatis_demo.model.UserInfo">
        select * from userinfo
    </select>
</mapper>

3.4.4 getAll()的单元测试

单元测试(unit testing)是指对软件中的最小可测试单元进行检查和验证的过程就叫做单元测试。

Spring Boot项目创建时会默认引用单元测试框架spring-boot-test,这个单元测试框架是依靠另一个著名的测试框架JUnit实现的,以下信息是Spring Boot项目创建时在pom.xml中自动添加的:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

通过单元测试我们可以以最小的成本进行测试对getAll()方法进行测试:

1. 右键点击generate,然后点击Test

11. MyBatis(一)

2. 勾选要测试的方法,其他的按默认就好

11. MyBatis(一)

此时就生成了测试类:

11. MyBatis(一)

3. 标识当前的测试环境为Spring Boot,并且注入要测试的类

使用@SpringBootTest注解标识测试类,并且依赖注入UserMapper

@SpringBootTest
class UserMapperTest {
    @Autowired
    private UserMapper userMapper;
    @Test
    void getAll() {
        
    }
}

4. 在方法中编写代码进行测试

@Test
void getAll() {
    List<UserInfo> list = userMapper.getAll();
    for (UserInfo user : list) {
        System.out.println(user);
    }
}

5. 运行方法并查看结果

11. MyBatis(一)

3.5 MyBatis 实现分析

就上面的查询用户信息为例,我们来简单分析下MyBatis做了什么:

  1. 项目运行的时候扫描到@Mapper修饰的接口;
  2. 扫描配置文件中所配置的xml文件存放的目录;
  3. 查看符合命名的xml文件中的namespace,如果与第1步中的接口的全限定类名相匹配,就根据该xml文件实现接口中的方法,使其变为实现类;
  4. 需要时,MyBatis会调用实现类中的getAll()方法。

4. 其他数据库操作

3.3第一个MyBatis程序中我们实现了不带参数的查询用户操作,接下来我们来实现一下其他的常用的数据库操作。

4.1 带参查询

接下来我们来编写根据id查询User信息的操作

1. 在接口中新增方法

@Mapper
public interface UserMapper {
    public List<UserInfo> getAll();
    
    public UserInfo getUserById(Integer id);
}

2. xml中新增SQL

<mapper namespace="com.chenshu.mybatis_demo.mapper.UserMapper">
    <select id="getAll" resultType="com.chenshu.mybatis_demo.model.UserInfo">
        select * from userinfo
    </select>
    <select id="getUserById" resultType="com.chenshu.mybatis_demo.model.UserInfo">
        select * from userinfo where id = 【这里不知道填啥】
    </select>
</mapper>

在我们编写SQL时我们开始犯难了,在where语句中的id值要怎么获取呢?

一个参数的情况下,这里只需要使用#{}将接口中传入的参数名包裹起来就好了:

<select id="getUserById" resultType="com.chenshu.mybatis_demo.model.UserInfo">
    select * from userinfo where id = #{id}
</select>

3.测试结果

@Test
void getUserById() {
    UserInfo user = userMapper.getUserById(1);
    System.out.println(user);
}

成功查询到id为1的记录:

UserInfo(id=1, username=zhangsan, password=123, photo=, createtime=2024-04-14 22:37:42.0, updatetime=2024-04-14 22:37:42.0, state=1)

4.2 @Param参数重命名

前面的写法虽然能运行成功,但是即使参数只有一个也不推荐这么干,因为参数超过一个,就会报找不到参数的错误,但是使用了@Param注解就能避免这些错误。

在接口方法的参数中添加@Param注解可对需要在SQL中传递的参数进行重命名

public UserInfo getUserById(@Param("uid") Integer id);

上面的代码中我们将参数"id"重命名为"uid",这样操作后,我们的SQL中传递的参数也得改为"uid",否则会报无法找到参数"id"的错误:

<select id="getUserById" resultType="com.chenshu.mybatis_demo.model.UserInfo">
    select * from userinfo where id = #{uid}
</select>

4.3 带参新增

接下来我们来完成文章的新增操作

1. 编写实体类

@Data
public class ArticleInfo {
    private int id;
    private String title;
    private String content;
    private Timestamp createtime;
    private Timestamp updatetime;
    private int uid;
    private int rcount;
    private int state;
}

2. 编写接口

@Mapper
public interface ArticleMapper {
    public int add(ArticleInfo articleInfo);
}

3. 在xml中写SQL

与前面查询用户表不同的点有:

  • 映射的接口不同:先前映射UserMapper,这次映射ArticleMapper
  • SQL类型不同:先前为select操作,这次为insert操作

因此要记得修改mapper标签中namespace的值以及使用insert标签的使用,并且由于插入操作并不需要将表中的一条记录与实体类映射起来,因此insert标签中不需要resultType属性:

<?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.chenshu.mybatis_demo.mapper.ArticleMapper">
    <insert id="add">
        
    </insert>
</mapper>

在这里我们开始犯难,前面的查询操作只传了一个参数,而我们这次传的是一个对象,这里应该怎么写呢?

前面提到过MyBatis的映射关系:字段 <==> 属性,在不带@Param参数的情况下,只需要传入实体类的属性名就可以了:

<insert id="add">
    insert into articleinfo(title, content, uid)
    values (#{title}, #{content}, #{uid})
</insert>

4. 单元测试

@SpringBootTest
class ArticleMapperTest {
    @Autowired
    private ArticleMapper articleMapper;

    @Test
    void add() {
        ArticleInfo articleInfo = new ArticleInfo();
        articleInfo.setTitle("MyBatis的插入操作");
        articleInfo.setContent("Insert into xxx.");
        articleInfo.setUid(1);
        int ret = articleMapper.add(articleInfo);
        System.out.println("添加结果:" + ret);
    }
}

成功添加数据:

11. MyBatis(一)

11. MyBatis(一)

用对象传参我们同样可以使用@Param进行重命名:

@Mapper
public interface ArticleMapper {
    public int add(@Param("article") ArticleInfo articleInfo);
}

此时的SQL语句中传递的参数就得改成"article.xxx"的形式,否则也会报无法找到参数的错误:

<insert id="add">
    insert into articleinfo(title, content, uid)
    values (#{article.title}, #{article.content}, #{article.uid})
</insert>

4.4 特殊的添加:返回自增id

有的时候,在业务中我们新增一条数据希望他返回的是新增数据的id,而非修改记录数

接口方法如下:

public int addAndGetId(ArticleInfo articleInfo);

mapper.xml实现如下,并设置useGeneratedKeyskeyProperty的值,作用是:将传入的对象的属性id的值设置为数据库里字段为id的值;如果实体类中的属性与数据库的字段不同的话,还需要加一个keyColumn属性来指定数据库表中的列名

<insert id="add" useGeneratedKeys="true" keyProperty="id">
    insert into articleinfo(title, content, uid)
    values (#{article.title}, #{article.content}, #{article.uid})
</insert>

单元测试:

@Test
void addAndGetId() {
    ArticleInfo articleInfo = new ArticleInfo();
    articleInfo.setTitle("MyBatis添加并返回自增id");
    articleInfo.setContent("设置xml中的useGenerateKeys以及keyProperty.");
    articleInfo.setUid(1);
    int ret = articleMapper.addAndGetId(articleInfo);
    System.out.println("添加结果:" + ret);
    System.out.println("自增id:"+articleInfo.getId());
}

输出结果: 11. MyBatis(一)

4.5 删除操作

这里的删除操作同样是针对文章表的

1. 编写接口

public int delById(Integer id);

2. 在xml中写SQL

<delete id="delById">
    delete from articleinfo where id = #{id};
</delete>

3. 单元测试

@Test
void delById() {
    int ret = articleMapper.delById(5);
    System.out.println("删除结果:" + ret);
}

运行结果:

11. MyBatis(一)

4.6 带参修改

这里我们实现修改文章标题的功能

1. 编写接口

这里有两个参数,因此一定要使用@Param注解

public int updateTitle(@Param("title") String title, @Param("id") Integer id);

2. 在xml中编写SQL

<update id="updateTitle">
    update articleinfo set title = #{title} where id = #{id}
</update>

3. 单元测试

@Test
void updateTitle() {
    int ret = articleMapper.updateTitle("修改标题", 4);
    System.out.println("修改结果:" + ret);
}

运行结果:

11. MyBatis(一)

11. MyBatis(一)

5. 层级调用 MyBatis

学习完了上面的知识,可以应对项目里绝大多数场景了,现在我们就来回顾回顾3.1.2中的关系图,并在代码中实现查询用户信息的层级调用:

1. 建立下面两个包

11. MyBatis(一)

2. 在service包下编写UserService类的业务代码

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public List<UserInfo> getAll() {
        return userMapper.getAll();
    }
}

3. 在controller包下编写UserController的业务代码

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    UserService userService;

    @RequestMapping("/getall")
    public List<UserInfo> getUsers() {
        return userService.getAll();
    }
}

此时运行项目,并且在浏览器中输入相应路由:http://localhost:8080/user/getall

后端向我们返回了一个json列表: 11. MyBatis(一)

此时我们就完成了一个:用户<=>Controller<=>Service<=>MyBatis<=>数据库的层级调用