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

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

猜你喜欢

转载自blog.csdn.net/l229568441/article/details/107350335