小黑子之——MybatiPlus整合

MybatiPlus學習

  • 一、MybatiPlus簡介
    • 1.1 入門案例
    • 1.2 mybatisPlus概述
    • 1.3 總結
  • 二、標準數據層開發
    • 2.1 標準的CRUD使用
    • 2.2 新增
    • 2.3 刪除
    • 2.4 修改
    • 2.5 根據Id查詢
    • 2.6 查詢全部
    • 2.7 Lombok
    • 2.8 分頁功能
  • 三、DQL控制
    • 3.1 條件查詢方式
      • 3.1.1 構建條件查詢
      • 3.1.2 多條件查詢
      • 3.1.3 null值判定
    • 3.2 查詢投影
      • 3.2.1 查詢指定字段
      • 3.2.2 聚合查詢
      • 3.2.3 分組查詢
    • 3.3 查詢條件設定
      • 3.3.1 等值查詢
      • 3.3.2 范圍查詢
      • 3.3.3 模糊查詢
      • 3.3.4 排序查詢
    • 3.4 字段映射與表名映射
  • 四、DQL控制
    • 4.1 id生成策略控制
      • 4.1.1 AUTO
      • 4.1.2 INPUT
      • 4.1.3 ASSIGN_ID
      • 4.1.4 ASSIGN_UUID
      • 4.1.5 ID生成策略對比
      • 4.1.6 簡化配置
    • 4.2 多記錄操作
    • 4.3 邏輯刪除
    • 4.4 樂觀鎖
      • 4.4.1 實現思路
      • 4.4.2 實現步驟
  • 五、快速開發
    • 5.1 代碼生成器實現
    • 5.2 MP中Service的CRUD
  • 六、復習

一、MybatiPlus簡介

1.1 入門案例

  • MyBatisPlus(簡稱MP)是基于MyBatis框架基礎上開發的增強型工具,旨在簡化開發,提高效率
  • 開發方式
    • 基于MyBatis使用MyBatisPlus
    • 基于Spring使用MyBatisPlus
    • 基于SpringBoot使用MyBatisPlus(重點)

現在直接使用SpringBoot來構建項目,官網的快速開始也是直接用的SpringBoot

  • 步驟一:創建數據庫和表

    CREATE TABLE user (
    id bigint(20) primary key auto_increment,
    name varchar(32) not null,
    password  varchar(32) not null,
    age int(3) not null ,
    tel varchar(32) not null
    );
    insert into user values(1,'Tom','tom',3,'18866668888');
    insert into user values(2,'Jerry','jerry',4,'16688886666');
    insert into user values(3,'Jock','123456',41,'18812345678');
    insert into user values(4,'略略略','nigger',15,'4006184000');
    
  • 步驟二:創建SpringBoot工程
    只需要勾選MySQL,不用勾選MyBatis了

  • 步驟三:補全依賴
    導入德魯伊和MyBatisPlus的坐標

    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
    </dependency>
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
    </dependency>
    
  • 步驟四:編寫數據庫連接四要素
    還是將application的后綴名改為yml,以后配置都是用yml來配置
    注意要設置一下時區,不然可能會報錯(指高版本的mysql)

    spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTCusername: rootpassword: YOUSONOFABTICH.## mybatis的日志信息l
    mybatis-plus:
    configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
  • 步驟五:根據數據表來創建對應的模型類
    注意id是Long類型,至于為什么是Long,接著往下看

    public class User {
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;@Override
    public String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +", password='" + password + '\'' +", age=" + age +", tel='" + tel + '\'' +'}';
    }public Long getId() {return id;
    }public void setId(Long id) {this.id = id;
    }public String getName() {return name;
    }public void setName(String name) {this.name = name;
    }public String getPassword() {return password;
    }public void setPassword(String password) {this.password = password;
    }public Integer getAge() {return age;
    }public void setAge(Integer age) {this.age = age;
    }public String getTel() {return tel;
    }public void setTel(String tel) {this.tel = tel;}
    }
    
  • 步驟六:創建dao接口

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

這樣寫就完事兒了,
只需要在類上方加一個@Mapper注解,同時繼承BaseMapper<>,泛型寫創建的模型類的類型
然后這樣就能完成單表的CRUD了

  • 步驟七:測試
    懶死,以后連簡單的CRUD都不用寫了
    SpringBoot的測試類也是簡單的一批,只需要一個@SpringBootTest注解就能完成(創建SpringBoot工程的時候已經幫我們自動弄好了)
    測試類里需要什么東西就用@Autowired自動裝配,測試方法上用@Test注解

    @SpringBootTest
    class MybatisplusApplicationTests {@Autowired
    private UserDao userDao;@Test
    void contextLoads() {List<User> users = userDao.selectList(null);for (User b : users) {System.out.println(b);}}
    }
    

selectList() 方法的參數為 MP 內置的條件封裝器 Wrapper,所以不填寫就是無任何條件

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

1.2 mybatisPlus概述

MyBatisPlus的官網
因為域名被搶注了,但是粉絲也捐贈了一個 https://mybatis.plus 域名
在這里插入圖片描述
在這里插入圖片描述

MP旨在成為MyBatis的最好搭檔,而不是替換掉MyBatis,從名稱上來看也是這個意思,一個MyBatis的plus版本,在原有的MyBatis上做增強,其底層仍然是MyBatis的東西,所以我們當然也可以在MP中寫MyBatis的內容

對于MP的深入學習,可以多看看官方文檔,鍛煉自己自學的能力,畢竟不是所有知識都有像這樣的網課,更多的還是自己看文檔,挖源碼。

MP的特性:

  • 無侵入:只做增強不做改變,引入它不會對現有工程產生影響,如絲般順滑
  • 損耗小:啟動即會自動注入基本 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 操作智能分析阻斷,也可自定義攔截規則,預防誤操作

1.3 總結

SpringBoot集成MyBatisPlus非常的簡單,只需要導入MyBatisPlus的坐標,然后令dao類繼承BaseMapper,寫上泛型,類上方加@Mapper注解

可能存在的疑問:

  • 我甚至都沒寫在哪個表里查,為什么能自動識別是在我剛剛創建的表里查?
    • 注意我們創建的表,和對應的模型類,是同一個名,默認情況是在同名的表中查找
  • 那我要是表明和模型類的名不一樣,那咋整?
    • 在模型類的上方加上@TableName注解
      • 例如數據表叫tb_user但數據類叫User,那么就在User類上加@TableName("tb_user")注解

二、標準數據層開發

2.1 標準的CRUD使用

功能自定義接口MP接口
新增boolean save(T t)int insert(T t)
刪除boolean delete(int id)int deleteById(Serializable id)
修改boolean update(T t)int updateById(T t)
根據id查詢T getById(int id)T selectById(Serializable id)
查詢全部List getAll()List selectList()
分頁查詢PageInfo getAll(int page,int size)IPage selectPage(IPage page)
按條件查詢List getAll(Condition condition)IPage selectPage(WrapperqueryWrapper)

2.2 新增

int insert(T t)

參數類型是泛型,也就是我們當初繼承BaseMapper的時候,填的泛型,返回值是int類型,0代表添加失敗,1代表添加成功

@Test
void testInsert(){User user = new User();user.setName("magua");user.setAge(23);user.setTel("4005129421");user.setPassword("MUSICIAN");userDao.insert(user);
}

