Springboot整合mybatisplus

ssm项目,用mybatisplus很普遍,在原来的mybatis基础上升级mybatisplus几乎不用改什么东西,mybatisplus只对mybatis做了功能扩展,不会影响原来的已有功能,单表查询的时候的确比单纯mybatis写sql自己做映射要节省很多时间,也方便很多。
baomidou mybatisplus教程文档可参考:https://baomidou.com/pages/226c21/

步骤

原来的Springboot ssm框架升级mybatisplus主要有以下几步:
1.注释掉原来的mybatis引入包
2.引入mybatisplus包
3.启动类配置@MapperScan注解扫描路径
4.Spring注入分页配置
5.实现MetaObjectHandler配置数据库自动填充(非必须)

实现

注释掉原来的mybatis包
   <!-- <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency> -->
引入mybatisplus包

maven引入(截止发文mvn仓库最新版本是3.5.1):

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
启动类配置@MapperScan注解扫描路径

比如我所有的Mapper接口都在com.zhaohy.app.dao包路径下,则在启动类加入
@MapperScan("com.zhaohy.app.dao")
贴一下启动类:

package com.zhaohy.app;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import com.zhaohy.app.sys.filter.LoginProcessFilter;
import com.zhaohy.app.utils.OnLineCountListener;

@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
@MapperScan("com.zhaohy.app.dao")
public class ImageSaveApp {

    public static void main(String[] args) {
        SpringApplication.run(ImageSaveApp.class, args);
        System.out.println("springboot started...");
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Bean
    public FilterRegistrationBean myFilterRegistration() {
        FilterRegistrationBean regist = new FilterRegistrationBean(new LoginProcessFilter());
        // 过滤全部请求
        regist.addUrlPatterns("/*");//过滤url
        regist.setOrder(1);//过滤器顺序
        return regist;
    }
    
    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Bean
    public ServletListenerRegistrationBean listenerRegist() {
        ServletListenerRegistrationBean srb = new ServletListenerRegistrationBean();
        srb.setListener(new OnLineCountListener());
        //System.out.println("listener====");
        return srb;
    }
}
spring注入分页配置

新建MybatisPlusConfig类(示例用的oracle数据库,所以DbType.ORACLE,其他数据库要改下这里):

package com.zhaohy.app.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;

@Configuration
public class MybatisPlusConfig {
    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.ORACLE));
        return interceptor;
    }

//    @Bean
//    public ConfigurationCustomizer configurationCustomizer() {
//        return configuration -> configuration.setUseDeprecatedExecutor(false);
//    }
}
实现MetaObjectHandler配置数据库自动填充

当数据库有些字段比如create_date或者update_date这种逻辑简单的固定字段可以用mybatisplus提供的自动填充来填充时间
新建AutoFillHandler类,如下代码所示,执行insert的时候会自动填充create_date和update_date字段,执行update的时候会自动填充update_date字段

package com.zhaohy.app.common;

import java.time.LocalDateTime;

import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;

/**
 * 数据库字段自动填充
 * 
 * @author ly-licy
 *
 */
@Component
public class AutoFillHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        if (metaObject.hasSetter("createDate") 
                && (metaObject.getValue("createDate") instanceof LocalDateTime 
                        || null == metaObject.getValue("createDate"))) {
            this.setFieldValByName("createDate", LocalDateTime.now(), metaObject);
        }
        if (metaObject.hasSetter("updateDate") 
                && (metaObject.getValue("updateDate") instanceof LocalDateTime 
                        || null == metaObject.getValue("updateDate"))) {
            this.setFieldValByName("updateDate", LocalDateTime.now(), metaObject);
        }
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        if (metaObject.hasSetter("updateDate") && (metaObject.getValue("updateDate") instanceof LocalDateTime 
                || null == metaObject.getValue("updateDate"))) {
            this.setFieldValByName("updateDate", LocalDateTime.now(), metaObject);
        }
    }

}

代码测试

数据库用oracle为例

oracle新建da_user表
CREATE TABLE "DA_USER" 
   (    "USER_ID" NUMBER, 
    "USER_NAME" VARCHAR2(128), 
    "PASSWORD" VARCHAR2(64), 
    "EMAIL" VARCHAR2(64), 
    "USER_TYPE_ID" NUMBER(*,0), 
    "CREATE_DATE" DATE, 
    "UPDATE_DATE" DATE, 
    "APP_TYPE" VARCHAR2(32), 
    "LOGIN_TYPE" NUMBER
   ) 
新建映射实体类UserPO
package com.zhaohy.app.po;

import java.time.LocalDateTime;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
@TableName("da_user")
public class UserPO {
    @TableId(value = "user_id", type = IdType.ASSIGN_ID)
//  @TableId(value = "user_id")
    private Long userId;
    
