欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

SpringBoot入门教程07——整合mybatis-plus(三)

程序员文章站 2022-03-26 21:19:04
SpringBoot入门教程07——整合mybatis-plus(三)大纲springboot整合mybatis-plus入门,以及mybatis-plus代码生成工具入门,传送门springboot整合mybatis-plus实现事务控制、分页、自定义SQL以及条件构造器Wrapper入门,传送门批量插入、更新字段填充逻辑删除lambda表达式格式的条件构造器用法条件构造器Wrapper的setEntity用法批量插入、更新最近股市很火,本人就写了一个爬虫,从财经网站爬取股票数据,...

SpringBoot入门教程07——整合mybatis-plus(三)

大纲

  • springboot整合mybatis-plus入门,以及mybatis-plus代码生成工具入门,传送门
  • springboot整合mybatis-plus实现事务控制、分页、自定义SQL以及条件构造器Wrapper入门,传送门
  • 批量插入、更新
  • 字段填充
  • 逻辑删除
  • lambda表达式格式的条件构造器用法
  • 条件构造器Wrapper的setEntity用法

批量插入、更新

最近股市很火,本人就写了一个爬虫,从财经网站爬取股票数据,然后存到本地数据库,由于数据量还比较大,就用到了批量插入、更新功能。

mybatis-plus提供了2套CRUD接口,一套是Mapper接口,另一套是Service接口,2种接口都可以通过mybatis-plus代码生成器生成。

前2篇文章已经详细介绍了Mapper接口的用法,但是Mapper本身并不支持批量插入功能,幸运的是Service接口提供了该功能。

Service接口

public interface IStockDetailService extends IService<StockDetail> {

}

Service接口默认实现类

@Service
public class StockDetailServiceImpl extends ServiceImpl<StockDetailMapper, StockDetail> implements IStockDetailService {

}

业务代码

@Autowired
private IStockDetailService stockDetailService;

@Transactional
public void loadAndSave() {
    List<StockDetail> list = new ArrayList<>(5000);
    //往list中添加数据省略
    stockDetailService.saveBatch(list);
}

如上所示,直接使用Service接口提供的saveBatch(Collection c)方法即可。

字段填充

每一个交易日,我们可能会去财经网站爬取多次数据,在保存数据的时候想记录数据创建时间和修改时间,就用到了字段填充。

创建自定义的MetaObjectHandler,并注入spring容器

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)  
    }
}

Entity实体类

