节省时间,提高效率:深入解析MyBatis Plus

1. MyBatis Plus 概述

  • Mybatis + 通用Mapper + PageHelper 升级成 MyBatis Plus

1.1 简介

官网:https://baomidou.com/

参考教程:https://baomidou.com/pages/24112f/

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

1.2 特点

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑

  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作

  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求

  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错

  • 支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer2005、SQLServer 等多种数据库

  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题

  • 支持 XML 热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动

  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作

  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )

  • 支持关键词自动转义:支持数据库关键词(order、key…)自动转义,还可自定义关键词

  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用

  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询

  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询

  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

  • 内置 Sql 注入剥离器:支持 Sql 注入剥离,有效预防 Sql 注入攻击

    select * from user where username = 'jack' and password = '1234';
    -- 将 jack 替换成 jack' #
    -- sql注入,用户输入的内容,称为sql语句语法的一部分。
    select * from user where username = 'jack' #' and password = '1234';
    

2. 入门案例

2.1 搭建环境

  • 步骤

    • 步骤一:创建项目:test-mybatis-plus
    • 步骤二:修改pom.xml,添加依赖
    • 步骤三:创建yml文件,配置数据库相关
  • 步骤一:创建项目:test-mybatis-plus

  • 步骤二:修改pom.xml,添加依赖

        <!--确定spring boot的版本--><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.5.RELEASE</version></parent>   
    
    <dependencies><!-- web 开发 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--MySQL数据库驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--支持lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--测试--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.0</version></dependency></dependencies>
    
  • 步骤三:创建yml文件,配置数据库相关

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/cloud_db1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTCusername: rootpassword: 1234
    mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  #输出日志
    

2.2 数据库和表

CREATE DATABASE cloud_db1;
USE cloud_db1;
CREATE TABLE `tmp_customer` (`cid` INT(11) NOT NULL AUTO_INCREMENT,`cname` VARCHAR(50) DEFAULT NULL,`password` VARCHAR(32) DEFAULT NULL,`telephone` VARCHAR(11) DEFAULT NULL,`money` DOUBLE DEFAULT NULL,`version` INT(11) DEFAULT NULL,`create_time` DATE DEFAULT NULL,`update_time` DATE DEFAULT NULL,PRIMARY KEY (`cid`)
);INSERT  INTO `tmp_customer`(`cid`,`cname`,`password`,`telephone`,`money`,`version`,`create_time`,`update_time`) 
VALUES (1,'jack','1234','110',1000,NULL,NULL,NULL),(2,'rose','1234','112',1000,NULL,NULL,NULL),(3,'tom','1234','119',1000,NULL,NULL,NULL);

2.3 入门:查询所有

  • 步骤

    • 步骤1:配置JavaBean,添加MyBatisPlus对应的注解(表、主键、字段等)
    • 步骤2:编写dao接口,并继承BaseMapper接口
    • 步骤3:编写启动类
    • 步骤4:编写测试类
  • 步骤1:配置JavaBean

    • @TableName 表名注解,value属性设置表名
package com.czxy.domain;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.util.List;/*** @author 桐叔* @email liangtong@itcast.cn*/
@Data
@TableName("tmp_customer")
public class Customer {@TableId(type = IdType.AUTO)private Integer cid;private String cname;private String password;private String telephone;private String money;private Integer version;@TableField(exist = false)private List<Integer> ids;
}
  • 步骤2:编写dao
package com.czxy.mp.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.czxy.mp.domain.Customer;
import org.apache.ibatis.annotations.Mapper;/*** Created by liangtong.*/
@Mapper
public interface CustomerMapper extends BaseMapper<Customer> {
}
  • 步骤3:编写启动类
package com.czxy.mp;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** Created by liangtong.*/
@SpringBootApplication
public class MybatisPlusApplication {public static void main(String[] args) {SpringApplication.run(MybatisPlusApplication.class, args);}
}
  • 步骤4:编写测试类
