mybatis mysql set命令_Mybatis-Plus 常用操作

MyBatis-Plus系列推荐阅读顺序:

本文目录结构

一、SQL日志开关

二、常用注解

三、代码生成器

四、分页查询

五、Mybatis-Plus Wrapper

六、自动填充数据功能

七、逻辑删除

八、乐观锁

一、SQL日志开关

配置文件application.properties,增加最后一行,执行时会打印出 sql 语句。

spring.application.name=mybatis-plus

# 应用服务 WEB 访问端口

server.port=8080

####数据库连接池###

spring.datasource.url=jdbc:mysql://101.133.227.13:3306/orders_1?useSSL=false&useUnicode=true&characterEncoding=UTF-8

spring.datasource.username=guo

spring.datasource.password=205010guo

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

####输出sql日志###

mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

类似JPA的日志输出配置:

jpa:

show-sql:true#打印SQL。

二、常用注解

2.1【@TableName 】

@TableName 用于定义表名

注:

常用属性:

value 用于定义表名

2.2【@TableId】

@TableId 用于定义表的主键

注:

常用属性:

value 用于定义主键字段名

type 用于定义主键类型(主键策略 IdType)

主键策略:

IdType.AUTO 主键自增,系统分配,不需要手动输入

IdType.NONE 未设置主键

IdType.INPUT 需要自己输入 主键值。

IdType.ASSIGN_ID 系统分配 ID,用于数值型数据(Long,对应 mysql 中 BIGINT 类型)。

IdType.ASSIGN_UUID 系统分配 UUID,用于字符串型数据(String,对应 mysql 中 varchar(32) 类型)。

2.3【@TableField】

@TableField 用于定义表的非主键字段。

注:

常用属性:

value 用于定义非主键字段名

exist 用于指明是否为数据表的字段, true 表示是,false 为不是。

fill 用于指定字段填充策略(FieldFill)。

字段填充策略:(一般用于填充 创建时间、修改时间等字段)

FieldFill.DEFAULT 默认不填充

FieldFill.INSERT 插入时填充

FieldFill.UPDATE 更新时填充

FieldFill.INSERT_UPDATE 插入、更新时填充。

2.4【@TableLogic】

@TableLogic 用于定义表的字段进行逻辑删除(非物理删除)

注:

常用属性:

value 用于定义未删除时字段的值

delval 用于定义删除时字段的值

2.5【@Version】

@Version 用于字段实现乐观锁

三、代码生成器

3.1 AutoGenerator 简介

AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。  与 mybatis 中的 mybatis-generator-core 类似。

3.2 添加依赖

com.baomidou

mybatis-plus-generator

${mp.version}

org.apache.velocity

velocity-engine-core

2.2

3.3 生成器代码分析

Step1:

创建一个 代码生成器。用于生成代码。

此处不用修改。

// Step1:代码生成器

AutoGenerator mpg = new AutoGenerator();

Step2:

配置全局信息。指定代码输出路径,以及包名、作者等信息。

此处按需添加,projectPath 需要修改,setAuthor 需要修改。

// Step2:全局配置

GlobalConfig gc = new GlobalConfig();

// 填写代码生成的目录(需要修改)

String projectPath = "E:\\myProject\\test\\test_mybatis_plus";

// 拼接出代码最终输出的目录

gc.setOutputDir(projectPath + "/src/main/java");

// 配置开发者信息(可选)(需要修改)

gc.setAuthor("郭秀志 jbcode@126.com");

// 配置是否打开目录,false 为不打开(可选)

gc.setOpen(false);

// 实体属性 Swagger2 注解,添加 Swagger 依赖,开启 Swagger2 模式(可选)

//gc.setSwagger2(true);

// 重新生成文件时是否覆盖,false 表示不覆盖(可选)

gc.setFileOverride(false);

// 配置主键生成策略,此处为 ASSIGN_ID(可选)

gc.setIdType(IdType.ASSIGN_ID);

// 配置日期类型,此处为 ONLY_DATE(可选)

gc.setDateType(DateType.ONLY_DATE);

// 默认生成的 service 会有 I 前缀

gc.setServiceName("%sService");

mpg.setGlobalConfig(gc);

Step3:

配置数据源信息。用于指定 需要生成代码的 数据仓库、数据表。

setUrl、setDriverName、setUsername、setPassword均需修改。

// Step3:数据源配置(需要修改)

DataSourceConfig dsc = new DataSourceConfig();

// 配置数据库 url 地址

dsc.setUrl("jdbc:mysql://localhost:3306/testMyBatisPlus?useUnicode=true&characterEncoding=utf8");

// dsc.setSchemaName("testMyBatisPlus"); // 可以直接在 url 中指定数据库名

// 配置数据库驱动

dsc.setDriverName("com.mysql.cj.jdbc.Driver");

// 配置数据库连接用户名

dsc.setUsername("root");

// 配置数据库连接密码

dsc.setPassword("123456");

mpg.setDataSource(dsc);

Step4:

配置包信息。

setParent、setModuleName均需修改。其余按需求修改.

// Step:4:包配置

PackageConfig pc = new PackageConfig();

// 配置父包名(需要修改)

pc.setParent("com.erbadagang.mybatis.plus");

// 配置模块名(需要修改)

//pc.setModuleName("mybatis-plus-starter");

// 配置 entity 包名

pc.setEntity("entity");

// 配置 mapper 包名

pc.setMapper("mapper");

// 配置 service 包名

pc.setService("service");

// 配置 controller 包名

pc.setController("controller");

mpg.setPackageInfo(pc);

Step5:

配置数据表映射信息。

setInclude 需要修改,其余按实际开发修改。

// Step5:策略配置(数据库表配置)

StrategyConfig strategy = new StrategyConfig();

// 指定表名(可以同时操作多个表,使用 , 隔开)(需要修改)

strategy.setInclude("t_user");

// 配置数据表与实体类名之间映射的策略

strategy.setNaming(NamingStrategy.underline_to_camel);

// 配置数据表的字段与实体类的属性名之间映射的策略

strategy.setColumnNaming(NamingStrategy.underline_to_camel);

// 配置 lombok 模式

strategy.setEntityLombokModel(true);

// 配置 rest 风格的控制器(@RestController)

strategy.setRestControllerStyle(true);

// 配置驼峰转连字符

strategy.setControllerMappingHyphenStyle(true);

// 配置表前缀,生成实体时去除表前缀

// 此处的表名为 test_mybatis_plus_user,模块名为 test_mybatis_plus,去除前缀后剩下为 user。

strategy.setTablePrefix(pc.getModuleName() + "_");

mpg.setStrategy(strategy);

表t_user建表SQL:

/*

Navicat Premium Data Transfer

Source Server : 上海

Source Server Type : MySQL

Source Server Version : 50636

Source Host : 101.133.227.13:3306

Source Schema : orders_1

Target Server Type : MySQL

Target Server Version : 50636

File Encoding : 65001

Date: 10/07/2020 16:28:23

*/

SET NAMES utf8mb4;

SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------

-- Table structure for t_user

-- ----------------------------

DROP TABLE IF EXISTS `t_user`;

CREATE TABLE `t_user` (

`id` bigint(20) NOT NULL AUTO_INCREMENT,

`user_name` varchar(25) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,

`password` varchar(55) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,

`pwd_cipher` varchar(55) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,

PRIMARY KEY (`id`) USING BTREE

) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

Step6:

执行代码生成操作。

此处不用修改。

// Step6:执行代码生成操作

mpg.execute();

完整代码:

package com.erbadagang.mybatis.plus.mybatisplus;

import com.baomidou.mybatisplus.annotation.IdType;

import com.baomidou.mybatisplus.generator.AutoGenerator;

import com.baomidou.mybatisplus.generator.config.DataSourceConfig;

import com.baomidou.mybatisplus.generator.config.GlobalConfig;

import com.baomidou.mybatisplus.generator.config.PackageConfig;

import com.baomidou.mybatisplus.generator.config.StrategyConfig;

import com.baomidou.mybatisplus.generator.config.rules.DateType;

import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;

import org.junit.jupiter.api.Test;

import org.springframework.boot.test.context.SpringBootTest;

/**

* AutoGenerationTest作用是:生成Mybatis-plus代码,AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

*

* @ClassName: AutoGenerationTest

* @author: 郭秀志 jbcode@126.com

* @date: 2020/7/10 15:08

* @Copyright:

*/

@SpringBootTest