隨便寫一個User的數據,運行程序,然后去數據庫看看新增是否成功

1572364408896622593 magua MUSICIAN 23 4005129421

這個主鍵自增id看著有點奇怪,但現在你知道為什么要將id設為long類型了吧

2.3 刪除

int deleteByIds(
//Serializable id
)
  • 參數類型為什么是一個序列化類Serializable

    • 通過查看String的源碼,你會發現String實現了Serializable接口,而且Number類也實現了Serializable接口
    • Number類又是Float,Double,Long等類的父類
    • 那現在能作為主鍵的數據類型,都已經是Serializable類型的子類了
    • MP使用Serializable類型當做參數類型,就好比我們用Object類型來接收所有類型一樣
  • 返回值類型是int

    • 數據刪除成功返回1
    • 未刪除數據返回0。
  • 那下面我們就來刪除剛剛添加的數據,注意末尾加個L

@Test
void testDelete(){userDao.deleteById(1572364408896622593L);
}

2.4 修改

int updateById(T t);
  • T:泛型,需要修改的數據內容,注意因為是根據ID進行修改,所以傳入的對象中需要有ID屬性值
  • int:返回值
    • 修改成功后返回1
    • 未修改數據返回0
@Test
void testUpdate(){User user = new User();user.setId(1L);user.setName("Alen");userDao.updateById(user);
}

修改功能只修改指定的字段,未指定的字段保持原樣,與比較之前方便許多,之前修改的話,要加很多判斷語句是否為空。

2.5 根據Id查詢

T selectById (Serializable id)

T selectById (Serializable id)

@Test
void testSelectById(){User user = userDao.selectById(1);System.out.println(user);
}

2.6 查詢全部

List<T> selectList(Wrapper<T> queryWrapper)

Wrapper:用來構建條件查詢的條件,目前我們沒有可直接傳為Null

@Test
void testSelectAll() {List<User> users = userDao.selectList(null);for (User u : users) {System.out.println(u);}
}

方法都測試完了,那你們有沒有想過,這些方法都是誰提供的呢?

  • 想都不用想,肯定是我們當初繼承的BaseMapper

2.7 Lombok

  • 代碼寫到這,我們發現之前的dao接口,都不用我們自己寫了,只需要繼承BaseMapper,用他提供的方法就好了

  • 但是現在我還想偷點懶,畢竟懶是第一生產力,之前我們手寫模型類的時候,創建好對應的屬性,然后用IDEA的Alt+Insert快捷鍵,快速生成get和set方法,toSring,各種構造器(有需要的話)等

  • 項目做這么久,寫模型類都給我寫煩了,有沒有更簡單的方式呢?

    • 答案當然是有的,可以使用Lombok,一個Java類庫,提供了一組注解,來簡化我們的POJO模型類開發

具體步驟如下

  • 步驟一:添加Lombok依賴

    <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><!--<version>1.18.12</version>-->
    </dependency>
    

版本不用寫,SpringBoot中已經管理了lombok的版本,

  • 步驟二:在模型類上添加注解
    Lombok常見的注解有:

    • @Setter:為模型類的屬性提供setter方法
    • @Getter:為模型類的屬性提供getter方法
    • @ToString:為模型類的屬性提供toString方法
    • @EqualsAndHashCode:為模型類的屬性提供equals和hashcode方法
      @Setter
      @Getter
      @ToString
      @EqualsAndHashCode
      public class User {
      private Long id;
      private String name;
      private String password;
      private Integer age;
      private String tel;
      }
      
    • @Data:是個組合注解,包含上面的注解的功能
    • @NoArgsConstructor:提供一個無參構造函數
    • @AllArgsConstructor:提供一個包含所有參數的構造函數
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
    }
    

說明:Lombok只是簡化模型類的編寫,我們之前的方法也能用
例如你有特殊的構造器需求,只想要name和password這兩個參數,那么可以手寫一個

public User(String name, String password) {this.name = name;this.password = password;
}

2.8 分頁功能

基礎的增刪改查功能就完成了,現在我們來進行分頁功能的學習

IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper)
  • IPage用來構建分頁查詢條件
  • Wrapper:用來構建條件查詢的條件,暫時沒有條件可以傳一個null
  • 返回值IPage是什么意思,后面我們會說明

具體的使用步驟如下

  • 步驟一:調用方法傳入參數獲取返回值

    @Test
    void testSelectPage() {
    IPage<User> page = new Page<>(1, 3);
    userDao.selectPage(page, null);
    System.out.println("當前頁碼" + page.getCurrent());
    System.out.println("本頁條數" + page.getSize());
    System.out.println("總頁數" + page.getPages());
    System.out.println("總條數" + page.getTotal());
    System.out.println(page.getRecords());
    }
    
  • 步驟二:設置分頁攔截器

    public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
    //1.設置一個MP的攔截器MybatisPlusInterceptor myInterceptor = new MybatisPlusInterceptor();//2.添加具體的攔截器(小的)myInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return myInterceptor;}
    }
    
  • 步驟三:運行測試程序

開啟日志可以查看其運行的整個sql語句
在這里插入圖片描述

三、DQL控制

增刪改查四個操作中,查詢是非常重要的也是非常復雜的操作,這部分我們主要學習的內容有:

3.1 條件查詢方式

在這里插入圖片描述
MP將復雜的SQL查詢語句都做了封裝,使用編程的方式來完成查詢條件的組合

之前我們在寫CRUD時,都看到了一個Wrapper類,我們當初都是賦一個null值,但其實這個類就是用來查詢的

3.1.1 構建條件查詢

  • QueryWrapper
    小于lt大于gt
    回想之前我們在html頁面中,如果需要用到小于號或者大于號,需要用對應的html實體來替換
    小于號的實體是 &lt;大于號的實體是&gt;

    @Test
    void testQueryWrapper(){
    QueryWrapper<User> qw = new QueryWrapper<>();
    //條件為 age字段小于18
    qw.lt("age",18);
    List<User> userList = userDao.selectList(qw);
    System.out.println(userList);
    }
    

這種方法有個弊端,那就是字段名是字符串類型,沒有提示信息和自動補全,如果寫錯了,那就查不出來

  • QueryWrapper的基礎上,使用lambda

    @Test
    void testQueryWrapper(){
    QueryWrapper<User> qw = new QueryWrapper<>();
    qw.lambda().lt(User::getAge,18);
    List<User> userList = userDao.selectList(qw);
    System.out.println(userList);
    }
    

User::getAget,為lambda表達式中的,類名::方法名

  • LambdaQueryWrapper
    方式二解決了方式一的弊端,但是要多些一個lambda(),那方式三就來解決方式二的弊端,使用LambdaQueryWrapper,就可以不寫lambda()

    @Test
    void testQueryWrapper(){LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.lt(User::getAge,18);List<User> userList = userDao.selectList(lqw);System.out.println(userList);
    }
    

3.1.2 多條件查詢

上面三種都是單條件的查詢,那我們現在想進行多條件的查詢,該如何編寫代碼呢?

需求:查詢表中年齡在10~30歲的用戶信息

@Test
void testQueryWrapper(){LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();//大于10lqw.gt(User::getAge,10);//小于30lqw.lt(User::getAge,30);List<User> userList = userDao.selectList(lqw);System.out.println(userList);
}

構建多條件的時候,我們還可以使用鏈式編程

