JDBCTemplate使用


業務上,手機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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM