当前位置 : 主页 > 编程语言 > 其它开发 >

Spring Boot 整合 MyBatis

来源:互联网 收集:自由互联 发布时间:2022-05-20
相信大家对 MyBatis 都有所了解,许多项目都使用 MyBatis 来操作数据库。最近在翻看笔记的时候,发现去年写了一篇关于 Spring Boot 项目整合 MyBatis 的文章,今天将这篇文章分享出来,既为
相信大家对 MyBatis 都有所了解,许多项目都使用 MyBatis 来操作数据库。最近在翻看笔记的时候,发现去年写了一篇关于 Spring Boot 项目整合 MyBatis 的文章,今天将这篇文章分享出来,既为刚入门 MyBatis 的同学提供一个参考,也为自己记录一下学习过程。 MyBatis 简介

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Ordinary Java Objects,简单 Java 对象)为数据库中的记录。

参考自 MyBatis 的官方简介。

MyBatis 作为一款优秀的持久层框架,具有如下优点:

  1. 小巧并且简单易学。

  2. 相比于 JDBC 减少了大量冗余的代码。

  3. 将 SQL 语句与程序代码进行分离,降低了耦合,便于管理。

  4. 提供 XML 标签,支持编写动态 SQL 语句。

  5. 提供映射标签,支持 Java 对象的属性与数据表字段的映射关系。

MyBatis 实践

下面我们创建一个 Spring Boot 项目,整合 MyBatis,实现简单的 CRUD 功能。

1. 引入依赖

POM 文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springboot-mybatis</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-mybatis</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

2. 配置 MySQL 和 MyBatis

配置文件 application.yml 的内容如下:

# 配置 MySQL
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

# 配置 MyBatis
mybatis:
  mapper-locations: classpath:mapper/*
  type-aliases-package: com.example.entity
  configuration:
    map-underscore-to-camel-case: true

MyBatis 的配置项中:

  • mapper-locations:用来指定 mapper.xml 文件的路径,该文件用于编写 SQL 语句。

  • type-aliases-package:用来设置别名,它的作用是告诉 MyBatis 需要设置别名的实体类的所在的包。默认情况下,MyBatis 会使用实体类的非限定类名来作为它的别名,如将 com.example.entity.User 的别名设置为 Useruser(别名不区分大小写)。当然,MyBatis 也支持自定义别名,这个我们在后文中再聊。

  • map-underscore-to-camel-case:用来开启驼峰命名自动映射,如将数据表中的字段 user_name 映射到实体对象的属性 userName。

3. 实体类

编写简单的 User 类:

package com.example.entity;

import lombok.Data;

import java.util.Date;

/**
 * @Author john
 * @Date 2021/11/14
 */
@Data
public class User {

    private long id;

    private String userName;

    private int age;

    private String address;

    private Date createTime;

    private Date updateTime;
}

User 类中封装了用户的 id、姓名、年龄、地址、创建时间以及修改时间等信息。

4. 创建 user 表

user 表的字段设计如下:

5. 编写 Mapper 接口和 mapper 文件

首先编写 UserMapper 接口:

package com.example.mapper;

import com.example.entity.User;

/**
 * @Author john
 * @Date 2021/11/16
 */
public interface UserMapper {
    
    void insertUser(User user);
    
    User findUserById(long id);
}

接口中定义了两个方法,insertUser 用来向数据表中插入一条记录,findUserById 用来通过 id 查询 User。