@Test
void testQueryWrapper() {LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.gt(User::getAge, 10).lt(User::getAge, 30);List<User> userList = userDao.selectList(lqw);System.out.println(userList);
}
  • 可能存在的疑問
    • MP怎么就知道你這倆條件是AND的關系呢,那我要是想用OR的關系,該咋整
  • 解答
    • 默認就是AND的關系,如果需要OR關系,用or()鏈接就可以了
lqw.gt(User::getAge, 10).or().lt(User::getAge, 30);

需求:查詢年齡小于10,或者年齡大于30的用戶信息

@Test
void testQueryWrapper() {LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);List<User> userList = userDao.selectList(lqw);System.out.println(userList);
}

3.1.3 null值判定

在這里插入圖片描述

  • 在做條件查詢的時候,一般都會有很多條件供用戶查詢

  • 這些條件用戶可以選擇用,也可以選擇不用

  • 之前我們是通過動態SQL來實現的

    <select id="selectByPageAndCondition" resultMap="brandResultMap">
    select *
    from tb_brand
    <where><if test="brand.brandName != null and brand.brandName != '' ">and  brand_name like #{brand.brandName}</if><if test="brand.companyName != null and brand.companyName != '' ">and  company_name like #{brand.companyName}</if><if test="brand.status != null">and  status = #{brand.status}</if>
    </where>
    limit #{begin} , #{size}
    </select>
    
  • 那現在我們來試試在MP里怎么寫

需求:查詢數據庫表中,根據輸入年齡范圍來查詢符合條件的記錄
用戶在輸入值的時候, ?如果只輸入第一個框,說明要查詢大于該年齡的用戶
?如果只輸入第二個框,說明要查詢小于該年齡的用戶
如果兩個框都輸入了,說明要查詢年齡在兩個范圍之間的用戶

  • 問題一:后臺如果想接收前端的兩個數據,該如何接收?

    • 我們可以使用兩個簡單數據類型,也可以使用一個模型類,但是User類中目前只有一個age屬性

      @TableName("tb_user")
      @Data
      public class User {private Long id;private String name;private String password;private Integer age;private String tel;
      }
      
    • 使用一個age屬性,如何去接收頁面上的兩個值呢?這個時候我們有兩個解決方案

      • 方案一:添加屬性age2,這種做法可以但是會影響到原模型類的屬性內容
      • 方案二:新建一個模型類,讓其繼承User類,并在其中添加age2屬性,UserQuery在擁有User屬性后同時添加了age2屬性。
    @Data
    @TableName("tb_user")
    public class UserQuery extends User{private Integer age2;
    }
    
    • 環境準備好后,我們來實現下剛才的需求:
    @Test
    void testQueryWrapper() {
    LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
    UserQuery uq = new UserQuery();
    uq.setAge(10);
    uq.setAge2(30);
    if (null != uq.getAge()) {lqw.gt(User::getAge, uq.getAge());
    }
    if (null != uq.getAge2()) {lqw.lt(User::getAge, uq.getAge2());
    }
    for (User user : userDao.selectList(lqw)) {System.out.println(user);}
    }
    
    • 上面的寫法可以完成條件為非空的判斷,但是問題很明顯,如果條件多的話,每個條件都需要判斷,代碼量就比較大,來看MP給我們提供的簡化方式
    • lt還有一個重載的方法,當condition為true時,添加條件,為false時,不添加條件
    public Children lt(boolean condition, R column, Object val) {return this.addCondition(condition, column, SqlKeyword.LT, val);
    }
    
  • 故我們可以把if的判斷操作,放到lt和gt方法中當做參數來寫

    @Test
    void testQueryWrapper() {
    LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
    UserQuery uq = new UserQuery();
    uq.setAge(10);
    uq.setAge2(30);
    lqw.gt(null != uq.getAge(), User::getAge, uq.getAge()).lt(null != uq.getAge2(), User::getAge, uq.getAge2());
    for (User user : userDao.selectList(lqw)) {System.out.println(user);}
    }
    

3.2 查詢投影

3.2.1 查詢指定字段

目前我們在查詢數據的時候,什么都沒有做默認就是查詢表中所有字段的內容,我們所說的查詢投影即不查詢所有字段,只查詢出指定內容的數據。

具體如何來實現?

@Test
void testQueryWrapper() {LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.select(User::getName,User::getName);for (User user : userDao.selectList(lqw)) {System.out.println(user);}
}

select(…)方法用來設置查詢的字段列,可以設置多個

lqw.select(User::getName,User::getName);

如果使用的不是lambda,就需要手動指定字段

@Test
void testQueryWrapper() {QueryWrapper<User> qw = new QueryWrapper<>();qw.select("name", "age");for (User user : userDao.selectList(qw)) {System.out.println(user);}
}

3.2.2 聚合查詢

需求:聚合函數查詢,完成count、max、min、avg、sum的使用

  • count:總記錄數
  • max:最大值
  • min:最小值
  • avg:平均值
  • sum:求和
  • count

    @Test
    void testQueryWrapper() {
    QueryWrapper<User> qw = new QueryWrapper<>();
    qw.select("count(*) as count");
    for (Map<String, Object> selectMap : userDao.selectMaps(qw)) {System.out.println(selectMap);}
    }
    
  • max

    @Test
    void testQueryWrapper() {
    QueryWrapper<User> qw = new QueryWrapper<>();
    qw.select("max(age) as maxAge");
    for (Map<String, Object> selectMap : userDao.selectMaps(qw)) {System.out.println(selectMap);}
    }
    
  • min

    @Test
    void testQueryWrapper() {
    QueryWrapper<User> qw = new QueryWrapper<>();
    qw.select("min(age) as minAge");
    for (Map<String, Object> selectMap : userDao.selectMaps(qw)) {System.out.println(selectMap);}
    }
    
  • avg

    @Test
    void testQueryWrapper() {
    QueryWrapper<User> qw = new QueryWrapper<>();
    qw.select("avg(age) as avgAge");
    for (Map<String, Object> selectMap : userDao.selectMaps(qw)) {System.out.println(selectMap);
    }
    }
    
  • sum

    @Test
    void testQueryWrapper() {
    QueryWrapper<User> qw = new QueryWrapper<>();
    qw.select("sum(age) as sumAge");
    for (Map<String, Object> selectMap : userDao.selectMaps(qw)) {System.out.println(selectMap);}
    }
    

3.2.3 分組查詢

@Test
void testQueryWrapper() {QueryWrapper<User> qw = new QueryWrapper<>();qw.select("max(age) as maxAge");qw.groupBy("tel");for (Map<String, Object> selectMap : userDao.selectMaps(qw)) {System.out.println(selectMap);}
}

注意:

  • 聚合與分組查詢,無法使用lambda表達式來完成
  • MP只是對MyBatis的增強,如果MP實現不了,我們可以直接在DAO接口中使用MyBatis的方式實現

在這里插入圖片描述

3.3 查詢條件設定

前面我們只使用了lt()和gt(),除了這兩個方法外,MP還封裝了很多條件對應的方法

  • 范圍匹配(> 、 = 、between)
  • 模糊匹配(like)
  • 空判定(null)
  • 包含性匹配(in)
  • 分組(group)
  • 排序(order)
  • ……

3.3.1 等值查詢

需求:根據用戶名和密碼查詢用戶信息