package com.czxy;import com.czxy.mp.MybatisPlusApplication;
import com.czxy.mp.domain.Customer;
import com.czxy.mp.mapper.CustomerMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import javax.annotation.Resource;
import java.util.List;/*** Created by liangtong.*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MybatisPlusApplication.class)
public class TestDemo01 {@Resourceprivate CustomerMapper customerMapper;@Testpublic void testFindAll() {List<Customer> list = customerMapper.selectList(null);list.forEach(System.out::println);}
}

3. 基本操作

3.1 常见API

BaseMapper 封装CRUD操作,泛型 T 为任意实体对象

  • 增删改
方法名描述
int insert(T entity)插入一条记录,entity 为 实体对象
int delete(Wrapper wrapper)根据 entity 条件,删除记录,wrapper 可以为 null
int deleteBatchIds(Collection idList)根据ID 批量删除
int deleteById(Serializable id)根据 ID 删除
int deleteByMap(Map<String, Object> map)根据 columnMap 条件,删除记录
int update(T entity, Wrapper updateWrapper)根据 whereEntity 条件,更新记录
int updateById(T entity);根据 ID 修改
  • 查询
方法名描述
T selectById(Serializable id)根据 ID 查询
T selectOne(Wrapper queryWrapper)根据 entity 条件,查询一条记录
List selectBatchIds(Collection idList)根据ID 批量查询
List selectList(Wrapper queryWrapper)根据 entity 条件,查询全部记录
List selectByMap(Map<String, Object> columnMap)根据 columnMap 条件
List<Map<String, Object>> selectMaps(Wrapper queryWrapper)根据 Wrapper 条件,查询全部记录
List selectObjs( Wrapper queryWrapper)根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
IPage selectPage(IPage page, Wrapper queryWrapper)根据 entity 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage page, Wrapper queryWrapper)根据 Wrapper 条件,查询全部记录(并翻页)
Integer selectCount(@Param(Constants.WRAPPER) Wrapper queryWrapper)根据 Wrapper 条件,查询总记录数

3.2 添加

    @Testpublic void testInsert() {Customer customer = new Customer();customer.setCname("测试");customerMapper.insert(customer);}
  • 获得自动增长列信息

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    @Testpublic void testInsert() {Customer customer = new Customer();customer.setCname("测试");customerMapper.insert(customer);System.out.println(customer);}

3.3 更新

  • 通过id更新
    @Testpublic void testUpdate() {Customer customer = new Customer();customer.setCid(15);customer.setCname("测试777");customer.setPassword("777");// 需要给Customer设置@TableIdcustomerMapper.updateById(customer);}
  • 更新所有
	@Testpublic void testUpdate2() {Customer customer = new Customer();customer.setCname("测试777");customer.setPassword("777");// 更新所有customerMapper.update(customer,null);}

3.4 删除

  • 根据id进行删除
@Testpublic void testDelete() {// 需要给Customer设置@TableIdint i = customerMapper.deleteById(11);System.out.println(i);}
  • 批量删除
    @Testpublic void testBatchDelete() {// 需要给Customer设置@TableIdint i = customerMapper.deleteBatchIds(Arrays.asList(9,10));System.out.println(i);}

4 查询

4.1 Map条件

    @Testpublic void testMap(){Map map = new HashMap();map.put("cname","测试");map.put("password","123456");List list = customerMapper.selectByMap(map);list.forEach(System.out::println);}

4.2 Wrapper条件

4.2.1 wrapper介绍

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • Wrapper : 条件构造抽象类,最顶端父类

    • AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
      • QueryWrapper : Entity 对象封装操作类,不是用lambda语法
      • UpdateWrapper : Update 条件封装,用于Entity对象更新操作
      • AbstractLambdaWrapper : Lambda 语法使用 Wrapper统一处理解析 lambda 获取 column。
        • LambdaQueryWrapper :看名称也能明白就是用于Lambda语法使用的查询Wrapper
        • LambdaUpdateWrapper : Lambda 更新封装Wrapper
  • 如果想进行复杂条件查询,那么需要使用条件构造器 Wapper,涉及到如下方法

方法名描述
selectOne根据条件查询一个,结果:0或1,如果查询多个异常
selectCount查询总条数
selectList查询所有
selectMaps将一条记录封装到Map中,最后返回List<Map<Sting,Object>>
selectObjs将一条记录封装到Object中,最后返回List<Object>
update更新指定条件
delete删除指定条件
  • 拼凑条件相关关键字
查询方式说明
setSqlSelect设置 SELECT 查询字段
whereWHERE 语句,拼接 + WHERE 条件
andAND 语句,拼接 + AND 字段=值
andNewAND 语句,拼接 + AND (字段=值)
orOR 语句,拼接 + OR 字段=值
orNewOR 语句,拼接 + OR (字段=值)
eq等于=
allEq基于 map 内容等于=
ne不等于<>
gt大于>
ge大于等于>=
lt小于<
le小于等于<=
like模糊查询 LIKE
notLike模糊查询 NOT LIKE
inIN 查询
notInNOT IN 查询
isNullNULL 值查询
isNotNullIS NOT NULL
groupBy分组 GROUP BY
havingHAVING 关键词
orderBy排序 ORDER BY
orderAscASC 排序 ORDER BY
orderDescDESC 排序 ORDER BY
existsEXISTS 条件语句
notExistsNOT EXISTS 条件语句
betweenBETWEEN 条件语句
notBetweenNOT BETWEEN 条件语句
addFilter自由拼接 SQL
last拼接在最后,例如:last(“LIMIT 1”)

4.2.2 条件查询

  • 基本多条件查询
	@Testpublic void testWrapper(){// 拼凑条件QueryWrapper<Customer> queryWrapper = new QueryWrapper();// 1)模糊查询queryWrapper.like("cname","测试");// 2)等值查询queryWrapper.eq("password","777");// 3)批量查询queryWrapper.in("cid",1,2,3,4);// 4) 范围queryWrapper.ge("money", 800);queryWrapper.le("money" , 1500);// 查询List<Customer> list = customerMapper.selectList(queryWrapper);list.forEach(System.out::println);}
  • 条件判断
    @Testpublic void findCondition2() {Customer customer = new Customer();customer.setPassword("777");customer.setCname("888");customer.setIdList(Arrays.asList(2,3,4));customer.setCid(3);//条件查询QueryWrapper<Customer> queryWrapper = new QueryWrapper<>();// 1) 等值查询queryWrapper.eq( customer.getPassword()!=null ,"password", customer.getPassword());// 2) 模糊查询queryWrapper.like(customer.getCname() != null , "cname",customer.getCname());// 3) in语句queryWrapper.in(customer.getIdList() != null , "cid",customer.getIdList());// 4) 大于等于queryWrapper.ge(customer.getCid() != null , "cid" , customer.getCid());//查询List<Customer> list = customerMapper.selectList(queryWrapper);//list.forEach(customer-> System.out.println(customer));list.forEach(System.out::println);}

4.3.3 条件更新

  • 基本更新
 	@Testpublic void testWrapperUpdate(){//1 更新数据Customer customer = new Customer();customer.setVersion(1);//2 更新条件UpdateWrapper<Customer> updateWrapper = new UpdateWrapper<>();updateWrapper.in("cid", 1,2,3);//3 更新int update = customerMapper.update(customer, updateWrapper);System.out.println(update);}

4.3 分页

4.3.1 内置插件

  • 主体插件: MybatisPlusInterceptor,该插件内部插件集:
    • 分页插件: PaginationInnerInterceptor
    • 多租户插件: TenantLineInnerInterceptor
    • 动态表名插件: DynamicTableNameInnerInterceptor
    • 乐观锁插件: OptimisticLockerInnerInterceptor
    • sql性能规范插件: IllegalSQLInnerInterceptor
    • 防止全表更新与删除插件: BlockAttackInnerInterceptor

4.3.2 配置类

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

package com.czxy.mp.config;import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @author 桐叔* @email liangtong@itcast.cn*/
@Configuration
public class MybatisPlusConfig {/*** 配置插件* @return*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();// 分页插件mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return mybatisPlusInterceptor;}/*** 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)* @return*/@Beanpublic ConfigurationCustomizer configurationCustomizer() {return configuration -> configuration.setUseDeprecatedExecutor(false);}
}

