MyBatis

MyBatis

JDBC执行步骤

JDBC(Java Data Base Connectivity),Java 语言访问数据库的标准 API

  1. 注册驱动
  2. 建立连接
  3. 创建SQL
  4. 执行SQL
  5. 封装结果集
  6. 释放资源
1
2
3
4
5
6
7
8
9
10
11
12
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立连接
Connection connection = DriverManager.getConnection(数据库地址, 用户名, 密码);
// 创建SQL
Statement statement = connection.createStatement();
// 执行SQL
ResultSet resultSet = statement.executeQueue(sql);
// 处理结果集
...
// 释放资源
.....close()

mybatis简化 了JDBC 的重复劳动

SpringBoot中使用MyBatis

导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- mybatis依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>

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

<!--mysql依赖-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>

<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

准备数据库信息

1
2
3
4
5
6
7
8
9
10
11
12
CREATE DATABASE test ;
USE test;

CREATE TABLE `mybatis_test` (
`id` INT(32) NOT NULL COMMENT "id" AUTO_INCREMENT,
`name` VARCHAR(32) NOT NULL COMMENT "名称",
`phone` VARCHAR(50) NOT NULL COMMENT "手机",
`email` VARCHAR(32) DEFAULT NULL COMMENT "邮箱",
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

INSERT INTO mybatis_test(id, NAME, phone, email) VALUES(1,'tom', '17778568765', '123456@qq.com');

添加配置信息

resource/application.yml中添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
spring:
# 数据库连接信息
datasource:
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
# mapper文件位置
mapper-locations: classpath*:org/example/maven_demo/mapper/*Mapper.xml
# 实体类的位置,在mapper中就不用写完整路径
type-aliases-package: org.example.maven_demo.entity

# 日志的打印级别, 常用的有info、debug等
logging:
level:
# mapper接口的报名
org.example.maven_demo.mapper: debug

添加三层架构

实体类entity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.example.maven_demo.entity;

import lombok.Data;

// @Data注解使用了lombok来快速实现Java类的getter()/setter()/toString()方法的生成
@Data
public class MybatisTest {
/**
* id
*/
private Integer id;
/**
* 名称
*/
private String name;
/**
* 手机
*/
private String phone;
/**
* 邮箱
*/
private String email;
}

mapper及其映射文件

MybatisTestMapper.xml放在了resource下和MybatisTestMapper.java一样名字的文件夹下,方便管理

1
2
3
4
5
6
7
8
9
10
11
package org.example.maven_demo.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.example.maven_demo.entity.MybatisTest;