@Test
void testQueryWrapper() {LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();qw.eq(User::getName,"Seto").eq(User::getPassword,"MUSICIAN");User user = userDao.selectOne(qw);System.out.println(user);
}
  • eq(): 相當于 =,對應的sql語句為

    SELECT * FROM tb_user WHERE name = 'seto' AND password = 'MUSICIAN';
    
  • selectList:查詢結果為多個或者單個

  • selectOne:查詢結果為單個

3.3.2 范圍查詢

需求:對年齡進行范圍查詢,使用lt()、le()、gt()、ge()、between()進行范圍查詢

@Test
void testQueryWrapper() {LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();qw.between(User::getAge,10,30);List<User> users = userDao.selectList(qw);for (User u : users) {System.out.println(u);}
}
  • gt():大于(>)
  • ge():大于等于(>=)
  • lt():小于(<)
  • lte():小于等于(<=)
  • between():between ? and ?

3.3.3 模糊查詢

需求:查詢表中name屬性的值以J開頭的用戶信息,使用like進行模糊查詢

@Test
void testQueryWrapper() {LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();qw.likeRight(User::getName,"J");List<User> users = userDao.selectList(qw);for (User u : users) {System.out.println(u);}
}
  • like():前后加百分號,如 %J%,相當于包含J的name
  • likeLeft():前面加百分號,如 %J,相當于J結尾的name
  • likeRight():后面加百分號,如 J%,相當于J開頭的name

需求:查詢表中name屬性的值包含e的用戶信息,使用like進行模糊查詢

@Test
void testQueryWrapper() {LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();qw.like(User::getName,"e");List<User> users = userDao.selectList(qw);for (User u : users) {System.out.println(u);}
}

3.3.4 排序查詢

需求:查詢所有數據,然后按照age降序

@Test
void testQueryWrapper() {LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();/*** condition :條件,返回boolean,當condition為true,進行排序,如果為false,則不排序* isAsc:是否為升序,true為升序,false為降序* columns:需要操作的列*/qw.orderBy(true,false,User::getAge);List<User> users = userDao.selectList(qw);for (User u : users) {System.out.println(u);}
}

遇到想用的功能,先自己用一個試試,方法名和形參名都很見名知意,遇到不確定的用法,再去官方文檔查閱資料

3.4 字段映射與表名映射

我們做查詢的時候,數據表中的字段名與模型類中的屬性名一致,查詢的時候沒有問題,那么問題就來了

  • 問題一:表字段與模型類編碼屬性不一致

    • 當表的列名和模型類的屬性名發生不一致,就會導致數據封裝不到模型對象,這個時候就需要其中一方做出修改,那如果前提是兩邊都不能改又該如何解決?
    • MP給我們提供了一個注解@TableField,使用該注解可以實現模型類屬性名和表的列名之間的映射關系
    • 例如表中密碼字段為pwd,而模型類屬性名為password,那我們就可以用@TableField注解來實現他們之間的映射關系
      在這里插入圖片描述
      在這里插入圖片描述
  • 問題二:編碼中添加了數據庫中未定義的屬性

    • 當模型類中多了一個數據庫表不存在的字段,就會導致生成的sql語句中在select的時候查詢了數據庫不存在的字段,程序運行就會報錯,錯誤信息為:Unknown column '多出來的字段名稱' in 'field list'
    • 具體的解決方案用到的還是@TableField注解,它有一個屬性叫exist,設置該字段是否在數據庫表中存在,如果設置為false則不存在,生成sql語句查詢的時候,就不會再查詢該字段了。
      在這里插入圖片描述
  • 問題三:采用默認查詢開放了更多的字段查看權限

    • 查詢表中所有的列的數據,就可能把一些敏感數據查詢到返回給前端,這個時候我們就需要限制哪些字段默認不要進行查詢。解決方案是@TableField注解的一個屬性叫select,該屬性設置默認是否需要查詢該字段的值,true(默認值)表示默認查詢該字段,false表示默認不查詢該字段。
    • 例如像密碼這種的敏感字段,不應該查詢出來作為JSON返回給前端,不安全
      在這里插入圖片描述

知識點:@TableField

名稱@TableField
類型屬性注解
位置模型類屬性定義上方
作用設置當前屬性對應的數據庫表中的字段關系
相關屬性value(默認):設置數據庫表字段名稱 exist:設置屬性在數據庫表字段中是否存在,默認為true,此屬性不能與value合并使用 select:設置屬性是否參與查詢,此屬性與select()映射配置不沖突
  • 問題四:表名與編碼開發設計不同步
    • 這個問題其實我們在一開始就解決過了,現在再來回顧一遍
    • 該問題主要是表的名稱和模型類的名稱不一致,導致查詢失敗,這個時候通常會報如下錯誤信息Table ‘databaseName.tableNaem’ doesn’t exist
    • 解決方案是使用MP提供的另外一個注解@TableName來設置表與模型類之間的對應關系。
      在這里插入圖片描述

知識點:@TableName

名稱@TableName
類型類注解
位置模型類定義上方
作用設置當前類對應于數據庫表關系
相關屬性value(默認):設置數據庫表名稱

四、DQL控制

4.1 id生成策略控制

前面我們在新增數據的時候,主鍵ID是一個很長的Long類型,我們現在想要主鍵按照數據表字段進行自增長,在解決這個問題之前,我們先來分析一下ID的生成策略

  • 不同的表,應用不同的id生成策略

    • 日志:自增(1 2 3 4)
    • 購物訂單:特殊規則(線下購物發票,下次可以留意一下)
    • 外賣訂單:關聯地區日期等信息(這個我熟,舉個例子10 04 20220921 13 14,例如10表示北京市,04表示朝陽區,20220921表示日期等)
    • 關系表:可以省略ID
      ……
  • 不同的業務采用的ID生成方式應該是不一樣的,那么在MP中都提供了哪些主鍵生成策略,以及我們該如何進行選擇?

    • 在這里我們又需要用到MP的一個注解叫@TableId

知識點:@TableId

名稱@TableId
類型屬性注解
位置模型類中用于表示主鍵的屬性定義上方
作用設置當前類中主鍵屬性的生成策略
相關屬性value(默認):設置數據庫表主鍵名稱type:設置主鍵屬性的生成策略,值查照IdType的枚舉值

4.1.1 AUTO

  • 步驟一:設置生成策略為AUTO

    @TableName("tb_user")
    @Data
    public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist = false)
    private Integer online;
    }
    
  • 步驟二:設置自動增量為5,將4之后的數據都刪掉,防止影響我們的結果

  • 步驟三:運行新增方法

    @Test
    void testInsert(){
    User user = new User();
    user.setName("Helsing");
    user.setAge(531);
    user.setPassword("HELL_SING");
    user.setTel("4006669999");
    userDao.insert(user);
    }
    

會發現,新增成功,并且主鍵id也是從5開始

我們進入源碼來看看還有什么生成策略

public enum IdType {AUTO(0),NONE(1),INPUT(2),ASSIGN_ID(3),ASSIGN_UUID(4),/** @deprecated */@DeprecatedID_WORKER(3),/** @deprecated */@DeprecatedID_WORKER_STR(3),/** @deprecated */@DeprecatedUUID(4);private final int key;private IdType(int key) {this.key = key;}public int getKey() {return this.key;}
}
  • NONE: 不設置id生成策略
  • INPUT:用戶手工輸入id
  • ASSIGN_ID:雪花算法生成id(可兼容數值型與字符串型)
  • ASSIGN_UUID:以UUID生成算法作為id生成策略
  • 其他的幾個策略均已過時,都將被ASSIGN_ID和ASSIGN_UUID代替掉。

