天天看點

MyBatis增強器——Mybatis-Plus

MyBatis增強器——Mybatis-Plus

  • ​​一、Mybatis-Plus簡介​​
  • ​​1.簡介​​
  • ​​2.特性​​
  • ​​3.支援資料庫​​
  • ​​4.架構結構​​
  • ​​二、入門案例​​
  • ​​1.開發環境​​
  • ​​2.建立資料庫及表​​
  • ​​建立表​​
  • ​​添加資料​​
  • ​​3.建立SpringBoot工程​​
  • ​​4.編寫代碼​​
  • ​​三、基本crud​​
  • ​​四、常用注解​​
  • ​​1.@TableName​​
  • ​​通過@TableName解決問題​​
  • ​​通過全局配置解決問題​​
  • ​​2.@TableId​​
  • ​​通過@TableId解決問題​​
  • ​​@TableId的value屬性​​
  • ​​@TableId的type屬性​​
  • ​​3.@TableField​​
  • ​​4.@TableLogic​​
  • ​​邏輯删除​​
  • ​​五、條件構造函數和常用接口​​
  • ​​1.wapper介紹​​
  • ​​2.QueryWrapper​​
  • ​​3.**UpdateWrapper**​​
  • ​​4、condition​​
  • ​​5、LambdaQueryWrapper​​
  • ​​6、LambdaUpdateWrapper​​
  • ​​六、插件​​
  • ​​1.分頁插件​​
  • ​​2.xml自定義分頁​​
  • ​​3.樂觀鎖​​
  • ​​a>場景​​
  • ​​b>樂觀鎖與悲觀鎖​​
  • ​​c>模拟修改沖突​​
  • ​​d>樂觀鎖實作流程​​
  • ​​e>Mybatis-Plus實作樂觀鎖​​
  • ​​七、通用枚舉​​
  • ​​八、代碼生成器​​
  • ​​九、多資料源​​
  • ​​十、MybatisX插件​​

一、Mybatis-Plus簡介

1.簡介

MyBatis-Plus(簡稱 MP)是一個 MyBatis的增強工具**,在 MyBatis 的基礎上隻做增強不做改變,為簡化開發、提高效率而生**。

2.特性

無侵入:隻做增強不做改變,引入它不會對現有工程産生影響,如絲般順滑

損耗小:啟動即會自動注入基本 CURD,性能基本無損耗,直接面向對象操作

強大的 CRUD 操作:内置通用 Mapper、通用 Service,僅僅通過少量配置即可實作單表大部分

CRUD 操作,更有強大的條件構造器,滿足各類使用需求

支援 Lambda 形式調用:通過 Lambda 表達式,友善的編寫各類查詢條件,無需再擔心字段寫錯

支援主鍵自動生成:支援多達 4 種主鍵政策(内含分布式唯一 ID 生成器 - Sequence),可自由

配置,完美解決主鍵問題

支援 ActiveRecord 模式:支援 ActiveRecord 形式調用,實體類隻需繼承 Model 類即可進行強

大的 CRUD 操作

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

内置代碼生成器:采用代碼或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、Controller 層代碼,支援模闆引擎,更有超多自定義配置等您來使用

内置分頁插件:基于 MyBatis 實體分頁,開發者無需關心具體操作,配置好插件之後,寫分頁等同于普通 List 查詢

分頁插件支援多種資料庫:支援 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多種資料庫

内置性能分析插件:可輸出 SQL 語句以及其執行時間,建議開發測試時啟用該功能,能快速揪出慢查詢

内置全局攔截插件:提供全表 delete 、 update 操作智能分析阻斷,也可自定義攔截規則,預防誤操作

3.支援資料庫

任何能使用MyBatis進行 CRUD, 并且支援标準 SQL 的資料庫,具體支援情況如下

MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb

達夢資料庫,虛谷資料庫,人大金倉資料庫,南大通用(華庫)資料庫,南大通用資料庫,神通資料庫,瀚高資料庫

4.架構結構

MyBatis增強器——Mybatis-Plus

二、入門案例

1.開發環境

IDE:idea 2019.2