public class AutoGenerationTest {

@Test

public void autoGenerate() {

// Step1:代码生成器

AutoGenerator mpg = new AutoGenerator();

// Step2:全局配置

GlobalConfig gc = new GlobalConfig();

// 填写代码生成的目录(需要修改)

String projectPath = "D:\\dev\\GitRepository\\mybatis-plus-starter";

// 拼接出代码最终输出的目录

gc.setOutputDir(projectPath + "/src/main/java");

// 配置开发者信息(可选)(需要修改)

gc.setAuthor("郭秀志 jbcode@126.com");

// 配置是否打开目录,false 为不打开(可选)

gc.setOpen(false);

// 实体属性 Swagger2 注解,添加 Swagger 依赖,开启 Swagger2 模式(可选)

//gc.setSwagger2(true);

// 重新生成文件时是否覆盖,false 表示不覆盖(可选)

gc.setFileOverride(false);

// 配置主键生成策略,此处为 ASSIGN_ID(可选)

gc.setIdType(IdType.AUTO);

// 配置日期类型,此处为 ONLY_DATE(可选)

gc.setDateType(DateType.ONLY_DATE);

// 默认生成的 service 会有 I 前缀

gc.setServiceName("I%sService");

mpg.setGlobalConfig(gc);

// Step3:数据源配置(需要修改)

DataSourceConfig dsc = new DataSourceConfig();

// 配置数据库 url 地址

dsc.setUrl("jdbc:mysql://101.133.227.13:3306/orders_1?useSSL=false&useUnicode=true&characterEncoding=UTF-8");

// dsc.setSchemaName("testMyBatisPlus"); // 可以直接在 url 中指定数据库名

// 配置数据库驱动

dsc.setDriverName("com.mysql.cj.jdbc.Driver");

// 配置数据库连接用户名

dsc.setUsername("guo");

// 配置数据库连接密码

dsc.setPassword("205010guo");

mpg.setDataSource(dsc);

// Step:4:包配置

PackageConfig pc = new PackageConfig();

// 配置父包名(需要修改)

pc.setParent("com.erbadagang.mybatis.plus.mybatisplus");

// 配置模块名(需要修改)

//pc.setModuleName("mybatis-plus-starter");

// 配置 entity 包名

pc.setEntity("entity");

// 配置 mapper 包名

pc.setMapper("mapper");

// 配置 service 包名

pc.setService("service");

// 配置 controller 包名

pc.setController("controller");

mpg.setPackageInfo(pc);

// Step5:策略配置(数据库表配置)

StrategyConfig strategy = new StrategyConfig();

// 指定表名(可以同时操作多个表,使用 , 隔开)(需要修改)

strategy.setInclude("t_user");//表名t_user

// 配置数据表与实体类名之间映射的策略

strategy.setNaming(NamingStrategy.underline_to_camel);

// 配置数据表的字段与实体类的属性名之间映射的策略

strategy.setColumnNaming(NamingStrategy.underline_to_camel);

// 配置 lombok 模式

strategy.setEntityLombokModel(true);

// 配置 rest 风格的控制器(@RestController)

strategy.setRestControllerStyle(true);

// 配置驼峰转连字符

strategy.setControllerMappingHyphenStyle(true);

// 配置表前缀,生成实体时去除表前缀

// 此处的表名为 test_mybatis_plus_user,模块名为 test_mybatis_plus,去除前缀后剩下为 user。

strategy.setTablePrefix(pc.getModuleName() + "_");

mpg.setStrategy(strategy);

// Step6:执行代码生成操作

mpg.execute();

}

}

3.4 测试生成的service

由于生成的Service接口及实现类有些问题,需要稍为改造一下:

Service接口:public interface ITUserService extends IService 增加泛型:public interface ITUserService extends IService 。

实现类:public class TUserServiceImpl extends ServiceImpl implements IService 实现接口由IService变成 ITUserService。

Junit 测试代码:

@Autowired

private ITUserService tUserService;

@Test

public void testService() {

TUser user = new TUser();

user.setUserName("trek");

user.setPassword("888999");

user.setPwdCipher("ewifwiEFafe==");

if (tUserService.save(user)) {

tUserService.list().forEach(System.out::println);

} else {

System.out.println("添加数据失败");

}

}

测试结果:

a0145342fea6

表插入新数据

控制台输出信息:

==> Preparing: INSERT INTO t_user ( user_name, password, pwd_cipher ) VALUES ( ?, ?, ? )

==> Parameters: trek(String), 888999(String), ewifwiEFafe==(String)

<== Updates: 1

Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@47acd13b]

Creating a new SqlSession

SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e26f1ed] was not registered for synchronization because synchronization is not active

JDBC Connection [HikariProxyConnection@633514467 wrapping com.mysql.cj.jdbc.ConnectionImpl@297c9a9b] will not be managed by Spring

==> Preparing: SELECT id,user_name,password,pwd_cipher FROM t_user