4.3.3 分页

@Testpublic void testPage(){// 分页数据int pageNum = 1;int pageSize = 3;Page<Customer> page = new Page<>(pageNum , pageSize);page.setSearchCount(true);// 查询customerMapper.selectPage(page, null);// 分页数据System.err.println("当前页码:" + page.getCurrent());System.err.println("每页显示记录数:" + page.getSize());System.err.println("总页数:" + page.getPages());System.err.println("总记录数:" + page.getTotal());System.err.println("是否有下一页:" + page.hasNext());System.err.println("是否有上一页:" + page.hasPrevious());// 分页数据列表page.getRecords().forEach(System.err::println);}

5. 常见注解

5.1 表名注解:@TableName

属性描述
value表名
keepGlobalPrefix是否保持使用全局的 tablePrefix 的值(如果设置了全局 tablePrefix 且自行设置了 value 的值)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5.2 主键注解:@TableId

属性描述
value主键字段名
type主键类型
IdType.ASSIGN_UUID ,分配UUID,MyBatisPlus维护String数据
IdType.ASSIGN_ID ,分配ID(默认使用雪花算法)MyBatisPlus维护Long数据
IdType.AUTO ,自动增长(数据库维护)
  • 测试表:User 和 Book

    CREATE TABLE tmp_user(uid VARCHAR(100),uname VARCHAR(50)
    );CREATE TABLE tmp_book(bid long,title VARCHAR(50)
    );
    
  • JavaBean

    • User

      package com.czxy.mp.domain;import com.baomidou.mybatisplus.annotation.IdType;
      import com.baomidou.mybatisplus.annotation.TableId;
      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;/*** @author 桐叔* @email liangtong@itcast.cn*/
      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class User {//@TableId(type = IdType.ASSIGN_UUID)		//随机一个字符串@TableId(type = IdType.ASSIGN_ID)			//随机一个数字(Long)private String uid;private String uname;
      }
    • Book

      package com.czxy.domain;import com.baomidou.mybatisplus.annotation.IdType;
      import com.baomidou.mybatisplus.annotation.TableId;
      import com.baomidou.mybatisplus.annotation.TableName;
      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;/*** @author 桐叔* @email liangtong@itcast.cn*/
      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      @TableName(value="book",keepGlobalPrefix = true)  //表名
      public class Book {@TableId(value = "bid", type = IdType.ASSIGN_ID)private Long bid;private String title;
      }
    • 测试:

      • User的UUID

        package com.czxy;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
        import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
        import com.czxy.domain.Customer;
        import com.czxy.domain.User;
        import com.czxy.mapper.CustomerMapper;
        import com.czxy.mapper.UserMapper;
        import org.junit.Test;
        import org.junit.runner.RunWith;
        import org.springframework.boot.test.context.SpringBootTest;
        import org.springframework.test.context.junit4.SpringRunner;import javax.annotation.Resource;/*** @author 桐叔* @email liangtong@itcast.cn*/
        @RunWith(SpringRunner.class)                                //spring 整合 junit测试
        @SpringBootTest(classes = TestMybatisPlusApplication.class) //spring boot整合junit测试
        public class TestCustomerMapper_04_user {@Resourceprivate UserMapper userMapper;@Testpublic void testUserInsert() {User user = new User();user.setUname(null);user.setUname("jack");int insert = userMapper.insert(user);System.out.println(insert);}}
      • Book的Long ID

        package com.czxy;import com.czxy.domain.Book;
        import com.czxy.domain.User;
        import com.czxy.mapper.BookMapper;
        import org.junit.Test;
        import org.junit.runner.RunWith;
        import org.springframework.boot.test.context.SpringBootTest;
        import org.springframework.test.context.junit4.SpringRunner;import javax.annotation.Resource;/*** @author 桐叔* @email liangtong@itcast.cn*/
        @RunWith(SpringRunner.class)                                //spring 整合 junit测试
        @SpringBootTest(classes = TestMybatisPlusApplication.class) //spring boot整合junit测试
        public class TestCustomerMapper_05_book {@Resourceprivate BookMapper bookMapper;@Testpublic void testUserInsert() {Book book = new Book();book.setTitle("测试");int insert = bookMapper.insert(book);System.out.println(insert);}}

5.2 字段注解(非主键) : @TableField

属性描述
value数据库列名
fill字段自动填充策略
FieldFill.INSERT 插入时填充字段
FieldFill.UPDATE 更新时填充字段
FieldFill.INSERT_UPDATE 插入和更新时填充字段
exist是否存储到数据库(是否是临时数据)

6. 常见配置

mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  #输出日志map-underscore-to-camel-case: true  #驼峰命名global-config:db-config:id-type: auto  #全局配置,id自动增强table-prefix: tmp_ #表名前缀type-aliases-package: com.czxy.mp.domain #别名包扫描路径mapper-locations: classpath*:/mapper/**/*.xml #映射文件位置
  • 整合xml

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.czxy.mp.mapper.CustomerMapper"><select id="findAll" resultType="customer">select * from tmp_customer</select></mapper>
    
  • CustomerMapper对应的方法

    package com.czxy.mp.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.czxy.mp.domain.Customer;
    import org.apache.ibatis.annotations.Mapper;import java.util.List;/*** @author 桐叔* @email liangtong@itcast.cn*/
    @Mapper
    public interface CustomerMapper extends BaseMapper<Customer> {public List<Customer> findAll();
    }

7. 高级(插件机制)

7.1 自动填充

项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。

我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作:

7.1.1 原理

  • 实现元对象处理器接口:com.baomidou.mybatisplus.core.handlers.MetaObjectHandler,确定填充具体操作
  • 注解填充字段:@TableField(fill = ...) 确定字段填充的时机
    • FieldFill.INSERT:插入填充字段
    • FieldFill.UPDATE:更新填充字段
    • FieldFill.INSERT_UPDATE:插入和更新填充字段

7.1.2 基本操作

  • 步骤一:修改表添加字段
alter table tmp_customer add column create_time date;
alter table tmp_customer add column update_time date;
  • 步骤二:修改JavaBean
package com.czxy.mp.domain;import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;import java.util.Date;/*** Created by liangtong.*/
@Data
@TableName("tmp_customer")
public class Customer {@TableId(type = IdType.AUTO)private Integer cid;private String cname;private String password;private String telephone;private String money;@TableField(value="create_time",fill = FieldFill.INSERT)private Date createTime;@TableField(value="update_time",fill = FieldFill.UPDATE)private Date updateTime;}
  • 步骤三:编写处理类

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

package com.czxy.mp.handler;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;import java.util.Date;/*** @author 桐叔* @email liangtong@itcast.cn*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {/*** 插入填充* @param metaObject*/@Overridepublic void insertFill(MetaObject metaObject) {this.setFieldValByName("createTime", new Date(), metaObject);}/*** 更新填充* @param metaObject*/@Overridepublic void updateFill(MetaObject metaObject) {this.setFieldValByName("updateTime", new Date(), metaObject);}
}
  • 步骤四:测试
    @Testpublic void testInsert() {Customer customer = new Customer();customer.setCname("测试888");customerMapper.insert(customer);}@Testpublic void testUpdate() {Customer customer = new Customer();customer.setCid(11);customer.setTelephone("999");customerMapper.updateById(customer);}

