1. 程式人生 > >利用spring改造傳統jdbc使其支援命名引數形式的SQL

利用spring改造傳統jdbc使其支援命名引數形式的SQL

傳統JDBC SQL呼叫形式一般為

   update customer set customerName=?, email=? where id=?
   insert into customer (customerName, email ) values(?, ?)

這種由於?與引數關係的不直觀,帶來的修改bug以及維護麻煩折磨著後續程式猿。那麼,有沒有可能通過對程式碼的改造,實現類似於命名引數SQL支援呢?

     update customer set customerName=:customerName, email=:email  where id=:id
     insert into customer (customerName, email ) values(:customerName, :email)

答案是肯定的, 本文通過對 NamedParameterJdbcTemplate 實現機制原始碼的解讀,借鑑了NamedParameterUtils 原始碼的方法對上面需求的實現做了可行性驗證。

關鍵程式碼


import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.TreeMap;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterUtils;

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

/**
* 
* 將namedParamSQL轉換為帶引數列表的傳統SQL執行
* 
* @author 00fly
* @version [版本號, 2018年11月10日]
* @see [相關類/方法]
* @since [產品/模組版本]
*/
public class ConvertSQLTest
{
   private static final Logger logger = LoggerFactory.getLogger(ConvertSQLTest.class);
   
   private static QueryRunner queryRunner;
   
   static
   {
       MysqlDataSource dataSource = new MysqlDataSource();
       ResourceBundle config = ResourceBundle.getBundle("jdbc");
       dataSource.setUrl(config.getString("jdbc.url"));
       dataSource.setUser(config.getString("jdbc.username"));
       dataSource.setPassword(config.getString("jdbc.password"));
       queryRunner = new QueryRunner(dataSource);
       logger.info("QueryRunner = {}", queryRunner);
   }
   
   @Test
   public void test1()
       throws SQLException
   {
       // 將namedParamSQL轉換為帶引數列表的傳統SQL
       String namedParamSQL = "select id, name from student where id<>:id and (id=:id1 or id=:id2)";
       Map<String, Object> paramMap = new TreeMap<>();
       paramMap.put("id", 1);
       paramMap.put("id1", 2);
       paramMap.put("id2", 3);
       paramMap.put("name", "001");
       paramMap.put("name1", "002");
       paramMap.put("name2", "003");
       String realSql = buildRealSQL(namedParamSQL, paramMap);
       Object[] paramArr = buildValueArray(namedParamSQL, paramMap);
       List<Map<String, Object>> list = queryRunner.query(realSql, new MapListHandler(), paramArr);
       logger.info("★★★★ before: namedParamSQL  = {}", namedParamSQL);
       logger.info("★★★★ before: paramMap = {}", paramMap);
       logger.info("★★★★ execute: realSql  = {}", realSql);
       logger.info("★★★★ execute: paramArr = {}", Arrays.asList(paramArr));
       logger.info("★★★★ execute: result   = {}", list);
   }
   
   @Test
   public void test2()
       throws SQLException
   {
       // IN條件
       String namedParamSQL = "select id, name from student where id in (:ids)";
       Object[] ids = new Integer[] {1, 2, 3, 4, 5};
       Map<String, Object> paramMap = new TreeMap<>();
       paramMap.put("ids", Arrays.asList(ids));
       String realSql = buildRealSQL(namedParamSQL, paramMap);
       Object[] paramArr = buildValueArray(namedParamSQL, paramMap);
       List<Map<String, Object>> list = queryRunner.query(realSql, new MapListHandler(), paramArr);
       logger.info("★★★★ before: namedParamSQL  = {}", namedParamSQL);
       logger.info("★★★★ before: paramMap = {}", paramMap);
       logger.info("★★★★ execute: realSql  = {}", realSql);
       logger.info("★★★★ execute: paramArr = {}", Arrays.asList(paramArr));
       logger.info("★★★★ execute: result   = {}", list);
       
   }
   