    private String userName;
    @JsonIgnore
    private String password;
    
    private String email;
    
    private Integer userTypeId;
    @JsonFormat(shape =JsonFormat.Shape.STRING,pattern ="yyyy-MM-dd HH:mm:ss",timezone ="GMT+8")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createDate;
    @JsonFormat(shape =JsonFormat.Shape.STRING,pattern ="yyyy-MM-dd HH:mm:ss",timezone ="GMT+8")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateDate;
    
    private String appType;
    
    private Integer loginType;

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getUserTypeId() {
        return userTypeId;
    }

    public void setUserTypeId(Integer userTypeId) {
        this.userTypeId = userTypeId;
    }

    public LocalDateTime getCreateDate() {
        return createDate;
    }

    public void setCreateDate(LocalDateTime createDate) {
        this.createDate = createDate;
    }

    public LocalDateTime getUpdateDate() {
        return updateDate;
    }

    public void setUpdateDate(LocalDateTime updateDate) {
        this.updateDate = updateDate;
    }

    public String getAppType() {
        return appType;
    }

    public void setAppType(String appType) {
        this.appType = appType;
    }

    public Integer getLoginType() {
        return loginType;
    }

    public void setLoginType(Integer loginType) {
        this.loginType = loginType;
    }
}

可以看到上面用到了以下几个注解:
@TableName("da_user"):指定实体类映射的表名

@TableId(value = "user_id", type = IdType.ASSIGN_ID):指定表的主键列,value可以配置数据库的列名,type用来指定生成ID的类型,因为在oracle数据库里没有配置自增序列,此时配置IdType.AUTO(数据库ID自增)是不生效的,可以在java程序里生成唯一id,IdType.ASSIGN_ID默认用的雪花算法生成的id,其实我在程序里也没有用到,每次新增的时候我是用redis自己生成id的,详细了解请看我另一篇文章:redis实现全局唯一id

@JsonIgnore:这是jackson的注解,当实体类传给前端序列化成json的时候可以忽略指定字段,比如这里的password是不想给前端看到的,序列化json的时候就不会有这个字段。

@JsonFormat(shape =JsonFormat.Shape.STRING,pattern ="yyyy-MM-dd HH:mm:ss",timezone ="GMT+8"):这是jackson的注解,序列化json的时候可以把LocalDateTime序列化成指定格式的String字符串

@TableField(fill = FieldFill.INSERT):用来指定填充策略,和上面建的AutoFillHandler配合使用。

@TableField(exist = false):用来指定是否为数据库表字段,默认为true,多余的字段可以设为false。

@TableField(updateStrategy = FieldStrategy.IGNORED):更新策略忽略判断,默认为FieldStrategy.DEFAULT(在全局里代表 NOT_NULL)。比如有些可为空的字段,默认全局配置是NOT_NULL的值才可以被更新,如果就是想更新这个字段为null,则要使用FieldStrategy.IGNORED单独把这个字段忽略,就可以更新了。

大体上面这些注解勉强够用,更多注解使用方式可以点击上面贴的教程文档地址,里面讲的很详细。

新建TestController
package com.zhaohy.app.controller;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zhaohy.app.dao.UserMapper;
import com.zhaohy.app.entity.ResponsePageVO;
import com.zhaohy.app.entity.ResponseVO;
import com.zhaohy.app.enums.ErrorCode;
import com.zhaohy.app.po.UserPO;
import com.zhaohy.app.service.GenerateIdService;
import com.zhaohy.app.utils.AppFrameworkUtil;
import com.zhaohy.app.utils.MD5Util;

@Controller
public class TestController {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private GenerateIdService generateIdService;
    