JDK:JDK8+

建構工具:maven 3.5.4

MySQL版本:MySQL 5.7

Spring Boot:2.6.3

MyBatis-Plus:3.5.1

2.建立資料庫及表

建立表
CREATE DATABASE `mybatis_plus` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; 
use `mybatis_plus`; 
CREATE TABLE `user` ( 
    `id` bigint(20) NOT NULL COMMENT '主鍵ID', 
    `name` varchar(30) DEFAULT NULL COMMENT '姓名', 
    `age` int(11) DEFAULT NULL COMMENT '年齡', 
    `email` varchar(50) DEFAULT NULL COMMENT '郵箱', 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8;      
添加資料
INSERT INTO user (id, name, age, email) VALUES 
(1, 'Jone', 18, '[email protected]'), 
(2, 'Jack', 20, '[email protected]'), 
(3, 'Tom', 28, '[email protected]'), 
(4, 'Sandy', 21, '[email protected]'), 
(5, 'Billie', 24, '[email protected]');      

3.建立SpringBoot工程

使用 Spring Initializr快速初始化一個SpringBoot工程

引入依賴

<dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>

        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>3.5.1version>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope> 
        dependency>
    dependencies>      

4.編寫代碼

配置application.yml

spring:
  #配置資料源資訊
  datasource:
#    配置資料源類型
    type: com.zaxxer.hikari.HikariDataSource

#    配置連接配接資料庫資訊
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSl=false
    username: root
    password: 123456      
注意連接配接位址url

MySQL5.7版本的url:
jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false

MySQL8.0版本的url:
jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false

否則運作測試用例報告如下錯誤:
java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more
      

啟動類

在Spring Boot啟動類中添加@MapperScan注解,掃描mapper包

@SpringBootApplication
//掃描mapper接口所在的包
@MapperScan("com.mybatisplus.mapper")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}      

添加實體

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email; 
}      

添加mapper

BaseMapper是Mybatis-plus提供的模闆mapper,其中包含了基本的CRUD方法,泛型為操作的實體類型

@Mapper
public interface UserMapper extends BaseMapper<User> { 
}      

測試

@SpringBootTest 
public class MybatisPlusTest { 
  @Autowired private UserMapper userMapper; 
  @Test public void testSelectList(){ 
  //selectList()根據MP内置的條件構造器查詢一個list集合,null表示沒有條    //件,即查詢所有             userMapper.selectList(null).forEach(System.out::println); 
  } 
}      

注意:

IDEA在userMapper處報錯,因為找不到注入的對象,因為類是動态建立的,但是程式可以正确的執行,為了避免報錯,可以在mapper接口上添加@Respository、

添加日志

在application.yml中配置日志輸出

# 配置MyBatis日志 
mybatis-plus: 
  configuration: 
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl      

三、基本crud

1.BaseMapper

Mybatis-plus中的基本CRUD在内置的BaseMapper中都已經得到了實作,我們可以直接使用

2.插入

@SpringBootTest
public class MybatisPlusTest {
    @Test
    public void testInsert(){
        User user = new User(null,"王世超",23,"[email protected]");
        int result = userMapper.insert(user);
        System.out.println("受影響行數"+result);
        System.out.println("id自動擷取"+user.getId());
    }
 }      

3.删除

通過id删除記錄

@Test
public void testDeleteById(){ 
    //通過id删除使用者資訊 
    //DELETE FROM user WHERE id=? int result =      userMapper.deleteById(1475754982694199298L);    System.out.println("受影響行數:"+result); 
}      

4.修改

@Test
public void testUpdateById(){ 
User user = new User(4L, "admin", 22, null); 
    //UPDATE user SET name=?, age=? WHERE id=? 
    int result = userMapper.updateById(user); 
    System.out.println("受影響行數:"+result); 
}      

5.查詢

@Test
public void testSelectById(){ 
    //根據id查詢使用者資訊
    //SELECT id,name,age,email FROM user WHERE id=? 
    User user = userMapper.selectById(4L); 
    System.out.println(user); 
 }      

6.通用Service

