SpringBoot入門チュートリアル07-mybatis-plusの統合(3)
概要
- mybatis-plusを統合するspringbootの使用を開始し、mybatis-plusコード生成ツールであるポータルの使用を開始します
- Springbootはmybatis-plusを統合して、トランザクション制御、ページング、カスタムSQL、条件付きビルダーのラッパーエントリ、ポータルを実現します
- バッチ挿入と更新
- フィールドフィル
- トゥームストーン
- ラムダ式形式での条件付きコンストラクターの使用
- 条件付きコンストラクターラッパーのSetEntityの使用法
バッチ挿入と更新
最近、株式市場は非常に暑いです。金融サイトから株式データをクロールしてローカルデータベースに保存するクローラーを作成しました。データ量はまだ比較的多いため、一括挿入および更新機能を使用します。
mybatis-plusは、2セットのCRUDインターフェイスを提供します。1つはマッパーインターフェイスで、もう1つはサービスインターフェイスです。どちらのインターフェイスもmybatis-plusコードジェネレーターで生成できます。
最初の2つの記事では、Mapperインターフェースの使用法を詳細に紹介していますが、Mapper自体は一括挿入機能をサポートしていません。幸い、Serviceインターフェースはこの機能を提供します。
サービスインターフェース
public interface IStockDetailService extends IService<StockDetail> {
}
サービスインターフェイスのデフォルトの実装クラス
@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);
}
上に示したように、サービスインターフェイスによって提供されるsaveBatch(Collection c)メソッドを直接使用できます。
フィールドフィル
取引日ごとに、金融Webサイトにアクセスしてデータを複数回クロールする場合があります。データを保存するときにデータの作成時間と変更時間を記録する場合は、フィールド入力を使用します。
カスタム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(推荐使用)
}
}
エンティティエンティティクラス
@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
- 論理値を削除しない値を設定します:0
- ロジック削除値の設定logic-delete-value:1
ビジネスコード
//逻辑删除旧数据
LambdaUpdateWrapper<StockDetail> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(StockDetail::getTradeDate, localDate);
stockDetailMapper.delete(updateWrapper);
通常の削除コードと同じですが、最終的にはデータベース内の現在の取引日のデータのdel_flag値を1に更新するだけです。
ただし、ここに落とし穴があります。グローバル論理削除フィールドを設定した後、データを追加するとき、del_flag値はnullであるため、データクエリは発生しません。このとき、フィールド入力関数が使用されます。挿入が設定されると、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
名前が示すように、これはラムダ構文をサポートする条件付きコンストラクターです。
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によって渡されるパラメーターはエンティティエンティティクラスフィールドであることがわかります。明らかに、後者の構文はJava開発の習慣に沿っており、スペルミスを回避できます。データベースフィールドで。
条件付きコンストラクターWrapperのsetEntityメソッド
多くの場合、バックエンド開発にはそのような要件があります。フロントエンドページには多くのフィールドがある検索ボックスがあります。ユーザーがいくつかのフィールド値を入力する場合、これらのフィールドをデータをフィルタリングするためのフィルター条件として使用する必要があります。
フィールドが空ではないと判断し、QueryWrapperメソッドを1つずつ呼び出すのが少しばかげている場合は、この時点でラッパーの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の下部にあるsetEntityメソッドを実装する方法-さらに、コードに深く従わなかったのですが、自分でメソッドを実装する方法についていくつかのアイデアがあります。
- メソッドパラメータはエンティティオブジェクトです
- エンティティオブジェクトによると、エンティティエンティティクラスの属性Fieldを取得できます。
- エンティティエンティティクラスの各フィールドに@TableFieldアノテーションがあるかどうかを確認します
- その場合、データベースのフィールド名を取得でき、フィールドの値も取得できます。
- そうでない場合、エンティティクラスのフィールド名はデータベースのフィールド名であると見なされます(または、データベースのフィールド名はキャメルケース戦略に従って逆方向に取得されます)。
- 最後に、データベースフィールド名と対応する値のマップを返すことができます
- このマップに基づいて、クエリSQLをスプライスできます(条件付きコンストラクターWrapperはマップパラメーターの受け渡しをサポートしています)
簡単な実装コードは次のとおりです。
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;
}
}