上述操作完成后,我们在 resources 文件夹中创建 mapper/user-mapper.xml 文件(文件路径在配置文件 application.yml 中设置)。user-mapper.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.example.mapper.UserMapper">

    <sql id="insertFields">
        user_name, age, address, gmt_create, gmt_modified
    </sql>

    <sql id="selectFields">
        id, user_name, age, address, gmt_create, gmt_modified
    </sql>

    <resultMap id="UserMap" type="User">
        <result column="id" jdbcType="INTEGER" property="id"/>
        <result column="user_name" jdbcType="VARCHAR" property="userName"/>
        <result column="age" jdbcType="INTEGER" property="age"/>
        <result column="address" jdbcType="VARCHAR" property="address"/>
        <result column="gmt_create" jdbcType="DATE" property="createTime" />
        <result column="gmt_modified" jdbcType="DATE" property="updateTime" />
    </resultMap>

    <select id="findUserById" parameterType="Long" resultMap="UserMap">
        select
        <include refid="selectFields"/>
        from user
        where id = #{id}
    </select>

    <insert id="insertUser" parameterType="User" keyProperty="id">
        insert into user (<include refid="insertFields"/>)
        values(#{userName}, #{age}, #{address}, UTC_TIMESTAMP(), UTC_TIMESTAMP())
    </insert>

</mapper>

可以看到,Mapper 接口中定义的是 CRUD 相关的方法,mapper.xml 文件中定义的是具体的 SQL 语句。MyBatis 允许我们将 Mapper 接口与 mapper.xml 文件关联在一起,这样当调用 Mapper 接口中的方法时,实际的处理逻辑为执行 mapper.xml 文件中对应的 SQL 语句。关联 Mapper 接口和 mapper.xml 文件时需要保证:

  • Mapper 接口的全限定名对应 mapper.xml 文件的 namespace 值。

  • Mapper 接口的方法名对应 statement(每一个 SQL 就是一个 statement)的 id 值。

  • Mapper 接口中方法接收的参数对应 statement 的入参。

  • Mapper 接口中方法的返回值对应 statement 的出参。

下面介绍一下 mapper.xml 文件中几个重要标签的含义:

  • <sql> 标签:用于定义复用的 SQL 片段,如果多个 SQL 需要操作相同的字段集,那么就可以使用 <sql> 标签将这些字段提取出来,然后在 SQL 语句中直接引用即可。引用的语法为 <include refid=" "/>,其中 refid 的值就是 <sql> 的 id 值。

  • <resultMap> 标签:用于创建数据表字段与实体属性的映射关系,在查询操作中,MyBatis 会根据查询到的字段名找到 POJO 对应的属性名,然后调用该属性的 setter 方法进行赋值。如果数据表的字段名与实体类的属性名完全相同,或者符合驼峰式命名映射的规则,那么 MyBatis 可以直接完成赋值操作。否则的话,就需要我们使用 <resultMap> 标签创建自定义的映射规则,告诉 MyBatis 字段和属性之间应该如何映射。本实验中,user 表的 id 会自动映射为 User 对象的 id,user 表的 user_name 也会自动映射为 User 对象的 userName。但是 gmt_create 和 gmt_modified 不会映射为 createTime 和 updateTime,因为字段名和属性名既不完全一致,也不符合驼峰式命名映射的规则,所以这里我们需要使用 <resultMap> 来创建新的映射关系,其中属性 id 用于指明该 resultMap 的标志,属性 type 用于指明映射的实体类。

  • <select> 标签:用于执行查询操作。

  • <insert> 标签:用于执行插入操作。

实际上,MyBatis 赋值时不一定会调用实体类属性的 setter 方法,因为我们在编码时可能并没有添加该方法。以 User 类的属性 id 为例,如果我们添加了 setId 方法,那么 MyBatis 会通过反射获取到 setId 对应的 MethodInvoker,然后调用 setId 方法为 id 赋值;如果未设置 setId 方法,那么 MyBatis 会获取属性 id 对应的 SetFieldInvoker,然后为属性赋值。详见 MetaObject 类的 setValue 方法。

接下来介绍 SQL 语句中几个重要属性的含义:

  • parameterType:用于指定 SQL 语句的入参类型(可以是基本数据类型或者 JavaBean),该类型需要与对应的接口方法的入参类型一致。如果我们设置了别名,那么也可以使用别名作为参数,例如使用 Useruser 代替 com.example.entity.User

  • resultMap:用于指定 SQL 语句的出参类型,以 insertUser 方法为例,在 Mapper 接口中,该方法的返回值为 User 类型,所以对应的 SQL 语句的返回值也应为 User 类型,由于 User 对象需要使用 <resultMap> 进行属性映射,所以我们将自定义的 UserMap 来作为 SQL 语句的返回值类型。

  • keyProperty:用于指定主键在 POJO 中对应的属性名,需要配合数据库的自增主键来使用。以 user 表为例,我们在建表的时候将表的主键 id 设置为了数据库自增 id,因此在将 User 对象持久化到数据库之前不需要为属性 id 设置初始值,MySQL 会自动帮我们赋值,keyProperty 的作用就是告诉 MyBatis 哪个属性是主键。

除了 resultMap 外,resultType 属性也可用于指定出参类型。如果我们将 user 表中的字段 gmt_create 和 gmt_modified 分别改为 create_time 和 update_time,那么就不需要使用 <resultMap> 标签来配置映射规则,因为 user 表的所有字段都可以和 User 对象的属性一一对应,这样在 SQL 语句中,就可以将 resultMap="UserMap" 替换为 resulType="User"resulType="user"。另外,在本实验中,resultMap 标签也可以定义为:

<resultMap id="UserMap" type="User">
    <result column="gmt_create" jdbcType="DATE" property="createTime" />
    <result column="gmt_modified" jdbcType="DATE" property="updateTime" />
</resultMap>

因为其他字段会自动映射,不需要额外书写。

6. 编写 Service

创建 UserService:

package com.example.service;

import com.example.entity.User;
import com.example.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Author john
 * @Date 2021/11/16
 */
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public void insertUser(User user) {
        userMapper.insertUser(user);
    }

    public User findUserById(long id) {
        return userMapper.findUserById(id);
    }
}