說明:
通用 Service CRUD 封裝IService接口,進一步封裝 CRUD 采用 get 查詢單行 remove 删除 list 查詢集合 page 分頁 字首命名方式區分 Mapper 層避免混淆,
泛型 T 為任意實體對象
建議如果存在自定義通用 Service 方法的可能,請建立自己的 IBaseService 繼承
Mybatis-Plus 提供的基類
官網位址:https://baomidou.com/pages/49cc81/#service-crud-%E6%8E%A5%E5%8F%A3
      

a>IService

MyBatis-Plus中有一個接口 IService和其實作類 ServiceImpl,封裝了常見的業務層邏輯,詳情檢視源碼IService和ServiceImpl

b>建立Service接口和實作類

/**
 * UserService繼承IService模闆提供的基礎功能
 */
public interface UserService extends IService<User> {}


/*** ServiceImpl實作了IService,提供了IService中基礎功能的實作 * 若ServiceImpl無法滿足業務需求,則可以使用自定的UserService定義方法,并在實作類中實作 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService{}      

四、常用注解

1.@TableName

經過以上的測試,在使用MyBatis-Plus實作基本的CRUD時,我們并沒有指定要操作的表,隻是在Mapper接口繼承BaseMapper時,設定了泛型User,而操作的表為user表
由此得出結論,MyBatis-Plus在确定操作的表時,由BaseMapper的泛型決定,即實體類型決定,且預設操作的表名和實體類型的類名一緻      

提出問題若實體類型的類名和要操作的表的表名不一緻,會出現什麼問題?

程式抛出異常,Table ‘mybatis_plus.user’ doesn’t exist,因為現在的表名為t_user,而預設操作的表名和實體類型的類名一緻,即user表

通過@TableName解決問題

隻需要在實體類上添加@TableName(“t_user”),辨別實體類對應的表,即可成功執行SQL語句

@TableName("t_user")
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}      
通過全局配置解決問題

在開發的過程中,我們經常遇到以上的問題,即實體類所對應的表都擁有固定的字首,例如t_或tbl_此時,可以使用MyBatis-Plus提供的全局配置,為實體類所對應的表名設定預設的字首,那麼就不需要在每個實體類上通過@TableName辨別實體類對應的表

mybatis-plus: 
  configuration: 
    # 配置MyBatis日志 
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 
  global-config: 
    db-config: 
            # 配置MyBatis-Plus操作表的預設字首 
            table-prefix: t_      

2.@TableId

經過以上的測試,MyBatis-Plus在實作CRUD時,會預設将id作為主鍵列,并在插入資料時,預設基于雪花算法的政策生成id

提出問題若實體類和表中表示主鍵的不是id,而是其它字段,例如uid,Mybatis-Plus會自動識别uid為主鍵列嗎?我們實體類中的屬性id改為uid,将表中的字段id也改為uid,測試添加功能

這時候程式抛出異常,Field ‘uid’ doesn’t have a default value,說明MyBatis-Plus沒有将uid作為主鍵指派

通過@TableId解決問題

在實體類中uid屬性上通過@TableId将其辨別為主鍵,即可成功執行SQL語句

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_user")
public class User {
    @TableId
    private Long uid;
    private String name;
    private Integer age;
    private String email;
}      
@TableId的value屬性

若實體類中對應的屬性為id,而表中表示主鍵的字段為uid,此時若隻在屬性id上添加注解@TableId,則抛出異常Unknown column ‘id’ in ‘field list’,即MyBatis-Plus仍然會将id作為表的主鍵操作,而表中表示主鍵的是字段uid

此時需要通過過@TableId注解的value屬性,指定表中的主鍵字段,@TableId(“uid”)或@TableId(value=“uid”)

@TableName("t_user")
public class User {
    @TableId(value = "uid")
    private Long id;
    private String name;
    private Integer age;
    private String email;
}      
@TableId的type屬性

type屬性用來定義主鍵政策

常用的主鍵政策:

描述
IdType.ASSIGN_ID(預設) 基于雪花算法的政策生成資料id,與資料庫id是否設定自增無關
IdType.AUTO 使用資料庫的自增政策,注意,該類型請確定資料庫設定了id自增,

配置全局主鍵政策:

mybatis-plus: 
  configuration: 
    # 配置MyBatis日志 
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 
  global-config: 
    db-config: 
      # 配置MyBatis-Plus操作表的預設字首 
      table-prefix: t_ 
      # 配置MyBatis-Plus的主鍵政策 
      id-type: auto      

3.@TableField

經過以上的測試,我們可以發現,MyBatis-Plus在執行SQL語句時,要保證明體類中的屬性名和表中的字段名一緻,如果實體類中的屬性名和字段名不一緻的情況,會出現什麼問題呢?

情況1

若實體類中的屬性使用的是駝峰命名風格,而表中的字段使用的是下劃線命名風格

例如實體類屬性userName,表中字段user_name

此時MyBatis-Plus會自動将下劃線命名風格轉化為駝峰命名風格

相當于在MyBatis中配置

情況2

若實體類中的屬性和表中的字段不滿足情況1

例如實體類屬性name,表中字段username

此時需要在實體類屬性上使用@TableField(“username”)設定屬性所對應的字段名

@TableName("t_user")
public class User {
    @TableId(value = "uid")
    private Long id;
    @TableField("username")
    private String name;
    private Integer age;
    private String email;
}      

4.@TableLogic

邏輯删除

實體删除:真實删除,将對應的資料從資料庫删除,之後查詢不到此條被删除的資料

邏輯删除:假删除,将對應資料中代表是否被删除字段的狀态修改為“被删除狀态”,之後在 資料庫中仍舊看到此條資料記錄

使用場景:可以進行資料恢複

@TableName("t_user")
public class User {
    @TableId(value = "uid")
    private Long id;
    @TableField("username")
    private String name;
    private Integer age;
    private String email;
    @TableLogic
    private Integer isDeleted;
}      

測試

測試删除功能,真正執行的是修改
UPDATE t_user SET is_deleted=1 WHERE id=? AND is_deleted=0

測試查詢功能,被邏輯删除的資料預設不會被查詢
SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0
      

五、條件構造函數和常用接口

1.wapper介紹

MyBatis增強器——Mybatis-Plus

Wrapper : 條件構造抽象類,最頂端父類

AbstractWrapper : 用于查詢條件封裝,生成 sql 的 where 條件

QueryWrapper : 查詢條件封裝

UpdateWrapper : Update 條件封裝

AbstractLambdaWrapper : 使用Lambda 文法

LambdaQueryWrapper :用于Lambda文法使用的查詢Wrapper

LambdaUpdateWrapper : Lambda 更新封裝Wrapper

2.QueryWrapper

a.組裝查詢條件

@Test
    public void test01(){
//     查詢條件包含a 年齡在20到30之間,并且郵箱不為null的使用者資訊
        //SELECT id,username AS name,age,email,is_deleted FROM t_user
        // WHERE is_deleted=0 AND (username LIKE ?
        // AND age BETWEEN ? AND ? AND email IS NOT NULL)
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.like("username","張")
                .between("age",20,30)
                .isNotNull("email");
        List<User> lists = userMapper.selectList(wrapper);
        lists.forEach(System.out::println);
    }      

b.組裝排序條件

@Test
    public void test02(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.orderByDesc("age")
                .orderByAsc("uid");
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }      

c.組裝删除條件

@Test
    public void test03(){
//        删除email為空的使用者
        //DELETE FROM t_user WHERE (email IS NULL)
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.isNull("email");
//        條件構造器也可以建構删除語句的條件
        int delete = userMapper.delete(queryWrapper);
        System.out.println("受影響的行數"+delete);
    }      

d.條件的優先級

@Test
    public void  test04(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like("username","a")
                .gt("age",20)
                .or()
                .isNull("email");
        User user = new User();
        user.setAge(18);
        user.setEmail("[email protected]");
        int update = userMapper.update(user, queryWrapper);
        System.out.println("受影響的行數"+update);
    }      
@Test
    public void  test041(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like("username","a")
                .and(i -> i.gt("age",20).or().isNull("email"));
        User user = new User();
        user.setAge(18);
        user.setEmail("[email protected]");
        int update = userMapper.update(user, queryWrapper);
        System.out.println("受影響的行數"+update);
    }      

e.組裝select子句

@Test
    public void test05(){
//        查詢使用者資訊的username和age字段
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("username","age");
        List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
        maps.forEach(System.out::println);
    }      

f.實作子查詢

@Test
    public void  test06(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.inSql("id","select id from t_user where id <= 3");
        List<User> list = userMapper.selectList(queryWrapper);
        list.forEach(System.out::println);
    }      

3.UpdateWrapper

@Test
    public void test07(){
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
//        将(年齡大于20或郵箱為null)并且使用者中包含有a的使用者資訊修改
//        組裝set子句以及修改條件
//        lambda表達式内的邏輯優先運算
        updateWrapper
                .set("age",18)
                .set("email","[email protected]")
                .like("username","a")
                .and(i -> i.gt("age",20).or().isNull("email"));
//        這裡必須要建立user對象,否則無法應用自動填充,如果沒有自動填充,可以設定為null
        int update = userMapper.update(null, updateWrapper);
        System.out.println(update);
    }      

4、condition

在真正的開發過程中,組裝條件是常見的功能,而這些條件資料來源與使用者輸入,是可選的,是以我們在組裝這些條件時,必須先判斷使用者是否選擇了這些條件,若選擇則需要組裝這些條件,若沒有選擇則一定不能組裝,以免影響SQL執行的結果

@Test
    public void test08(){
//        定義查詢條件,有可能為空 (使用者未輸入或未選擇)
        String username = null;
        Integer ageBegin = 10;
        Integer ageEnd = 24;
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//        Stringutils.isNotBlank判斷某字元串是否不為空且長度不為0且不由空白符(whitespace)構成
        if (StringUtils.isBlank(username)){
            queryWrapper.like("username","a");
        }
        if (ageBegin != null){
            queryWrapper.ge("age",ageBegin);
        }
        if (ageEnd != null){
            queryWrapper.le("age",ageEnd);
        }
//        select id,username as name,age,email,is_deleted from t_user where(age >= ? AND age<= ?)
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }      
@Test
    public void test081(){
//        定義查詢條件,有可能為null(使用者魏輸入或未選擇)
        String username = null;
        Integer ageBegin = 10;
        Integer ageEnd = 24;
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper
                .like(StringUtils.isNotBlank(username),"username","張")
                .ge(ageBegin != null,"age",ageBegin)
                .le(ageEnd != null,"age",ageEnd);
        //SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE (age >= ? AND age <= ?)
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }      

5、LambdaQueryWrapper

@Test 
public void test09() { 
    //定義查詢條件,有可能為null(使用者未輸入) 
    String username = "a"; Integer ageBegin = 10; Integer ageEnd = 24; LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); 
    //避免使用字元串表示字段,防止運作時錯誤 
    queryWrapper 
        .like(StringUtils.isNotBlank(username), User::getName, username)         .ge(ageBegin != null, User::getAge, ageBegin) 
        .le(ageEnd != null, User::getAge, ageEnd); 
    List<User> users = userMapper.selectList(queryWrapper); users.forEach(System.out::println); }      

6、LambdaUpdateWrapper

@Test public void test10() { 
    //組裝set子句 
    LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper 
        .set(User::getAge, 18) 
        .set(User::getEmail, "[email protected]") 
        .like(User::getName, "a") 
        .and(i -> i.lt(User::getAge, 24)
             .or().isNull(User::getEmail)); 
    //lambda 表達式内的邏輯優先運算 
    User user = new User(); 
    int result = userMapper.update(user, updateWrapper);                    System.out.println("受影響的行數:" + result); 
}      

六、插件

1.分頁插件

a添加配置類

@Configuration
@MapperScan("com.mybatisplus.mapper")
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}      
b測試
      
@Test
    public void testPage(){
//        設定分頁參數
        Page<User> page = new Page<>(2, 5);
        userMapper.selectPage(page,null);
//         擷取分頁資料
        List<User> list = page.getRecords();
        list.forEach(System.out::println);
        System.out.println("目前頁"+page.getCurrent());
        System.out.println("每頁顯示的條數"+page.getSize());
        System.out.println("總記錄數:"+page.getTotal());
        System.out.println("總頁數"+page.getPages());
        System.out.println("是否有上一頁"+page.hasPrevious());
        System.out.println("是否有下一頁"+page.hasNext());
    }      

2.xml自定義分頁

a.UserMapper中定義接口方法

/**
     * 根據年齡查詢使用者清單,分頁顯示
     * @param page 分頁對象 xml中可以從裡面進行取值,傳遞參數PAGE即自動分頁,必須放在第一位
     * @param age 年齡
     * @return
     */
    Page<User> selectPageVo(@Param("page") Page<User> page,
                            @Param("age") Integer age);      