7.2 乐观锁

  • 基于数据库的锁,处理的并发问题(两个操作对应一个数据)

    • 乐观锁:数据不一致,不会发生。读锁。

    • 悲观锁:数据不一致,肯定发生。写锁,又称为排它锁。

7.2.1 什么是乐观锁

  • 目的:数据必须同步。当要更新一条记录的时候,希望这条记录没有被别人更新

  • 乐观锁实现方式:(万一发生了数据不一致的解决方案)

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败

7.2.2. 实现

  • 步骤:

    • 步骤1:环境准备(表version字段、JavaBean versoin属性、必须提供默认值)
    • 步骤2:使用乐观锁版本控制 @Version
    • 步骤3:开启乐观锁插件配置
  • 步骤一:修改表结构,添加version字段

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 步骤二:修改JavaBean,添加version属性

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    package com.czxy.mp.domain;import com.baomidou.mybatisplus.annotation.*;
    import lombok.Data;/*** Created by liangtong.*/
    @Data
    @TableName("tmp_customer")
    public class Customer {@TableId(type = IdType.AUTO)private Integer cid;private String cname;private String password;private String telephone;private String money;@Version@TableField(fill = FieldFill.INSERT)private Integer version;
    }
  • 步骤三:元对象处理器接口添加version的insert默认值 (保证version有数据)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