在 UserService 中注入 UserMapper 对象,并调用相关方法来添加/查询 User。

为了能够正常注入 UserMapper 对象,我们还需要再启动类上添加 @MapperScan 注解,并指定 Mapper 接口所在的包:

package com.example;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.example.mapper")
public class SpringbootMybatisApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootMybatisApplication.class, args);
    }

}

com.example.mapper 包下的所有 Mapper 接口都会被 Spring 扫描。

除了在启动类上添加 @MapperScan 注解外,还可以在 Mapper 接口上直接添加 @Mapper 注解,这种方法相对比较麻烦,因为实际中我们可能会有多个 Mapper 接口,这样就需要添加多个注解。

7. 测试

编写测试接口:

package com.example;

import com.example.entity.User;
import com.example.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringbootMybatisApplicationTests {

    @Autowired
    private UserService service;

    @Test
    public void addUser(){
        User user = new User();
        user.setUserName("John");
        user.setAge(24);
        user.setAddress("BUPT");
        service.insertUser(user);
    }

    @Test
    public void findUser(){
        System.out.println(service.findUserById(1));
    }
}

首先执行 addUser() 方法,执行成功后查询数据表,得到如下信息:

然后执行 findUser() 方法,执行结果如下:

至此,SpringBoot 整合 MyBatis 测试成功!

MyBatis 设置别名的方式

方式一:在配置文件 application.yml 中添加配置。

yml 文件的配置内容如下:

mybatis:
  type-aliases-package: com.example.entity

本实验采用此种方式设置别名,默认情况下实体类的别名为其类名,严格来说是首字母小写的非限定类名,由于别名不区分大小写,所以 UseruseruSer 的效果都是相同的。

MyBatis 也支持自定义别名,我们只需要在实体类上添加 @Alias 注解,就可以为其设置别名:

package com.example.entity;

import lombok.Data;
import org.apache.ibatis.type.Alias;

import java.util.Date;

/**
 * @Author john
 * @Date 2021/11/14
 */
@Data
@Alias("hello")
public class User {

    private long id;

    private String userName;

    private int age;

    private String address;

    private Date createTime;

    private Date updateTime;
}

上述代码中,我们将 User 类的别名设置为了 hello。注意,若要使 @Alias 注解生效,必须配置 type-aliases-package 来指定实体类的包路径。另外,@Alias 会使默认的别名变得无效,例如在本实验中,User 类的别名只能是 hello,而不能是 Useruser 等。

方式二:使用 MyBatis 的配置文件 filename.xml。

首先在 yml 文件中设置 MyBatis 配置文件 filename.xml(filename 是配置文件的名称)的路径:

# 配置MyBatis
mybatis:
  mapper-locations: classpath:mapper/*
  config-location: classpath:mybatis/mybatis-config.xml #MyBatis配置文件

然后在 resource 文件夹下创建 MyBatis 的配置文件 mapper/mybatis-config.xml(路径和文件名在 config-location 中设置),配置文件内容如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <typeAliases>
        <package name="com.example.entity"/>
    </typeAliases>

</configuration>

几个重要标签的含义为:

  • <setting> 标签:用于开启驼峰命名映射,其效果与在 yml 文件中配置 map-underscore-to-camel-case: true 是相同的。

  • <typeAliases> 标签:用于配置别名,子标签 <package> 可以让 MyBatis 扫描指定包下的实体类,其效果与在 yml 文件中配置 type-aliases-package: com.example.entity 是相同的。

在方式一中,我们可以使用 @Alias 注解自定义别名,而在方式二中,我们可以通过<typeAliases> 的子标签 <typeAlias> 来设置别名:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <typeAliases>
        <typeAlias type="com.example.entity.User" alias="hello"/>
    </typeAliases>

</configuration>

<typeAlias> 标签不需要配置 type-aliases-package 就可以生效,且该标签与 <package> 标签并不冲突,也就是说如果我们添加了 <package name="com.example.entity"/>,那么 User 类的别名既可以是 hello,也可以是 Useruser 等。当然,方式二中也可以添加 @Alias 注解,但添加了该注解后,User 类的别名只能为 hello 或 @Alias 注解指定的别名。

网友评论