b>UserMapper.xml中編寫SQL

<select id="selectPageVo" resultType="User">
        select uid,username,age,email from t_user where age > #{age}
    select>      

c>測試

@Test
    public void testPage(){
//        設定分頁參數
        Page<User> page = new Page<>(1, 5);
        userMapper.selectPageVo(page,20);
//         擷取分頁資料
        List<User> list = page.getRecords();
        list.forEach(System.out::println);
        System.out.println("目前頁"+page.getCurrent());
        System.out.println("每頁顯示的條數"+page.getSize());
        System.out.println("總記錄數:"+page.getTotal());
        System.out.println("總頁數"+page.getPages());
        System.out.println("是否有上一頁"+page.hasPrevious());
        System.out.println("是否有下一頁"+page.hasNext());

    }      

3.樂觀鎖

a>場景

一件商品,成本價是80元,售價是100元,老闆先是通知小李,說你去把商品價格增加50元。小李正在玩遊戲,耽擱了一個小時,正好一個小時後,老闆覺得商品價格增加到150元,價格太高,可能影響銷量,又通知小王,你把商品價格降低30元。

此時,小李和小王同時操作商品背景系統。小李操作的時候,系統先取出商品價格100元;小王也在操作,取出的商品價格也是100元。小李将價格加了50元,并将100+50=150元存入了資料庫;小王将商品減了30元,并将100-30=70元存入了資料庫。是的,如果沒有鎖,小李的操作就完全被小王的覆寫了。現在商品價格是70元,比成本價低10元。幾分鐘後,這個商品很快出售了1千多件商品,老闆虧1萬多。

