シーン
バックエンドサービスの開発では、一般的なフレームワークの組み合わせはSSM(SpringBoot + Spring + MyBatis)です。一部のビジネスシステムを開発する場合、多くのビジネスデータテーブルがあり、テーブル内の情報は新しい挿入から始まります。多くの操作は、ライフサイクル中に実行される可能性があります。
たとえば、特定のWebサイトで商品を購入すると、注文レコードが生成されます。金額を支払うと、注文ステータスが支払われます。最終的に注文商品を受け取ると、注文ステータスが完了します。
注文テーブルt_orderの結果が次のようになっているとします。
注文が作成されると、insert_by、insert_time、update_by、update_timeの値を設定する必要があります。
注文ステータスを更新する場合、update_byとupdate_timeの値のみを更新する必要があります。
どのように処理する必要がありますか?
マグル練習
最も簡単な方法、およびそれを考える最も簡単な方法は、各ビジネス処理のコード内の関連するフィールドを処理することです。
たとえば、注文作成の方法では、処理は次のようになります。
public void create(Order order){
// ...其他代码
// 设置审计字段
Date now = new Date();
order.setInsertBy(appContext.getUser());
order.setUpdateBy(appContext.getUser());
order.setInsertTime(now);
order.setUpdateTime(now);
orderDao.insert(order);
}
复制代码
注文更新メソッドは、updateByとupdateTimeのみを設定します。
public void update(Order order){
// ...其他代码
// 设置审计字段
Date now = new Date();
order.setUpdateBy(appContext.getUser());
order.setUpdateTime(now);
orderDao.insert(order);
}
复制代码
このメソッドは機能を完了できますが、いくつかの問題があります。
- さまざまなビジネスロジックに従って、各メソッドに設定するフィールドを決定する必要があります。
- ビジネスモデルが多い場合は、各モデルのビジネス方法で設定する必要があり、繰り返しコードが多すぎます。
そして、この方法に問題があることを知った後、私たちは良い方法を見つけなければなりませんよね?見下ろしてください!
エレガントなアプローチ
永続層フレームワークはMyBatisをより多く使用するため、MyBatisのインターセプターを使用して関数を完成させます。
まず、インターセプターとは何ですか?
インターセプターとは何ですか?
MyBatisのインターセプターは、その名前が示すように、特定の操作をインターセプトすることです。インターセプターを使用して、実行の前後に特定のメソッドをインターセプトし、処理ロジックを追加できます。
MyBatisのインターセプターは、Executor、StatementHandler、PameterHandler、およびResultSetHandlerインターフェースをインターセプトできます。つまり、これらの4種類のオブジェクトをプロキシします。
インターセプター設計の本来の目的は、ユーザーがMyBatisの処理プロセスでMyBatisのソースコードを変更しないようにすることであり、プラグインの形で実行プロセス全体に統合することができます。
たとえば、MyBatisのエグゼキュータには、BatchExecutor、ReuseExecutor、SimpleExecutor、CachingExecutorが含まれます。これらの実装されたクエリメソッドのいずれもニーズを満たせない場合は、MyBatisのソースコードを直接変更せずにExecutorインターフェイスをインターセプトできます。独自のクエリメソッドロジックを実装します。
MyBatisのインターセプターは、3つのメソッドを持つインターセプターインターフェイスで表されます。
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
复制代码
プラグインメソッドは、インターセプターがターゲットオブジェクトをカプセル化するために使用します。このメソッドを使用して、ターゲットオブジェクト自体を返すか、そのプロキシを返すことができます。
プロキシが返されると、その中のメソッドをインターセプトしてインターセプトメソッドを呼び出すことができます。もちろん、他のメソッドを呼び出すこともできます。
setPropertiesメソッドは、Mybatis構成ファイルでいくつかのプロパティを指定するために使用されます。
インターセプターで監査フィールドを更新する
では、インターセプターを介して監査フィールドに値を割り当てる機能をどのように実装する必要がありますか?
注文を作成および変更する場合、基本的にMyBatisを介して挿入および更新ステートメントを実行し、MyBatisはExecutorを介してそれらを処理します。
インターセプターを介してエグゼキューターをインターセプトし、実行されたステートメントに従って、データオブジェクトがインターセプターに挿入されるようにinsert_by、insert_time、update_by、update_timeなどの属性値を設定できます。
カスタムインターセプター
インターセプターをカスタマイズするための最も重要なことは、プラグインメソッドとインターセプトメソッドを実装することです。
プラグインメソッドでは、インターセプトするかどうかを決定してから、返すターゲットオブジェクトの種類を決定できます。
インターセプト法は、インターセプトを実行するときに実行されるメソッドです。
プラグインメソッドについては、Mybatisはすでに実装を提供しています。MybatisにはPluginというクラスがあり、静的メソッドwrap(Object target、Interceptorinterceptor)があります。これを使用して、返されるオブジェクトがターゲットオブジェクトであるか対応するプロキシであるかを判断できます。
しかし、ここにはまだ問題があります。つまり、サーフェスに挿入するためにどのフィールドを処理する必要があるかをインターセプターでどのように知るのでしょうか。
テーブル内のすべてのテーブルがビジネステーブルであるとは限らないため、監査フィールドのないディクショナリテーブルまたは定義テーブルが存在する可能性があり、インターセプターでそのようなテーブルを処理する必要はありません。
つまり、監査フィールドを更新する必要があるオブジェクトを区別できる必要があります。
ここでインターフェースを定義して、監査フィールドを更新する必要のあるすべてのモデルがこのインターフェースを均一に実装するようにすることができます。このインターフェースはマーカーとして機能します。
public interface BaseDO {
}
public class Order implements BaseDO{
private Long orderId;
private String orderNo;
private Integer orderStatus;
private String insertBy;
private String updateBy;
private Date insertTime;
private Date updateTime;
//... getter ,setter
}
复制代码
次に、カスタムインターセプターを実装できます。
@Component("ibatisAuditDataInterceptor")
@Intercepts({@Signature(method = "update", type = Executor.class, args = {MappedStatement.class, Object.class})})
public class IbatisAuditDataInterceptor implements Interceptor {
private Logger logger = LoggerFactory.getLogger(IbatisAuditDataInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 从上下文中获取用户名
String userName = AppContext.getUser();
Object[] args = invocation.getArgs();
SqlCommandType sqlCommandType = null;
for (Object object : args) {
// 从MappedStatement参数中获取到操作类型
if (object instanceof MappedStatement) {
MappedStatement ms = (MappedStatement) object;
sqlCommandType = ms.getSqlCommandType();
logger.debug("操作类型: {}", sqlCommandType);
continue;
}
// 判断参数是否是BaseDO类型
// 一个参数
if (object instanceof BaseDO) {
if (SqlCommandType.INSERT == sqlCommandType) {
Date insertTime = new Date();
BeanUtils.setProperty(object, "insertedBy", userName);
BeanUtils.setProperty(object, "insertTimestamp", insertTime);
BeanUtils.setProperty(object, "updatedBy", userName);
BeanUtils.setProperty(object, "updateTimestamp", insertTime);
continue;
}
if (SqlCommandType.UPDATE == sqlCommandType) {
Date updateTime = new Date();
BeanUtils.setProperty(object, "updatedBy", userName);
BeanUtils.setProperty(object, "updateTimestamp", updateTime);
continue;
}
}
// 兼容MyBatis的updateByExampleSelective(record, example);
if (object instanceof ParamMap) {
logger.debug("mybatis arg: {}", object);
@SuppressWarnings("unchecked")
ParamMap<Object> parasMap = (ParamMap<Object>) object;
String key = "record";
if (!parasMap.containsKey(key)) {
continue;
}
Object paraObject = parasMap.get(key);
if (paraObject instanceof BaseDO) {
if (SqlCommandType.UPDATE == sqlCommandType) {
Date updateTime = new Date();
BeanUtils.setProperty(paraObject, "updatedBy", userName);
BeanUtils.setProperty(paraObject, "updateTimestamp", updateTime);
continue;
}
}
}
// 兼容批量插入
if (object instanceof DefaultSqlSession.StrictMap) {
logger.debug("mybatis arg: {}", object);
@SuppressWarnings("unchecked")
DefaultSqlSession.StrictMap<ArrayList<Object>> map = (DefaultSqlSession.StrictMap<ArrayList<Object>>) object;
String key = "collection";
if (!map.containsKey(key)) {
continue;
}
ArrayList<Object> objs = map.get(key);
for (Object obj : objs) {
if (obj instanceof BaseDO) {
if (SqlCommandType.INSERT == sqlCommandType) {
Date insertTime = new Date();
BeanUtils.setProperty(obj, "insertedBy", userName);
BeanUtils.setProperty(obj, "insertTimestamp", insertTime);
BeanUtils.setProperty(obj, "updatedBy", userName);
BeanUtils.setProperty(obj, "updateTimestamp", insertTime);
}
if (SqlCommandType.UPDATE == sqlCommandType) {
Date updateTime = new Date();
BeanUtils.setProperty(obj, "updatedBy", userName);
BeanUtils.setProperty(obj, "updateTimestamp", updateTime);
}
}
}
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
复制代码
上記のコードからわかるように、カスタムインターセプターIbatisAuditDataInterceptorはインターセプターインターフェイスを実装しています。
インターセプターの@Interceptsアノテーションでは、typeパラメーターは、インターセプトされたクラスがExecutorインターフェースの実装であることを指定し、methodパラメーターは、データベース操作の追加、削除、および変更が実行されるため、インターセプトされたExecutorの更新メソッドを指定します。 updateメソッドを介して。
インターセプタープラグインを構成する
インターセプターを定義した後、インターセプターをSqlSessionFactoryBeanのプラグインに割り当てて有効にする必要があります。したがって、次のように構成します。
<bean id="transSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="transDataSource" />
<property name="mapperLocations">
<array>
<value>classpath:META-INF/mapper/*.xml</value>
</array>
</property>
<property name="plugins">
<array>
<!-- 处理审计字段 -->
<ref bean="ibatisAuditDataInterceptor" />
</array>
</property>
复制代码
この時点で、カスタムインターセプターが有効になります。テストにより、ビジネスコードで監査フィールドの値を手動で設定する代わりに、トランザクションの送信後にインターセプタープラグインを介して監査フィールドが自動的に割り当てられることがわかります。 。
まとめ
この号では、Xiao Heiが、日常の開発で頻繁に監査される監査フィールドの更新操作を適切に処理する方法を紹介します。
MyBatisのインターセプターをカスタマイズすることにより、監査フィールドを備えた一部のビジネスモデルがプラグインの形式で自動的に割り当てられ、退屈で反復的なコードの反復的な記述を回避します。
結局のところ、人生は短く、書くコードを減らし、釣りを増やすことができます。
著者:Xiao Hei Said Java
リンク:https://juejin.cn/post/7061250661828001800
出典:RareEarthNuggets
著作権は著者に帰属します。商用の再版については、著者に連絡して許可を求め、非商用の再版については、出典を示してください。