package com.czxy.mp.config;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;import java.util.Date;/*** Created by liangtong.*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {/*** 插入填充* @param metaObject*/@Overridepublic void insertFill(MetaObject metaObject) {this.setFieldValByName("createTime", new Date(), metaObject);this.setFieldValByName("version", 1, metaObject);}/*** 更新填充* @param metaObject*/@Overridepublic void updateFill(MetaObject metaObject) {this.setFieldValByName("updateTime", new Date(), metaObject);}
}
  • 步骤四:修改 MybatisPlusConfig 开启乐观锁
package com.czxy.mp.config;import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** Created by liangtong.*/
@Configuration
public class MybatisPlusConfig {*//*** 配置插件* @return*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();// 分页插件mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));// 乐观锁mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return mybatisPlusInterceptor;}
}
  • 步骤五:测试
    • 先添加一条,保证version有数据
    • 在更新该条
    @Testpublic void testUpdate() {Customer customer = new Customer();customer.setCid(14);customer.setCname("测试999");// 与数据库中数据一致,将更新成功,否则返回失败。customer.setVersion(1);int i = customerMapper.updateById(customer);System.out.println(i);}

7.2.3 注意事项

  • 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
  • 整数类型下 newVersion = oldVersion + 1
  • newVersion 会回写到 entity
  • 仅支持 updateById(id)update(entity, wrapper) 方法
  • update(entity, wrapper) 方法下, wrapper 不能复用!!!
  • 数据库表的version字段,必须有默认值(SQL语句默认值、或MyBatisPlus自动填充)
  • 在进行更新操作时,必须设置version值,否则无效。

7.3 逻辑删除

7.3.1 什么是逻辑删除

  • 逻辑删除,也称为假删除。就是在表中提供一个字段用于记录是否删除,实际该数据没有被删除。

7.3.2 实现

  • 步骤:

    • 步骤一:环境(表提供字段deleted、JavaBean属性 deleted、填充默认值0)
    • 步骤二:修改JavaBean,添加注解 @TableLogic
  • 步骤一:修改表结构添加deleted字段

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 步骤二:修改JavaBean,给deleted字段添加@TableLogic

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    package com.czxy.mp.domain;import com.baomidou.mybatisplus.annotation.*;
    import lombok.Data;/*** Created by liangtong.*/
    @Data
    @TableName("tmp_customer")
    public class Customer {@TableId(type = IdType.AUTO)private Integer cid;private String cname;private String password;private String telephone;private String money;@Version@TableField(fill = FieldFill.INSERT)private Integer version;@TableLogic@TableField(fill = FieldFill.INSERT)private Integer deleted;
    }
  • 步骤三:添加数据时,设置默认“逻辑未删除值”

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    package com.czxy.mp.config;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
    import org.apache.ibatis.reflection.MetaObject;
    import org.springframework.stereotype.Component;import java.util.Date;/*** Created by liangtong.*/
    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {/*** 插入填充* @param metaObject*/@Overridepublic void insertFill(MetaObject metaObject) {this.setFieldValByName("createTime", new Date(), metaObject);this.setFieldValByName("version", 1, metaObject);this.setFieldValByName("deleted", 0, metaObject);}/*** 更新填充* @param metaObject*/@Overridepublic void updateFill(MetaObject metaObject) {this.setFieldValByName("updateTime", new Date(), metaObject);}
    }
  • 步骤四:测试

        @Testpublic void testDelete() {// 删除时,必须保证deleted数据为“逻辑未删除值”int i = customerMapper.deleteById(12);System.out.println(i);}
    

7.3.3 注意

  • 如果使用逻辑删除,将delete语句,修改成了update,条件 where deleted=0
  • 同时,查询语句自动追加一个查询条件 WHERE deleted=0。如果查询没有数据,检查deleted字段的值。

7.3.4 全局配置

  • 如果使用了全局配置,可以不使用注解@TableLogic
mybatis-plus:global-config:db-config:logic-delete-field: deleted  # 局逻辑删除的实体字段名logic-delete-value: 1  # 逻辑已删除值(默认为 1)logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

7.3.5 恢复

  • 问题:进行逻辑删除后的数据,如何恢复(recovery)?

    • 需要自己编写SQL语句
  • 方案1:使用sql具有,更新deleted=0即可

    UPDATE `tmp_customer` SET `deleted`='0' WHERE `cid`='13';
    
  • 方案2:直接使用update语句,不能解决问题。

        @Testpublic void testUpdate() {Customer customer = new Customer();customer.setCid(13);customer.setDeleted(0);//更新customerMapper.updateById(customer);}
    
  • 方案3:修改Mapper,添加 recoveryById 方法,进行数据恢复。

    @Mapper
    public interface CustomerMapper extends BaseMapper<Customer> {@Update("update tmp_customer set deleted = 0 where cid = #{cid}")public void recoveryById(@Param("cid") Integer cid);
    }

8. 通用Service

8.1 分析

  • 通用service封装了service层常见的CURD方法

  • 通用Service分析

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

8.2 基本使用

  • 标准service:接口 + 实现

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    • service接口

        package com.czxy.service;import com.baomidou.mybatisplus.extension.service.IService;import com.czxy.domain.Customer;/*** @author 桐叔* @email liangtong@itcast.cn*/public interface CustomerService extends IService<Customer> {}
    • service实现类

      package com.czxy.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
      import com.czxy.domain.Customer;
      import com.czxy.mapper.CustomerMapper;
      import com.czxy.service.CustomerService;
      import org.springframework.stereotype.Service;
      import org.springframework.transaction.annotation.Transactional;/*** @author 桐叔* @email liangtong@itcast.cn*/
      @Service
      @Transactional
      public class CustomerServiceImpl extends ServiceImpl<CustomerMapper,Customer> implements CustomerService {}

8.3 常见方法

  • 查询所有
  • 添加
  • 修改
  • 删除
package com.czxy.test;import com.czxy.mp.Day62MybatisPlusApplication;
import com.czxy.mp.domain.Customer;
import com.czxy.mp.service.CustomerService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import javax.annotation.Resource;
import java.util.List;/*** @author 桐叔* @email liangtong@itcast.cn*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Day62MybatisPlusApplication.class)
public class TestDay62CustomerService {@Resourceprivate CustomerService customerService;@Testpublic void testSelectList() {List<Customer> list = customerService.list();list.forEach(System.out::println);}@Testpublic void testInsert() {Customer customer = new Customer();customer.setCname("张三");customer.setPassword("9999");// 添加customerService.save(customer);}@Testpublic void testUpdate() {Customer customer = new Customer();customer.setCid(14);customer.setCname("777");customer.setPassword("777");customerService.updateById(customer);}@Testpublic void testSaveOrUpdate() {Customer customer = new Customer();customer.setCid(15);customer.setCname("999");customer.setPassword("99");customerService.saveOrUpdate(customer);}@Testpublic void testDelete() {customerService.removeById(15);}
}

9 新功能

9.1 执行SQL分析打印

该功能依赖 p6spy 组件,完美的输出打印 SQL 及执行时长

  • p6spy 依赖引入

    <dependency><groupId>p6spy</groupId><artifactId>p6spy</artifactId><version>3.9.1</version>
    </dependency>
    
  • 核心yml配置

    spring:datasource:# p6spy 提供的驱动代理类,driver-class-name: com.p6spy.engine.spy.P6SpyDriver# url 固定前缀为 jdbc:p6spy,跟着冒号为对应数据库连接地址url: jdbc:p6spy:mysql://127.0.0.1:3306...
    

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • spy.properties 配置

    #3.2.1以上使用
    modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
    #3.2.1以下使用或者不配置
    #modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
    # 自定义日志打印
    logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
    #日志输出到控制台
    appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
    # 使用日志系统记录 sql
    #appender=com.p6spy.engine.spy.appender.Slf4JLogger
    # 设置 p6spy driver 代理
    deregisterdrivers=true
    # 取消JDBC URL前缀
    useprefix=true
    # 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
    excludecategories=info,debug,result,commit,resultset
    # 日期格式
    dateformat=yyyy-MM-dd HH:mm:ss
    # 实际驱动可多个
    #driverlist=org.h2.Driver
    # 是否开启慢SQL记录
    outagedetection=true
    # 慢SQL记录标准 2 秒
    outagedetectioninterval=2

9.2 数据安全保护

为了保护数据库配置及数据安全,在一定的程度上控制开发人员流动导致敏感信息泄露

  • 步骤:

    • 步骤1:使用 AES 工具类,生成秘钥
    • 步骤2:使用 AES工具类,根据步骤1生成的秘钥对敏感信息进行加密
    • 步骤3:设置加密后的配置信息
    • 步骤4:启动服务时,使用秘钥
  • 步骤1-2:使用工具类生成秘钥以及对敏感信息进行加密

    package com.czxy;import com.baomidou.mybatisplus.core.toolkit.AES;
    import org.junit.Test;/*** @author 桐叔* @email liangtong@itcast.cn*/
    public class TestAES {@Testpublic void testAes() {String randomKey = AES.generateRandomKey();String url =  "jdbc:p6spy:mysql://127.0.0.1:3306/zx_edu_teacher?useUnicode=true&characterEncoding=utf8";String username = "root";String password = "1234";String urlAES = AES.encrypt(url, randomKey);String usernameAES = AES.encrypt(username, randomKey);String passwordAES = AES.encrypt(password, randomKey);System.out.println("--mpw.key=" + randomKey);System.out.println("mpw:" + urlAES);System.out.println("mpw:" + usernameAES);System.out.println("mpw:" + passwordAES);}
    }
    // Jar 启动参数( idea 设置 Program arguments , 服务器可以设置为启动环境变量 )
    //--mpw.key=fddd2b7a67460e16
    //mpw:7kSEISvq3QWfnSh6vQZc2xgE+XF/sJ0WS/sgGkYpCOTQRjO1poLi3gfmGZNOwKzfqZUec0odiwAdmxcS7lfueENGIx8OmIe//d9imrGFpnkrf8jNSHdzfNPCUi3MbmUb
    //mpw:qGbCMksqA90jjiGXXRr7lA==
    //mpw:xKG9GABlywqar6CGPOSJKQ==
    
  • 步骤3:配置加密信息

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 步骤4:使用秘钥启动服务

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

end

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/209228.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

QT之常用按钮组件

QT之常用按钮组件 导入图标 布局 显示选中 实验结果 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }void Widget::on_push…

mybatis 的快速入门以及基于spring boot整合mybatis(一)

MyBatis基础 MyBatis是一款非常优秀的持久层框架&#xff0c;用于简化JDBC的开发 准备工作&#xff1a; 1&#xff0c;创建sprong boot工程&#xff0c;引入mybatis相关依赖2&#xff0c;准备数据库表User&#xff0c;实体类User3&#xff0c; 配置MyBatis&#xff08;在applic…

前端打包环境配置步骤

获取node安装包并解压 获取node安装包 wget https://npmmirror.com/mirrors/node/v16.14.0/node-v16.14.0-linux-x64.tar.xz 解压 tar -xvf node-v16.14.0-linux-x64.tar.xz 创建软链接 sudo ln -s 此文件夹的绝对路径/bin/node /usr/local/bin/node&#xff0c;具体执行如下…

实现手机扫码——扫描识别路由器参数

有个应用是批量自动检测无线路由器&#xff0c;检测前需要自动登录路由器的管理界面进行设置&#xff0c;如设置wifi参数、连接模式&#xff0c;或者恢复出厂设置等。进入管理界面的登录用户名是admin&#xff0c;密码则各不相同。此外也需要知道路由器的MAC地址&#xff0c;因…

【已解决】Win7虚拟机安装VMtools报错

在做以前的实验的时候发现要用到Win7虚拟机&#xff0c;于是就安装了一个Win7的虚拟机&#xff0c;但是发现屏幕太小&#xff0c;而且来回复制文本、复制文件太不方便了&#xff0c;索性就安装了VMtools&#xff0c;发现还安装不成– 情况1 报错&#xff1a;本程序需要您将此…

视频转场PR素材|专业级电影故障闪光效果视频过渡PR转场模板

这是一个以故障为主题的专业级电影故障闪光效果视频过渡pr转场模板。使用这些效果来增强视觉效果。包含视频教程。适用软件&#xff1a;Premiere Pro 2023|分辨率&#xff1a;38402160&#xff08;4K&#xff09; 来自PR模板网&#xff1a;https://prmuban.com/36092.html

数据库后门是什么?我们要如何预防它的危害

数据库后门是黑客在数据库中安装的一种特殊程序或代码&#xff0c;可以绕过正常的认证和授权机制&#xff0c;从而获取数据库的敏感信息或者控制整个数据库。黑客可以通过各种方式安装后门&#xff0c;比如利用漏洞、钓鱼、社会工程学等。 数据库后门的危害主要体现在以下几个方…

10 大 Android 手机系统修复软件深度评测

您的新 Android 手机可能因其令人兴奋的性能而印象深刻。然而&#xff0c;随着时间的推移&#xff0c;您可能会发现系统有些地方与以前不太一样。您可能会遇到屏幕无响应、 Android应用程序崩溃、连接问题、电池耗尽等现象。 10 大 Android 手机系统修复软件 好吧&#xff0c;…

Java网络通信-第21章

Java网络通信-第21章 1.网络程序设计基础 网络程序设计基础涵盖了许多方面&#xff0c;包括网络协议、Web开发、数据库连接、安全性等。 1.1局域网与互联网 局域网&#xff08;LAN&#xff09;与互联网&#xff08;Internet&#xff09;是两个不同的概念&#xff0c;它们分…

老胡的周刊(第119期)

老胡的信息周刊[1]&#xff0c;记录这周我看到的有价值的信息&#xff0c;主要针对计算机领域&#xff0c;内容主题极大程度被我个人喜好主导。这个项目核心目的在于记录让自己有印象的信息做一个留存以及共享。 &#x1f3af; 项目 Weekly Hub[2] 汇聚优质精选技术周刊&#x…

虹科Pico汽车示波器 | 汽车免拆检修 | 2018款东风风神AX7车发动机怠速抖动、加速无力

一、故障现象 一辆2018款东风风神AX7车&#xff0c;搭载10UF01发动机&#xff0c;累计行驶里程约为5.3万km。该车因发动机怠速抖动、加速无力及发动机故障灯异常点亮而进厂维修&#xff0c;维修人员用故障检测仪检测&#xff0c;提示气缸3失火&#xff1b;与其他气缸对调点火线…

【开源】基于Vue.js的房屋出售出租系统

文末获取源码&#xff0c;项目编号&#xff1a; S 083 。 \color{red}{文末获取源码&#xff0c;项目编号&#xff1a;S083。} 文末获取源码&#xff0c;项目编号&#xff1a;S083。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 房屋销售模块2.2 房屋出租模块2.3 预…

HDFS Java API 基本操作实验

文章目录 一、实验环境二、实验内容&#xff08;一&#xff09;数据准备&#xff08;二&#xff09;编程环境准备&#xff08;三&#xff09;使用Hadoop API操作HDFS文件系统&#xff08;四&#xff09;使用Hadoop API Java IO流操作HDFS文件系统 三、实验步骤&#xff08;一&…

智慧城市是什么?为什么要建智慧城市?

智慧城市是一个通过现代科技手段推动城市管理和服务创新的概念。 具体来说&#xff0c;它利用信息技术和创新概念&#xff0c;将城市的各个系统和服务集成起来&#xff0c;以提升城市运行效率、优化城市管理和服务&#xff0c;改善市民的生活质量。 为什么要建智慧城市呢&…

土壤水分传感器土壤体积含水率含量监测仪器

产品概述 外型小巧轻便&#xff0c;便于携带和连接。 土壤水分传感器由电源模块、变送模块、漂零及温度补偿模块、数据处理模块等组成。传感器内置信号采样及放大、漂零及温度补偿功能&#xff0c;用户接口简洁、方便。 功能特点 ◆本传感器体积小巧化设计&#xff0c;测量…

pr抖音素材42个手机竖屏抖音视频转场特效PR剪辑模板

酷炫、富有创意的Premiere Pro 视频转场动画过渡效果pr模板免费下载。增强内容演示、幻灯片、抖音、社交媒体广告、预告片、促销等视频画面切换效果。 来自PR模板网&#xff1a;https://prmuban.com/36404.html

Git使用指南

文章目录 一、Git概念二、Git命令清单1、新建代码库2、配置3、增加/删除文件4、代码提交5、分支6、标签7、查看信息8、远程同步9、撤销10、其他 三、常用高级操作1、把其他分支代码摘到当前分支2、重置刚刚提交的代码 一、Git概念 Git流程图 相关概念说明&#xff1a; Work…

书籍推荐: 深入理解Go并发编程

一书在手&#xff0c;并发无忧 收到了鸟窝老师历时五载写就的新作「深入理解Go并发编程」 迫不及待开卷阅览&#xff0c;大呼过瘾&#xff0c;最大感触是诚如副标题所言&#xff0c;“从原理到实践&#xff0c;看这本就够了”。 对并发编程优雅简洁的支持&#xff0c;是Go最大的…

探索无监督域自适应,释放语言模型的力量:基于检索增强的情境学习实现知识迁移...

深度学习自然语言处理 原创作者: Xnhyacinth 在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;如何有效地进行无监督域自适应(Unsupervised Domain Adaptation, UDA) 一直是研究的热点和挑战。无监督域自适应的目标是在目标域无标签的情况下&#xff0c;将源域的知识…