==> Parameters:

<== Columns: id, user_name, password, pwd_cipher

<== Row: 1, guo, bwMhZeGXyD98aToKQdXLcw==, null

<== Row: 2, guo, bwMhZeGXyD98aToKQdXLcw==, null

<== Row: 3, guo, 123456, bwMhZeGXyD98aToKQdXLcw==

<== Row: 4, trek, 888999, ewifwiEFafe==

<== Total: 4

Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e26f1ed]

TUser(id=1, userName=guo, password=bwMhZeGXyD98aToKQdXLcw==, pwdCipher=null)

TUser(id=2, userName=guo, password=bwMhZeGXyD98aToKQdXLcw==, pwdCipher=null)

TUser(id=3, userName=guo, password=123456, pwdCipher=bwMhZeGXyD98aToKQdXLcw==)

TUser(id=4, userName=trek, password=888999, pwdCipher=ewifwiEFafe==)

四、分页查询

4.1 配置拦截器组件

MybatisPlusApplication启动类添加代码:

/**

* 分页插件

*/

@Bean

public PaginationInterceptor paginationInterceptor() {

return new PaginationInterceptor();

}

4.2 编写分页代码

直接 new 一个 Page 对象,对象需要传递两个参数(当前页,每页显示的条数)。

调用 mybatis-plus 提供的分页查询方法,其会将 分页查询的数据封装到 Page 对象中。

@Test

public void selectPage() {

// 根据Wrapper 自定义条件查询

QueryWrapper queryWrapper = new QueryWrapper<>();

queryWrapper.gt("age", "18");

queryWrapper.orderByDesc("age");

Page userPage = new Page(2, 2);

// userPage.setCurrent(2L); //当前是第几页 默认为1

// userPage.setSize(2); //每页大小

IPage userIPage = userMapper.selectPage(userPage, queryWrapper);

System.out.println("当前页" + userIPage.getCurrent()); //当前页

System.out.println("总页数" + userIPage.getPages()); //总页数

System.out.println("返回数据" + userIPage.getRecords()); //返回数据

System.out.println("每页大小" + userIPage.getSize()); //每页大小

System.out.println("满足符合条件的条数" + userIPage.getTotal()); //满足符合条件的条数

System.out.println("下一页" + userPage.hasNext()); //下一页

System.out.println("上一页" + userPage.hasPrevious()); //上一页

}

运行结果:

a0145342fea6

结果说明

控制台System.out.println代码部分日志输出:

当前页2

总页数2

返回数据[User(id=4, name=Oliver, age=21, email=xds@erbadagang.com), User(id=2, name=xiu, age=20, email=specialized@erbadagang.com)]

每页大小2

满足符合条件的条数4

下一页false

上一页true

五、Mybatis-Plus Wrapper

5.1 删除

/**

*

* 根据根据 entity 条件,删除记录,QueryWrapper实体对象封装操作类(可以为 null)

* 下方获取到queryWrapper后删除的查询条件为name字段为null的and年龄大于等于12的and email字段不为null的

* 同理写法条件添加的方式就不做过多介绍了。

*

*/

@Test

public void delete() {

QueryWrapper queryWrapper = new QueryWrapper<>();

queryWrapper

.isNull("name")

.ge("age", 12)

.isNotNull("email");

int delete = userMapper.delete(queryWrapper);

System.out.println("delete return count = " + delete);

}

SQL输出:

==> Preparing: DELETE FROM user WHERE (name IS NULL AND age >= ? AND email IS NOT NULL)

==> Parameters: 12(Integer)

<== Updates: 0

5.2 selectOne

/**

*

* 根据 entity 条件,查询一条记录,

* 这里和上方删除构造条件一样,只是seletOne返回的是一条实体记录,当出现多条时会报错

*

*/

@Test

public void selectOne() {

QueryWrapper queryWrapper = new QueryWrapper<>();

queryWrapper.eq("name", "guo");

User user = userMapper.selectOne(queryWrapper);

System.out.println(user);

}

SQL输出:

==> Preparing: SELECT id,name,age,email FROM user WHERE (name = ?)

==> Parameters: guo(String)

<== Columns: id, name, age, email

<== Row: 1, Guo , 18, trek@erbadagang.com

<== Total: 1

5.3 selectCount

/**

*

* 根据 Wrapper 条件,查询总记录数

*

*

* @param queryWrapper 实体对象

*/

@Test

