MybatisPlus學習整理(一)

本文是經過慕課網相關課程學習MyBatisPlus整理的筆記。
MyBatisPlus入門 : - ) 老師講的挺好的,還不會MyBatisPlus的小夥伴門能夠聽一下。
MyBatisPlus官網
MyBatisPlus源碼地址

MyBatisPlus架構圖(盜用官網的,侵,刪。)

mybatis-plus.png

SpringBoot第一個簡單應用

  1. 數據庫建表
#建立用戶表
CREATE TABLE user (
    id BIGINT(20) PRIMARY KEY NOT NULL COMMENT '主鍵',
    name VARCHAR(30) DEFAULT NULL COMMENT '姓名',
    age INT(11) DEFAULT NULL COMMENT '年齡',
    email VARCHAR(50) DEFAULT NULL COMMENT '郵箱',
    manager_id BIGINT(20) DEFAULT NULL COMMENT '直屬上級id',
    create_time DATETIME DEFAULT NULL COMMENT '建立時間',
    CONSTRAINT manager_fk FOREIGN KEY (manager_id)
        REFERENCES user (id)
)  ENGINE=INNODB CHARSET=UTF8;

#初始化數據:
INSERT INTO user (id, name, age, email, manager_id
    , create_time)
VALUES (1087982257332887553, '大boss', 40, 'boss@baomidou.com', NULL
        , '2019-01-11 14:20:20'),
    (1088248166370832385, '王天風', 25, 'wtf@baomidou.com', 1087982257332887553
        , '2019-02-05 11:12:22'),
    (1088250446457389058, '李藝偉', 28, 'lyw@baomidou.com', 1088248166370832385
        , '2019-02-14 08:31:16'),
    (1094590409767661570, '張雨琪', 31, 'zjq@baomidou.com', 1088248166370832385
        , '2019-01-14 09:15:15'),
    (1094592041087729666, '劉紅雨', 32, 'lhm@baomidou.com', 1088248166370832385
        , '2019-01-14 09:48:16');
  1. 依賴
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.2</version>
        </dependency>
  1. springboot配置文件
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/test?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
logging:
  level:
    root: warn
    org.ywb.demo.dao: trace
  pattern:
    console: '%p%m%n'
  1. 建立相關包,如圖:

image.png

  1. 在pojo包中新建和數據庫user表映射的類
@Data
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private String managerId;
    private LocalDateTime createTime;
}
  1. 在dao包中建立mapper接口,並集成mybatisPlus的BaseMapper
public interface UserMapper extends BaseMapper<User> {

}
  1. 在springboot啓動類添加@MapperScan掃描dao層接口
@MapperScan("org.ywb.demo.dao")
@SpringBootApplication
public class MybatisPlusDemoApplication {

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

}

8.編寫測試類java

@RunWith(SpringRunner.class)
@SpringBootTest
public class MybatisPlusDemoApplicationTests {

    @Resource
    private UserMapper userMapper;
    
    @Test
    public void select(){
        List<User> users = userMapper.selectList(null);
        users.forEach(System.out::println);
    }

}

運行結果:
image.pngmysql


經常使用註解

MyBatisPlus提供了一些註解供咱們在實體類和表信息出現不對應的時候使用。經過使用註解完成邏輯上匹配。git

註解名稱 說明
@TableName 實體類的類名和數據庫表名不一致
@TableId 實體類的主鍵名稱和表中主鍵名稱不一致
@TableField 實體類中的成員名稱和表中字段名稱不一致
@Data
@TableName("t_user")
public class User {
    @TableId("user_id")
    private Long id;
    @TableField("real_name")
    private String name;
    private Integer age;
    private String email;
    private Long managerId;
    private LocalDateTime createTime;
}

排除實體類中非表字段

  1. 使用transient關鍵字修飾非表字段,可是被transient修飾後,沒法進行序列化。
  2. 使用static關鍵字,由於咱們使用的是lombok框架生成的get/set方法,因此對於靜態變量,咱們須要手動生成get/set方法。
  3. 使用@TableField(exist = false)註解