   @Test
   public void test3()
       throws SQLException
   {
       // IN條件&&其他條件
       String namedParamSQL = "select id, name from student where id=:id or id in (:ids)";
       Object[] ids = new Integer[] {1, 2, 3, 4, 5};
       Map<String, Object> paramMap = new TreeMap<>();
       paramMap.put("id", 1);
       paramMap.put("ids", Arrays.asList(ids));
       String realSql = buildRealSQL(namedParamSQL, paramMap);
       Object[] paramArr = buildValueArray(namedParamSQL, paramMap);
       List<Map<String, Object>> list = queryRunner.query(realSql, new MapListHandler(), paramArr);
       logger.info("★★★★ before: namedParamSQL  = {}", namedParamSQL);
       logger.info("★★★★ before: paramMap = {}", paramMap);
       logger.info("★★★★ execute: realSql  = {}", realSql);
       logger.info("★★★★ execute: paramArr = {}", Arrays.asList(paramArr));
       logger.info("★★★★ execute: result   = {}", list);
   }
   
   /**
    * 將namedParamSQL轉換為帶?的傳統SQL
    * 
    * @param namedParamSQL 命名引數SQL
    * @param paramMap Map引數
    * @return
    * @see [類、類#方法、類#成員]
    */
   private String buildRealSQL(String namedParamSQL, Map<String, Object> paramMap)
   {
       return NamedParameterUtils.substituteNamedParameters(NamedParameterUtils.parseSqlStatement(namedParamSQL), new MapSqlParameterSource(paramMap));
   }
   
   /**
    * 濾除無效引數列表後,以Object[]返回
    * 
    * @param namedParamSQL 命名引數SQL
    * @param paramMap Map引數
    * @return
    * @see [類、類#方法、類#成員]
    */
   private Object[] buildValueArray(String namedParamSQL, Map<String, Object> paramMap)
   {
       Object[] params = NamedParameterUtils.buildValueArray(namedParamSQL, paramMap);
       List<Object> paramList = new ArrayList<>();
       for (Object obj : params)
       {
           if (List.class.isInstance(obj))
           {
               paramList.addAll((List<?>)obj);
           }
           else
           {
               paramList.add(obj);
           }
       }
       return paramList.toArray();
   }
}


執行結果

2018-11-10 13:33:24 |INFO |main|★★★★ before: namedParamSQL  = select id, name from student where id<>:id and (id=:id1 or id=:id2)|
2018-11-10 13:33:24 |INFO |main|★★★★ before: paramMap = {id=1, id1=2, id2=3, name=001, name1=002, name2=003}|
2018-11-10 13:33:24 |INFO |main|★★★★ execute: realSql  = select id, name from student where id<>? and (id=? or id=?)|
2018-11-10 13:33:24 |INFO |main|★★★★ execute: paramArr = [1, 2, 3]|
2018-11-10 13:33:24 |INFO |main|★★★★ execute: result   = [{id=2, name=Phil}, {id=3, name=Jenny}]|

2018-11-10 13:33:24 |INFO |main|★★★★ before: namedParamSQL  = select id, name from student where id in (:ids)|
2018-11-10 13:33:24 |INFO |main|★★★★ before: paramMap = {ids=[1, 2, 3, 4, 5]}|
2018-11-10 13:33:24 |INFO |main|★★★★ execute: realSql  = select id, name from student where id in (?, ?, ?, ?, ?)|
2018-11-10 13:33:24 |INFO |main|★★★★ execute: paramArr = [1, 2, 3, 4, 5]|
2018-11-10 13:33:24 |INFO |main|★★★★ execute: result   = [{id=1, name=Jack}, {id=2, name=Phil}, {id=3, name=Jenny}]|

2018-11-10 13:33:24 |INFO |main|★★★★ before: namedParamSQL  = select id, name from student where id=:id or id in (:ids)|
2018-11-10 13:33:24 |INFO |main|★★★★ before: paramMap = {id=1, ids=[1, 2, 3, 4, 5]}|
2018-11-10 13:33:24 |INFO |main|★★★★ execute: realSql  = select id, name from student where id=? or id in (?, ?, ?, ?, ?)|
2018-11-10 13:33:24 |INFO |main|★★★★ execute: paramArr = [1, 1, 2, 3, 4, 5]|
2018-11-10 13:33:24 |INFO |main|★★★★ execute: result   = [{id=1, name=Jack}, {id=2, name=Phil}, {id=3, name=Jenny}]|

完整的程式碼請參考: 完整的專案程式碼請參考:
https://gitee.com/00fly/java-code-frame/tree/master/jdbc