public void selectCount() {

QueryWrapper queryWrapper = new QueryWrapper<>();

queryWrapper.eq("name", "guo");

Integer count = userMapper.selectCount(queryWrapper);

System.out.println(count);

}

SQL输出:

==> Preparing: SELECT COUNT( 1 ) FROM user WHERE (name = ?)

==> Parameters: guo(String)

<== Columns: COUNT( 1 )

<== Row: 1

<== Total: 1

5.4 selectList

/**

*

* 根据 entity 条件,查询全部记录

*

*

* @param queryWrapper 实体对象封装操作类(可以为 null)为null查询全部

*/

@Test

public void selectListByEntity() {

List list = userMapper.selectList(null);//null为无条件

System.out.println(list);

}

/**

*

* 根据 Wrapper 条件,查询全部记录

*

*

* @param queryWrapper 实体对象封装操作类(可以为 null)

*/

@Test

public void selectListByMapper() {

QueryWrapper queryWrapper = new QueryWrapper<>();

queryWrapper.eq("name", "guo");

List list = userMapper.selectList(queryWrapper);//null为无条件

System.out.println(list);

}

5.5 selectMaps

@Test

public void selectMaps() {

Page page = new Page(1, 5);

QueryWrapper queryWrapper = new QueryWrapper<>();

List> maps = userMapper.selectMaps(queryWrapper);

maps.forEach(map -> {

System.out.println("name-->" + map.get("name"));

System.out.println("email-->" + map.get("email"));

});

System.out.println(maps);

}

返回类型List>。Map的key为字段名称,value为对应的字段值。

控制台输出:

==> Preparing: SELECT id,name,age,email FROM user

==> Parameters:

<== Columns: id, name, age, email

<== Row: 1, Guo , 18, trek@erbadagang.com

<== Row: 2, xiu, 20, specialized@erbadagang.com

<== Row: 3, zhi, 28, giant@erbadagang.com

<== Row: 4, Oliver, 88, winspace@erbadagang.com

<== Row: 5, Messi, 24, look@erbadagang.com

<== Total: 5

Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@294aba23]

name-->Guo

email-->trek@erbadagang.com

name-->xiu

email-->specialized@erbadagang.com

name-->zhi

email-->giant@erbadagang.com

name-->Oliver

email-->winspace@erbadagang.com

name-->Messi

email-->look@erbadagang.com

[{name=Guo , id=1, age=18, email=trek@erbadagang.com}, {name=xiu, id=2, age=20, email=specialized@erbadagang.com}, {name=zhi, id=3, age=28, email=giant@erbadagang.com}, {name=Oliver, id=4, age=88, email=winspace@erbadagang.com}, {name=Messi, id=5, age=24, email=look@erbadagang.com}]

六、自动填充数据功能

添加、修改数据时,每次都会使用相同的方式进行填充。比如: 数据的创建时间、修改时间、操作者等。

6.1 数据库准备

Mybatis-plus 支持自动填充这些字段的数据。给之前的数据表新增3个字段:创建时间、修改时间、操作人。

SQL语句:

ALTER TABLE `orders_1`.`user`

ADD COLUMN `create_time` datetime(0) COMMENT '创建时间' AFTER `email`,

ADD COLUMN `update_time` datetime(0) COMMENT '修改时间' AFTER `create_time`,

ADD COLUMN `operator` varchar(20) COMMENT '操作人' AFTER `update_time`;

6.2 重新生成代码

并使用 代码生成器重新生成代码,注意修改生成器配置为可覆盖老代码。

// 重新生成文件时是否覆盖,false 表示不覆盖(可选)

gc.setFileOverride(true);

6.3 修改entity

使用@TableField注解,标注需要进行填充的字段。

package com.erbadagang.mybatis.plus.mybatisplus.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;

import com.baomidou.mybatisplus.annotation.IdType;

import com.baomidou.mybatisplus.annotation.TableField;

import com.baomidou.mybatisplus.annotation.TableId;

import lombok.Data;

import lombok.EqualsAndHashCode;

import java.io.Serializable;

import java.util.Date;

/**

*

*

*

*

* @author 郭秀志 jbcode@126.com

* @since 2020-07-11

*/

@Data

@EqualsAndHashCode(callSuper = false)

public class User implements Serializable {

private static final long serialVersionUID=1929834928304L;

/**

* 主键ID

*/

@TableId(value = "id", type = IdType.AUTO)

private Long id;

/**

* 姓名

*/

private String name;

/**

* 年龄

*/

private Integer age;

/**

* 邮箱

*/

private String email;

/**

* 创建时间

*/

@TableField(fill = FieldFill.INSERT)

private Date createTime;

/**

* 修改时间

*/

@TableField(fill = FieldFill.INSERT_UPDATE)

private Date updateTime;

/**

* 操作人

*/

@TableField(fill = FieldFill.INSERT_UPDATE)

private String operator;

}