拓展: 分布式ID是什么?

  • 當數據量足夠大的時候,一臺數據庫服務器存儲不下,這個時候就需要多臺數據庫服務器進行存儲
  • 比如訂單表就有可能被存儲在不同的服務器上
  • 如果用數據庫表的自增主鍵,因為在兩臺服務器上所以會出現沖突
  • 這個時候就需要一個全局唯一ID,這個ID就是分布式ID。

4.1.2 INPUT

  • 步驟一:將ID生成策略改為INPUT

    @TableName("tb_user")
    @Data
    public class User {
    @TableId(type = IdType.INPUT)
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist = false)
    private Integer online;
    }
    
  • 步驟二:運行新增方法
    注意這里需要手動設置ID了

    @Test
    void testInsert(){
    User user = new User();
    user.setId(6L);
    user.setName("Helsing");
    user.setAge(531);
    user.setPassword("HELL_SING");
    user.setTel("4006669999");
    userDao.insert(user);
    }
    

查看數據庫,ID確實是我們設置的值

4.1.3 ASSIGN_ID

  • 步驟一:設置生成策略為ASSIGN_ID

    @TableName("tb_user")
    @Data
    public class User {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist = false)
    private Integer online;
    }
    
  • 步驟二:運行新增方法
    這里就不要手動設置ID了

    @Test
    void testInsert(){
    User user = new User();
    user.setName("Helsing");
    user.setAge(531);
    user.setPassword("HELL_SING");
    user.setTel("4006669999");
    userDao.insert(user);
    }
    

查看結果,生成的ID就是一個Long類型的數據,生成ID時,使用的是雪花算法
雪花算法(SnowFlake),是Twitter官方給出的算法實現 是用Scala寫的。其生成的結果是一個64bit大小整數
在這里插入圖片描述
在這里插入圖片描述

  1. 1bit,不用,因為二進制中最高位是符號位,1表示負數,0表示正數。生成的id一般都是用整數,所以最高位固定為0。
  2. 41bit-時間戳,用來記錄時間戳,毫秒級
  3. 10bit-工作機器id,用來記錄工作機器id,其中高位5bit是數據中心ID其取值范圍0-31,低位5bit是工作節點ID其取值范圍0-31,兩個組合起來最多可以容納1024個節點
  4. 序列號占用12bit,每個節點每毫秒0開始不斷累加,最多可以累加到4095,一共可以產生4096個ID

4.1.4 ASSIGN_UUID

  • 步驟一:設置生成策略為ASSIGN_UUID

    @TableName("tb_user")
    @Data
    public class User {
    @TableId(type = IdType.ASSIGN_UUID)
    private String id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist = false)
    private Integer online;
    }
    
  • 步驟二:修改表的主鍵類型
    主鍵類型設置為varchar,長度要大于32,因為UUID生成的主鍵為32位,如果長度小的話就會導致插入失敗。

  • 步驟三:運行新增方法

    @Test
    void testInsert(){
    User user = new User();
    user.setName("Helsing");
    user.setAge(531);
    user.setPassword("HELL_SING");
    user.setTel("4006669999");
    userDao.insert(user);
    }
    

4.1.5 ID生成策略對比

介紹完了這些主鍵ID的生成策略,那么以后我們開發用哪個呢?

  • NONE:不設置ID生成策略,MP不自動生成,約定于INPUT,所以這兩種方式都需要用戶手動設置(SET方法),但是手動設置的第一個問題就是容易出錯,加了相同的ID造成主鍵沖突,為了保證主鍵不沖突就得做很多判定,實現起來較為復雜
  • AUTO:數據庫ID自增,這種策略適合在數據庫服務器只有一臺的情況下使用,不可作為分布式ID使用
  • ASSIGN_UUID:可以在分布式的情況下使用,而且能夠保證ID唯一,但是聲稱的主鍵是32位的字符串,長度過長占用空間,而且不能排序,查詢性能也慢
  • ASSIGN_ID:可以在分布式的情況下使用,生成的是Long類型的數字,可以排序,性能也高,但是生成的策略與服務器時間有關,如果修改了系統時間,也有可能出現重復的主鍵
  • 綜上所述,每一種主鍵的策略都有自己的優缺點,根據自己的項目業務需求的實際情況來使用,才是最明智的選擇

4.1.6 簡化配置

在這里插入圖片描述

  • 模型類主鍵策略設置
    如果要在項目中的每一個模型類上都需要使用相同的生成策略,比如你有Book表,User表,Student表,Score表等好多個表,如果你每一個表的主鍵生成策略都是ASSIGN_ID,那我們就可以用yml配置文件來簡化開發,不用在每一個表的id上都加上@TableId(type = IdType.ASSIGN_ID)

    mybatis-plus:global-config:db-config:id-type: assign_id
    
  • 數據庫表與模型類的映射關系
    MP會默認將模型類的類名名首字母小寫作為表名使用,假如數據庫表的名稱都以tb_開頭,那么我們就需要將所有的模型類上添加@TableName("tb_TABLENAME"),這樣做很繁瑣,有沒有更簡單的方式呢?

    • 我們可以在配置文件中設置表的前綴
    mybatis-plus:global-config:db-config:id-type: assign_idtable-prefix: tb_
    

設置表的前綴內容,這樣MP就會拿 tb_加上模型類的首字母小寫,就剛好組裝成數據庫的表名(前提是你的表名得規范命名,別瞎起花里胡哨的名)。將User類的@TableName注解去掉,再次運行新增方法

@Data
public class User {@TableId(type = IdType.ASSIGN_ID)private Long id;private String name;private String password;private Integer age;private String tel;
}

4.2 多記錄操作

在這里插入圖片描述

需求:根據傳入的id集合將數據庫表中的數據刪除掉。

  • deleteBatchIds
    @Test
    void testDeleteByIds(){
    ArrayList<Long> list = new ArrayList<>();
    list.add(1572543345085964289L);
    list.add(1572554951983460354L);
    list.add(1572555035978534913L);
    userDao.deleteBatchIds(list);
    }
    

執行成功后,數據庫表中的數據就會按照指定的id進行刪除。上面三個數據是我之前新增插入的,可以隨便換成數據庫中有的id

需求:根據傳入的ID集合查詢用戶信息

  • selectBatchIds
@Test
void testSelectByIds() {ArrayList<Long> list = new ArrayList<>();list.add(1L);list.add(2L);list.add(3L);for (User user : userDao.selectBatchIds(list)) {System.out.println(user);}
}

4.3 邏輯刪除

邏輯刪除是刪除操作中比較重要的一部分,先來講個案例
在這里插入圖片描述

  • 這是一個員工和其所辦理的合同表,一個員工可以辦理多張合同表

  • 員工ID為1的張業績,辦理了三個合同,但是她現在想離職跳槽了,我們需要將員工表中的數據進行刪除,執行DELETE操作

  • 如果表在設計的時候有主外鍵關系,那么同時也要將合同表中的張業績的數據刪掉
    在這里插入圖片描述

  • 后來公司要統計今年的總業績,發現這數據咋對不上呢,業績這么少,原因是張業績辦理的合同信息被刪掉了

  • 如果只刪除員工,卻不刪除員工對應的合同表數據,那么合同的員工編號對應的員工信息不存在,那么就會產生垃圾數據,出現無主合同,根本不知道有張業績這個人的存在

  • 經過我們的分析之后,我們不應該將表中的數據刪除掉,得留著,但是又得把離職的人和在職的人區分開,這樣就解決了上述問題
    在這里插入圖片描述

  • 區分的方式,就是在員工表中添加一列數據deleted,如果為0說明在職員工,如果離職則將其改完1,(0和1所代表的含義是可以自定義的)

