最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

通過反射注解批量插入數(shù)據(jù)到DB的實(shí)現(xiàn)方法

 更新時間:2019年03月15日 10:48:44   作者:Scub  
今天小編就為大家分享一篇關(guān)于通過反射注解批量插入數(shù)據(jù)到DB的實(shí)現(xiàn)方法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧

批量導(dǎo)入思路

最近遇到一個需要批量導(dǎo)入數(shù)據(jù)問題。后來考慮運(yùn)用反射做成一個工具類,思路是首先定義注解接口,在bean類上加注解,運(yùn)行時通過反射獲取傳入Bean的注解,自動生成需要插入DB的SQL,根據(jù)設(shè)置的參數(shù)值批量提交。不需要寫具體的SQL,也沒有DAO的實(shí)現(xiàn),這樣一來批量導(dǎo)入的實(shí)現(xiàn)就和具體的數(shù)據(jù)庫表徹底解耦。實(shí)際批量執(zhí)行的SQL如下:

insert into company_candidate(company_id,user_id,card_id,facebook_id,type,create_time,weight,score) VALUES (?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE type=?,weight=?,score=?

第一步,定義注解接口

注解接口Table中定義了數(shù)據(jù)庫名和表名。RetentionPolicy.RUNTIME表示該注解保存到運(yùn)行時,因?yàn)槲覀冃枰谶\(yùn)行時,去讀取注解參數(shù)來生成具體的SQL。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
  /**
   * 表名
   * @return
   */
  String tableName() default "";
  /**
   * 數(shù)據(jù)庫名稱
   * @return
   */
  String dbName();
}

注解接口TableField中定義了數(shù)據(jù)庫表名的各個具體字段名稱,以及該字段是否忽略(忽略的話就會以數(shù)據(jù)庫表定義默認(rèn)值填充,DB非null字段的注解不允許出現(xiàn)把ignore注解設(shè)置為true)。update注解是在主鍵在DB重復(fù)時,需要更新的字段。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TableField {
  /**
   * 對應(yīng)數(shù)據(jù)庫字段名稱
   * @return
   */
  String fieldName() default "";
  /**
   * 是否是主鍵
   * @return
   */
  boolean pk() default false;
  /**
   * 是否忽略該字段
   * @return
   */
  boolean ignore() default false;
  /**
   * 當(dāng)數(shù)據(jù)存在時,是否更新該字段
   * @return
   */
  boolean update() default false;
}

第二步,給Bean添加注解

給Bean添加注解(為了簡潔省略了import和set/get方法以及其他屬性),@TableField(fieldName = "company_id")表示companyId字段對應(yīng)DB表的字段名為"company_id",其中updateTime屬性的注解含有ignore=true,表示該屬性值會被忽略。另外serialVersionUID屬性由于沒有@TableField注解,在更新DB時也會被忽略。

代碼如下:

