MyBatis-Plus
コースの事前指示
**対象者:MyBatisとSpring Bootの基礎を習得した開発者または学生
レッスン例環境 Spring Boot 3.2.x、MyBatis ¥-Plus 3.5.6、MySQL 8.x
コースの目標:
- MyBatis-Plusのコア機能、自動 CRUD、条件付きコンストラクタの習得
- ページング、ソート、論理削除、楽観的ロックなどの高度な機能に精通している
- MyBatis-Plusの基礎となる実行原理、自動注入メカニズムの理解
- 高頻度 MyBatis-Plus 面接の原則
主な利点(ネイティブMyBatisと比較して)
ネイティブMyBatisは、多数のXML/注釈 SQLを手動で書く必要があります。MyBatis-Plusは、MyBatisの強化に基づいており、非侵入、強化のみ、汎用 CRUDをカプセル化し、重複 SQLに別れを告げ、開発効率を大幅に向上させます。
MyBatis-Plusクイックスタート
MyBatis-Plusコアの概要
MyBatis-Plus(略してMP)は、MyBatisに基づいて変更せずに強化する MyBatisの拡張ツール で、開発を簡素化し、効率を向上させるために生まれました。
*主な特徴:
- 非侵入:拡張のみ、ネイティブMyBatisコードの変更なし、オリジナルのMyBatisプロジェクトと互換性があります。
- 低損失:ユニバーサルCRUDの自動注入を開始し、ほぼ損失のない性能を実現
- 強力なCRUD 内蔵の汎用 Mapper、汎用サービス、単一テーブル操作ゼロSQL
- 条件コンストラクタ:Wrapper 動的にSQLをスプライシングし、ハードコードSQLにさよならを言う
- 高度な機能:ページング、論理削除、楽観的ロック、プライマリキー自動生成、マルチテナントなど
環境の構築(Spring BootとMP)
コア依存性の導入
pom.xmlコア依存 Spring Boot 亲プロジェクトが済み
<!-- MyBatis-Plus 的 Spring Boot 3 Starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.6</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok,可选 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>グローバル構成 application.yml
spring:
# 数据源配置
datasource:
url: jdbc:mysql://localhost:3306/java2601?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
# MyBatis-Plus 全局配置
mybatis-plus:
# 映射文件路径
mapper-locations: classpath:mapper/*.xml
# 实体类别名包
type-aliases-package: com.mp.demo.entity
configuration:
# 开启下划线转驼峰自动映射
map-underscore-to-camel-case: true
# 开发环境打印 SQL,生产环境通常关闭
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 主键自增策略
id-type: auto
# 数据库表前缀(可选)
# table-prefix: tb_
# 逻辑删除全局配置
logic-delete-field: deleteFlag
logic-delete-value: 1 # 已删除
logic-not-delete-value: 0 # 未删除クラス注釈の
@MapperScan Unified Scan Mapperインターフェイスを推奨します。各インタフェースに@Mapperを追加することもでき、どちらかを選択することができます。
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.Spring BootApplication;
@Spring BootApplication
@MapperScan("com.mp.demo.mapper")
public class MpDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MpDemoApplication.class, args);
}
}インフラ工学構造物
com.mp.demo
├── entity // 数据库实体类
├── mapper // Mapper 接口(继承BaseMapper)
├── service // 业务层
│ └── impl // 业务实现类(继承ServiceImpl)
└── controller // 控制层
コア基盤:エンティティークラス注釈とプライマリキー戦略
データベース表の
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(30) DEFAULT NULL COMMENT '姓名',
`age` int DEFAULT NULL COMMENT '年龄',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
`delete_flag` tinyint(1) DEFAULT 0 COMMENT '逻辑删除标识 0-未删除 1-已删除',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';エンティティークラスのコア注釈
MPはエンティティークラスアノテーションを介してエンティティとデータベーステーブルとフィールドのマッピングを完了し、ネイティブのMyBatis 手動マッピング設定を置き換えます。
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.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
// 对应数据库表名(若实体类名与表名一致可省略)
@TableName("user")
public class User {
// 主键ID
@TableId(type = IdType.AUTO)
private Long id;
// 姓名(字段名与数据库一致可省略注解)
@TableField("name")
private String name;
// 年龄
private Integer age;
// 邮箱
private String email;
// 逻辑删除字段
@TableLogic
@TableField(fill = FieldFill.INSERT)
private Integer deleteFlag;
// 创建时间(自动填充)
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
// 更新时间(插入+更新自动填充)
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}@TableIdは、プライマリ·キー·プロパティがidでない場合、または明示的にプライマリ·キー·ポリシーを指定する必要がある場合に使用します。
プライマリキー生成ポリシー
@TableIdのtype属性は、プライマリ·キー·ポリシーを指定するために使用されます。ポリシーは、データベースのフィールドタイプとテーブル構造と一貫性が必要です。
| プライマリ·キー·ポリシー | 説明 | 適用可能なシナリオ |
|---|---|---|
AUTO | データベースのインクリメントプライマリキーを使用すると、データベースフィールドはインクリメントをサポートする必要があります。 | My SQL 単一ライブラリのプライマリキー。 |
NONE | エンティティはポリシーを明示的に指定せず、グローバル構成で処理されます。 | プライマリ·キー·ポリシーは、プロジェクトによって統一的に構成されます。 |
| “ | 挿入前にビジネスコードから手動で割り当てられます。 | 業務コード、外部システム主キー。 |
ASSIGN_ID | MyBatis − PlusのIdentifierGeneratorから数値型 IDを生成する. | 分散システムでのグローバルID。 |
ASSIGN_ | ハイフンなしのUUID 文字列を生成します。 | 主キー文字列 |
ASSIGN_ID
デフォルトのIdentifierGeneratorでは、時間、ワーカーノード、シリアル番号に基づく分散 IDアルゴリズムが使用されます。一般的な構造は以下の通りである。
1 位符号位 + 41 位时间戳 + 10 位工作节点标识 + 12 位序列号
- タイムスタンプは、おおよその増加傾向を示します。
- 作業ノードIDは、異なるプロセスやサーバーを区別するために使用されます。
- シーケンス番号は、同じミリ秒内に生成された複数のIDを区別するために使用されます。
このIDは厳密に連続的に増加するのではなく、全体的に増加傾向と考えることができる。分散展開では、ワーカーノードの競合やシステムクロックのコールバックに注意が必要です。特別な要件がある場合は、カスタムIdentifierGeneratorを実装することができます。
@Data
@TableName("user")
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String name;
}
ASSIGN_UUID
ASSIGN_UUIDは文字列プライマリ·キーに使用されます。MyBatis-PlusはデフォルトでハイフンなしのUUIDを生成するため、データベース·フィールドには通常CHAR(32)またはVARCHAR(32)が使用されます。
@Data
@TableName("user")
public class User {
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String name;
}
UUIDの利点は、生成が簡単でデータベースに依存しないことです。欠点は、文字列の占有面積が大きく、値が順序がなく、クラスタードインデックスへの書き込みがローカルであることです。プライマリキースキームは、データサイズ、インデックス構造、システムアーキテクチャに基づいて選択する必要があり、1つのポリシーをすべてのプロジェクトの統一仕様に絶対化してはいけません。
ユニバーサルCRUD
MPはBaseMapper、IService、ServiceImplを内蔵し、一般的なシングルテーブルCRUDを直接実行できます。複雑なクエリ、コネクテッド·テーブル·クエリ、および特殊なパフォーマンス要件には、依然としてカスタムSQLが必要です。
BaseMapperデータ層 CRUDコア
Mapperインターフェイス定義
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mp.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
// 无需编写任何代码,继承BaseMapper即可拥有所有CRUD方法
}
共通のCRUDメソッドのコード例
import com.mp.demo.entity.User;
import com.mp.demo.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.Spring BootTest;
import java.util.List;
@Spring BootTest
public class MapperCrudTest {
@Autowired
private UserMapper userMapper;
// 新增
@Test
void testInsert() {
User user = new User();
user.setName("张三");
user.setAge(20);
user.setEmail("zhangsan@163.com");
// 返回受影响行数
int insert = userMapper.insert(user);
System.out.println("新增ID:" + user.getId());
}
// 根据ID查询
@Test
void testSelectById() {
User user = userMapper.selectById(1L);
System.out.println(user);
}
// 查询所有
@Test
void testSelectList() {
List<User> userList = userMapper.selectList(null);
userList.forEach(System.out::println);
}
// 根据ID更新
@Test
void testUpdateById() {
User user = new User();
user.setId(1L);
user.setAge(22);
user.setEmail("update@163.com");
int rows = userMapper.updateById(user);
System.out.println("更新行数:" + rows);
}
// 根据ID删除
@Test
void testDeleteById() {
int rows = userMapper.deleteById(1L);
System.out.println("删除行数:" + rows);
}
}サービス·ビジネス·レベルCRUD
ビジネス層は、バッチ操作やチェーンコールなどの機能をカプセル化します。コントローラはサービスを通じてビジネスを組織し、データアクセスロジックを直接担当しないでください。
ビジネス層インタフェースと実装クラス
// 接口
import com.baomidou.mybatisplus.extension.service.IService;
import com.mp.demo.entity.User;
public interface UserService extends IService<User> {
}
// 实现类
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mp.demo.entity.User;
import com.mp.demo.mapper.UserMapper;
import com.mp.demo.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}Serviceコアメソッドの例
@Spring BootTest
public class ServiceCrudTest {
@Autowired
private UserService userService;
// 批量新增
@Test
void testBatchSave() {
List<User> list = new ArrayList<>();
list.add(new User(null, "李四", 25, "lisi@163.com", null, null, null));
list.add(new User(null, "王五", 28, "wangwu@163.com", null, null, null));
// 批量插入
boolean batch = userService.saveBatch(list);
System.out.println("批量新增结果:" + batch);
}
// 新增或更新:主键非空且对应记录存在时更新,否则新增
@Test
void testSaveOrUpdate() {
User user = new User(2L, "李四", 26, "lisi_new@163.com", null, null, null);
boolean result = userService.saveOrUpdate(user);
}
// 批量删除
@Test
void testBatchDelete() {
userService.removeByIds(Arrays.asList(3L,4L));
}
}キーワード:ConditionalコンストラクタWrapper
Wrapperはクエリの構築と条件の更新に使用され、単純な動的 SQLの重複コードを削減します。複雑なSQLでは、Mapper XMLまたはカスタムメソッドを使用してください。
Wrapperのアーキテクチャ
- QueryWrapper:クエリと削除条件コンストラクタ(セットフィールドなし)
- UpdateWrapper:更新条件コンストラクタ(setフィールド+条件をサポート)
- Lambda Query Wrapper:Lambdaクエリコンストラクタ(ハードコードされたフィールド名は不要、推奨)
- Lambda UpdateWrapper:Lambda Updateコンストラクタ
QueryWrapper 条件付きクエリーの例
// 条件:年龄大于20,姓名包含"李",按年龄降序
@Test
void testQueryWrapper() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.gt("age", 20) // age > 20
.like("name", "李") // name like '%李%'
.orderByDesc("age");// order by age desc
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}LambdaQuery Wrapper(推奨、ハードコードなし)
Lambda Wrapperはメソッド参照でフィールドを取得し、文字列フィールド名のスペルミスを減らし、リファクタリングの安全性を向上させます。
// 等价上述条件,无硬编码字段
@Test
void testLambdaQueryWrapper() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 20)
.like(User::getName, "李")
.orderByDesc(User::getAge);
List<User> userList = userMapper.selectList(wrapper);
}UpdateWrapper 動的なアップデート
// 条件:年龄=25,更新邮箱和姓名
@Test
void testUpdateWrapper() {
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getAge, 25)
.set(User::getName, "小李")
.set(User::getEmail, "xiaoli@163.com");
userMapper.update(null, wrapper);
}一般的な条件付き方法の概要
| 方法 | SQL対応 | 説明 |
|---|---|---|
| EQは | = | 等しい。 |
| neさん | != | 等しくない。 |
| GT/Lt | >/< | より大きい/より小さい |
| GEとLE | >=/<= | 等しい以上/等しい未満 |
| Like Like | %xx%のように | ファジー·クエリー |
| in inに | in xx xx | クエリーを含む |
| isNull/isNotNull | null /nullではない | 空の判断 |
MPコアの高度な機能
オートフィル(作成/更新)
ビジネスシーン:すべてのテーブルには作成、更新があり、で代入する必要がなく、的に入力する
MetaObjectHandlerのフィルプロセッサの実装
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
// 插入时自动填充
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class);
this.strictInsertFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
this.strictInsertFill(metaObject, "deleteFlag", () -> 0, Integer.class);
}
// 更新时自动填充
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
}
}エンティティークラス@TableField fill = xxx 注釈とともに、コードの割り当てなしで完全自動入力を実装します。
論理的削除
論理削除は実際にはデータベースレコードを削除せず、削除マークを更新します。構成が完了すると、MyBatis-Plusの一般的な方法では、論理削除ルールごとにSQLが生成されます。
グローバル構成グローバル構成
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleteFlag
logic-delete-value: 1
logic-not-delete-value: 0
グローバル構成内のフィールド名はエンティティークラス属性名です。@TableLogicは、単一のエンティティフィールドでのみ使用することもできます。
@TableLogic
private Integer deleteFlag;
一般的なSQL 動作は次のとおりです。
-- 逻辑删除前
DELETE FROM user WHERE id = ?;
-- 逻辑删除后生成的效果
UPDATE user
SET delete_flag = 1
WHERE id = ? AND delete_flag = 0;
-- 普通查询会过滤已删除记录
SELECT id, name, age
FROM user
WHERE delete_flag = 0;論理削除レコードはデフォルトでは汎用クエリおよび更新メソッドでは続行されません。“削除された”データを頻繁にクエリする必要がある場合は、フィールドが削除タグか通常のビジネスステータスかを検討し、カスタムSQLで明示的に処理する必要があります。
ページングプラグイン必須
ページング·クエリにはPaginationInnerInterceptorの登録が必要です。プラグインはSQLをデータベース·ダイアレクトに基づいて書き換え、合計クエリを実行します。
ページングプラグイン構成クラス
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MyBatis-PlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MyBatis-Plus 插件配置
*/
@Configuration
public class MpConfig {
/**
* 注册MP核心插件:分页插件 + 乐观锁插件
*/
@Bean
public MyBatis-PlusInterceptor mybatisPlusInterceptor() {
MyBatis-PlusInterceptor interceptor = new MyBatis-PlusInterceptor();
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 多插件并用时,分页插件通常放在最后
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}ページングクエリーのコード例
@Test
void testPage() {
// 参数1:当前页,参数2:每页条数
Page<User> page = new Page<>(1, 2);
// 分页查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 18);
Page<User> userPage = userMapper.selectPage(page, wrapper);
// 分页结果参数
System.out.println("当前页:" + userPage.getCurrent());
System.out.println("每页条数:" + userPage.getSize());
System.out.println("总条数:" + userPage.getTotal());
System.out.println("总页数:" + userPage.getPages());
System.out.println("数据列表:" + userPage.getRecords());
}楽観的ロック(同時更新の問題を解決)
ビジネスシナリオ:複数のユーザーが同時に同じデータを変更し、データの上書き損失を防止
原理原理は
楽観的ロックは、バージョンフィールドを使用して、レコードが他のトランザクションによって変更されたかどうかを判断する。更新時に古いバージョン番号を運び、一致が成功した後に新しいバージョン番号を書き込みます。一致が失敗した場合、更新行数は0です。
実装ステップは
- データベースversionフィールドの追加{{でーたべ ーすversionふぃーるどのついか}}
ALTER TABLE `user` ADD COLUMN `version` int DEFAULT 1 COMMENT '乐观锁版本号';
- エンティティークラスバージョン番号注記の追加{{エンティティークラスばーじょんばんごうのちゅうしゃくのついか}}
@Version
private Integer version;
- 楽観的ロックプラグインを有効にする(MyBatis-Plus Interceptorに追加)
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
一般的なSQL 効果は次のとおりです。
UPDATE user
SET age = 22, version = 2
WHERE id = 1 AND version = 1;
楽観的ロックの実行原理フローチャート
正常更新流程(无并发冲突)
┌─────────┐ 1.查询数据 ┌──────────┐
│ 客户端 │─────────────▶│ 数据库 │
└─────────┘ └────┬─────┘
│ │
│ 2.返回version=1 │
│◀────────────────────┘
│
▼
┌────────────────────────────┐
│ 3.提交更新:携带id+version │
└──────────────┬─────────────┘
│
▼
┌────────────────────────────┐
│ 4.校验version一致,执行更新 │
│ 5.自动version+1 → version=2 │
└──────────────┬─────────────┘
│
▼
更新成功
并发冲突流程(多线程同时更新)
┌─────线程1─────┐ ┌─────线程2─────┐
查询version=1 查询version=1
│ │
更新携带version=1 更新携带version=1
│ │
▼ ▼
执行更新、版本变为2 校验version≠1,更新失败MyBatis-Plusの基本原理とよくある質問
汎用 CRUDの注入プロセス
プロジェクトが起動すると、MyBatis-Plusはエンティティのテーブル情報を解決し、SQL Injectorを介してBaseMapperを継承するインタフェースの汎用マッピング文を登録します。実行時にMapperメソッドが呼び出されると、MyBatisのMapper Agent、MappedStatement、Executor、JDBCプロセスが入ります。
したがって、一般的なCRUDステートメント定義は起動時に登録されますが、実際のSQLパラメータ、条件フラグメント、プラグイン処理は現在のパラメータに基づいて呼び出されるたびに生成され実行されます。“すべてのSQLは起動時に固定され、実行時にスプライシングコストが発生しない”と単純に理解することはできません。
Wrapperの実行原理
Wrapperは条件フラグメントとパラメータを保持し、Mapperメソッド呼び出し時にMyBatis-Plusがこれらを対応するマッピング文に結合し、MyBatisがBoundSqlを生成してプリコンパイルされたパラメータをバインドします。
通常の条件値はパラメータバインディングを使用します。SQL 構造に影響を与えるフィールド名、ソートフィールド、last()、apply()などのコンテンツは、未検証のユーザー入力を直接使用しないように注意する必要があります。
MyBatisとMyBatis-Plusの関連性
MyBatis-PlusはMyBatis 拡張をベースにしており、Mapper XML、注釈付きSQL、プラグイン、型プロセッサなどのネイティブ機能を保持しています。主に汎用 CRUD、Wrapper、ページング、論理削除、楽観的ロック、オートフィルなどの機能を提供し、相互排他的なフレームワークではない。
ページングプラグインが構成されていません
selectPage()を呼び出すときにページングプラグインが正しく登録されないと、SQLは意図したとおりにデータベーページング文を追加せず、現在のページよりもはるかに多くのデータをクエリする可能性があります。プラグインBean、データベースの種類、および依存バージョンを確認してください。
複数の内部プラグインを使用する場合は、順序に注意する必要があります。ページングプラグインは通常、最後に配置されます。3.5.9以降にアップグレードする場合は、JSQLParserサポートモジュールを別途導入する必要があるかどうかを、対応するバージョンのドキュメントで確認してください。
楽観的なロック故障の原因
OptimisticLockerInnerInterceptorは未登録。- エンティティのバージョンフィールドに
@Versionが追加されませんでした。 - 更新前に古いバージョン番号を読み取って携帯していない。
- 使用されたメソッドパラメータ形式は、プラグイン識別規則を満たさない。
update(entity, wrapper)を呼び出すときに同じWrapperを繰り返し使用します。- 更新行数
0は無視され,並行衝突として扱われなかった.
自動入力 FAQ
MetaObjectHandlerはSpring Beanとして登録されていません。- フィールドに正しく
FieldFillが構成されていません。 - フィールドにはすでに値があり、
strictInsertFill()またはstrictUpdateFill()は規則的に上書きされません。 - Javaのプロパティー名、フィールド型がパディングコードと矛盾しています。
論理的削除 FAQ
- グローバル構成では、エンティティ属性名ではなく、データベース列名が入力されます。
- データベースのデフォルト値、移入値の挿入、および論理削除されていない値に矛盾があります。
- カスタムSQLでは、ビジネスニーズごとに論理的な削除条件が処理されません。
主キーよくある質問
AUTOポリシーに対応するデータベース列には、自己増分が設定されていません。ASSIGN_UUIDでは文字列プライマリ·キーを使用していますが、データベース·フィールドの長さが不十分です。- カスタム
IdentifierGeneratorでは、複数のノードで重複したノードIDが使用されます。 - トレンドインクリメントIDを厳密連続 IDと間違え、ビジネスロジックを実装するためにその連続性に依存する。
気に入ったならばコメントを残してくださいね~