    @RequestMapping("common/insertTest.do")
    @ResponseBody
    public ResponseVO<List<UserPO>> insertTest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        UserPO user = new UserPO();
        //user.setUserId(Long.parseLong(generateIdService.getGenerateId()));
        user.setUserId(1651912471041000011L);
        user.setUserName("insertTest");
        user.setPassword(MD5Util.string2MD5("111"));
        user.setEmail("test@qq.com");
        user.setUserTypeId(2);
        user.setAppType("test");
        user.setLoginType(0);
        userMapper.insert(user);
        List<UserPO> userList = userMapper.selectList(null);
        return new ResponseVO<>(userList);
    }
    
    @RequestMapping("common/updateTest.do")
    @ResponseBody
    public ResponseVO<UserPO> updateTest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        UserPO user = new UserPO();
        user.setUserId(1651912471041000011L);
        user.setUserName("insertTest1");
        userMapper.updateById(user);
        user = userMapper.selectById(user.getUserId());
        return new ResponseVO<>(user);
    }
    
    @RequestMapping("common/deleteTest.do")
    @ResponseBody
    public ResponseVO<?> deleteTest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        UserPO user = new UserPO();
        user.setUserId(1651912471041000011L);
        userMapper.deleteById(user.getUserId());
        return new ResponseVO<>();
    }
    
    @RequestMapping("common/userList.do")
    @ResponseBody
    public ResponseVO<List<UserPO>> userList(HttpServletRequest request, HttpServletResponse response) throws Exception {
        IPage<UserPO> page = new Page(1,2);
        page = userMapper.selectPage(page, new LambdaQueryWrapper<UserPO>().orderByDesc(UserPO::getCreateDate));
        List<UserPO> list = page.getRecords();
        ResponsePageVO vo = new ResponsePageVO();
        vo.setCode(ErrorCode.SUCCESS.getCode());
        vo.setMsg(ErrorCode.SUCCESS.getCn());
        vo.setPage(page.getCurrent());
        vo.setTotalPages(page.getPages());
        vo.setTotalRecords(page.getTotal());
        vo.setRecords(page.getSize());
        vo.setData(list);
        return vo;
    }
    
    @RequestMapping("common/userList1.do")
    @ResponseBody
    public ResponseVO<List<UserPO>> userList1(HttpServletRequest request, HttpServletResponse response) throws Exception {
        IPage<UserPO> page = new Page(1,2);
        Map<String, Object> paramsMap = new HashMap<>();
        paramsMap.put("userTypeId", "2");
        List<UserPO> list = userMapper.getUserList(page, paramsMap);
        ResponsePageVO vo = new ResponsePageVO();
        vo.setCode(ErrorCode.SUCCESS.getCode());
        vo.setMsg(ErrorCode.SUCCESS.getCn());
        vo.setPage(page.getCurrent());
        vo.setTotalPages(page.getPages());
        vo.setTotalRecords(page.getTotal());
        vo.setRecords(page.getSize());
        vo.setData(list);
        return vo;
    }
}

上面代码里简单写了增删改查的例子,用起来比较简单,更多用法可以去查上线贴的文档地址,这里着重说一下里面的两个分页接口吧。

上面的两个分页,分别用了两种方式:
1.mybatisplus自带的selectPage方法,里面可以传两个参数,一个是不能为null的IPage<T>对象,可以用IPage<UserPO> page = new Page(page,size);来构建,page参数代表当前页,size代表每页显示的条数,此方法返回一个IPage<T>对象,可以通过getRecords()方法获取结果集合,getCurrent()方法获取当前页,page.getPages()方法获取总页数,page.getTotal()方法获取总条数,getSize()方法获取每页显示条数;
另一个是LambdaQueryWrapper对象,用来构建查询条件

2.用传统的实体类或者HashMap传参,自己构建查询方法,直接返回List集合,List<UserPO> list = userMapper.getUserList(page, paramsMap);
此时的page对象可以为null,如果为null则代表不分页,这时返回的list就是要查询的集合,但是page对象里的getRecords()方法返回的就是空数组了,所以用这种方法就不要用page.getRecords()获取结果集合了。

UserMapper接口:
package com.zhaohy.app.dao;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.Param;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.zhaohy.app.po.UserPO;

public interface UserMapper extends BaseMapper<UserPO> {

    List<UserPO> getUserList(IPage<UserPO> page, @Param("params") Map<String, Object> paramsMap);

}

上面的getUserList是自己创建的分页,可以在xml里直接写sql,但是此时,必须要指定@Param("params"),这样在sql里拿参数的时候才可以找得到,比如:#{params.userTypeId}
UserMapper.xml里如下:

<select id="getUserList" resultType="com.zhaohy.app.po.UserPO">
        select * from da_user where user_type_id = #{params.userTypeId}
        order by user_id desc
</select>

如此,分页查询的时候既可以直接用selectPage方法单表分页查询,也可以用传统的xml sql方式进行多表关联查询。

打印日志可以看到后台执行分页查询的时候用了两步:
1.SELECT COUNT(*) AS total FROM da_user WHERE user_type_id = ?

  1. SELECT * FROM ( SELECT TMP.*, ROWNUM ROW_ID FROM ( select * from da_user where user_type_id = ? order by user_id desc ) TMP WHERE ROWNUM <=?) WHERE ROW_ID > ?
    这个和我们自己写分页是差不多的,都是先算总数,再根据参数计算rowNum用sql分页。

分页插件虽然方便,用的久了很容易忘掉分页原理,有些场景还是要自己写分页的,比如调用别人接口分页查询大数据量的时候,就得根据这个原理自己实现分页了,之前写过一篇关于自己实现分页的文章:
java后台分页算法小记

以上~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容