所以對于刪除操作業務問題來說有:

  • 物理刪除:業務數據從數據庫中丟棄,執行的是delete操作
  • 邏輯刪除:為數據設置是否可用狀態字段,刪除時設置狀態字段為不可用狀態,數據保留在數據庫中,執行的是update操作

MP中邏輯刪除具體該如何實現?

  1. 步驟一:修改數據庫表,添加deleted列
    字段名任意,類型int,長度1,默認值0(個人習慣,你隨便)
    在這里插入圖片描述

  2. 步驟二:實體類添加屬性
    還得修改對應的pojo類,增加delete屬性(屬性名也任意,對不上用@TableField來添加映射關系
    標識新增的字段為邏輯刪除字段,使用@TableLogic
    在這里插入圖片描述

    //表名前綴和id生成策略在yml配置文件寫了
    @Data
    public class User {
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
    //新增delete屬性
    //value為正常數據的值(在職),delval為刪除數據的值(離職)
    @TableLogic(value = "0",delval = "1")
    private Integer deleted;
    }
    
  3. 步驟三:運行刪除方法

    @Test
    void testLogicDelete(){userDao.deleteById(1);
    }
    

從測試結果來看,邏輯刪除最后走的是update操作,執行的是UPDATE tb_user SET deleted=1 WHERE id=? AND deleted=0,會將指定的字段修改成刪除狀態對應的值。

  • 思考:邏輯刪除,對查詢有沒有影響呢?
    • 執行查詢操作
    @Test
    void testSelectAll() {
    for (User user : userDao.selectList(null)) {System.out.println(user);}
    }
    

從日志中可以看到執行的SQL語句如下,WHERE條件中,規定只查詢deleted字段為0的數據

SELECT id,name,password,age,tel,deleted FROM tb_user WHERE deleted=0

輸出結果當然也沒有ID為1的數據了

如果還是想把已經刪除的數據都查詢出來該如何實現呢?

@Mapper
public interface UserDao extends BaseMapper<User> {//查詢所有數據包含已經被刪除的數據@Select("select * from tb_user")public List<User> selectAll();
}
  • 如果每個表都要有邏輯刪除,那么就需要在每個模型類的屬性上添加@TableLogic注解,如何優化?
    • 在配置文件中添加全局配置,如下:
    mybatis-plus:global-config:db-config:## 邏輯刪除字段名logic-delete-field: deleted## 邏輯刪除字面值:未刪除為0logic-not-delete-value: 0## 邏輯刪除字面值:刪除為1logic-delete-value: 1
    

在這里插入圖片描述

使用yml配置文件配置了之后,就不需要在模型類上用@TableLogic注解

介紹完邏輯刪除,邏輯刪除的本質為修改操作。如果加了邏輯刪除字段,查詢數據時也會自動帶上邏輯刪除字段。
執行的SQL語句為:

UPDATE tb_user SET deleted=1 WHERE id=? AND deleted=0

知識點:@TableLogic

名稱@TableLogic
類型屬性注解
位置模型類中用于表示刪除字段的屬性定義上方
作用標識該字段為進行邏輯刪除的字段
相關屬性value:邏輯未刪除值;delval:邏輯刪除值

4.4 樂觀鎖

在學樂觀鎖之前,我們還是先由一個案例來引入

  • 業務并發現象帶來的問題:秒殺
    在這里插入圖片描述

    • 加入有100個商品在售,為了保證每個商品只能被一個人購買,如何保證不會超買或者重復賣
    • 對于這一類的問題,其實有很多的解決方案可以使用
    • 第一個最先想到的就是鎖,鎖在一臺服務器中是可以解決的,但是如果在多臺服務器下就沒辦法控制,比如12306有兩臺服務器,再進行賣票,在兩臺服務器上都添加鎖的話,那也有可能會在同一時刻有兩個線程在賣票,還是會出現并發問題
    • 我們接下來介紹的這種方式就是針對于小型企業的解決方案,因為數據庫本身的性能就是個瓶頸,如果對其并發超過2000以上的就需要考慮其他解決方案了

簡單來說,樂觀鎖主要解決的問題是,當要更新一條記錄的時候,希望這條記錄沒有被別人更新

4.4.1 實現思路

  • 數據庫表中添加version字段,比如默認值給個1
  • 第一個線程要修改數據之前,取出記錄時,獲取當前的version=1
  • 第二個線程要修改數據之前,取出記錄時,獲取當前的version=1
    第一個線程執行更新時
    • set version = newVersion where version = oldVersion
      • newVersion = version + 1
      • oldVersion = version
  • 第二個線程執行更新時
    set version = newVersion where version = oldVersion
    • newVersion = version + 1
    • oldVersion = version
  • 假如這兩個線程都來更新數據,第一個和第二個線程都可能先執行
    • 假如第一個線程先執行更新,會將version改為2
      • 那么第二個線程再更新的時候,set version = 2 where version = 1,此時數據庫表的version已經是2了,所以第二個線程修改失敗
    • 假如第二個線程先執行更新,會將version改為2
      • 那么第一個線程再更新的時候,set version = 2 where version = 1,此時數據庫表的version已經是2了,所以第一個線程修改失敗

4.4.2 實現步驟

  • 步驟一:數據庫表添加列
    加一列version,長度給個11,默認值設為1

  • 步驟二:在模型類中添加對應的屬性

    @Data
    public class User {
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
    @TableLogic(value = "0", delval = "1")
    private Integer deleted;
    @Version
    private Integer version;
    }
    
  • 步驟三:添加樂觀鎖攔截器

    @Configuration
    public class MpConfig {
    @Bean
    public MybatisPlusInterceptor mpInterceptor() {//1.定義Mp攔截器MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();//2.添加樂觀鎖攔截器mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return mpInterceptor;}
    }
    
  • 步驟四:執行更新操作

    @Test
    void testUpdate(){
    //1. 先通過要修改的數據id將當前數據查詢出來
    User user = userDao.selectById(1L);
    //2. 修改屬性
    user.setName("Person");
    userDao.updateById(user);
    }
    

    在這里插入圖片描述

我們傳遞的是1(oldVersion),MP會將1進行加1,變成2,然后更新回到數據庫中(newVersion)

大概分析完樂觀鎖的實現步驟以后,我們來模擬一種加鎖的情況,看看能不能實現多個人修改同一個數據的時候,只能有一個人修改成功。

@Test
void testUpdate() {User userA = userDao.selectById(1L); //version=1User userB = userDao.selectById(1L); //version=1userB.setName("Jackson");userDao.updateById(userB);  //B修改完了之后,version=2userA.setName("Person");//A拿到的version是1,但現在的version已經是2了,那么A在執行 UPDATE ... WHERE version = 1時,就必然會失敗userDao.updateById(userA);  
}

至此,樂觀鎖的實現就已經完成了

五、快速開發

官方文檔地址
通過觀察我們之前寫的代碼,會發現其中有很多重復的內容,于是MP抽取了這些重復的地方,做成了一個模板供我們使用
要想完成代碼自動生成,我們需要有以下內容:

  • 模板: MyBatisPlus提供,可以自己提供,但是麻煩,不建議
  • 數據庫相關配置:讀取數據庫獲取表和字段信息
  • 開發者自定義配置:手工配置,比如ID生成策略

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

5.1 代碼生成器實現

  • 步驟一:創建一個Maven項目

  • 步驟二:導入對應的jar包

    <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.1</version>
    </parent>
    <groupId>com.blog</groupId>
    <artifactId>mybatisplus_04_generator</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties><java.version>1.8</java.version>
    </properties>
    <dependencies><!--spring webmvc--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--mybatisplus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.1</version></dependency><!--druid--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.16</version></dependency><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!--test--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.12</version></dependency><!--代碼生成器--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.4.1</version></dependency><!--velocity模板引擎--><dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>2.3</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins>
    </build></project>
    
  • 步驟三:編寫引導類

    @SpringBootApplication
    public class Mybatisplus04GeneratorApplication {
    public static void main(String[] args) {SpringApplication.run(Mybatisplus04GeneratorApplication.class, args);
    }}
    
  • 步驟四:創建代碼生成類

    public class CodeGenerator {
    public static void main(String[] args) {//1.獲取代碼生成器的對象AutoGenerator autoGenerator = new AutoGenerator();//設置數據庫相關配置DataSourceConfig dataSource = new DataSourceConfig();dataSource.setDriverName("com.mysql.cj.jdbc.Driver");dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");dataSource.setUsername("root");dataSource.setPassword("YOURPASSWORD");autoGenerator.setDataSource(dataSource);//設置全局配置GlobalConfig globalConfig = new GlobalConfig();globalConfig.setOutputDir(System.getProperty("user.dir")+"/項目名/src/main/java");    //設置代碼生成位置globalConfig.setOpen(false);    //設置生成完畢后是否打開生成代碼所在的目錄globalConfig.setAuthor("heima");    //設置作者globalConfig.setFileOverride(true);     //設置是否覆蓋原始生成的文件globalConfig.setMapperName("%sDao");    //設置數據層接口名,%s為占位符,指代模塊名稱globalConfig.setIdType(IdType.ASSIGN_ID);   //設置Id生成策略autoGenerator.setGlobalConfig(globalConfig);//設置包名相關配置PackageConfig packageInfo = new PackageConfig();packageInfo.setParent("com.aaa");   //設置生成的包名,與代碼所在位置不沖突,二者疊加組成完整路徑packageInfo.setEntity("domain");    //設置實體類包名packageInfo.setMapper("dao");   //設置數據層包名autoGenerator.setPackageInfo(packageInfo);//策略設置StrategyConfig strategyConfig = new StrategyConfig();strategyConfig.setInclude("tb_user");  //設置當前參與生成的表名,參數為可變參數strategyConfig.setTablePrefix("tb_");  //設置數據庫表的前綴名稱,模塊名 = 數據庫表名 - 前綴名  例如: User = tb_user - tb_strategyConfig.setRestControllerStyle(true);    //設置是否啟用Rest風格strategyConfig.setVersionFieldName("version");  //設置樂觀鎖字段名strategyConfig.setLogicDeleteFieldName("deleted");  //設置邏輯刪除字段名strategyConfig.setEntityLombokModel(true);  //設置是否啟用lombokautoGenerator.setStrategy(strategyConfig);//2.執行生成操作autoGenerator.execute();}
    }
    

對于代碼生成器中的代碼內容,我們可以直接從官方文檔中獲取代碼進行修改,

  • 步驟五:運行程序
    運行成功后,會在當前項目中生成很多代碼,代碼包含controller,servicemapperentity

至此代碼生成器就已經完成工作,我們能快速根據數據庫表來創建對應的類,簡化我們的代碼開發。

初期還是不建議直接使用代碼生成器,還是多自己手寫幾遍比較好

5.2 MP中Service的CRUD

回顧我們之前業務層代碼的編寫,編寫接口和對應的實現類:

回顧我們之前業務層代碼的編寫,編寫接口和對應的實現類:

public interface UserService{}@Service
public class UserServiceImpl implements UserService{}

接口和實現類有了以后,需要在接口和實現類中聲明方法

public interface UserService{public List<User> findAll();
}@Service
public class UserServiceImpl implements UserService{@Autowiredprivate UserDao userDao;public List<User> findAll(){return userDao.selectList(null);}
}

MP看到上面的代碼以后就說這些方法也是比較固定和通用的,那我來幫你抽取下,所以MP提供了一個Service接口和實現類,分別是:IService和ServiceImpl,后者是對前者的一個具體實現。

以后我們自己寫的Service就可以進行如下修改:

public interface UserService extends IService<User>{}@Service
public class UserServiceImpl extends ServiceImpl<UserDao, User> implements UserService{}

修改以后的好處是,MP已經幫我們把業務層的一些基礎的增刪改查都已經實現了,可以直接進行使用。

編寫測試類進行測試:

@SpringBootTest
class Mybatisplus04GeneratorApplicationTests {private IUserService userService;@Testvoid testFindAll() {List<User> list = userService.list();System.out.println(list);}
}

六、復習

之前如果要寫動態SQL查詢,需要用XML配置文件,用<where><if>標簽來自動去除and連接詞啥的。

<select id="selectByCondition" resultMap="brandResultMap">select *from tb_brand<where><if test="brand.brandName != null and brand.brandName != '' ">and  brand_name like #{brand.brandName}</if><if test="brand.companyName != null and brand.companyName != '' ">and  company_name like #{brand.companyName}</if><if test="brand.status != null">and  status = #{brand.status}</if></where>limit #{begin} , #{size}
</select>

學完MyBatisPlus之后,我們可以不用XML配置文件,就用MP也能寫動態SQL,用Wrapper類。

  • 針對圖書類別和名稱做的一個動態SQL就長這個樣子
@Override
public List<Book> getByCondition(String type,String name) {LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<>();lqw.like(!(type == null || "".equals(type)), Book::getType, type).like(!(name == null || "".equals(name)),Book::getName, name);return bookDao.selectList(lqw);
}

MP里的and不用顯示聲明,而且還可以很簡單的幫我們完成模糊查詢,當判斷條件為false時,則不會進行SQL語句的拼接。而且也不需要創建文件,寫配置

不過復雜的SQL語句還是要用XML寫的,用MP寫的話,可讀性不

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/212003.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/212003.shtml
英文地址,請注明出處:http://en.pswp.cn/news/212003.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

運維05:自動化

人工運維時代 運維人員早期需要維護眾多的機器&#xff0c;因此需要執行很多重復的勞動&#xff0c;很多機器需要同時部署相同的服務或者是執行相同的命令&#xff0c;還得反復地登錄不同的機器&#xff0c;執行重復的動作 自動化運維時代 早期運維人員會結合ssh免密登錄&…

Java基礎——對象類型轉換(向上、向下轉型)

非繼承關系的類之間對象類型不可以互相類型轉換&#xff0c;只有繼承關系才可以互相轉換。 簡單說&#xff0c;對象類型轉換的前提要是繼承關系。 對象類型轉換分為&#xff1a;向上轉型和向下轉型。多態就是一種自動向上轉型。 向上轉型&#xff1a;子類對象用父類類型接收…

Leetcode 2963. Count the Number of Good Partitions

Leetcode 2963. Count the Number of Good Partitions 1. 解題思路2. 代碼實現 題目鏈接&#xff1a;2963. Count the Number of Good Partitions 1. 解題思路 這一題根據題意&#xff0c;顯然我們可以將其先分為 n n n個原子partition&#xff0c;確保任意兩個partition之間…

git 常用的使用方法

1.查看分支 $ git branch #查看本地分支 $ git branch -r #查看遠程分支 $ git branch -a #查看所有分支 $ git branch -vv #查看本地分支及追蹤的分支 2.創建分支 方法1 $ git branch 分支名 #創建本地分支 #將本地分支push&#xff0c;就創建了遠程分支方法2 #創建本地分…

載入了名字空間‘htmltools’ 0.5.6,但需要的是>= 0.5.7解決方案

解決方案&#xff1a;刪除之前的舊版本安裝包&#xff0c;安裝新的包 1.卸載之前的安裝包 2.關閉R&#xff0c;重新打開 3. # install.packages("htmltools") library(htmltools)

Java 并發編程(一)

1、在 java 中守護線程和本地線程區別&#xff1f; java 中的線程分為兩種&#xff1a;守護線程&#xff08;Daemon&#xff09;和用戶線程&#xff08;User&#xff09; 任何線程都可以設置為守護線程和用戶線程&#xff0c;通過方法 Thread.setDaemon(boolon)&#xff1b;tru…

HarmonyOS學習--了解基本工程目錄

1.工程級目錄 工程的目錄結構如下&#xff1a; 其中詳細如下&#xff1a; AppScope中存放應用全局所需要的資源文件。entry是應用的主模塊&#xff0c;存放HarmonyOS應用的代碼、資源等。oh_modules是工程的依賴包&#xff0c;存放工程依賴的源文件。build-profile.json5是工…

如何找到MACOS系統更新的安裝包

首先在應用商店中下載新系統的安裝包&#xff0c;然后在設置中不要點安裝&#xff0c;會自動跳出安裝的界面&#xff0c;不要關閉界面&#xff0c;打開命令行用root權限輸入命令cat /var/log/install.log | grep *.dmg&#xff0c; 就會顯示 sh-3.2# cat /var/log/install.log …

算法基礎十

加一 給定一個由 整數 組成的 非空 數組所表示的非負整數&#xff0c;在該數的基礎上加一。最高位數字存放在數組的首位&#xff0c; 數組中每個元素只存儲單個數字。 示例 1&#xff1a; 輸入&#xff1a;digits [1,2,3] 輸出&#xff1a;[1,2,4] 解釋&#xff1a;輸入數組表…

YOLO_embedded: YOLO算法快速嵌入式部署

YOLO_embedded&#xff1a; YOLO算法快速嵌入式部署 for UbuntuBased on YOLOXOpenVINO & TensorRT 本項目提供c和python兩種語言&#xff0c;詳情請見各個文件夾下的README.md 安裝OpenVINO 點此進入官網選擇版本進行下載&#xff0c;然后打開install_openvino.sh將相…

ORACLE 19c 統一恢復處于ASM中的CDB含PDB數據文件到某一個文件目錄下面

NOCDB情況下&#xff0c;要把ASM中的文件恢復到文件系統&#xff0c;大家都知道分別設置每個文件的路徑即可&#xff0c;但如果是租戶環境&#xff0c;每個PDB都有不同路徑&#xff0c;而且每個PDB都有SYSTEM&#xff0c;SYSAUX等一些表空降&#xff0c;不可能放在同一個目錄中…

Linux_CentOS_7.9 VNC安裝卸載以及相關配置開機自啟動服務簡易記錄

VNC安裝卸載以及相關配置開機自啟動服務&#xff1a; 查看環境&#xff1a;&#xff08;yum鏡像源配置可以參考我之前文章里面有詳細參考http://t.csdnimg.cn/mzGoI&#xff09; [rootorcl238 ~]# rpm -qa | grep vnc ##查看系統現有VNC軟件版本 gtk-vnc2-0.7.0-3.el7.x86…

道可云元宇宙每日資訊|青島市元宇宙領域新產品推介暨產學研對接會舉行

道可云元宇宙每日簡報&#xff08;2023年12月7日&#xff09;訊&#xff0c;今日元宇宙新鮮事有&#xff1a; 青島市元宇宙領域新產品推介暨產學研對接會舉行 為加快推動青島市元宇宙技術和產業創新&#xff0c;引領下一代互聯網發展&#xff0c;青島市元宇宙領域新產品推介暨…

算法基礎九

螺旋矩陣2 給你一個正整數 n &#xff0c;生成一個包含 1 到 n2 所有元素&#xff0c;且元素按順時針順序螺旋排列的 n x n 正方形矩陣 matrix。 示例 1&#xff1a; 輸入&#xff1a;n 3 輸出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]] 示例 2&#xff1a; 輸入&#xff1a;n …

第12節: Vue3 修飾符

如何在UniApp中使用Vue3框架使用修飾符&#xff1a; <template> <view> <button click"toggleVisibility ^ :disabledisDisabled">點擊切換顯示狀態</button> <text>{{ isVisible ? 顯示 : 隱藏 }}</text> </view> …

簡易加減運算器的制作----數字電路設計(含proteus仿真)

簡易加減運算器的制作 一、功能要求—基本功能 1、自制0-9按鍵&#xff0c;在一個LED數碼管上穩定地顯示當前按下的值。&#xff08;基本功能&#xff09; 2、增加、兩個按鍵&#xff0c;實現0-9兩個一位數的加法運算&#xff0c;同時在兩位LED上穩定地顯示運算結果。&#…

React中每次渲染都會傳入一個新的props.children到子組件?

傳入props.children后, 為什么會導致組件的重新渲染&#xff1f; 問題描述 在 react 中, 我想要對組件的渲染進行優化, 遇到了一個非常意思的問題, 當我向一個組件中傳入了 props.children 之后, 每次父組件重新渲染都會導致這個組件的重新渲染; 它看起來的表現就像是被memo包…

MTU與MSS

MTU&#xff1a;一個網絡包的最大長度&#xff0c;以太網中一般為1500各字節。 MSS&#xff1a;除去頭部之后&#xff0c;一個網絡包所能容納的TCP數據的最大長度。 應用程序調用write后&#xff0c;將要發送的數據被交給TCP/IP協議棧進行。 協議棧不關心應用的數據內容&…

四:爬蟲-Cookie與Session實戰

四&#xff1a;Cookie與Session實戰 ? 在瀏覽網站的過程中&#xff0c;我們經常會遇到需要登錄的情況&#xff0c;有些頁面只有登錄之后才可以訪問。在登錄之后可以連續訪問很多次網站&#xff0c;但是有時候過一段時間就需要重新登錄。還有一些網站&#xff0c;在打開瀏覽器…

c語言歸并排序(詳解)

歸并排序是一種分治算法&#xff0c;它將列表分割成較小的子列表&#xff0c;然后遞歸地對子列表進行排序&#xff0c;最后將這些子列表合并以產生已排序的列表。基本概念包括&#xff1a; 分割&#xff1a;將列表分割成較小的子列表&#xff0c;直到子列表的長度為1或0。排序…