@Mapper
public interface MybatisTestMapper {
MybatisTest queryById(int id);

int insert(MybatisTest mybatisTest);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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="org.example.maven_demo.mapper.MybatisTestMapper">
<insert id="insert">
insert into mybatis_test(name, phone, email) values(#{name}, #{phone}, #{email})
</insert>

<select id="queryById" resultType="MybatisTest">
select * from mybatis_test where id = #{id}
</select>

</mapper>

Service层

接口

1
2
3
4
5
6
7
8
9
10
package org.example.maven_demo.service;

import org.example.maven_demo.entity.MybatisTest;

public interface MybatisTestService {
MybatisTest queryById(int id);

int insert(MybatisTest mybatisTest);
}

实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package org.example.maven_demo.service.impl;

import org.example.maven_demo.entity.MybatisTest;
import org.example.maven_demo.mapper.MybatisTestMapper;
import org.example.maven_demo.service.MybatisTestService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class MybatisTestServiceImpl implements MybatisTestService {
@Resource
private MybatisTestMapper mybatisTestMapper;

@Override
public MybatisTest queryById(int id) {
return mybatisTestMapper.queryById(id);
}

@Override
public int insert(MybatisTest mybatisTest) {
return mybatisTestMapper.insert(mybatisTest);
}
}

Controller层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package org.example.maven_demo.controller;

import org.example.maven_demo.entity.MybatisTest;
import org.example.maven_demo.service.MybatisTestService;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class MybatisTestController {
@Resource
private MybatisTestService mybatisTestService;

@RequestMapping("/getMybatisInfo/{id}")
public String getMybatisInfo(@PathVariable int id){
return mybatisTestService.queryById(id).toString();
}

@RequestMapping("/saveMybatisInfo")
public void saveMybatisInfo(@RequestBody MybatisTest mybatisTest){
mybatisTestService.insert(mybatisTest);
}
}

启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example.maven_demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MavenDemoApplication {

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

}

测试

  1. 浏览器访问http://localhost:8080/getMybatisInfo/1

  2. api测试工具访问localhost:8080/saveMybatisInfo,body中带上json

    1
    2
    3
    4
    5
    {
    "name": "shone",
    "phone": "123456789",
    "email": "46465456@qq.com"
    }

相关配置

数据源

1
2
3
4
5
6
7
8
spring:
datasource:
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
transaction:
type: spring # spring 管理事务

mybatis基础配置

1
2
3
4
5
6
7
8
mybatis:
mapper-locations: classpath:mapper/*Mapper.xml # Mapper XML 文件位置
type-aliases-package: com.example.mybatis.entity # Java 类别名扫描路径
configuration:
map-underscore-to-camel-case: true # 数据库下划线自动映射成驼峰
cache-enabled: true # 是否开启二级缓存
lazy-loading-enabled: true # 是否开启懒加载
multiple-result-sets-enabled: true # 支持多结果集
  • 一级缓存(默认):SqlSession 级别,每个 SqlSession 独立缓存,SqlSession 关闭就失效。
  • 二级缓存:Mapper 级别,跨 SqlSession 缓存,多个 SqlSession 都可以共享缓存数据,只对同一个Mapper生效,不适合频繁更新的表。
  • 懒加载:查询的一个对象里面还有嵌套另一个对象时,先不查出来,等到get的时候再查

相关注解

@Mapper

作用:标记一个接口是 MyBatis 的 Mapper,Spring 扫描后会自动生成代理对象。

使用场景:如果不使用 @MapperScan,就需要在每个 Mapper 接口上写 @Mapper

1
2
3
4
@Mapper
public interface UserMapper {
User selectUserById(int id);
}

@MapperScan

作用:扫描指定包下的 Mapper 接口,自动注册为 Spring Bean。

使用场景:推荐放在 Spring Boot 启动类上,避免每个接口都写 @Mapper

1
2
3
4
5
@SpringBootApplication
@MapperScan("com.example.mybatis.mapper")
public class Application {
...
}

@Param

作用:指定方法参数的名字,在 SQL 中引用

1
User getUser(@Param("name") String name, @Param("age") int age);

在实体类上使用的lombok注解

构造、getter/setter等自己写比较好,用lombok注解可能配合其他框架会出问题

@Getter/ @Setter

  • 作用:自动生成 getter 和 setter 方法。

@ToString

  • 作用:自动生成 toString() 方法。

@EqualsAndHashCode

  • 作用:生成 equals()hashCode() 方法。

@Data

  • 作用:相当于 @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor 的组合注解。

@NoArgsConstructor

  • 作用:生成无参构造函数。

@AllArgsConstructor

  • 作用:生成全参构造函数(所有字段)。

XML映射文件的语法

顶层结构

namespace=Mapper接口的全限定类名

1
2
3
4
5
6
7
8
<?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.mybatis.mapper.UserMapper">
<!-- SQL 定义区域 -->
</mapper>

操作标签

标签 说明 示例
<select> 查询语句 <select id="getUserById" resultType="User">SELECT * FROM user WHERE id=#{id}</select>
<insert> 插入语句 <insert id="insertUser" parameterType="User">INSERT INTO user(name, age) VALUES(#{name}, #{age})</insert>
<update> 更新语句 <update id="updateUser" parameterType="User">UPDATE user SET name=#{name} WHERE id=#{id}</update>
<delete> 删除语句 <delete id="deleteUserById" parameterType="long">DELETE FROM user WHERE id=#{id}</delete>

结果映射标签

适合实体类和表的字段名不一致的情况

1
2
3
4
5
6
7
8
9
<resultMap id="userResultMap" type="User">
<id property="id" column="id"/>
<result property="name" column="user_name"/>
<result property="age" column="user_age"/>
</resultMap>

<select id="getUserById" parameterType="long" resultMap="userResultMap">
SELECT id, user_name, user_age FROM user WHERE id=#{id}
</select>

占位符

  • ${}:字符串拼接,直接替换,不安全,一般用于表名、列名
  • #{}:参数绑定,防止 SQL 注入(PreparedStatement)

动态sql标签

标签 作用 示例
<if> 条件判断 <if test="age != null">AND age = #{age}</if>
<choose> 类似 if-else <choose><when test="age != null">AND age = #{age}</when><otherwise>AND age > 0</otherwise></choose>
<trim> 去掉多余前后关键字 `<trim prefix=”WHERE” prefixOverrides=”AND
<where> 自动处理 WHERE 和多余的 AND/OR <where><if test="name != null">AND name = #{name}</if></where>
<set> 自动处理 UPDATE SET 多余逗号 <set><if test="name != null">name=#{name},</if></set>
<foreach> 遍历集合或数组 <foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach>

SQL 语句复用

1
2
3
4
5
<sql id="userColumns">id, name, age</sql>

<select id="getAllUsers" resultType="User">
SELECT <include refid="userColumns"/> FROM user
</select>

MyBatis Plus

代码生成器

官网文档

使用方式

  1. 导入坐标

    注意mybatis-plus-generator的版本必须为3.5.1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!--MybatisPlus、MybatisPlus生成器-->
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.2</version>
    </dependency>
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.1</version>
    </dependency>
    <dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.30</version>
    </dependency>
  2. 编写配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    server:
    port: 8080
    spring:
    datasource:
    url: jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456

    mybatis:
    mapper-locations:
    - classpath:mapper/*.xml
    - classpath*:com/**/mapper/*.xml
  3. 编写代码,执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    package com.xw.mallLearning.generator;

    /**
    * 用于生产MBG的代码
    */
    public class Generator {
    // 基础信息配置
    private static final String URL = "jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "123456";
    private static final String PARENT_PACKAGE_NAME = "com.xw.mallLearning.generator"; // 包名
    private static final String PROJECT_ROOT_PATH = System.getProperty("user.dir");
    private static final String PROJECT_NAME = "mall-learning"; // 当前项目名称
    private static final String MODULE_NAME = "mbg"; // 模块名称, 代码会生成在generator/MODULE_NAME下


    /**
    * 执行此处
    */
    public static void main(String[] args) {
    simpleGenerator();
    }

    protected static void simpleGenerator() {
    // 添加需要自动生成代码的表名, 为空则生成所有表对应代码
    List<String> tables = new ArrayList<>();
    tables.add("pms_brand");

    // 包路径
    String packagePath = PROJECT_ROOT_PATH + "/" + PROJECT_NAME + "/src/main/java";
    // XML文件的路径
    String mapperXmlPath = PROJECT_ROOT_PATH + "/" + PROJECT_NAME + "/src/main/resources/mapper";

    // 开始执行代码生成
    FastAutoGenerator.create(URL, USERNAME, PASSWORD)
    // 1. 全局配置
    .globalConfig(builder -> builder
    // 作者名称
    .author("xw")
    // 开启覆盖已生成的文件。注释掉则关闭覆盖。
    .fileOverride()
    // 禁止打开输出目录。注释掉则生成完毕后,自动打开生成的文件目录。
    .disableOpenDir()
    // 指定输出目录。如果指定,Windows生成至D盘根目录下,Linux or MAC 生成至 /tmp 目录下。
    .outputDir(packagePath)
    // 开启swagger2.注释掉则默认关闭。
    // .enableSwagger()
    // 指定时间策略。
    .dateType(DateType.TIME_PACK)
    // 注释时间策略。
    .commentDate("yyyy-MM-dd")
    )

    // 2. 包配置
    .packageConfig(builder -> builder
    // 设置父表名
    .parent(PARENT_PACKAGE_NAME)
    .moduleName(MODULE_NAME)
    .entity("entity")
    .service("service")
    .serviceImpl("service.impl")
    .controller("controller")
    .mapper("mapper")
    .xml("mapper")
    // mapper.xml 文件的路径。单模块下,其他文件路径默认即可。
    .pathInfo(Collections.singletonMap(OutputFile.mapperXml, mapperXmlPath))
    )

    // 3. 策略配置
    .strategyConfig(builder -> builder.addInclude(tables)
    // 阶段1:Entity实体类策略配置
    .entityBuilder()
    // 开启生成实体时生成字段注解。
    // 会在实体类的属性前,添加[@TableField("nickname")]
    .enableTableFieldAnnotation()
    // 逻辑删除字段名(数据库)。
    //.logicDeleteColumnName("is_delete")
    // 逻辑删除属性名(实体)。
    // 会在实体类的该字段属性前加注解[@TableLogic]
    //.logicDeletePropertyName("isDelete")
    // 会在实体类的该字段上追加注解[@TableField(value = "create_time", fill = FieldFill.INSERT)]
    .addTableFills(new Column("create_time", FieldFill.INSERT))
    // 会在实体类的该字段上追加注解[@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)]
    .addTableFills(new Column("update_time", FieldFill.INSERT_UPDATE))
    // 阶段2:Mapper策略配置
    .mapperBuilder()
    // 开启 @Mapper 注解。
    // 会在mapper接口上添加注解[@Mapper]
    .enableMapperAnnotation()
    // 启用 BaseResultMap 生成。
    // 会在mapper.xml文件生成[通用查询映射结果]配置。
    .enableBaseResultMap()
    // 启用 BaseColumnList。
    // 会在mapper.xml文件生成[通用查询结果列 ]配置
    .enableBaseColumnList()
    // 阶段4:Controller策略配置
    .controllerBuilder()
    // 会在控制类中加[@RestController]注解。
    .enableRestStyle()
    // 开启驼峰转连字符
    .enableHyphenStyle()
    .build()
    )

    // 4. 模板引擎配置,默认 Velocity 可选模板引擎 Beetl 或 Freemarker
    //.templateEngine(new BeetlTemplateEngine())
    .templateEngine(new FreemarkerTemplateEngine())

    // 5. 执行
    .execute();
    }
    }

分页插件

  • 在mp配置类中添加分页插件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Configuration
    public class MpConfig {
    /**
    * 分页插件
    * @return
    */
    @Bean
    public MybatisPlusInterceptor paginationInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    return interceptor;
    }
    }