填充策略FieldFill.INSERT_UPDATE表示插入和更新都进行自动填充。

6.4 自定义MetaObjectHandler

自定义一个类,实现 MetaObjectHandler 接口,并重写方法。添加 @Component 注解,交给 Spring 去管理。

package com.erbadagang.mybatis.plus.mybatisplus.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;

import org.apache.ibatis.reflection.MetaObject;

import org.springframework.stereotype.Component;

import java.util.Date;

/**

* @description 自定义的数据填充handler,分别写insert和update的写入策略。

* @ClassName: MyFillDataMetaObjectHandler

* @author: 郭秀志 jbcode@126.com

* @date: 2020/7/11 9:39

* @Copyright:

*/

@Component

public class MyFillDataMetaObjectHandler implements MetaObjectHandler {

@Override

public void insertFill(MetaObject metaObject) {

this.strictInsertFill(metaObject, "createTime", Date.class, new Date());

this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());

this.strictInsertFill(metaObject, "operator", String.class, "梅西爱骑车");

}

@Override

public void updateFill(MetaObject metaObject) {

this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());

this.strictInsertFill(metaObject, "operator", String.class, "梅西爱骑车");

}

}

6.5 测试

6.5.1 插入测试

/**

* 测试插入的自动填充数据功能。

*/

@Test

public void testAutoFillInsert() {

User user = new User();

user.setId(0l);

user.setName("崔克");

user.setAge(18);

user.setEmail("trek@erbadagang.cn");

int id = userMapper.insert(user);//自动返回插入的id

System.out.println(id);

}

运行测试用例,如果报错:Caused by: java.sql.SQLException: Field 'id' doesn't have a default value

需要把id列勾上自增。

a0145342fea6

自增

输出的SQL信息:

==> Preparing: INSERT INTO user ( name, age, email, create_time, update_time, operator ) VALUES ( ?, ?, ?, ?, ?, ? )

==> Parameters: 崔克(String), 18(Integer), trek@erbadagang.cn(String), 2020-07-11 09:57:43.386(Timestamp), 2020-07-11 09:57:43.388(Timestamp), 梅西爱骑车(String)

<== Updates: 1

a0145342fea6

表中数据

6.5.2 更新测试

更新name为英文的trek,age为28。

/**

* 测试更新的自动填充数据功能。

*/

@Test

public void testAutoFillUpdate() {

User user = new User();

user.setId(7l);

user.setName("trek");

user.setAge(28);

user.setEmail("trek@erbadagang.cn");

int id = userMapper.updateById(user);//自动返回插入的id

System.out.println(id);

}

运行测试。

输出的SQL信息,只更新了update_time没更新create_time字段:

==> Preparing: UPDATE user SET name=?, age=?, email=?, update_time=?, operator=? WHERE id=?

==> Parameters: trek(String), 28(Integer), trek@erbadagang.cn(String), 2020-07-11 10:05:30.249(Timestamp), 梅西爱骑车(String), 7(Long)

<== Updates: 1

如果入库的时间跟上面打印的SQL不一致,需要在jdbc连接加入时区设置:

jdbc:mysql://101.133.227.13:3306/orders_1?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai

七、逻辑删除

删除数据,可以通过物理删除,也可以通过逻辑删除。

物理删除指的是直接将数据从数据库中删除,不保留。

逻辑删除指的是修改数据的某个字段,使其表示为已删除状态,而非删除数据,保留该数据在数据库中,但是查询时不显示该数据(查询时过滤掉该数据)。

7.1 表结构

给数据表增加一个字段:delete_flag,用于表示该数据是否被逻辑删除。

SQL语句:

ALTER TABLE `orders_1`.`user`

ADD COLUMN `delete_flag` tinyint(1) COMMENT '逻辑删除(0 未删除、1 删除)' AFTER `operator`;

7.3 使用逻辑删除。

可以定义一个自动填充规则,初始值为 0。0 表示未删除, 1 表示删除。

在Entity类新增:

/**

* 逻辑删除(0 未删除、1 删除)

*/

@TableLogic(value = "0", delval = "1")//定义逻辑删除功能。

@TableField(fill = FieldFill.INSERT)//定义在insert的时候自动填充功能