b>樂觀鎖與悲觀鎖

上面的故事,如果是樂觀鎖,小王儲存價格前,會檢查價格是否被修改過了,如果被修改過了,則重新取出的被修改後的價格,150元,這樣它會将120元存入資料庫。

如果是悲觀鎖,小李取出資料後,小王隻能等小李操作完之後,才能對價格進行操作,也會保證最終的價格是120元。

c>模拟修改沖突

資料庫中增加商品表

CREATE TABLE t_product ( 
id BIGINT(20) NOT NULL COMMENT '主鍵ID', 
NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名稱',
price INT(11) DEFAULT 0 COMMENT '價格', 
VERSION INT(11) DEFAULT 0 COMMENT '樂觀鎖版本号',
PRIMARY KEY (id) );      

添加資料

INSERT INTO t_product (id, NAME, price) VALUES (1, '外星人筆記本', 100);      

添加實體

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_product")
public class Product {
    private Long id;
    private String name;
    private Integer price;
    private Integer version;
}      

添加mapper

@Mapper
public interface ProductMapper extends BaseMapper<Product> {
}      

測試

@Test
    public void testConUpdate(){
//        1.小李
        Product p1 = productMapper.selectById(1L);
        System.out.println("小李取出的價格"+p1.getPrice());

//        2.小王
        Product p2 = productMapper.selectById(1L);
        System.out.println("小王取出的價格"+p2.getPrice());

//        3.小李價格加了50元,存入了資料庫
        p1.setPrice(p1.getPrice()+50);
        int update = productMapper.updateById(p1);
        System.out.println("小李修改的結果"+update);

//        4.小娃昂将商品減了元,存入資料庫
        p2.setPrice(p2.getPrice()-30);
        int result = productMapper.updateById(p2);
        System.out.println("小王修改的結果"+result);

//        最後的結果
        Product p3 = productMapper.selectById(1L);
        System.out.println("最後的結果魏"+p3);
    }      
d>樂觀鎖實作流程

資料庫中添加version字段

取出記錄時,擷取目前version

SELECT id,`name`,price,`version` FROM product WHERE id=1      

更新時,version + 1,如果where語句中的version版本不對,則更新失敗

UPDATE product SET price=price+50, `version`=`version` + 1 WHERE id=1 AND `version`=1      
e>Mybatis-Plus實作樂觀鎖

修改實體類

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_product")
public class Product {
    private Long id;
    private String name;
    private Integer price;
    @Version
    private Integer version;
}      

添加樂觀鎖配置

@Configuration
@MapperScan("com.mybatisplus.mapper")
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        //建立樂觀鎖插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}      

測試修改沖突

小李查詢商品資訊:
SELECT id,name,price,version FROM t_product WHERE id=?

小王查詢商品資訊:
SELECT id,name,price,version FROM t_product WHERE id=?

小李修改商品價格,自動将version+1
UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?
Parameters: 外星人筆記本(String), 150(Integer), 1(Integer), 1(Long), 0(Integer)

小王修改商品價格,此時version已更新,條件不成立,修改失敗
UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?
Parameters: 外星人筆記本(String), 70(Integer), 1(Integer), 1(Long), 0(Integer)
最終,小王修改失敗,查詢價格:150

SELECT id,name,price,version FROM t_product WHERE id=?      