  • 测试selectPage

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Test
    public void testSelectPage() {
    Page<User> page = new Page<>(1, 3);
    Page<User> userPage = mapper.selectPage(page, null);
    //返回对象得到分页所有数据
    long pages = userPage.getPages(); //总页数
    long current = userPage.getCurrent(); //当前页
    List<User> records = userPage.getRecords(); //查询数据集合
    long total = userPage.getTotal(); //总记录数
    boolean hasNext = userPage.hasNext(); //下一页
    boolean hasPrevious = userPage.hasPrevious(); //上一页

    System.out.println(pages);
    System.out.println(current);
    System.out.println(records);
    System.out.println(total);
    System.out.println(hasNext);
    System.out.println(hasPrevious);
    }

公共字段自动填充

  1. 在需要自动填充的字段上添加@TableField(fill)注解

参数:

fill

可填入的值 说明
FieldFill.INSERT 插入表时填充
FieldFill.UPDATE 更新时填充
FieldFill.INSERT_UPDATE 更新和插入表时填充
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@TableField(fill = FieldFill.INSERT)	//插入表时时填充
private LocalDateTime createTime;


@TableField(fill = FieldFill.INSERT_UPDATE) //更新和插入表时填充
private LocalDateTime updateTime;


@TableField(fill = FieldFill.INSERT) //插入表时时填充
private Long createUser;


@TableField(fill = FieldFill.INSERT_UPDATE) //更新和插入表时填充
private Long updateUser;
  1. 实现MetaObjectHandler接口, 重写insertFill()updateFill()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
public class MyMetaObjectHandle implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
//插入时填充的字段名及填充的值
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("createUser", BaseContext.getCurrentId());
metaObject.setValue("updateUser", BaseContext.getCurrentId());
}