private Integer deleteFlag;

@TableLogic定义逻辑删除功能,若去除 TableLogic 注解,再执行 Delete 时进行物理删除,直接删除这条数据。

@TableField定义在自动填充功能。

在自动填充规则MyFillDataMetaObjectHandler类的insertFill方法添加:

@Override

public void insertFill(MetaObject metaObject) {

......

this.strictInsertFill(metaObject, "deleteFlag", Integer.class, 0);

}

7.4 测试

新增一条闪电牌自行车数据:

User user = new User();

user.setId(0l);

user.setName("闪电");

user.setAge(18);

user.setEmail("specialized@erbadagang.cn");

int id = userMapper.insert(user);//自动返回插入的id

delete_flag字段为自动填充代码定义的默认值0,当然也可以使用数据库定义默认值。

a0145342fea6

表层面定义的初始值0

新增数据delete_flag值:

a0145342fea6

delete_flag=0

删除数据:

//这次使用IUserService而不是mapper进行测试

@Autowired

private IUserService userService;

/**

* 逻辑删除测试。

*/

@Test

public void testDelete() {

if (userService.removeById(8)) {

System.out.println("删除数据成功");

userService.list().forEach(System.out::println);

} else {

System.out.println("删除数据失败");

}

}

执行测试,输出的日志:

==> Preparing: UPDATE user SET delete_flag=1 WHERE id=? AND delete_flag=0

==> Parameters: 8(Integer)

<== Updates: 1

Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@22bdb1d0]

删除数据成功

==> Preparing: SELECT id,name,age,email,create_time,update_time,operator,delete_flag FROM user WHERE delete_flag=0

==> Parameters:

<== Total: 0

可以看到更新delete_flag=1的操作,以及查询时自动加上了WHERE delete_flag=0的判断。

表数据变化:

a0145342fea6

delete_flage变为1

八、乐观锁

8.1 基础知识

(1)首先认识一下: 读问题、写问题

操作数据库数据时,遇到的最基本问题就是 读问题与写问题。

读问题 指的是从数据库中读取数据时遇到的问题,比如:脏读、幻读、不可重复读。

脏读、幻读、不可重复读 参考地址

写问题 指的是数据写入数据库时遇到的问题,比如:丢失更新(多个线程同时对某条数据更新,无论执行顺序如何,都会丢失其他线程更新的数据)

(2)如何解决写问题?

乐观锁、悲观锁就是为了解决 写问题而存在的。

乐观锁:总是假设最好的情况,每次读取数据时认为数据不会被修改(即不加锁),当进行更新操作时,会判断这条数据是否被修改,未被修改,则进行更新操作。若被修改,则数据更新失败,可以对数据进行重试(重新尝试修改数据)。

悲观锁:总是假设最坏的情况,每次读取数据时认为数据会被修改(即加锁),当进行更新操作时,直接更新数据,结束操作后释放锁(此处才可以被其他线程读取)。

(3)乐观锁、悲观锁使用场景?

乐观锁一般用于读比较多的场合,尽量减少加锁的开销。

悲观锁一般用于写比较多的场合,尽量减少 类似 乐观锁重试更新引起的性能开销。

(4)乐观锁两种实现方式

方式一:通过版本号机制实现。

在数据表中增加一个 version 字段。

取数据时,获取该字段,更新时以该字段为条件进行处理(即set version = newVersion where version = oldVersion),若 version 相同,则更新成功(给新 version 赋一个值,一般加 1)。若 version 不同,则更新失败,可以重新尝试更新操作。

方式二:通过 CAS 算法实现。

CAS 为 Compare And Swap 的缩写,即比较交换,是一种无锁算法(即在不加锁的情况实现多线程之间的变量同步)。

CAS 操作包含三个操作数 —— 内存值(V)、预期原值(A)和新值(B)。如果内存地址里面的值 V 和 A 的值是一样的,那么就将内存里面的值更新成B。若 V 与 A 不一致,则不执行任何操作(可以通过自旋操作,不断尝试修改数据直至成功修改)。即 V == A ? V = B : V = V。

CAS 可能导致 ABA 问题(两次读取数据时值相同,但不确定值是否被修改过),比如两个线程操作同一个变量,线程 A、线程B 初始读取数据均为 A,后来 线程B 将数据修改为 B,然后又修改为 A,此时线程 A 再次读取到的数据依旧是 A,虽然值相同但是中间被修改过,这就是 ABA 问题。可以加一个额外的标志位 C,用于表示数据是否被修改。当标志位 C 与预期标志位相同、且 V == A 时,则更新值 B。