優化流程

@Test
    public void testConUpdate(){
//        1.小李
        Product p1 = productMapper.selectById(1L);
        System.out.println("小李取出的價格"+p1.getPrice());

//        2.小王
        Product p2 = productMapper.selectById(1L);
        System.out.println("小王取出的價格"+p2.getPrice());

//        3.小李價格加了50元,存入了資料庫
        p1.setPrice(p1.getPrice()+50);
        int update = productMapper.updateById(p1);
        System.out.println("小李修改的結果"+update);

//        4.小娃昂将商品減了元,存入資料庫
        p2.setPrice(p2.getPrice()-30);
        int result = productMapper.updateById(p2);
        System.out.println("小王修改的結果"+result);
        if (result == 0){
            p2=productMapper.selectById(1L);
            p2.setPrice(p2.getPrice()-30);
            result = productMapper.updateById(p2);
        }
        System.out.println("小王修改重試的結果"+result);

//        最後的結果
        Product p3 = productMapper.selectById(1L);
        System.out.println("最後的結果魏"+p3);
    }      

七、通用枚舉

表中的有些字段值是固定的,例如性别(男或女),此時我們可以使用Mybatis-Plus的通用美劇來實作

1.資料庫添加字段sex

MyBatis增強器——Mybatis-Plus

2.建立通用枚舉類型

@Getter
public enum SexEnum {
    MALE(1,"男"),
    FEMALE(2,"女");

    @EnumValue
    private Integer sex;
    private String sexName;

    SexEnum(Integer sex, String sexName) {
        this.sex = sex;
        this.sexName = sexName;
    }
}      

3.配置掃描通用枚舉

mybatis-plus:
  configuration:
#    配置Mybatis日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      #        配置Mybatis-plus操作表的預設字首
      table-prefix: t_

  #      配置Mybatis-plus的主鍵政策
#      id-type: auto
#  配置掃描通用枚舉
  type-enums-package: com.mybatisplus.enums
#  配置類型别名所對應的包
  type-aliases-package: com.mybatisplus.pojo      

4.測試

@Test
    public void test01(){
        User user = new User();
        user.setName("Enum");
        user.setAge(20);
//        設定性别資訊為枚舉項,會将@EnumValue注解所辨別的屬性值存儲到資料庫
        user.setSex(SexEnum.MALE);
        //INSERT INTO t_user ( username, age, sex ) VALUES ( ?, ?, ? )
        // Parameters: Enum(String), 20(Integer), 1(Integer)
        userMapper.insert(user);
    }      

