背景
の仕事は時折、例えば、以下のテーブル構造をSQLの見直しを統一する必要性が発生します。
CREATE TABLE test_user
(id
INT(11)NOT NULL AUTO_INCREMENT、account
VARCHAR(70)NOT NULL COMMENT '口座番号'、user_name
VARCHAR(60)NOT NULL COMMENT '名称'、age
INT(11)NOT NULL COMMENT '高齢'、sex
「ビット(1)NOT NULL COMMENT '性別:0男性、女性1'、create_time
タイムスタンプのNOT NULL DEFAULT '2019年1月1日夜12時〇 〇分00秒' COMMENT '作成時間'、update_time
タイムスタンプのNOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMPのCOMMENT '更新'、PRIMARY KEY( id
)、UNIQUE KEY uk_account
(account
))= ENGINEのInnoDB DEFAULT CHARSET = UTF8 COMMENT = 'ユーザ情報テーブル';
想定以下マッパーSQL:
挿入test_user
(account
、user_name
、age
、sex
、create_time
重複キーの更新で)の値( 'TEST1'、 'test_user_1'、1、0、今())user_name
= 'test_user_1'、age
= 1、sex
= 0;
あなたはMyBatisのプラグインドルイドファイラー書き換えSQLを使用する方法だけでなく、文化と教育
層内のサービスコードはマッパー効果がSQL実行するかどうかを識別するために正常に返される行の数によって決定される1に等しいです。重複キーの更新に設けられたデータベースレコードの値とフィールド値が全く同じ場合JDBC衝撃の行数は、論理エラーサービス層が得られ、ゼロに復帰するようにしかし、それは、MySQLのアップデートを実行しません。
解決策は単純で、ただUPDATE_TIMEが重複キーの更新(になりました=追加)であってもよいが、この文が広まっている場合は、最も簡単な方法は、SQLの書き換えによって達成することができます。
設計・選定
SQLの修正
ORMとしてシステムMyBatisのは、アリババは、データベース接続プールとしてドルイド。
MyBatisのは、例えばMyBatisの-PageHelperは、SQL文を修正してカウントするメカニズムをページング追加するプラグインを使用することで、SQLを修正するためのプラグインメカニズムを提供します。
SQLを変更するにはドルイド申し出フィルタ機構は、例えばEncodingConvertFilterは、実際の実行前にトランスコーディングを実装するためにフィルタ機構を使用することです。
私たちはときに変更を持っていることを選択するので、両方ので、変更するSQL以上行うことができますか?実際には、2と長所と短所の間に有意差、と私は個人的に、次の2つの方法を見て:
異なる移植。例えば、この変形MyBatisの層におけるより適切には、今度は、選択されたフレームは、HibernateのORMウイザードである場合がより好適であるDBCPヒカリまたはを使用してJDBC接続プール。
さまざまなワークロード。ORMとなぜなら、異なるレベルの実装につながっ抽象JDBCコード書き換え作業の異なる程度の考慮すべき、低レベルJDBCためのワークロードは、はるかに少ないORMベースMyBatisの層の書き換え可能ドルイドJDBCベース層を書き換えるよりも、全く異なりますより多く、例えば、または実行モードがPreparedStatmentステートメントは、CallableStatementの、または、これらの書き換えの全てをカバーする必要があり、同様に、微細にかかわらずORM層を書き換えられます。
SQLパーサ選択
SQLを書き換える、最初にすべての必要性はどの部分を書き換え、書き換えするかどうかを判断するためにSQL、SQL意味解析を解析する必要があり、SQLパーサフレームワークは非常に重要であるので、字句解析は常に、非常に時間がかかるとなっています。より人気SQLパーサのJavaエコシステムには、次のとおりです。
Appleは現在メンテナンスされていないオープンソースのSQLパーサを取得する前にFDB-SQLパーサーがFoundationDBされます。
jsqlparser JavaCCのは、オープンソースのSQLパーサに基づいており、一般的なSQLパーサは、Java実装のバージョンです。
それは次のように、SQL、SQL検証、クエリ最適化を解析含むオープンソースのApache方解石動的データ管理フレームワークであり、多くの場合、大容量のデータのSQLツールを提供するために使用されるSQLクエリ関数とデータ接続を生成し、例えばハイブ、FLINK。方解石の良い標準SQLのサポートが、従来のリレーショナルデータ方言の貧しい人々のためのサポート。
アリババAlibabaのドルイドは、オープンソースのJDBCデータベース接続プールですが、それは、SQLパーサの自然な能力を許可しているモニタリングのアイデアが生まれました。独自のウォールファイラー、StatFilerは、すべてのASTのSQLパーサの解析に基づいています。そして、データベースの方言の様々なサポートしています。
それはSQLの書き換えに来るとき、我々はSQLパーサは、これらのよく知られたデータベースミドルウェアを参照することができます選択したので、実際には、我々は簡単に、データベースミドルウェアのサブライブラリーのサブテーブルと考えることができます。Apacheのシャーディングスフィア(以前详细シャーディング-JDBC)、Mycat両方SQLパーサモジュールのどの使用alibabaのウイザードで、現在オープンソース・データベース・ミドルウェア家庭用の多数であり、そしてMycatもの選択において、その比較分析を開きMycat新しいパーサルーティングの選択と分析結果が.docxの。
注:Apacheのシャーディングスフィアではなく、1.5.xのバージョンでは、独自のSQLパーサを開発したシャーディング球は、完全なSQLのASTを必要としないので、その理由は、したがって、アップグレードポイントの整合性の費用を削減するためにSQLパーサSQLの解決の自己啓発に切り替えていますシャーディング-JDBCの理解の深さで詳述ライブラリサブテーブルの効率、:最も軽量データベース中間層を実行します。
あなたはMyBatisのプラグインドルイドファイラー書き換えSQLを使用する方法だけでなく、文化と教育
要約すると、我々はアリババの選択は、SQLパーサによって提供さドルイドことを安心することができ、唯一の問題は、ドルイドSQLパーサーを使用する方法です。ドルイド当局者は、SQLパーサビジターズ(ドキュメントとコードのコメントにTucaoルック再び不完全国内のオープンソース・プロジェクト、ドルイドソース基本的にはノーコメント)に私達ができるので、唯一の他の関連文書をAPIドキュメントを詳細に説明していない、とされていますいくつかのビジターの参照は、以下のSQLパーサとビジターの上のドルイドのすべての公式ドキュメントです。
パーサSQL
MySQLのSQLパーサ
Druid_SQL_AST
WallVisitor
設定-WallFilter
EvalVisitor
SchemaStatVisitor
ExportParameterVisitor_demo_cn
ParameterizedOutputVisitor
SQL_Format
SQL_Parser_Demo_visitor(カスタムVistor)
SQL_Parser_Parameterize
SQL_RemoveCondition_demo
SQL_Schema_Repository
TableMapping_cn
SQLの追加条件を変更する方法
デモは
)2つのモードでMyBatisのプラグインとドルイドフィルターデモを実現し、機能は非常に単純ですが、中にキーupdatesqlに...インサートの開口部にそれを複製プラスUPDATE_TIME(現在=。
デモアドレスMyBatisの-プラグイン・オア・ドルイド・フィルタ書き換え-SQL。
H2のシミュレーションデモの使用でのMysql、H2は、table文参照のsrc /テスト/リソース/スキーマh2.sqlを構築しました。
プラグインMyBatisの
プラグインコードのsrc /メイン/ javaの/ COM / githubの/幼虫/張/問題/ SimpleRewriteSqlMybatisPlugin.java。
@ SLF4J @インターセプト({署名(タイプ= Executor.class、方法=「更新」、引数= {MappedStatement.class、Object.classを})@})パブリッククラスSimpleRewriteSqlMybatisPluginが実装インターセプタ{民間最終SimpleAppendUpdateTimeVisitorビジター=新しいSimpleAppendUpdateTimeVisitor() ; @Overrideパブリックオブジェクトインターセプト(呼び出しの呼び出し)がスローされたThrowable {オブジェクト[]引数= invocation.getArgs()。MappedStatement mappedStatement =(MappedStatement)引数[0]。SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType()。(もし!sqlCommandType = SqlCommandType.INSERT){//只处理、インサートリターンinvocation.proceed(); } BoundSql boundSql = mappedStatement.getBoundSql(引数[1])。文字列のSQL = boundSql.getSql()。リストsqlStatements = SQLUtils.parseStatements(SQL、JdbcConstants.MYSQL)。(CollectionUtils.isNotEmpty(sqlStatements)){(のSQLStatementのSQLStatementのための場合:sqlStatements){sqlStatement.accept(訪問者)。}}もし(visitor.getAndResetRewriteStatus()){//改写了SQL、需要替换MappedStatementストリングnewSql = SQLUtils.toSQLString(sqlStatements、JdbcConstants.MYSQL)。log.info(「リライトSQL、原点SQL:[{}]、新しいSQL:[{}]」、SQL、newSql)。BoundSql newBoundSql =新しいBoundSql(mappedStatement.getConfiguration()、newSql、boundSql.getParameterMappings()、boundSql.getParameterObject())。//コピー原始MappedStatement的各项属性MappedStatement.Builderビルダー=新しいMappedStatement.Builder(mappedStatement.getConfiguration()、mappedStatement.getId()、新WarpBoundSqlSqlSource(newBoundSql)、mappedStatement.getSqlCommandType()); builder.cache(mappedStatement.getCache())。databaseId(mappedStatement.getDatabaseId()).fetchSize(mappedStatement.getFetchSize()).flushCacheRequired(mappedStatement.isFlushCacheRequired()).keyColumn(のStringUtils。参加(mappedStatement.getKeyColumns() ' ')).keyGenerator(mappedStatement.getKeyGenerator()).keyProperty(StringUtils.join(mappedStatement.getKeyProperties()'、')).lang(mappedStatement.getLang())。なparameterMap(mappedStatement.getParameterMap()).resource(mappedStatement.getResource())。resultMaps(mappedStatement.getResultMaps()).resultOrdered(mappedStatement.isResultOrdered()).resultSets(StringUtils.join(mappedStatement.getResultSets()」、 「)).resultSetType(mappedStatement.getResultSetType())するStatementType(mappedStatement.getStatementType()).timeout(mappedStatement.getTimeout())useCache(mappedStatement.isUseCache())。MappedStatement newMappedStatement = builder.build()。//将新生成的MappedStatement对象替换到参数列表中引数[0] = newMappedStatement。})(invocation.proceedを返します。
-
*
- {@link StatementHandlerは} JDBC {@link java.sql.Statementの}オブジェクトを作成する責任があります *
- 実際のパラメータの{@link ParameterHandler}責任を充填JDBC {@linkによりjava.sql.Statement}オブジェクト *
- {@link ResultSetHandler}責任JDBC {@link java.sql.Statementの#(文字列)を実行} *戻り処理を{@link}のjava.sql.ResultSet *
SRCを参照して、単に使用にMyBatisの設定ビーンインターセプタリストを宣言するプラグインにインスタンスを追加/試験/ジャワ/ COM / githubの/幼虫/張/問題/ MyBatisの/ TestMybatisPluginRewriteSqlConfig.java。
@Bean @Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public org.apache.ibatis.session.Configuration mybatisConfiguration() { org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); // 各项属性设置 ... // 使用Mybatis Plugin机制改写SQL configuration.addInterceptor(mybatisPlugin()); return configuration; } @Bean public SimpleRewriteSqlMybatisPlugin mybatisPlugin() { return new SimpleRewriteSqlMybatisPlugin(); }
あなたはMyBatisのプラグインドルイドファイラー書き換えSQLを使用する方法だけでなく、文化と教育
ドルイドフィルター
フィルター代码是のsrc /メイン/ javaの/ COM / githubの/幼虫/張/問題/ SimpleRewriteSqlDruidFilter.java。
Slf4jpublicクラスSimpleRewriteSqlDruidFilterはFilterAdapter {民間最終SimpleAppendUpdateTimeVisitorビジター=新しいSimpleAppendUpdateTimeVisitorを()を拡張します@。。@Overrideパブリックブールstatement_execute(れるFilterChain鎖、StatementProxy文、文字列のSQLは)のSQLException {文字列のdbType = chain.getDataSource()getDbType()を投げます。リストsqlStatements = SQLUtils.parseStatements(SQL、のdbType)。sqlStatements.forEach(のSQLStatement - > sqlStatement.accept(ビジター)); IF(visitor.getAndResetRewriteStatus()){//改写了SQL、需要替换列newSql = SQLUtils.toSQLString(sqlStatements、のdbType)。log.info(「リライトSQL、原点SQL:[{}]、新しいSQL:[{}]」、SQL、newSql)。リターンsuper.statement_execute(チェーン、声明、newSql)。}戻りsuper.statement_execute(チェーン、ステートメント、SQL)。} @Override公共PreparedStatementProxy connection_prepareStatement(れるFilterChain鎖 ConnectionProxy接続文字列のSQL、int型autoGeneratedKeysは)のSQLException {リストsqlStatements = SQLUtils.parseStatements(SQL、JdbcConstants.MYSQL)を投げます。sqlStatements.forEach(のSQLStatement - > sqlStatement.accept(ビジター)); IF(visitor.getAndResetRewriteStatus()){//改写了SQL、需要替换列newSql = SQLUtils.toSQLString(sqlStatements、JdbcConstants.MYSQL)。log.info(「リライトSQL、原点SQL:[{}]、新しいSQL:[{}]」、SQL、newSql)。リターンsuper.connection_prepareStatement(チェーン、接続、newSql、autoGeneratedKeys)。} super.connection_prepareStatement(チェーン、接続、SQL、autoGeneratedKeys)を返します。}} getAndResetRewriteStatus()){//改写了SQL、需要替换列newSql = SQLUtils.toSQLString(sqlStatements、JdbcConstants.MYSQL)。log.info(「リライトSQL、原点SQL:[{}]、新しいSQL:[{}]」、SQL、newSql)。リターンsuper.connection_prepareStatement(チェーン、接続、newSql、autoGeneratedKeys)。} super.connection_prepareStatement(チェーン、接続、SQL、autoGeneratedKeys)を返します。}} getAndResetRewriteStatus()){//改写了SQL、需要替换列newSql = SQLUtils.toSQLString(sqlStatements、JdbcConstants.MYSQL)。log.info(「リライトSQL、原点SQL:[{}]、新しいSQL:[{}]」、SQL、newSql)。リターンsuper.connection_prepareStatement(チェーン、接続、newSql、autoGeneratedKeys)。} super.connection_prepareStatement(チェーン、接続、SQL、autoGeneratedKeys)を返します。}}
声明の履行を支援するフィルタとPreparedStatementのSQLリライトの二つのモードが、SQLの他のタイプのためのサポートの欠如。
MyBatisのプラグインの悪い点と比較すると、SQLはもちろん、ASTを解析SQLパーサを通過に必要なものに関係なく、これはprepareStatement_executeではなくconnection_prepareStatement段階よりも、SQLを書き換えも可能であるということです。
prepareStatement_execute書き換えステージとPreparedStatementProxyを再構築する必要が、トラブルによりSQL connection_prepareStatementをオフ書き換えのこの段階をJdbcParametersをリセットします。
使用している場合のみ使用タイプDruidのWallFilterにフィルタリストにドルイドのDataSourceインスタンス宣言で加えます。リファレンスのsrc /テスト/ javaの/ COM / githubの/幼虫/張/問題/ドルイド/ DruidFilterRewriteSqlConfig.java。
@Bean(initMethod = "init", destroyMethod = "close") public DruidDataSource dataSource(@Value("${spring.datasource.url}") String url, @Value("${spring.datasource.username}") String username, @Value("${spring.datasource.password}") String password) throws SQLException { DruidDataSource druidDataSource = new DruidDataSource(); // 各项属性设置 ... // 添加改写SQL的Filter druidDataSource.setProxyFilters(Collections.singletonList(simpleRewriteSqlDruidFilter())); return druidDataSource; } @Bean public FilterAdapter simpleRewriteSqlDruidFilter() { return new SimpleRewriteSqlDruidFilter(); }
ドルイドビジターは、
上記のコードから見るとプラグインをフィルタリングすることができ、実際のSQLはSRCに書き換えられて/メイン/ javaの/ COM / githubの/幼虫/張/問題/ SimpleAppendUpdateTimeVisitor.java。
Slf4jpublicクラスSimpleAppendUpdateTimeVisitorはMySqlASTVisitorAdapterを{プライベート静的最終ThreadLocalのREWRITE_STATUS_CACHE =新しいThreadLocalの<>()を拡張します@。プライベート静的最後の文字列UPDATE_TIME_COLUMN =「UPDATE_TIME」。@Overrideパブリックブール訪問(MySqlInsertStatement x)から{ブールhasUpdateTimeCol = FALSE; //重複したキー更新得到的都是SQLBinaryOpExpr一覧duplicateKeyUpdate = x.getDuplicateKeyUpdate(); IF(CollectionUtils.isNotEmpty(duplicateKeyUpdate)){ため(SQLExpr sqlExpr:duplicateKeyUpdate){IF(sqlExprのinstanceof SQLBinaryOpExpr &&((SQLBinaryOpExpr)sqlExpr).conditionContainsColumn(UPDATE_TIME_COLUMN)){hasUpdateTimeCol = TRUE。ブレーク; }}もし{//追記更新時刻欄列tableAlias = x.getTableSource()getAliasは()(hasUpdateTimeCol!)。StringBuilderのsetUpdateTimeBuilder =新しいStringBuilderの(); (!の場合のStringUtils。
あなたは良い記事を感じる場合は最後に、〜次の焦点を合わせるために転送されます