@Override
public void updateFill(MetaObject metaObject) {
//更新时填充的字段名及填充的值
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("updateUser", BaseContext.getCurrentId());
}
}

乐观锁

  • 在mp配置类中添加乐观锁插件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Configuration
    public class MpConfig {

    /**
    * 乐观锁插件
    * @return
    */
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
    return new OptimisticLockerInterceptor();
    }
    }
  • 给类上添加version属性

    1
    2
    3
    @Version
    @TableField(fill = FieldFill.INSERT)
    private Integer version;
  • 插入时自动填充

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
    this.setFieldValByName("createTime",new Date(),metaObject);
    this.setFieldValByName("updateTime",new Date(),metaObject);
    this.setFieldValByName("version",1,metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
    this.setFieldValByName("updateTime",new Date(),metaObject);
    }
    }
  • 每次要修改时先查询出要修改的数据,修改后再放回数据库,version值会自动加一

逻辑删除

删除数据只是逻辑上删除,数据库中还能查到

使用场景:

  • 可以进行数据恢复

  • 有关联数据,不便删除

  • 表中添加deleted字段,类型为boolean默认值为false

  • 修改实体类,添加deleted字段

    1
    2
    @TableLogic
    private Integer deleted;
  • 配置(下面是默认配置,可配可不配)

    1
    2
    3
    # 逻辑删除
    mybatis-plus.global-config.db-config.logic-delete-value=1
    mybatis-plus.global-config.db-config.logic-not-delete-value=0
  • 测试


MyBatis
http://xwww12.github.io/2023/05/24/后端/Mybatis/
作者
xw
发布于
2023年5月24日
许可协议