八、代碼生成器

1.引入依賴

<dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-generatorartifactId>
            <version>3.5.1version>
        dependency>
        <dependency>
            <groupId>org.freemarkergroupId>
            <artifactId>freemarkerartifactId>
            <version>2.3.31version>
        dependency>      

2.快速生成

public class Test03 {
    public static void main(String[] args) {
        FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/mybatis_plus? characterEncoding=utf-8&userSSL=false", "root", "123456")
                .globalConfig(builder -> {
                    builder.author("mybatisplus") //設定作者
                        .fileOverride() //覆寫已經生成的檔案
                        .outputDir("D://mybatis_plus");//指定輸出目錄
                })
                .packageConfig(builder -> {
                            builder.parent("com.mybatisplus")//設定父包名
                                .moduleName("mybatisplus")//設定父包子產品名
                            .pathInfo((Collections.singletonMap(OutputFile.mapperXml, "D://mybatis_plus")));
//                            設定mapperXml生成路徑
                })
                .strategyConfig(builder -> {
                    builder.addInclude("t_user")//設定需要生成的表名
                    .addTablePrefix("t_","c_");//設定字首
                })
                .templateEngine(new FreemarkerTemplateEngine()).execute(); //使用Freemarker引擎模闆,預設的是velocity引擎模闆)
    }
}      

九、多資料源

适用于多種場景:純粹多庫、 讀寫分離、 一主多從、 混合模式等

目前我們就來模拟一個純粹多庫的一個場景,其他場景類似

場景說明:

我們建立兩個庫,分别為:mybatis_plus(以前的庫不動)與mybatis_plus_1(建立),将

mybatis_plus庫的product表移動到mybatis_plus_1庫,這樣每個庫一張表,通過一個測試用例

分别擷取使用者資料與商品資料,如果擷取到說明多庫模拟成功

1.建立資料庫及表

CREATE DATABASE `mybatis_plus_1` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; 
use `mybatis_plus_1`; 
CREATE TABLE product ( 
id BIGINT(20) NOT NULL COMMENT '主鍵ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名稱', 
price INT(11) DEFAULT 0 COMMENT '價格', 
version INT(11) DEFAULT 0 COMMENT '樂觀鎖版本号',
PRIMARY KEY (id) );
添加測試資料
INSERT INTO product (id, NAME, price) VALUES (1, '外星人筆記本', 100);

删除mybatis_plus庫product表
use mybatis_plus; DROP TABLE IF EXISTS product;      

2.引入依賴

<dependency> 
  <groupId>com.baomidougroupId> 
  <artifactId>dynamic-datasource-spring-boot-starterartifactId>  <version>3.5.0version> 
dependency>      

3.配置多資料源

spring:
#  配置資料源資訊
  datasource:
    dynamic:
#      設定預設的資料源或者資料源組,預設值即為master
      primary:
#      嚴格比對資料源,預設false true 未比對到指定資料時抛異常,false使用預設資料源
      strict: false
      datasource:
        master :
          url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: 123456
        slave_1:
          url: jdbc:mysql://localhost:3306/mybatis_plus_1?characterEncoding=utf-8&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: 123456      

4.建立使用者service

public interface UserService extends IService<User> {

}      
@DS("master")
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{
}      

5.建立商品service

public interface ProductService extends IService<Product> { }      
@DS("slave_1")
@Service 
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService { }      

6.測試

@Autowired
    private UserService userService;
    @Autowired
    private ProductService productService;

    @Test
    public void testDataSource(){
        System.out.println(userService.getById(1L));
        System.out.println(productService.getById(1L));
    }      

十、MybatisX插件

MyBatis-Plus為我們提供了強大的mapper和service模闆,能夠大大的提高開發效率

但是在真正開發過程中,MyBatis-Plus并不能為我們解決所有問題,例如一些複雜的SQL,多表

聯查,我們就需要自己去編寫代碼和SQL語句,我們該如何快速的解決這個問題呢,這個時候可

以使用MyBatisX插件