CURD

BaseMapper中封裝了不少關於增刪該查的方法,後期自動生成,咱們直接調用接口中的相關方法便可完成相應的操做。
BaseMapper部分代碼github

public interface BaseMapper<T> extends Mapper<T> {

    int insert(T entity);
   
    int deleteById(Serializable id);

    int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);

    int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

    int updateById(@Param(Constants.ENTITY) T entity);

...
}

插入一條記錄測試:web

@Test
    public void insert(){
        User user = new User();
        user.setAge(31);
        user.setManagerId(1088250446457389058L);
        user.setCreateTime(LocalDateTime.now());
        int insert = userMapper.insert(user);
        System.out.println("影像記錄數:"+insert);
    }

image.png

條件構造器查詢

除了BaseMapper中提供簡單的增刪改查方法以外,還提供了不少關於區間查詢,多表鏈接查詢,分組等等查詢功能,實現的類圖以下所示:
image.png
經過觀察類圖可知,咱們須要這些功能時,只須要建立QueryWrapper對象便可。spring

  1. 模糊查詢
/**
     * 查詢名字中包含'雨'而且年齡小於40
     * where name like '%雨%' and age < 40
     */
    @Test
    public void selectByWrapper(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like("name","雨").lt("age",40);
        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }

image.png

  1. 嵌套查詢
/**
     * 建立日期爲2019年2月14日而且直屬上級姓名爲王姓
     * date_format(create_time,'%Y-%m-%d') and manager_id in (select id from user where name like '王%')
     */
    @Test
    public void selectByWrapper2(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.apply("date_format(create_time,'%Y-%m-%d')={0}","2019-02-14")
                .inSql("manager_id","select id from user where name like '王%'");
        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }

image.png
注意
上面的日期查詢使用的是佔位符的形式進行查詢,目的就是爲了防止SQL注入的風險。
apply方法的源碼sql

/**
     * 拼接 sql
     * <p>!! 會有 sql 注入風險 !!</p>
     * <p>例1: apply("id = 1")</p>
     * <p>例2: apply("date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")</p>
     * <p>例3: apply("date_format(dateColumn,'%Y-%m-%d') = {0}", LocalDate.now())</p>
     *
     * @param condition 執行條件
     * @return children
     */
    Children apply(boolean condition, String applySql, Object... value);

SQL 注入的例子:數據庫

queryWrapper.apply("date_format(create_time,'%Y-%m-%d')=2019-02-14 or true=true")
              .inSql("manager_id","select id from user where name like '王%'");

sql注入例子

  1. and & or
/**
     * 名字爲王姓,(年齡小於40或者郵箱不爲空)
     */
    @Test
    public void selectByWrapper3(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.likeRight("name","王").and(wq-> wq.lt("age",40).or().isNotNull("email"));

        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);

    }
  1. between & and
/**
     * 名字爲王姓,(年齡小於40,而且年齡大於20,而且郵箱不爲空)
     */
    @Test
    public void selectWrapper4(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.likeRight("name", "王").and(wq -> wq.between("age", 20, 40).and(wqq -> wqq.isNotNull("email")));
        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }

image.png

  1. nested
/**
     * (年齡小於40或者郵箱不爲空)而且名字爲王姓
     * (age<40 or email is not null)and name like '王%'
     */
    @Test
    public void selectWrapper5(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();

        queryWrapper.nested(wq->wq.lt("age",40).or().isNotNull("email")).likeRight("name","王");

        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }

image.png

  1. in
/**
     * 年齡爲30,31,35,34的員工
     */
    @Test
    public void selectWrapper6(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();

        queryWrapper.in("age", Arrays.asList(30,31,34,35));

        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }

image.png

  1. last 有SQL注入的風險!!!
