記事ディレクトリ
墓石
- 論理削除の操作は、このデータの状態を示すフィールドを追加することです。データの一部を削除する必要がある場合は、このデータの状態を変更することでそれを実現できます。これにより、このデータが削除されたことを示すだけでなく、は削除されますが、将来の統計のためにデータも保持されます。
- テーブルにフィールドの列を追加して、削除するかどうかを示します。ここで使用されるフィールドの型は int 型です。1 はデータが利用可能であることを示し、0 はデータが利用できないことを示します。
- エンティティ クラスは、テーブル内のフィールドに対応するために使用されるフィールドを整数として追加します。
@Data @AllArgsConstructor @NoArgsConstructor public class User extends Model<User> { private Long id; private String name; private Integer age; private String email; @TableLogic(value = "1",delval = "0") private Integer status; }
- 墓石効果をテストする
@Test
void logicDelete(){
userMapper.deleteById(7L);
}
- トゥームストーンの削除の効果は、グローバル構成によっても実現できます。
一般的な列挙
- 情報の集合を表現したい場合、この情報の集合はいくつかの固定値から選択することしかできず、自由に書き込むことができないため、このシナリオでは列挙が非常に適しています。
- 性別を表すフィールドをテーブルに追加し、int を使用して記述します。int 型は 0 と 1 の 2 つの値を通じて 2 つの異なる性別を表すことができるためです。
- 列挙型クラスを作成する
public enum GenderEnum {
MAN(0,"男"),
WOMAN(1,"女");
@EnumValue
private Integer gender;
private String genderName;
GenderEnum(Integer gender, String genderName) {
this.gender = gender;
this.genderName = genderName;
}
}
- 関連フィールドを追加するエンティティクラス
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User extends Model<User> {
private Long id;
private String name;
private Integer age;
private String email;
private GenderEnum gender;
private Integer status;
}
- データの追加
@Test
void enumTest(){
User user = new User();
user.setName("liu");
user.setAge(29);
user.setEmail("[email protected]");
user.setGenderEnum(GenderEnum.MAN);
user.setStatus(1);
userMapper.insert(user);
}
フィールド型ハンドラー
- 一部のシナリオでは、エンティティ クラスで、フロント エンドによって渡されたデータを受け取る属性として Map コレクションが使用されますが、これらのデータがデータベースに保存される場合、それらは json 形式で保存されます。json は本質的に文字列です。それはvarchar型です。次に、エンティティクラスの Map 型とデータベースの varchar 型の間で変換する方法ですが、ここではフィールド型プロセッサを使用して完了する必要があります。
- エンティティクラス
@Data @AllArgsConstructor @NoArgsConstructor public class User extends Model<User> { private Long id; private String name; private Integer age; private String email; private GenderEnum gender; private Integer status; private Map<String,String> contact;//联系方式 }
- データベースに varchar 型のフィールドを追加します。
- 対応するアノテーションをエンティティ クラスに追加して、フィールド タイプ プロセッサを使用してさまざまなタイプのデータ変換を実装します。
@Data @AllArgsConstructor @NoArgsConstructor @TableName(autoResultMap = true)//查询时将json字符串封装为Map集合 public class User extends Model<User> { private Long id; private String name; private Integer age; private String email; private GenderEnum gender; private Integer status; @TableField(typeHandler = FastjsonTypeHandler.class)//指定字段类型处理器 private Map<String,String> contact;//联系方式 }
- フィールド型プロセッサの導入はFastjsonに依存します
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version> </dependency>
自動入力
- プロジェクトにはいくつかのプロパティがあります。毎回入力したくない場合は、共通時刻、作成時刻、更新時刻などを自動入力に設定できます。
- エンティティクラスに対応するフィールドを追加し、自動的に入力する必要がある属性の入力タイミングを指定します。
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName(autoResultMap = true)
public class User extends Model<User> {
@TableId
private Long id;
private String name;
private Integer age;
private String email;
private Integer status;
private GenderEnum gender;
@TableField(typeHandler = FastjsonTypeHandler.class)
private Map<String,String> contact;
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
- 自動入力プロセッサを作成し、入力戦略を指定する
@Component public class MyMetaHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { setFieldValByName("createTime",new Date(),metaObject); setFieldValByName("updateTime",new Date(),metaObject); } @Override public void updateFill(MetaObject metaObject) { setFieldValByName("updateTime",new Date(),metaObject); } }
- mysql タイムゾーンを設定し、yml 接続構成を更新します。
set GLOBAL time_zone='+8:00'
select NOW();
アンチフルテーブル更新および削除プラグイン
- 実際の開発においてテーブルの全更新や削除は非常に危険な操作ですが、MybatisPlusではそのような危険な操作を防止するためのプラグインが提供されています。
- 実装手順:
- MybatisPlusInterceptor クラスを挿入し、Block AttackInnerInterceptor インターセプターを構成します。
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
}
- テーブル全体の更新をテストすると、例外がスローされ、テーブル全体の更新が妨げられます。
@SpringBootTest
public class QueryTest {
@Autowired
private UserService userService;
@Test
void allUpdate(){
User user = new User();
user.setId(999L);
user.setName("wang");
user.setEmail("[email protected]");
userService.saveOrUpdate(user,null);
}
}
MybatisX 高速開発プラグイン
プラグインのインストール
- MybatisX は IDEA が提供するプラグインで、Mybatis および MybatisPlus フレームワークを簡素化するように設計されています。
- IDEA にプラグインをインストールする
- 最初に選択してください
File -> Settings->Plugins
- 「MybatisX」を検索し、「インストール」をクリックします。
- IDEA を再起動してプラグインを有効にします。これまでのところ、MybatisX プラグインはインストールされています
- プラグインをインストールしたら、プラグインの機能を見てみましょう
- Mapperインターフェースとマッピングファイルのジャンプ機能
リバースエンジニアリング
- リバース エンジニアリングとは、次の点を含むデータベース テーブル構造を通じて Java プロジェクトの構造を逆生成することです。
- エンティティクラス
- マッパーインターフェース
- マッパーマッピングファイル
- サービスインターフェース
- サービス実装クラス
- 実装手順:
- まずIDEAを使用してmysqlに接続し、接続情報を入力して、接続が成功するかテストします。
- テーブルを右クリックし、プラグインのリバース エンジニアリング オプションを選択します。
- リバースエンジニアリング構成情報の書き込み
- ビルド情報を書き込む
共通要件のコード生成
- Mapper インターフェイスにはいくつかの共通メソッドが用意されており、これらの共通メソッドを直接使用して SQL 操作を完了できますが、実際のシナリオでのさまざまな複雑な操作要件にはまだ十分ではないため、MybatisX はより多くのメソッドを提供し、対応する SQL ステートメントを直接実行できます。これらの方法に従って生成されるため、開発が容易になります。
- 一般的な操作を名前に関連付けることができます
@Mapper
public interface UserMapper extends BaseMapper<User> {
//添加操作
int insertSelective(User user);
//删除操作
int deleteByNameAndAge(@Param("name") String name, @Param("age") Integer age);
//修改操作
int updateNameByAge(@Param("name") String name, @Param("age") Integer age);
//查询操作
List<User> selectAllByAgeBetween(@Param("beginAge") Integer beginAge, @Param("endAge") Integer endAge);
}
- マッピング設定ファイルでは、対応する SQL が生成されるため、次のように記述する必要はありません。
<insert id="insertSelective">
insert into powershop_user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,</if>
<if test="name != null">name,</if>
<if test="age != null">age,</if>
<if test="email != null">email,</if>
</trim>
values
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">#{id,jdbcType=BIGINT},</if>
<if test="name != null">#{name,jdbcType=VARCHAR},</if>
<if test="age != null">#{age,jdbcType=INTEGER},</if>
<if test="email != null">#{email,jdbcType=VARCHAR},</if>
</trim>
</insert>
<delete id="deleteByNameAndAge">
delete
from powershop_user
where name = #{name,jdbcType=VARCHAR}
AND age = #{age,jdbcType=NUMERIC}
</delete>
<update id="updateNameByAge">
update powershop_user
set name = #{name,jdbcType=VARCHAR}
where age = #{age,jdbcType=NUMERIC}
</update>
<select id="selectAllByAgeBetween" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from powershop_user
where
age between #{beginAge,jdbcType=INTEGER} and #{endAge,jdbcType=INTEGER}
</select>
楽観的なロック
問題の紹介
- 同時リクエストとは、複数のリクエストが同時にサーバーリソースを要求することであり、情報の取得であれば問題ありませんが、情報の変更であれば問題が発生します。
- たとえば、商品の在庫が 1 つだけ残っているとします。このとき、複数のユーザーがこの商品を購入したいと考えており、全員が商品の購入リクエストを開始します。したがって、これらの複数のユーザーを許可することは絶対に不可能です。複数のユーザーがこの商品を購入しているため、売れすぎの問題が発生し、在庫が不足している場合は商品を発送できません。したがって、開発中にこの過剰販売の問題を解決する必要があります。
主な問題: リクエストの実行中、他のリクエストはデータを変更できません。リクエストが完全であり、リクエストのプロセス中に他のリクエストがデータを変更しない場合、リクエストは通常どおりデータを変更できます。データ変更の過程で他のリクエストがすでにデータを変更している場合、リクエストはデータを変更しません
-
この種の問題を解決するための最も一般的なアイデアは、リクエストの実行中にデータが変更されたかどうかを確認するために使用できるロックを追加することです。
-
データベース ロックには、悲観的ロックと楽観的ロックの 2 つの一般的なタイプがあります。1 回限りの変更操作では、まずデータをクエリしてから、データを変更します。
- 悲観的ロック: 悲観的ロックは、クエリ時にデータをロックし、リクエストが完了するまでロックは解放されません。このリクエストが完了するとロックが解除され、ロックが解除された後は、他のリクエストでこのデータの読み取りと書き込みを完了できるようになります。
- ペシミスティック ロックの長所と短所: 読み取られた情報が最新の情報であることを保証し、情報の正確性を保証できますが、同時実行効率は非常に低くなります。
- 同時実行時の効率を確保する必要があるため、実際の開発で悲観的ロックが使用されるシナリオはほとんどありません。
- オプティミスティック ロック: オプティミスティック ロックは、テーブル フィールドを通じて設計されています。中心的な考え方は、読み取り時にデータがロックされず、他のリクエストは引き続きデータを読み取ることができるということです。変更時に、データが変更されたかどうかが判断されます。変更されている場合、今回要求された変更操作は無効になります。
-
具体的にはSQLを通じて達成することです
Update 表 set 字段 = 新值,version = version + 1 where version = 1
- この方法の操作はデータの読み取りに影響を与えず、同時実行の効率が高くなります。ただし、現在表示されているデータは実際の情報データではない可能性があり、変更される前のものですが、多くのシナリオでは許容可能であり、大きな影響はありません。
楽観的ロックの使用
- データベース テーブルにバージョンを示すフィールド バージョンを追加します。デフォルト値は 1 です。
- エンティティ クラスを検索し、対応する属性を追加し、@Version を使用してそれをオプティミスティック ロック フィールド情報としてマークします。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
@Version
private Integer version;
}
- インターセプターの構成により、変更された各 SQL ステートメントの実行時にバージョン管理の機能が追加されます。
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
効果試験
- 次に、複数の変更要求があった場合に楽観的ロックの効果が得られるかどうかをシミュレーションします。
- オプティミスティック ロックの効果は、変更プロセス中にリクエストが別のリクエストによってクエリされることを許可されますが、変更はバージョン番号が変更されたかどうかによって決定されます。バージョン番号が変更された場合、それはリクエストがあったことを証明します。データを変更すると、この変更は有効になりません。バージョン番号が変更されていない場合は、変更を完了してください。
@Test
void updateTest2(){
//模拟操作1的查询操作
User user1 = userMapper.selectById(6L);
//模拟操作2的查询操作
User user2 = userMapper.selectById(6L);
//模拟操作2的修改操作
user2.setName("lisi");
userMapper.updateById(user2);
//模拟操作1的修改操作
user1.setName("zhangsan");
userMapper.updateById(user1);
}
- コード実行プロセス
- 操作 1 のクエリ: 現時点ではバージョンは 2
- 操作 2 のクエリ: 現時点のバージョンは 2
- 操作修正2:この時点でバージョンを確認するとバージョンは変わっていないので修正を完了しバージョンを3に変更します
- 動作修正1:この時点でバージョンを確認すると、当初取得していたバージョン情報からバージョンが変わっているため、改変を禁止します
- 操作 1 のクエリ: 現時点ではバージョンは 2
コードジェネレーター
- コード ジェネレーターとリバース エンジニアリングの違いは、コード ジェネレーターではより多くの構造とコンテンツを生成でき、生成のためにより多くのオプションを構成できることです。
- 依存関係を導入する
<!--代码生成器依赖--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.3</version> </dependency> <!--freemarker模板依赖--> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.31</version> </dependency>
- コードジェネレーターコードを書く
- 例 1:
public class CodeGenerator { /* SELECT table_name FROM information_schema.tables WHERE table_schema = 'xxx' ORDER BY table_name DESC; */ public static String scanner(String tip) { Scanner scanner = new Scanner(System.in); StringBuilder help = new StringBuilder(); help.append("请输入" + tip + ":"); System.out.println(help.toString()); if (scanner.hasNext()) { String ipt = scanner.next(); if (!ipt.equals("")) { return ipt; } } throw new MybatisPlusException("请输入正确的" + tip + "!"); } public static void main(String[] args) { // 代码生成器 AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); gc.setOutputDir(projectPath + "/src/main/java");//设置代码生成路径 gc.setFileOverride(true);//是否覆盖以前文件 gc.setOpen(false);//是否打开生成目录 gc.setAuthor("xxx");//设置项目作者名称 gc.setIdType(IdType.AUTO);//设置主键策略 gc.setBaseResultMap(true);//生成基本ResultMap gc.setBaseColumnList(true);//生成基本ColumnList gc.setServiceName("%sService");//去掉服务默认前缀 gc.setDateType(DateType.ONLY_DATE);//设置时间类型 mpg.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/care_home?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8"); dsc.setDriverName("com.mysql.cj.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("xxx"); mpg.setDataSource(dsc); // 包配置 PackageConfig pc = new PackageConfig(); pc.setParent("com.test"); pc.setMapper("com/test/mapper"); pc.setXml("mapper.xml"); pc.setEntity("com/test/pojo"); pc.setService("com/test/service"); pc.setServiceImpl("service.impl"); pc.setController("com/test/controller"); mpg.setPackageInfo(pc); // 策略配置 StrategyConfig sc = new StrategyConfig(); sc.setNaming(NamingStrategy.underline_to_camel); sc.setColumnNaming(NamingStrategy.underline_to_camel); sc.setEntityLombokModel(true); //自动lombok sc.setRestControllerStyle(true); sc.setControllerMappingHyphenStyle(true); sc.setLogicDeleteFieldName("deleted");//设置逻辑删除 //设置自动填充配置 TableFill gmt_create = new TableFill("create_time", FieldFill.INSERT); TableFill gmt_modified = new TableFill("update_time", FieldFill.INSERT_UPDATE); ArrayList<TableFill> tableFills=new ArrayList<>(); tableFills.add(gmt_create); tableFills.add(gmt_modified); sc.setTableFillList(tableFills); //乐观锁 sc.setVersionFieldName("version"); sc.setRestControllerStyle(true);//驼峰命名 // sc.setTablePrefix("tbl_"); 设置表名前缀 sc.setInclude(scanner("表名,多个英文逗号分割").split(",")); mpg.setStrategy(sc); // 生成代码 mpg.execute(); } }
- 例 2:
/**
* @author 缘友一世
* date 2023/7/19-16:09
*/
public class CodeGenerator {
public static void main(String[] args) {
String projectPath = System.getProperty("user.dir"); // 获取当前项目的绝对路径
String MainPath=projectPath+"/src/main/java";
String url="jdbc:mysql://localhost:3306/db2?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false";
String username="root";
String password="xxx";
String author="yang";
String moduleName="system";
String mapperLocation=projectPath+"/src/main/resources/mapper/"+moduleName;
String parentPackageName="com.yang";
/*
CREATE TABLE x_user (
id int(11) NOT NULL AUTO_INCREMENT,
username varchar(50) NOT NULL ,
password varchar(100) DEFAULT NULL,
email varchar(50) DEFAULT NULL,
phone varchar(20) DEFAULT NULL,
status int(1) DEFAULT NULL,
avatar varchar(200) DEFAULT NULL,
deleted INT(1) DEFAULT 0,
PRIMARY KEY (id)
)ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
DELETE FROM x_user
WHERE id > 20;
*/
/*
* SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'xxx'
ORDER BY table_name DESC; */
String tables="x_user";
FastAutoGenerator.create(url, username, password)
.globalConfig(builder -> {
builder.author(author) // 设置作者
//.enableSwagger() // 开启 swagger 模式
//.fileOverride() // 覆盖已生成文件
.outputDir(MainPath); // 指定输出目录
})
.dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
int typeCode = metaInfo.getJdbcType().TYPE_CODE;
if (typeCode == Types.SMALLINT) {
// 自定义类型转换
return DbColumnType.INTEGER;
}
return typeRegistry.getColumnType(metaInfo);
}))
.packageConfig(builder -> {
builder.parent(parentPackageName) // 设置父包名
.moduleName(moduleName) // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.xml, mapperLocation)); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude(tables) // 设置需要生成的表名
.addTablePrefix("x_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}
SQL解析印刷の実行
- MybatisPlusが提供するSQL解析・出力機能を利用して、SQL文の実行時間を取得できます。
- この関数は p6spy コンポーネントに依存しているため、最初に pom.xml に導入する必要があります
<dependency> <groupId>p6spy</groupId> <artifactId>p6spy</artifactId> <version>3.9.1</version> </dependency>
- application.ymlで設定する
spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql
- 「リソース」の下に、spy.properties 構成ファイルを作成します。
#3.2.1以上使用modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2
- テスト: クエリのすべての操作を実行すると、SQL ステートメントの実行時間を確認できます。
複数のデータソース
- サブデータベースとサブテーブル: プロジェクトのデータベース内のデータが非常に大きい場合、SQL 操作を完了するときにより多くのデータを取得する必要があり、パフォーマンスの問題と SQL 実行効率の低下が発生します。
- この問題に対し、当社では、アクセス要求の負荷分散とデータベース内のデータ量の削減という2つの側面から、1つのデータベース内のデータを複数のデータベースに分割し、1つのデータベース内のデータ量を削減するという解決策を講じています。単一データベース、すべての効率が向上します。
- MybatisPlus で、データ ソース切り替えの効果を実証する方法
- まず新しいモジュールを作成し、前のモジュールの内容をコピーします。
- 依存関係を導入する
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
- 新しいデータベースを作成し、マルチデータ ソース環境を提供する
- 複数のデータソース情報を指定する構成ファイルを作成する
spring:
datasource:
dynamic:
primary: master
strict: false
datasource:
master:
username: root
password: xxx
url: jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
slave_1:
username: root
password: xxx
url: jdbc:mysql://localhost:3306/mybatisplus2?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
- 複数のサービスを作成し、@DS アノテーションを使用してさまざまなデータ ソース情報を記述します
@Service
@DS("master")
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
}
@Service
@DS("slave_1")
public class UserServiceImpl2 extends ServiceImpl<UserMapper, User> implements UserService{
}
- サービスのマルチデータソース環境の実行結果をテストする
@SpringBootTest
class Mp03ApplicationTests {
@Autowired
private UserServiceImpl userServiceImpl;
@Autowired
private UserServiceImpl2 userServiceImpl2;
@Test
public void select(){
User user = userServiceImpl.getById(1L);
System.out.println(user);
}
@Test
public void select2(){
User user = userServiceImpl2.getById(1L);
System.out.println(user);
}
}
- テスト結果を観察し、結果が 2 つのデータ ソースから取得できることを確認します。