@Table(dbName = "company", tableName = "company_candidate")
public class CompanyCandidateModel implements Serializable{
 private static final long serialVersionUID = -1234554321773322135L;
 @TableField(fieldName = "company_id")
 private int companyId;
 @TableField(fieldName = "user_id")
 private int userId;
 //名片id
 @TableField(fieldName = "card_id")
 private int cardId;
 //facebookId
 @TableField(fieldName = "facebook_id")
 private long facebookId;
  @TableField(fieldName="type", update = true)
 private int type;
 @TableField(fieldName = "create_time")
 private Date createTime;
 @TableField(fieldName = "update_time", ignore=true)
 private Date updateTime;
 // 權(quán)重
  @TableField(fieldName="weight", update = true)
 private int weight;
 // 分值
  @TableField(fieldName="score", update = true)
 private double score;

第三步,讀取注解的反射工具類

讀取第二步Bean類的注解的反射工具類。利用反射getAnnotation(TableField.class)讀取注解信息,為批量SQL的拼接最好準(zhǔn)備。

getTableBeanFieldMap()方法里生成一個LinkedHashMap對象,是為了保證生成插入SQL的field順序,之后也能按同樣的順序給參數(shù)賦值,避免錯位。getSqlParamFields()方法也類似,是為了給PreparedStatement設(shè)置參數(shù)用。

代碼如下:

public class ReflectUtil {
  /**
   * <Class,<表定義Field名,Bean定義Field>>的map緩存
   */
  private static final Map<Class<?>, Map<string field="">> classTableBeanFieldMap = new HashMap<Class<?>, Map<string field="">>();
  // 用來按順序填充SQL參數(shù),其中存儲的Field和classTableBeanFieldMap保存同樣的順序,但數(shù)量多出ON DUPLICATE KEY UPDATE部分Field
  private static final Map<Class<?>, List<field>> sqlParamFieldsMap = new HashMap<Class<?>, List<field>>(); 
  private ReflectUtil(){};
  /**
   * 獲取該類上所有@TableField注解,且沒有忽略的字段的Map。
   * <br />返回一個有序的LinkedHashMap類型
   * <br />其中key為DB表中的字段,value為Bean類里的屬性Field對象
   * @param clazz
   * @return
   */
  public static Map<string field=""> getTableBeanFieldMap(Class<?> clazz) {
   // 從緩存獲取
   Map<string field=""> fieldsMap = classTableBeanFieldMap.get(clazz);
   if (fieldsMap == null) {
   fieldsMap = new LinkedHashMap<string field="">();
      for (Field field : clazz.getDeclaredFields()) {// 獲得所有聲明屬性數(shù)組的一個拷貝
       TableField annotation = field.getAnnotation(TableField.class);
        if (annotation != null && !annotation.ignore() && !"".equals(annotation.fieldName())) {
          field.setAccessible(true);// 方便后續(xù)獲取私有域的值
         fieldsMap.put(annotation.fieldName(), field);
        }
  }
      // 放入緩存
      classTableBeanFieldMap.put(clazz, fieldsMap);
   }
   return fieldsMap;
  }
  /**
   * 獲取該類上所有@TableField注解,且沒有忽略的字段的Map。ON DUPLICATE KEY UPDATE后需要更新的字段追加在list最后,為了填充參數(shù)值準(zhǔn)備
   * <br />返回一個有序的ArrayList類型
   * <br />其中key為DB表中的字段,value為Bean類里的屬性Field對象
   * @param clazz
   * @return
   */
  public static List<field> getSqlParamFields(Class<?> clazz) {
   // 從緩存獲取
   List<field> sqlParamFields = sqlParamFieldsMap.get(clazz);
   if (sqlParamFields == null) {
   // 獲取所有參數(shù)字段
     Map<string field=""> fieldsMap = getTableBeanFieldMap(clazz);
   sqlParamFields = new ArrayList<field>(fieldsMap.size() * 2);
     // SQL后段ON DUPLICATE KEY UPDATE需要更新的字段
     List<field> updateParamFields = new ArrayList<field>();
   Iterator<Entry<string field="">> iter = fieldsMap.entrySet().iterator();
   while (iter.hasNext()) {
    Entry<string field=""> entry = (Entry<string field="">) iter.next();
    Field field = entry.getValue();
    // insert語句對應(yīng)sql參數(shù)字段
    sqlParamFields.add(field);
        // ON DUPLICATE KEY UPDATE后面語句對應(yīng)sql參數(shù)字段
        TableField annotation = field.getAnnotation(TableField.class);
    if (annotation != null && !annotation.ignore() && annotation.update()) {
    updateParamFields.add(field);
    }
   }
   sqlParamFields.addAll(updateParamFields);
      // 放入緩存
   sqlParamFieldsMap.put(clazz, sqlParamFields);
   }
   return sqlParamFields;
  }
  /**
   * 獲取表名,對象中使用@Table的tableName來標(biāo)記對應(yīng)數(shù)據(jù)庫的表名,若未標(biāo)記則自動將類名轉(zhuǎn)成小寫
   * 
   * @param clazz
   * @return
   */
  public static String getTableName(Class<?> clazz) {
    Table table = clazz.getAnnotation(Table.class);
    if (table != null && table.tableName() != null && !"".equals(table.tableName())) {
      return table.tableName();
    }
    // 當(dāng)未配置@Table的tableName,自動將類名轉(zhuǎn)成小寫
    return clazz.getSimpleName().toLowerCase();
  }
  /**
   * 獲取數(shù)據(jù)庫名,對象中使用@Table的dbName來標(biāo)記對應(yīng)數(shù)據(jù)庫名
   * @param clazz
   * @return
   */
  public static String getDBName(Class<?> clazz) {
    Table table = clazz.getAnnotation(Table.class);
    if (table != null && table.dbName() != null) {
      // 注解@Table的dbName
      return table.dbName();
    }
    return "";
  }

第四步,生成SQL語句

根據(jù)上一步的方法,生成真正執(zhí)行的SQL語句。

insert into company_candidate(company_id,user_id,card_id,facebook_id,type,create_time,weight,score) VALUES (?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE type=?,weight=?,score=?

代碼如下:

public class SQLUtil {
  private static final char COMMA = ',';
  private static final char BRACKETS_BEGIN = '(';
  private static final char BRACKETS_END = ')';
  private static final char QUESTION_MARK = '?';
  private static final char EQUAL_SIGN = '=';
  private static final String INSERT_BEGIN = "INSERT INTO ";
  private static final String INSERT_VALURS = " VALUES ";
  private static final String DUPLICATE_UPDATE = " ON DUPLICATE KEY UPDATE ";
  // 數(shù)據(jù)庫表名和對應(yīng)insertupdateSQL的緩存
  private static final Map<string string=""> tableInsertSqlMap = new HashMap<string string="">();
  /**
   * 獲取插入的sql語句,對象中使用@TableField的fieldName來標(biāo)記對應(yīng)數(shù)據(jù)庫的列名,若未標(biāo)記則忽略
   * 必須標(biāo)記@TableField(fieldName = "company_id")注解
   * @param tableName
   * @param fieldsMap
   * @return
   * @throws Exception
   */
  public static String getInsertSql(String tableName, Map<string field=""> fieldsMap) throws Exception {
   String sql = tableInsertSqlMap.get(tableName);
   if (sql == null) {
   StringBuilder sbSql = new StringBuilder(300).append(INSERT_BEGIN);
   StringBuilder sbValue = new StringBuilder(INSERT_VALURS);
   StringBuilder sbUpdate = new StringBuilder(100).append(DUPLICATE_UPDATE);
   sbSql.append(tableName);
   sbSql.append(BRACKETS_BEGIN);
   sbValue.append(BRACKETS_BEGIN);
   Iterator<Entry<string field="">> iter = fieldsMap.entrySet().iterator();
   while (iter.hasNext()) {
    Entry<string field=""> entry = (Entry<string field="">) iter.next();
    String tableFieldName = entry.getKey();
    Field field = entry.getValue();
    sbSql.append(tableFieldName);
    sbSql.append(COMMA);
    sbValue.append(QUESTION_MARK);
    sbValue.append(COMMA);
    TableField tableField = field.getAnnotation(TableField.class);
    if (tableField != null && tableField.update()) {
    sbUpdate.append(tableFieldName);
    sbUpdate.append(EQUAL_SIGN);
    sbUpdate.append(QUESTION_MARK);
    sbUpdate.append(COMMA);
    }
   }
   // 去掉最后的逗號
   sbSql.deleteCharAt(sbSql.length() - 1);
   sbValue.deleteCharAt(sbValue.length() - 1);
   sbSql.append(BRACKETS_END);
   sbValue.append(BRACKETS_END);
   sbSql.append(sbValue);
   if (!sbUpdate.toString().equals(DUPLICATE_UPDATE)) {
    sbUpdate.deleteCharAt(sbUpdate.length() - 1);
    sbSql.append(sbUpdate);
   }
   sql = sbSql.toString();
   tableInsertSqlMap.put(tableName, sql);
   }
    return sql;
  }

第五步,批量SQL插入實(shí)現(xiàn)

從連接池獲取Connection,SQLUtil.getInsertSql()獲取執(zhí)行的SQL語句,根據(jù)sqlParamFields來為PreparedStatement填充參數(shù)值。當(dāng)循環(huán)的值集合到達(dá)batchNum時就提交一次。

代碼如下:

  /**
   * 批量插入,如果主鍵一致則更新。結(jié)果返回更新記錄條數(shù)<br />
   * @param dataList
   *      要插入的對象List
   * @param batchNum
   *      每次批量插入條數(shù)
   * @return 更新記錄條數(shù)
   */
  public int batchInsertSQL(List<? extends Object> dataList, int batchNum) throws Exception {
   if (dataList == null || dataList.isEmpty()) {
   return 0;
   }
    Class<?> clazz = dataList.get(0).getClass();
    String tableName = ReflectUtil.getTableName(clazz);
    String dbName = ReflectUtil.getDBName(clazz);
    Connection connnection = null;
    PreparedStatement preparedStatement = null;
    // 獲取所有需要更新到DB的屬性域
    Map<string field=""> fieldsMap = ReflectUtil.getTableBeanFieldMap(dataList.get(0).getClass());
    // 根據(jù)需要插入更新的字段生成SQL語句
    String sql = SQLUtil.getInsertSql(tableName, fieldsMap);
    log.debug("prepare to start batch operation , sql = " + sql + " , dbName = " + dbName);
    // 獲取和SQL語句同樣順序的填充參數(shù)Fields
    List<field> sqlParamFields = ReflectUtil.getSqlParamFields(dataList.get(0).getClass());
    // 最終更新結(jié)果條數(shù)
    int result = 0;
    int parameterIndex = 1;// SQL填充參數(shù)開始位置為1
    // 執(zhí)行錯誤的對象
    List<object> errorsRecords = new ArrayList</object><object>(batchNum);//指定數(shù)組大小
    // 計數(shù)器,batchNum提交后內(nèi)循環(huán)累計次數(shù)
    int innerCount = 0;
    try {
      connnection = this.getConnection(dbName);
      // 設(shè)置非自動提交
      connnection.setAutoCommit(false);
      preparedStatement = connnection.prepareStatement(sql);
      // 當(dāng)前操作的對象
      Object object = null;
      int totalRecordCount = dataList.size();
      for (int current = 0; current < totalRecordCount; current++) {
        innerCount++;
        object = dataList.get(current);
       parameterIndex = 1;// 開始參數(shù)位置為1
       for(Field field : sqlParamFields) {
       // 放入insert語句對應(yīng)sql參數(shù)
          preparedStatement.setObject(parameterIndex++, field.get(object));
       }
       errorsRecords.add(object);
        preparedStatement.addBatch();
        // 達(dá)到批量次數(shù)就提交一次
        if (innerCount >= batchNum || current >= totalRecordCount - 1) {
          // 執(zhí)行batch操作
          preparedStatement.executeBatch();
          preparedStatement.clearBatch();
          // 提交
          connnection.commit();
          // 記錄提交成功條數(shù)
          result += innerCount;
          innerCount = 0;
          errorsRecords.clear();
        }
        // 盡早讓GC回收
        dataList.set(current, null);
      }
      return result;
    } catch (Exception e) {
      // 失敗后處理方法
      CallBackImpl.getInstance().exectuer(sql, errorsRecords, e);
      BatchDBException be = new BatchDBException("batch run error , dbName = " + dbName + " sql = " + sql, e);
      be.initCause(e);
      throw be;
    } finally {
      // 關(guān)閉
      if (preparedStatement != null) {
       preparedStatement.clearBatch();
        preparedStatement.close();
      }
      if (connnection != null)
        connnection.close();
    }
  }

最后,批量工具類使用例子

在mysql下的開發(fā)環(huán)境下測試,5萬條數(shù)據(jù)大概13秒。

List<companycandidatemodel> updateDataList = new ArrayList<companycandidatemodel>(50000);
// ...為updateDataList填充數(shù)據(jù)
int result = batchJdbcTemplate.batchInsertSQL(updateDataList, 50);

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請查看下面相關(guān)鏈接

相關(guān)文章

  • 詳解Spring如何整合Mybatis

    詳解Spring如何整合Mybatis

    今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識,文章圍繞著Spring如何整合Mybatis展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • ServletWebServerApplicationContext創(chuàng)建Web容器Tomcat示例

    ServletWebServerApplicationContext創(chuàng)建Web容器Tomcat示例

    這篇文章主要為大家介紹了ServletWebServerApplicationContext創(chuàng)建Web容器Tomcat示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • java中switch條件語句的三種語法、用法及支持的參數(shù)類型

    java中switch條件語句的三種語法、用法及支持的參數(shù)類型

    Java中的switch語句是一種多分支選擇結(jié)構(gòu),可以一個變量的值改變程序的控制流,這篇文章主要給大家介紹了關(guān)于java中switch條件語句的三種語法、用法及支持的參數(shù)類型的相關(guān)資料,需要的朋友可以參考下
    2024-06-06
  • maven安裝、使用、配置本地倉庫、idea配置maven以及解決plugins報錯問題

    maven安裝、使用、配置本地倉庫、idea配置maven以及解決plugins報錯問題

    本地倉庫是遠(yuǎn)程倉庫的一個緩沖和子集,當(dāng)你構(gòu)建Maven項(xiàng)目時首先會從本地倉庫查找資源,如果沒有那么Maven會從遠(yuǎn)程倉庫下載到你本地倉庫,這篇文章主要給大家介紹了關(guān)于maven安裝、使用、配置本地倉庫、idea配置maven以及解決plugins報錯問題的相關(guān)資料,需要的朋友可以參考下
    2024-01-01
  • MyBatis是如何實(shí)現(xiàn)日志模塊的詳解

    MyBatis是如何實(shí)現(xiàn)日志模塊的詳解

    這篇文章主要給大家介紹了關(guān)于MyBatis是如何實(shí)現(xiàn)日志模塊的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者使用MyBatis具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • Java變量和對象的作用域

    Java變量和對象的作用域

    本文主要介紹了Java變量和對象的作用域的相關(guān)知識。具有很好的參考價值,下面跟著小編一起來看下吧
    2017-03-03
  • Java中Request請求轉(zhuǎn)發(fā)詳解

    Java中Request請求轉(zhuǎn)發(fā)詳解

    這篇文章主要介紹了Java中Request請求轉(zhuǎn)發(fā)詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • Spring?Boot?整合?Fisco?Bcos部署、調(diào)用區(qū)塊鏈合約的案例

    Spring?Boot?整合?Fisco?Bcos部署、調(diào)用區(qū)塊鏈合約的案例

    本篇文章介紹?Spring?Boot?整合?Fisco?Bcos?的相關(guān)技術(shù),最最重要的技術(shù)點(diǎn),部署、調(diào)用區(qū)塊鏈合約的工程案例,本文通過流程分析給大家介紹的非常詳細(xì),需要的朋友參考下吧
    2022-01-01
  • 深入理解Java注解類型(@Annotation)

    深入理解Java注解類型(@Annotation)

    這篇文章主要介紹了深入理解Java注解類型(@Annotation),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • java合成模式之神奇的樹結(jié)構(gòu)

    java合成模式之神奇的樹結(jié)構(gòu)

    這篇文章主要介紹了java合成模式,文中運(yùn)用大量的代碼進(jìn)行詳細(xì)講解,希望大家看完本文后能學(xué)習(xí)到相關(guān)的知識,需要的朋友可以參考一下
    2021-08-08

最新評論

翁源县| 咸宁市| 茶陵县| 娱乐| 叙永县| 嘉峪关市| 鄱阳县| 郁南县| 盘锦市| 岳阳县| 江津市| 左贡县| 西乡县| 博乐市| 大理市| 米易县| 万年县| 固安县| 岱山县| 育儿| 大姚县| 台山市| 宝坻区| 克拉玛依市| 日照市| 巨鹿县| 自治县| 沭阳县| 广河县| 阿城市| 乌兰县| 肥城市| 巴楚县| 临汾市| 乾安县| 南江县| 海安县| 福鼎市| 巴中市| 龙山县| 阳谷县|