/**
     * 無視優化規則直接拼接到 sql 的最後(有sql注入的風險,請謹慎使用)
     * <p>例: last("limit 1")</p>
     * <p>注意只能調用一次,屢次調用以最後一次爲準</p>
     *
     * @param condition 執行條件
     * @param lastSql   sql語句
     * @return children
     */
    Children last(boolean condition, String lastSql);
/**
     * 只返回知足條件的一條語句便可
     * limit 1
     */
    @Test
    public void selectWrapper7(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();

        queryWrapper.in("age", Arrays.asList(30,31,34,35)).last("limit 1");

        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }

image.png

  1. 查詢指定部分列
/**
     * 查找爲王姓的員工的姓名和年齡
     */
    @Test
    public void selectWrapper8(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("name","age").likeRight("name","王");
        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }

image.png

  1. 使用過濾器查詢指定列
/**
     * 查詢全部員工信息除了建立時間和員工ID列
     */
    @Test
    public void selectWrapper9(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.select(User.class,info->!info.getColumn().equals("create_time")
                &&!info.getColumn().equals("manager_id"));
        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }

image.png

condition 的做用

在咱們調用的查詢語句中,經過查看源碼(這裏以apply方法爲例)能夠看出,每一個查詢方法的第一個參數都是boolean類型的參數,重載方法中默認給咱們傳入的都是true。編程

default Children apply(String applySql, Object... value) {
        return apply(true, applySql, value);
    }
    Children apply(boolean condition, String applySql, Object... value);

這個condition的做用是爲true時,執行其中的SQL條件,爲false時,忽略設置的SQL條件。segmentfault

實體做爲條件構造方法的參數

在web開發中,controller層經常會傳遞給咱們一個用戶的對象,好比經過用戶姓名和用戶年齡查詢用戶列表。
咱們能夠將傳遞過來的對象直接以構造參數的形式傳遞給QueryWrapper,MyBatisPlus會自動根據實體對象中的屬性自動構建相應查詢的SQL語句。

@Test
    public void selectWrapper10(){
        User user = new User();
        user.setName("劉紅雨");
        user.setAge(32);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);
        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }

image.png
若是想經過對象中某些屬性進行模糊查詢,咱們能夠在跟數據庫表對應的實體類中相應的屬性標註註解便可。
好比咱們想經過姓名進行模糊查詢用戶列表。

@TableField(condition = SqlCondition.LIKE)
    private String name;
@Test
    public void selectWrapper10(){
        User user = new User();
        user.setName("紅");
        user.setAge(32);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);
        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }

image.png

Lambda條件構造器

MybatisPlus提供了4種方式建立lambda條件構造器,前三種分別是這樣的

LambdaQueryWrapper<User> lambdaQueryWrapper = new QueryWrapper<User>().lambda();
        LambdaQueryWrapper<User> lambdaQueryWrapper1 = new LambdaQueryWrapper<>();
        LambdaQueryWrapper<User> lambdaQueryWrapper2 = Wrappers.lambdaQuery();
  1. 查詢名字中包含‘雨’而且年齡小於40的員工信息
@Test
    public void lambdaSelect(){
        LambdaQueryWrapper<User> lambdaQueryWrapper = Wrappers.lambdaQuery();
        lambdaQueryWrapper.like(User::getName,"雨").lt(User::getAge,40);

        List<User> userList = userMapper.selectList(lambdaQueryWrapper);
        userList.forEach(System.out::println);
    }

image.png

QueryWrapper類已經提供了很強大的功能,而lambda條件構造器作的和QueryWrapper的事也是相同的爲何要冗餘的存在lambda條件構造器呢?
QueryWrapper是經過本身寫表中相應的屬性進行構造where條件的,容易發生拼寫錯誤,在編譯時不會報錯,只有運行時纔會報錯,而lambda條件構造器是經過調用實體類中的方法,若是方法名稱寫錯,直接進行報錯,因此lambda的糾錯功能比QueryWrapper要提早不少。
舉個例子:
查找姓名中包含「雨」字的員工信息。
使用QueryWrapper