(5)mybatis-plus 实现乐观锁(通过 version 机制)

实现思路:

Step1:取出记录时,获取当前version

Step2:更新时,带上这个version

Step3:执行更新时, set version = newVersion where version = oldVersion

Step4:如果version不对,就更新失败

(6)mybatis-plus 代码实现乐观锁

8.2 MP实现乐观锁

配置乐观锁插件。

启动类MybatisPlusApplication新增如下代码(类似分页插件),将 OptimisticLockerInterceptor通过@Bean交给 Spring 管理。

/**

* 乐观锁插件

* @return 乐观锁插件的实例

*/

@Bean

public OptimisticLockerInterceptor optimisticLockerInterceptor() {

return new OptimisticLockerInterceptor();

}

8.3 定义一个数据库字段 version

ALTER TABLE `orders_1`.`user`

ADD COLUMN `version` int COMMENT '版本号(用于乐观锁, 默认为 1)' AFTER `delete_flag`;

8.4 实体类

使用@Version注解标注对应的实体类。可以通过@TableField进行数据自动填充。

/**

* 版本号(用于乐观锁, 默认为 1)

*/

@Version

@TableField(fill = FieldFill.INSERT)

private Integer version;

8.5 自动填充规则

在自动填充规则MyFillDataMetaObjectHandler类的insertFill方法添加:

@Override

public void insertFill(MetaObject metaObject) {

......

//乐观锁version初始化值为1

this.strictInsertFill(metaObject, "version", Integer.class, 1);

}

8.6 测试

/**

* 乐观锁测试

*/

@Test

public void testVersion() {

User user = new User();

user.setName("Look");

user.setAge(8);

user.setEmail("look@erbadagang.cn");

userService.save(user);//新增数据

userService.list().forEach(System.out::println);//查询数据

user.setName("梅花");

userService.update(user, null);//修改数据

userService.list().forEach(System.out::println);//查询数据

}

运行结果(语句增加了我的注释):

##插入数据,version=1

==> Preparing: INSERT INTO user ( name, age, email, create_time, update_time, operator, delete_flag, version ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ? )

==> Parameters: Look(String), 8(Integer), look@erbadagang.cn(String), 2020-07-11 12:03:50.712(Timestamp), 2020-07-11 12:03:50.715(Timestamp), 梅西爱骑车(String), 0(Integer), 1(Integer)

<== Updates: 1

##查询数据,读取的version为1

==> Preparing: SELECT id,name,age,email,create_time,update_time,operator,delete_flag,version FROM user WHERE delete_flag=0

==> Parameters:

<== Columns: id, name, age, email, create_time, update_time, operator, delete_flag, version

<== Row: 9, Look, 8, look@erbadagang.cn, 2020-07-11 12:03:51, 2020-07-11 12:03:51, 梅西爱骑车, 0, 1

<== Total: 1

##更新数据,条件是version=1如果此时被其他程序更新了,这里条件不满足不会更新数据。

##version的值自动+1,现在是2。

==> Preparing: UPDATE user SET name=?, age=?, email=?, create_time=?, update_time=?, operator=?, version=? WHERE delete_flag=0 AND (version = ?)

==> Parameters: 梅花(String), 8(Integer), look@erbadagang.cn(String), 2020-07-11 12:03:50.712(Timestamp), 2020-07-11 12:03:50.715(Timestamp), 梅西爱骑车(String), 2(Integer), 1(Integer)

<== Updates: 1

##再次查询version为2。

==> Preparing: SELECT id,name,age,email,create_time,update_time,operator,delete_flag,version FROM user WHERE delete_flag=0

==> Parameters:

<== Columns: id, name, age, email, create_time, update_time, operator, delete_flag, version

<== Row: 9, 梅花, 8, look@erbadagang.cn, 2020-07-11 12:03:51, 2020-07-11 12:03:51, 梅西爱骑车, 0, 2

<== Total: 1

##查询出来的最新数据,delete_flag=0

User(id=9, name=梅花, age=8, email=look@erbadagang.cn, createTime=Sat Jul 11 12:03:51 CST 2020, updateTime=Sat Jul 11 12:03:51 CST 2020, operator=梅西爱骑车, deleteFlag=0, version=2)

底线

本文源代码使用 Apache License 2.0开源许可协议,可从Gitee代码地址通过git clone命令下载到本地或者通过浏览器方式查看源代码。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值