天天看點

提高開發效率的mybatis-plus

mybatis-plus

也是我在項目中發現的一個好用的架構,可能知道的人不多,是以很少看到關于它的介紹。正如官方介紹的:“我們的願景是成為

MyBatis

最好的搭檔,就像魂鬥羅中的

1P

2P

,基友搭配,效率翻倍”。目的是增強

MyBatis

,簡化開發、提高效率。

使用

mybatis-plus

可以少寫很多常用的

SQL

,通過繼承

BaseMapper

使用,還可以動态拼接

SQL

。第一眼看到我還以為是

JPA

。對分頁查詢也做了很好的支援,實際上分頁的實作還是基于

mybatis

提供的插件(攔截器)特性去實作的。本篇将介紹如何快速上手

mybatis-plus

,并重點從源碼分析分頁

PaginationInterceptor

的性能。

在項目中添加依賴

// mybatis and mybatis-plus
    compile group: 'com.baomidou', name: 'mybatisplus-spring-boot-starter', version: '1.0.5'
    compile group: 'com.baomidou', name: 'mybatis-plus', version: '2.2.0'
複制代碼           

編寫對應資料庫表的實體類。使用

@TableName

注解聲明對應資料庫中的哪個表,使用

@TableId

注解注釋的字段就是對應資料庫表的主鍵列。如果不開啟駝峰映射,還可以使用

@TableField

注解将字段與表的字段名做映射。

@TableName("company")
public class Company {
    @TableId
    private Long id;
    private String company;
    private String email;
    private String details;
    // @TableField("create_tm")
    private Date createTm;
    // @TableField("modified_tm")
    private Date modifiedTm;

}
複制代碼           

如需開啟駝峰映射,隻需在

yml

配置檔案中将

db-column-underline

改為

true

mybatis-plus:
  global-config:
    # 配置開啟駝峰映射
    db-column-underline: true
複制代碼           

編寫

Mapper

接口,需要繼承

BaseMapper

,泛型指定為對應表的實體類。無需編寫

mapper.xml

檔案,就能使用一些常用的

CRUD

功能,當然,也包括了分頁查詢。

/**
 * 需要繼承BaseMapper接口
 */
@Mapper
public interface CompanyMapper extends BaseMapper<Company> {

}
複制代碼           

現在就可以使用

CompanyMapper

了。

CompanyMapper companyMapper = applicationContext.getBean(CompanyMapper.class);
companyMapper.selectById(3);
companyMapper.selectBatchIds(Arrays.asList(3,4,5));
複制代碼           

複雜一點的查詢需要通過

Wrapper

去構造查詢條件,以分頁查詢為列。

Page<Company> pageResult = this.selectPage(
    // 擷取第一頁,頁大小為2
    new Page(1,2),
    // 查詢id>=1 且 小于等于100的記錄
    new EntityWrapper<Company>().between("id", "1", "100"));
    
// 分頁資訊
System.out.println("current page:"+pageResult.getCurrent());
System.out.println("page size:"+pageResult.getSize());
System.out.println("total records:"+pageResult.getTotal());
System.out.println("page count:"+pageResult.getPages());

// 查詢結果
pageResult.getRecords().stream().forEach(System.out::println);
複制代碼           

EntityWrapper

Wrapper

的一個子類。

Wrapper

封裝了

where

group by

order by

having

部分

sql

的生成邏輯。提供

between

notIn

in

eq

or

and

le

lt

gt

notExists

exists

isNull

isNotNull

link

等方法指定查詢條件,并支援鍊式調用。

Wrapper

非常适用于動态編寫一些簡單的

sql

,省去在

mapper

接口添加方法、再去

xml

檔案編寫

sql

的工作量,而且使用這種方式的好處還在于,簡化

mapper

接口,但是有利也有弊吧。複雜的

sql

如果這麼寫,開發效率反而會下降,也不易于看懂,是以複雜的

sql

并不推薦這麼寫。

使用分頁查詢要注意,需要配置分頁攔截器

PaginationInterceptor

,用于擷取記錄總數。