queryWrapper.like("name","雨");

使用lambda

lambdaQueryWrapper.like(User::getName,"雨");

若是在拼寫name的時候不當心,寫成了naem,程序並不會報錯,可是若是把方法名寫成了getNaem程序當即報錯。

第四種lambda構造器
細心的人都會發現不管是以前的lambda構造器仍是queryWrapper,每次編寫完條件構造語句後都要將對象傳遞給mapper 的selectList方法,比較麻煩,MyBatisPlus提供了第四種函數式編程方式,不用每次都傳。

  1. 查詢名字中包含「雨」字的,而且年齡大於20的員工信息
@Test
    public void lambdaSelect(){
        List<User> userList = new LambdaQueryChainWrapper<>(userMapper).like(User::getName, "雨").ge(User::getAge, 20).list();
        userList.forEach(System.out::println);
    }

image.png

自定義SQL

  1. 在resources資源文件夾下新建mapper文件夾,並將mapper文件夾的路徑配置到配置文件中

image.png

mybatis-plus:
  mapper-locations: mapper/*.xml
  1. 在mapper 文件夾中新建UserMapper.xml。
  2. 像mybatis那樣在UseMapper接口中寫接口,在UserMapper接口中寫SQL便可。

UserMapper

public interface UserMapper extends BaseMapper<User> {

    /**
     * 查詢全部用戶信息
     * @return list
     */
    List<User> selectAll();
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.ywb.demo.dao.UserMapper">

    <select id="selectAll" resultType="org.ywb.demo.pojo.User">
        select * from user
    </select>
</mapper>

分頁查詢

MyBatis分頁提供的是邏輯分頁,每次將全部數據查詢出來,存儲到內存中,而後根據頁容量,逐頁返回。若是表很大,無疑是一種災難!
MyBatisPlus物理分頁插件

  1. 新建config類,在config類中建立PaginationInterceptor對象
@Configuration
public class MybatisPlusConfig {

    @Bean
    public PaginationInterceptor paginationInterceptor(){
        return new PaginationInterceptor();
    }
}
  1. 測試:查詢年齡大於20 的用戶信息,並以每頁容量爲兩條分頁的形式返回。
@Test
    public void selectPage(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.ge("age",20);

        //設置當前頁和頁容量
        Page<User> page = new Page<>(1, 2);

        IPage<User> userIPage = userMapper.selectPage(page, queryWrapper);

        System.out.println("總頁數:"+userIPage.getPages());
        System.out.println("總記錄數:"+userIPage.getTotal());
        userIPage.getRecords().forEach(System.out::println);
    }

image.png

  1. 測試:不查詢總記錄數,分頁查詢

IPage類的構造參數提供了參數的重載,第三個參數爲false時,不會查詢總記錄數。