@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_stock_detail")
public class StockDetail implements Serializable {
    /**
     * 创建时间
     */
    @TableField(value = "create_time",fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    @TableField(value = "update_time",fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

如上所示,指定createTime填充策略为插入填充,指定updateTime字段填充策略为插入和更新时填充

逻辑删除

同一个交易日,每次获取到最新数据后,需要把当天历史数据删除,做过开发的都知道,数据最好逻辑删除,万一删错了,还能想办法恢复。

逻辑删除需要在application.yml中增加mybatis-plus的配置

  • 指定全局逻辑删除字段 logic-delete-field: delFlag
  • 设置逻辑未删除值 logic-not-delete-value: 0
  • 设置逻辑已删除值 logic-delete-value: 1

业务代码

//逻辑删除旧数据
LambdaUpdateWrapper<StockDetail> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(StockDetail::getTradeDate, localDate);
stockDetailMapper.delete(updateWrapper);

跟正常删除代码一样,不过最终仅仅是把数据库中当前交易日数据的del_flag值更新为1

但是这里有一个坑,设置了全局逻辑删除字段之后,新增数据时del_flag值为null,就导致查询不到数据,此时就用到了字段填充功能,设置insert时为delFlag字段填充值0即可。

完整的mybatis-plus配置

mybatis-plus:
  #外部化xml配置
  #config-location: classpath:mybatis-config.xml
  #指定外部化 MyBatis Properties 配置,通过该配置可以抽离配置,实现不同环境的配置部署
  #configuration-properties: classpath:mybatis/config.properties
  #xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)
  mapper-locations: classpath*:/mapper/*.xml
  #MyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名
  #type-aliases-package: net.xinhuamm.noah.api.model.entity,net.xinhuamm.noah.api.model.dto
  #如果配置了该属性,则仅仅会扫描路径下以该类作为父类的域对象
  #type-aliases-super-type: java.lang.Object
  #枚举类 扫描路径,如果配置了该属性,会将路径下的枚举类进行注入,让实体类字段能够简单快捷的使用枚举属性
  #type-enums-package: com.baomidou.mybatisplus.samples.quickstart.enums
  #项目启动会检查xml配置存在(只在开发时候打开)
  check-config-location: true
  #SIMPLE:该执行器类型不做特殊的事情,为每个语句的执行创建一个新的预处理语句,REUSE:该执行器类型会复用预处理语句,BATCH:该执行器类型会批量执行所有的更新语句
  default-executor-type: REUSE
  configuration:
    # 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属性名 aColumn(驼峰命名) 的类似映射
    map-underscore-to-camel-case: false
    # 全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存,默认为 true
    cache-enabled: false
    #懒加载
    #aggressive-lazy-loading: true
    #NONE:不启用自动映射 PARTIAL:只对非嵌套的 resultMap 进行自动映射 FULL:对所有的 resultMap 都进行自动映射
    #auto-mapping-behavior: partial
    #NONE:不做任何处理 (默认值)WARNING:以日志的形式打印相关警告信息 FAILING:当作映射失败处理,并抛出异常和详细信息
    #auto-mapping-unknown-column-behavior: none
    #如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段
    call-setters-on-nulls: true
    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      #表名下划线命名默认true
      table-underline: true
      #id类型
      id-type: auto
      #是否开启大写命名,默认不开启
      #capital-mode: false
      #全局逻辑删除字段
      logic-delete-field: delFlag
      #逻辑未删除值,(逻辑删除下有效)
      logic-not-delete-value: 0
      #逻辑已删除值,(逻辑删除下有效)
      logic-delete-value: 1
      #数据库类型
      db-type: mysql

LambdaQueryWrapper和LambdaUpdateWrapper

见名知意,就是支持lambda语法的条件构造器。

QueryWrapper用法

QueryWrapper<StockTradeDate> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("is_open",1)
    .le("trade_date","2020-0714")
    .orderByDesc("trade_date")
    .last("limit 1");

LambdaQueryWrapper用法

LambdaQueryWrapper<StockTradeDate> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(StockTradeDate::getIsOpen, 1)
    .le(StockTradeDate::getTradeDate,date)
    .orderByDesc(StockTradeDate::getTradeDate)
    .last("limit 1");

通过对比可知,queryWrapper传的参数是数据库列名,而LambdaQueryWrapper则传的是Entity实体类字段,显而易见后一种语法更符合java开发的习惯,而且能避免数据库字段拼写错误。

条件构造器Wrapper的setEntity方法

后端开发经常会有这样的需求,前端页面有一个搜索框,里面有很多字段,如果用户输入某些字段值,就要把这些字段当做筛选条件去筛选数据。

如果通过判断字段非空,然后一个一个调用QueryWrapper方法有点太蠢,此时可以使用wrapper的setEntity方法

@RequestMapping("/list1")
public Object list1(){
    QueryWrapper<StockTradeDate> wrapper = new QueryWrapper<>();
    StockTradeDate stockTradeDate = new StockTradeDate();
    stockTradeDate.setIsOpen(1L);
    stockTradeDate.setTradeDate("2020-07-14");
    wrapper.setEntity(stockTradeDate);
    List<StockTradeDate> list = tradeDateMapper.selectList(wrapper);
    return list;
}

代码简单明了,就不多说。

mybatis-plus底层如何实现setEntity方法我没有深追代码,但是如何自己实现该方法,本人倒是有一些想法

  • 方法参数是entity对象
  • 根据entity对象可以获取entity实体类的属性Field
  • 判断entity实体类的各个Field上有没有@TableField注解
  • 如果有,则可获得数据库的字段名,也可以获取该字段的值
  • 如果没有,则认为entity实体类的字段名就是数据库的字段名(或者根据转驼峰策略反向得到数据库字段名)
  • 最终就能返回一个数据库字段名和对应值的map
  • 基于这个map就能拼接出查询sql (条件构造器Wrapper支持传map参数)

简单实现代码如下:

public class MybatisPlusUtil<T> {

    public Map<String, Object> convert(T t) {
        Map<String, Object> map = new HashMap<>();
        try {

            Field[] fields = t.getClass().getDeclaredFields();
            if (fields != null) {
                for (Field field : fields) {
                    if (Modifier.isStatic(field.getModifiers())) {
                        continue;
                    }
                    if (!field.isAccessible()) {
                        field.setAccessible(true);
                    }

                    Object o = field.get(t);
                    if(o!=null){
                        TableField annotation = field.getAnnotation(TableField.class);
                        if (annotation != null) {
                            String value = annotation.value();
                            if (value != null) {
                                map.put(value, o);
                            }
                        }else{
                            map.put(field.getName(), o);
                        }
                    }
                }
            }
        } catch (Exception e) {
            System.out.println(e);
        }

        return map;
    }
}

本文地址:https://blog.csdn.net/l229568441/article/details/107350335