@Configuration
public class MybatisplusConfig {
    /**
     * 要使用分頁查詢功能,就需要配置分頁攔截器
     *
     * @return
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
}
複制代碼           

除此之外,我們還可以在

Service

層通過繼承

ServiceImpl

Mapper

。如果

Service

類有接口,則需要接口也繼承

IService

接口。

// 繼承IService接口,以使用CompanyService時能夠調用方法
public interface CompanyService extends IService<Company> {

}

@Service
public class CompanyServiceImpl extends ServiceImpl<CompanyMapper, Company> 
        implements CompanyService {
}

// 使用
 public static void main(String[] args) {
        try {
            ApplicationContext applicationContext = SpringApplication.run(MybatisplusStuApplication.class);
            CompanyService companyService = applicationContext.getBean(CompanyService.class);
            Company company = companyService.selectById(4);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
複制代碼           

如果

Service

類沒有接口,直接繼承

ServiceImpl

就可以了。

// 非接口方式,直接繼承ServiceImpl類即可。
@Service
public class CompanyServiceNotInterface extends ServiceImpl<CompanyMapper, Company> {
}

// 使用
 public static void main(String[] args) {
        try {
            ApplicationContext applicationContext = SpringApplication.run(MybatisplusStuApplication.class);
            CompanyServiceNotInterface companyServiceNotInterface = 
                applicationContext.getBean(CompanyServiceNotInterface.class);
            Company company1 = companyServiceNotInterface.selectById(3);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
複制代碼           

現在來看下,為什麼自己寫的

Service

沒有注入

Mapper

也能使用

Mapper

接口的方法。其實很簡單,就是

ServiceImpl

中巧用泛型聲明了

Mapper

字段,并添加了

@Autowired

注解,由

Spring

自動注入了。

public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {

    @Autowired
    protected M baseMapper;

}
複制代碼           

直接調用

Mapper

接口的方法與調用

ServiceImpl

的方法最大的差別在于

ServiceImpl

在插入和更新的方法上添加了

@Transactional

注解,支援事務。

最後說下關于分頁的支援。通過分析

PaginationInterceptor

攔截器的源碼,看看它是怎麼去擷取總數的。下面是摘抄

intercept

方法的部分源碼。

// 生成擷取總數的sql
SqlInfo sqlInfo = SqlUtils.getOptimizeCountSql(page.isOptimizeCountSql(), sqlParser, originalSql);
orderBy = sqlInfo.isOrderBy();
// 執行sql,将擷取的總數寫回page
this.queryTotal(overflowCurrent, sqlInfo.getSql(), mappedStatement, boundSql, page, connection);
複制代碼           

SqlUtils.getOptimizeCountSql

這句是生成“擷取總數”的

sql

,是以重點也是在這裡。因為生成的擷取總數的

sql

決定了這個分頁插件是否可用,是否隻是簡單的在原來的查詢語句外面去掉

limt

之後包裝一層

select

public static SqlInfo getOptimizeCountSql(boolean optimizeCountSql, ISqlParser sqlParser, String originalSql) {
    if (!optimizeCountSql) {
        return SqlInfo.newInstance().setSql(getOriginalCountSql(originalSql));
    }
    // COUNT SQL 解析器
    if (null == COUNT_SQL_PARSER) {
        if (null != sqlParser) {
            // 使用者自定義 COUNT SQL 解析
            COUNT_SQL_PARSER = sqlParser;
        } else {
            // 預設 JsqlParser 優化 COUNT
            try {
                COUNT_SQL_PARSER = DEFAULT_CLASS.newInstance();
            } catch (Exception e) {
                throw new MybatisPlusException(e);
            }
        }
    }
    // 傳回COUNT SQL 解析器生成的查詢總數的sql
    return COUNT_SQL_PARSER.optimizeSql(null, originalSql);
}
複制代碼           

optimizeCountSql

false

,則隻是簡單的在原來的

sql

外包裝一層

select

擷取總數。該字段在

Page

類中,預設為

true

。不建議設定為

false

/**
     * 優化 Count Sql 設定 false 執行 select count(1) from (listSql)
     */
    private boolean optimizeCountSql = true;
複制代碼           

optimizeCountSql

true

,則會使用

COUNT SQL 解析器

生成優化後的查詢總數的

sql

。如果指定了

COUNT SQL 解析器

則使用指定的解析器,如果沒有則使用預設的解析器。這個就是在建立分頁攔截器時配置的。

@Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor interceptor =  new PaginationInterceptor();
        // 自定義count sql解析器
        interceptor.setSqlParser(new ISqlParser() {
            @Override
            public SqlInfo optimizeSql(MetaObject metaObject, String sql) {
                return null;
            }
        });
        return interceptor;
    }
複制代碼           
public static ISqlParser COUNT_SQL_PARSER = null;
    private static Class<ISqlParser> DEFAULT_CLASS = null;
    static {
        try {
            DEFAULT_CLASS = (Class<ISqlParser>) Class.forName("com.baomidou.mybatisplus.plugins.pagination.optimize.JsqlParserCountOptimize");
        } catch (ClassNotFoundException e) {
          
        }
    }
複制代碼           

繼續閱讀