public Page(long current, long size, boolean isSearchCount) {
        this(current, size, 0, isSearchCount);
}
~~·
## 更新
1. 經過userMapper提供的方法更新用戶信息
@Test
public void updateTest1(){
    User user = new User();
    user.setId(1088250446457389058L);
    user.setEmail("update@email");
    int rows = userMapper.updateById(user);
    System.out.println(rows);
}
![image.png](https://upload-images.jianshu.io/upload_images/11774306-f9daecdc4b339612.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

2. 使用UpdateWrapper更新數據(至關於使用聯合主鍵)
@Test
public void updateTest2(){
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    updateWrapper.eq("name","李藝偉").eq("age",26);

    User user = new User();
    user.setEmail("update2@email");
    int rows = userMapper.update(user, updateWrapper);
    System.out.println(rows);
}
![image.png](https://upload-images.jianshu.io/upload_images/11774306-74dbadc4c7b22a31.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
3. 當咱們更新少許用戶信息的時候,能夠不用建立對象,直接經過調用set方法更新屬性便可。
@Test
public void updateTest3(){
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    updateWrapper.eq("name","李藝偉").eq("age",26).set("email","update3@email.com");
    userMapper.update(null,updateWrapper);
}
![image.png](https://upload-images.jianshu.io/upload_images/11774306-b786482a228c6fb5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
4. 使用lambda更新數據
@Test
public void updateByLambda(){
    LambdaUpdateWrapper<User> lambdaUpdateWrapper = Wrappers.lambdaUpdate();
    lambdaUpdateWrapper.eq(User::getName,"李藝偉").eq(User::getAge,26).set(User::getAge,27);
    userMapper.update(null,lambdaUpdateWrapper);
}
![image.png](https://upload-images.jianshu.io/upload_images/11774306-32e7875e86108986.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
## 刪除
刪除方式和update極其相似。
## AR模式(Active Record)
直接經過實體類完成對數據的增刪改查。
1. 實體類繼承Model類

@Data
@EqualsAndHashCode(callSuper = false)
public class User extends Model<User> {

private Long id;
@TableField(condition = SqlCondition.LIKE)
private String name;
private Integer age;
private String email;
private Long managerId;
private LocalDateTime createTime;

}

Model類中封裝了不少增刪改查方法,不用使用UserMapper便可完成對數據的增刪改查。
1. 查詢全部用戶信息
@Test
public void test(){
    User user = new User();
    user.selectAll().forEach(System.out::println);
}
![image.png](https://upload-images.jianshu.io/upload_images/11774306-6acd5af7b02fb310.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

## 主鍵策略
MyBatisPlus的主鍵策略封裝在`IdType`枚舉類中。

@Getter
public enum IdType {

/**
 * 數據庫ID自增
 */
AUTO(0),
/**
 * 該類型爲未設置主鍵類型(將跟隨全局)
 */
NONE(1),
/**
 * 用戶輸入ID
 * <p>該類型能夠經過本身註冊自動填充插件進行填充</p>
 */
INPUT(2),

/* 如下3種類型、只有當插入對象ID 爲空,才自動填充。 */
/**
 * 全局惟一ID (idWorker)
 */
ID_WORKER(3),
/**
 * 全局惟一ID (UUID)
 */
UUID(4),
/**
 * 字符串全局惟一ID (idWorker 的字符串表示)
 */
ID_WORKER_STR(5);

private final int key;

IdType(int key) {
    this.key = key;
}

}

在實體類中對應數據庫中的主鍵id屬性上標註註解`TableId(type='xxx')`便可完成主鍵配置。
@TableId(type = IdType.AUTO)
private Long id;
這種配置方式的主鍵策略只能在該表中生效,可是其餘表還須要進行配置,爲了不冗餘,麻煩,MybatisPlus提供了全局配置,在配置文件中配置主鍵策略便可實現。

mybatis-plus:
mapper-locations: mapper/*.xml
global-config:

db-config:
  id-type: auto
若是全局策略和局部策略全都設置,局部策略優先。
## 基本配置
[MyBatisPlus官方文檔](https://baomidou.gitee.io/mybatis-plus-doc/#/api?id=globalconfiguration)

mybatis-plus:
mapper-locations: mapper/*.xml
global-config:

db-config:
  # 主鍵策略
  id-type: auto
  # 表名前綴
  table-prefix: t
  # 表名是否使用下劃線間隔,默認:是
  table-underline: true

# 添加mybatis配置文件路徑
config-location: mybatis-config.xml
# 配置實體類包地址
type-aliases-package: org.ywb.demo.pojo
# 駝峯轉下劃線
configuration:

map-underscore-to-camel-case: true
- 附錄
1. mybatisPlus進階功能請戳[ MyBatisPlus學習整理(二)](https://www.jianshu.com/p/5c9e6acf9a70)
2. 源碼地址:[https://github.com/xiao-ren-wu/notebook/tree/master/mybatis-plus-demo](https://github.com/xiao-ren-wu/notebook/tree/master/mybatis-plus-demo)
相關文章
相關標籤/搜索