使用Mybatis-Plus提高开发效率

原创 吴就业 102 0 2020-03-06

本文为博主原创文章,未经博主允许不得转载。

本文链接:https://wujiuye.com/article/688b6890364e451087775ebe119c36ed

作者:吴就业
链接:https://wujiuye.com/article/688b6890364e451087775ebe119c36ed
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。

mybatis-plus也是我在项目中发现的一个好用的框架,可能知道的人不多,所以很少看到关于它的介绍。正如官方介绍的:“我们的愿景是成为MyBatis最好的搭档,就像魂斗罗中的1P2P,基友搭配,效率翻倍”。目的是增强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);

EntityWrapperWrapper的一个子类。Wrapper封装了wheregroup byorder byhaving部分sql的生成逻辑。提供betweennotInineqorandleltgtnotExistsexistsisNullisNotNulllink等方法指定查询条件,并支持链式调用。

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);
}

如果optimizeCountSqlfalse,则只是简单的在原来的sql外包装一层select获取总数。该字段在Page类中,默认为true。不建议设置为false

/**
     * 优化 Count Sql 设置 false 执行 select count(1) from (listSql)
     */
    private boolean optimizeCountSql = true;

如果optimizeCountSqltrue,则会使用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;
    }

默认的COUNT SQL解析器JsqlParserCountOptimize,由于它是使用jsqlparser这个工具去优化sql的,所以不打算继续往下分析了,感兴趣可以自己去看下。

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) {
          
        }
    }

优化过的获取分页总数的SQL与自己手写获取总数的SQL没有区别,因此,这个分页插件还是推荐使用的。

关于jsqlparser这个工具,我也是之前为插入语句自动添加create_time这些通用字段写的一个插件时使用过,了解得不多,只记得jsqlparser修改sql使用了访问者模式。

#后端

声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。

文章推荐

一道很有意思的Redis面试题,关于Bitmap算法,我选出了一些优质评论

起源于我在一个短视频中分享的一道面试题,当然,这道面试题我确实在工作中用过,只是业务场景不同。

一篇文章说清楚Java的全局异常处理,深入到hotspot源码

本篇将介绍如何使用Java提供的全局异常处理,以及分析一点hotspot虚拟机的源码,让大家了解虚拟机是如何将异常交给全局异常处理器处理的。

使用Docker部署用于学习的ElasticSearch集群

在Linux服务器上使用 Docker安装ElasticSearch集群。

ElasticSearch高版本API的使用姿势

如何在`Java`项目中使用`elasticsearch-rest-high-level-client`。

64位JVM的Java对象头详解

在学习并发编程知识`synchronized`时,我们总是难以理解其实现原理,因为偏向锁、轻量级锁、重量级锁都涉及到对象头,所以了解`java`对象头是我们深入了解`synchronized`的前提条件。

Java锁事之Unsafe、CAS、AQS知识点总结

Unsafe、CAS、AQS是我们了解Java中除synchronized之外的锁必须要掌握的重要知识点。CAS是一个比较和替换的原子操作,AQS的实现强依赖CAS,而在Java中,CAS操作需通过使用Unsafe提供的方法实现。