Mybatis之攔截器--擷取執行SQL實現多用戶端資料同步

來源:互聯網
上載者:User

標籤:autowire   XML   ada   delete   調用   執行sql   lse   修改   aaa   

最近的一個項目是將J2EE環境打包安裝在用戶端(使用 nwjs + NSIS 製作安裝包)運行, 所有的業務操作在用戶端完成, 資料存放區在用戶端資料庫中. 伺服器端資料庫匯總各用戶端的資料進行分析. 其中用戶端ORM使用Mybatis. 通過Mybatis攔截器擷取所有在執行的SQL語句, 定期同步至伺服器.

本文通過在用戶端攔截SQL的操作介紹Mybatis攔截器的使用方法.

1. 項目需求

客戶分店較多且比較分散, 部分店內網路不穩定, 客戶要求每個分店在無網路的情況下也能正常使用系統, 同時所有店面資料需要進行匯總分析. 綜合客戶的需求, 項目架構如下:

將WEB項目及其運行環境通過NSIS製作安裝包在各分店進行安裝, 每個分店是一個獨立的WEB服務, 這樣就保證店內在無網路(有區域網路,無法訪問互連網)的情況下也可以正常使用系統. 此時每個分店的資料庫儲存自己店內的運營資料, 各店之間的資料相互隔離.

但運營方無法分析所有店面的摘要資料(如商品整體銷售情況等), 因此需要將每個店面的資料定期同步至伺服器的資料庫中.

由於店內可能無網路(無網時不能受資料同步影響,系統需正常運行), 即時同步方案被排除.
為保證資料庫安全性, 伺服器資料庫不能對外暴露, 使用資料庫的同步機制方案被排除.
部分業務需要記錄資料變化日誌(資料從1到0又到1, 需記錄過程), 增量同步處理方案被排除.
最終採用了將用戶端所有更新(增,刪,改)的SQL按照執行順序儲存至資料庫中, 定期同步並在伺服器的資料庫按照順序執行SQL, 以此來保證伺服器資料庫的資料是各用戶端資料的匯總.

2. 解決方案

項目採用Mybatis, Mapper 中定義SQL時可以使用Mybatis的標籤及參數標識符, Mybatis會解析標籤替換參數產生最終的SQL在資料庫中執行, 而我們需要的是最終在資料庫中執行的SQL.

Mybatis中SQL的寫法:

