業務上,手機App(離線狀態下的數據),在在線的時候需要往服務端上傳,由於App上的SQLite數據庫里的需要 同步數據的表 跟服務端的數據庫表結構一致,所以為了同步數據的方便性,我們決定App在進行insert update delete 操作時,將SQL語句(已拼裝好參數的sql)
記錄到Sqlite內的一張記錄表內,結構如下
package com.darkBlue.web.mobile; import java.util.Date; /** * Created by root on 2018/7/5 0005. */ public class OperateLog { private Long id; /** * 操作類型 */ private String type; /** * 拼裝好參數的SQL語句 */ private String statement; /** * 拼裝好參數的SQL語句 */ private Date operateDate; /** * 操作人 */ private String operateUser; /** * 操作的表名 */ private String tableName; } get set方法省略
這樣的話,APP端需要同步數據,只需要查詢這張表的數據,將其傳到服務端即可,其中兩個字段是必須的 即
type 表示,SQL的類型是update 還是delete 還是insert
statement 表示SQL語句
具體如何上傳就不細說了,項目使用springMVC
服務端代碼:
首先,服務端想使用jdbcTemplate,需要在applicationContext.xml中進行配置
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource" /> </bean>
同時,我們還需要進行事物配置,因為我們需要保證,所有的操作要么全部成功,要么全部失敗
<!-- ===============事務控制的配置 ================--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--控制住數據源 --> <property name="dataSource" ref="dataSource"></property> </bean> <!--開啟基於注解的事務,使用xml配置形式的事務(必要主要的都是使用配置式) --> <tx:annotation-driven transaction-manager="transactionManager"/> <aop:aspectj-autoproxy proxy-target-class="true"/> <aop:config> <!-- 切入點表達式 --> <aop:pointcut expression="execution(* com.demo.service..*(..))" id="txPoint"/> <!-- 配置事務增強 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"/> </aop:config> <!--配置事務增強,事務如何切入 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- 所有方法都是事務方法 --> <tx:method name="*"/> <!--以get開始的所有方法 --> <tx:method name="get*" read-only="true"/> </tx:attributes> </tx:advice>
看看上傳代碼:
package com.demo.web.mobile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Controller; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.annotation.Resource; import java.sql.Connection; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; /** * Created by root on 2018/7/5 0005. */ @Controller @RequestMapping("mobile/uploadOfflineData") public class UploadOfflineDataController { private static final Logger logger = LoggerFactory.getLogger(UploadOfflineDataController.class); @Resource private JdbcTemplate jdbcTemplate; @Resource private PlatformTransactionManager transactionManager; private static Connection con = null; @RequestMapping("/uploadDB") @ResponseBody public BaseResponse uploadDB(@RequestBody List<OperateLog> operateLogs) { // 該bean是我自定義的返回Bean BaseResponse ret = new BaseResponse(); List<String> insertSqls = operateLogs.stream().filter(t -> "insert".equals(t.getType()) ).map(OperateLog::getStatement).collect(Collectors.toList()); List<String> updateSqls = operateLogs.stream().filter(t -> "update".equals(t.getType()) ).map(OperateLog::getStatement).collect(Collectors.toList()); List<String> deleteSqls = operateLogs.stream().filter(t -> "delete".equals(t.getType()) ).map(OperateLog::getStatement).collect(Collectors.toList()); // 定義事務隔離級別,傳播行為 DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); // 事務狀態類,通過PlatformTransactionManager的getTransaction方法根據事務定義獲取;獲取事務狀態后,Spring根據傳播行為來決定如何開啟事務 TransactionStatus transactionStatus = transactionManager.getTransaction(def); try { con = jdbcTemplate.getDataSource().getConnection(); // 這里設置是沒有效果的[是不能實現事物操作的] // con.setAutoCommit(false); // 執行順序不能錯 execSQL(insertSqls); execSQL(updateSqls); execSQL(deleteSqls); // 提交status中綁定的事務 transactionManager.commit(transactionStatus); ret.setStatus(true); // 這里設置是沒有效果的[是不能實現事物操作的] // con.commit(); } catch (Exception e) { try { // 這里設置是沒有效果的[是不能實現事物操作的] // con.rollback(); // 提交status中綁定的事務 transactionManager.rollback(transactionStatus); } catch (Exception e1) { logger.error("上傳數據,回滾報錯,", e1); } logger.error("上傳數據,SQL報錯,", e); if (e instanceof UploadOfflineDataException) { ret.setData(e); } else { ret.setMsg("未知錯誤"); } ret.setStatus(false); } finally { try { con.close(); } catch (Exception e) { logger.error("上傳數據,關閉鏈接報錯,", e); } } return ret; } public void execSQL(List<String> sqls) throws UploadOfflineDataException { for (String sql : sqls) { try { jdbcTemplate.update(sql); } catch (Exception e) { // 這是我自定義的異常類 UploadOfflineDataException exception = new UploadOfflineDataException(); String tableName = matchSql(sql); String info = matchInfo(sql); if (null != tableName) { if (tableName.equals("t_jzw_fangjian")) { exception.setTableName("房間"); exception.setInfo(info); } else { exception.setTableName("人員"); exception.setInfo(info); } } else { exception.setTableName("未知錯誤"); } throw exception; } } } /** * 表名獲取 * * @param sql lowcase * @return */ public static String matchSql(String sql) { Matcher matcher = null; //SELECT 列名稱 FROM 表名稱 //SELECT * FROM 表名稱 if (sql.startsWith("select")) { matcher = Pattern.compile("select\\s.+from\\s(.+)where\\s(.*)").matcher(sql); if (matcher.find()) { return matcher.group(1); } } //INSERT INTO 表名稱 VALUES (值1, 值2,....) //INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....) if (sql.startsWith("insert")) { matcher = Pattern.compile("insert\\sinto\\s(.+)\\(.*\\)\\s.*").matcher(sql); if (matcher.find()) { return matcher.group(1); } } //UPDATE 表名稱 SET 列名稱 = 新值 WHERE 列名稱 = 某值 if (sql.startsWith("update")) { matcher = Pattern.compile("update\\s(.+)set\\s.*").matcher(sql); if (matcher.find()) { return matcher.group(1); } } //DELETE FROM 表名稱 WHERE 列名稱 = 值 if (sql.startsWith("delete")) { matcher = Pattern.compile("delete\\sfrom\\s(.+)where\\s(.*)").matcher(sql); if (matcher.find()) { return matcher.group(1); } } return null; } /** * @describe: 錯誤SQL獲取錯誤的參數 * @params: * @Author: Kanyun * @Date: 2018/7/20 11:31 */ public static String matchInfo(String sql) { String[] infos = sql.split(" "); String regex = "^[\\u4e00-\\u9fa5]*$"; Pattern p = Pattern.compile(regex); List info = new ArrayList(); for (String s : infos) { Matcher m = p.matcher(s); if (m.find()) { info.add(s); } } return info.toString(); } }
我自定義的異常類 [按業務需求定義字段]
public class UploadOfflineDataException extends Exception { private String idCard; private String name; private String addr; private String tableName; private String info; }
我自定義的返回bean[按業務需求定義字段]
public class BaseResponse implements Serializable { private boolean status; private String msg; private Object data; }
基礎類就已經寫完了,主要關注的點是jdbcTemplate的事物控制
雖然Connection 可以設置setAutoCommit(false),但是並不能實現事物控制,
原因是因為:
因為jdbcTemplate.getDataSource().getConnection()獲取的connection與每次jdbcTemplate.update用到的connection都是從連接池中獲取的,不能保證是一個connection
同時需要注意的是,對於Mysql來說,存儲引擎對事物的支持也是不一樣的,InnoDB支持事物,MyISM不支持事物
所以我采用了spring的編程式事物[Spring事物分兩種,一種是編程式事物,一種是聲明式事物]
我這里采用編程式事物,主要是因為我要控制代碼塊,而編程式事物的優點就是事物的管理是代碼塊級的,而聲明式的是方法級的(雖然可以通過重構方法達到和編程式事物一樣的效果)
更多詳見:https://blog.csdn.net/zhj870975587/article/details/75152604