<insert id="insert">
INSERT INTO atd681_mybatis_test ( dv ) VALUES ( #{dv} )
</insert>
複製代碼
需要同步至伺服器執行的SQL:

INSERT INTO atd681_mybatis_test ( dv ) VALUES ( ‘aaa‘ )
複製代碼
3. 攔截器

3.1 什麼是攔截器
想這樣一個情境, 你做飯的時候可能需要以下步驟:

買菜>> 洗菜 >> 切菜 >> 做菜 >> 上菜 >> 洗碗

開始洗菜前, 買菜操作已經完成, 可以知道買了什麼菜.
洗菜時還未開始做菜, 因此不知道菜是什麼口味的.
在上菜前(此時做菜已經完成), 可以知道菜的口味.
在上菜時不知道有沒有剩菜
在洗碗時我們可以知道有沒有剩菜.
上面的做飯流程是按照步驟一步一步的進行, 我們既可以在其中的某個步驟中擷取前幾步的成果, 也可以在某個步驟開始之前做些額外的事情, 比如: 切菜前對菜稱重等.

Mybatis提供了這樣一個組件: 他可以在某個步驟執行之前先執行自訂的操作. 這個組件叫做 攔截器 . 所謂攔截器, 顧名思義: 需要定義攔截哪個操作步驟及攔截後做什麼事情.

3.2 定義攔截器

攔截器需要實現 org.apache.ibatis.plugin.Interceptor 介面並指定攔截的方法.

// 攔截器
@Intercepts(@Signature(type = StatementHandler.class,
method = "update",
args = Statement.class)
)
public class SQLInterceptor implements Interceptor {

// 攔截方法後執行的邏輯@Overridepublic Object intercept(Invocation invocation) throws Throwable {    // 繼續執行Mybatis原有的邏輯    // proceed中通過反射執行被攔截的方法    return invocation.proceed();}// 返回當前攔截的對象(StatementHandler)的動態代理// 當攔截對象的方法被執行時, 動態代理中執行攔截器intercept方法.@Overridepublic Object plugin(Object target) {    return Plugin.wrap(target, this);}// 設定屬性@Overridepublic void setProperties(Properties properties) {}

}

複製代碼
@Intercepts 為Mybatis提供的攔截器註解, @Signature 指定攔截的方法.
如果一個攔截器攔截多個方法時, 在 @Intercepts 中配置多個 @Signature (數組)即可.
由於JAVA的方法可以重載, 確定唯一方法需要指定類(type), 方法(method), 參數(args).
攔截器可攔截 Executor , ParameterHandler , ResultSetHandler , StatementHandler 下的方法.
3.3 配置攔截器

在Spring設定檔中, 聲明攔截器並將其配置到 SqlSessionFactoryBean 中 plugins 屬性中

// Mybatis攔截器
sqlInterceptor(SQLInterceptor)

// Mybatis配置
sqlSessionFactory(SqlSessionFactoryBean) {
dataSource = ref("dataSource")
mapperLocations = "classpath:/com/atd681/mybatis/interceptor/_mapper.xml"

// 配置Mybatis攔截器plugins = [    sqlInterceptor] 

}
複製代碼
4. 擷取並儲存SQL

Mybatis處理SQL的大致流程如下:

載入SQL>> 解析SQL >> 替換SQL參數 >> 執行SQL >> 擷取返回結果

攔截[ 執行SQL ]操作, 此時Mybatis已經完成SQL解析及替換參數, 所得的SQL即為發送資料庫執行的SQL. 我們只需要擷取該SQL並儲存至資料庫即可.

// Mybatis攔截器:攔截所有的增刪改SQL,將SQL保持至資料庫
// 攔截StatementHandler.update方法
@Intercepts(@Signature(type = StatementHandler.class,
method = "update",
args = Statement.class)
)
public class SQLInterceptor implements Interceptor {

@Overridepublic Object intercept(Invocation invocation) throws Throwable {    // invocation.getArgs()可以擷取到被攔截方法的參數    // StatementHandler.update(Statement s)的參數為Statement    Statement s = (Statement) invocation.getArgs()[0];    // 資料來源為DRUID, Statement為DRUID的Statement    Statement stmt = ((DruidPooledPreparedStatement) s).getStatement();    // 配置druid串連時使用filters: stat配置    if (stmt instanceof PreparedStatementProxyImpl) {        stmt = ((PreparedStatementProxyImpl) stmt).getRawObject();    }    // 資料庫提供的Statement可擷取參數替換後的SQL(JDBC和DRUID擷取的是帶?的)    // 資料庫為MySQL,可以直接強制轉換為MySQL的PreparedStatement擷取SQL    // SQL在書寫時為了格式容器閱讀會有分行符號(多個空格)存在    // 為了儲存和查看方便去除SQL中的換行及多個空格    String sql = ((com.mysql.jdbc.PreparedStatement) stmt).asSql().replaceAll("\\s+", " ");    // 儲存SQL的操作必須和當前執行的SQL在同一事務中    // 使用當前SQL所在的資料庫連接執行儲存操作即可    // 目標sql成功時儲存sql的方法也同步成功    Connection conn = stmt.getConnection();    // 將SQL儲存至資料庫中    PreparedStatement ps = null;    try {        ps = conn.prepareStatement("INSERT INTO atd681_mybatis_sql (v_sql) VALUES (?)");        ps.setString(1, sql);        // 因為和Mybatis的操作在同一事務中        // 如果本次操作如果失敗, 所有操作都復原        ps.execute();    }    finally {        if (ps != null) {            ps.close();        }    }    // 繼續執行StatementHandler.update方法    return invocation.proceed();}

}

複製代碼
只有MySQL提供的PreparedStatement對象中可以擷取到最終的SQL.
儲存SQL操作需要和Mybatis的操作在同一事務中, 必須同時成功或失敗.

  1. 測試

在資料庫中建立兩張表:

atd681_mybatis_test
atd681_mybatis_sql
建立 DAO 和 Mapper , 建立增加, 刪除, 修改的方法及SQL

// 資料DAObr/>@Repository

// 添加資料void insert(String dv);// 更新資料void update(String dv);// 刪除資料void delete();

}
複製代碼
<mapper namespace="com.atd681.mybatis.interceptor.DataDAO">

<!-- 添加資料,內容為參數i的值 --><insert id="insert">    INSERT INTO atd681_mybatis_test ( dv ) VALUES ( #{dv} )</insert><!-- 更新資料,更新為參數u的值 --><update id="update">    UPDATE atd681_mybatis_test1 SET dv = #{dv}</update><!-- 刪除資料 --><delete id="delete">    DELETE FROM atd681_mybatis_test</delete>

</mapper>
複製代碼
控制器中添加方法, 依次調用刪除, 添加, 更新. 保證三個操作在同一個事務中.

@RestController
public class DataController {

// 注入DAO@Autowiredprivate DataDAO dao;// 分別執行刪除,插入,更新操作// 參數i: 插入時的字串// 參數u: 更新時的字串@GetMapping("/mybatis/test")@Transactionalpublic String excuteSql(String i, String u) {    // 刪除資料後將參數i的內容外掛程式資料庫,將資料更新成參數u的內容    // 該方法添加了事務,3次資料庫操作會在同一個事務中執行.    // Mybatis攔截器會捕獲三次資料庫SQL插入至資料庫中(詳見攔截器)    dao.delete();    dao.insert(i);    dao.update(u);    return "success";}

}
複製代碼
啟動服務, 訪問 http://localhost:3456/mybatis/test?i=insert&u=update

程式依次執行刪除、添加(內容為 "insert" )、更新(內容為 "update" )三個操作, 執行完成後資料庫中有一條記錄(內容為 "update" ). 由於配置了攔截器, 在每個操作執行前將SQL保持至資料庫中, 因此三條SQL也被儲存至資料庫中.

上述過程中除了3次業務操作, 還有3次保持SQL的操作, 因此資料庫總共會執行6條SQL.

執行DELETE操作
儲存1中DELETE操作的SQL
執行INSERT SQL
儲存3中INSERT操作的SQL
執行UPDATE SQL
儲存5中UPDATE操作的SQL
上述6次資料庫操作必須在同一事務中, 否則一旦出現業務操作成功但儲存SQL失敗的情況. 伺服器端同步處理的資料就會與用戶端本地不一致.

Mybatis之攔截器--擷取執行SQL實現多用戶端資料同步

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.