亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > Java > 正文

通過反射注解批量插入數據到DB的實現方法

2024-07-14 08:43:31
字體:
來源:轉載
供稿:網友

批量導入思路

最近遇到一個需要批量導入數據問題。后來考慮運用反射做成一個工具類,思路是首先定義注解接口,在bean類上加注解,運行時通過反射獲取傳入Bean的注解,自動生成需要插入DB的SQL,根據設置的參數值批量提交。不需要寫具體的SQL,也沒有DAO的實現,這樣一來批量導入的實現就和具體的數據庫表徹底解耦。實際批量執行的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中定義了數據庫名和表名。RetentionPolicy.RUNTIME表示該注解保存到運行時,因為我們需要在運行時,去讀取注解參數來生成具體的SQL。

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

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

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

第二步,給Bean添加注解

給Bean添加注解(為了簡潔省略了import和set/get方法以及其他屬性),@TableField(fieldName = "company_id")表示companyId字段對應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; // 權重  @TableField(fieldName="weight", update = true) private int weight; // 分值  @TableField(fieldName="score", update = true) private double score;

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

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

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

代碼如下:

public class ReflectUtil {  /**   * <Class,<表定義Field名,Bean定義Field>>的map緩存   */  private static final Map<Class<?>, Map<string field="">> classTableBeanFieldMap = new HashMap<Class<?>, Map<string field="">>();  // 用來按順序填充SQL參數,其中存儲的Field和classTableBeanFieldMap保存同樣的順序,但數量多出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()) {// 獲得所有聲明屬性數組的一個拷貝       TableField annotation = field.getAnnotation(TableField.class);        if (annotation != null && !annotation.ignore() && !"".equals(annotation.fieldName())) {          field.setAccessible(true);// 方便后續獲取私有域的值         fieldsMap.put(annotation.fieldName(), field);        }  }      // 放入緩存      classTableBeanFieldMap.put(clazz, fieldsMap);   }   return fieldsMap;  }  /**   * 獲取該類上所有@TableField注解,且沒有忽略的字段的Map。ON DUPLICATE KEY UPDATE后需要更新的字段追加在list最后,為了填充參數值準備   * <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) {   // 獲取所有參數字段     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語句對應sql參數字段    sqlParamFields.add(field);        // ON DUPLICATE KEY UPDATE后面語句對應sql參數字段        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來標記對應數據庫的表名,若未標記則自動將類名轉成小寫   *    * @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();    }    // 當未配置@Table的tableName,自動將類名轉成小寫    return clazz.getSimpleName().toLowerCase();  }  /**   * 獲取數據庫名,對象中使用@Table的dbName來標記對應數據庫名   * @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語句

根據上一步的方法,生成真正執行的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 ";  // 數據庫表名和對應insertupdateSQL的緩存  private static final Map<string string=""> tableInsertSqlMap = new HashMap<string string="">();  /**   * 獲取插入的sql語句,對象中使用@TableField的fieldName來標記對應數據庫的列名,若未標記則忽略   * 必須標記@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插入實現

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

代碼如下:

  /**   * 批量插入,如果主鍵一致則更新。結果返回更新記錄條數<br />   * @param dataList   *      要插入的對象List   * @param batchNum   *      每次批量插入條數   * @return 更新記錄條數   */  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());    // 根據需要插入更新的字段生成SQL語句    String sql = SQLUtil.getInsertSql(tableName, fieldsMap);    log.debug("prepare to start batch operation , sql = " + sql + " , dbName = " + dbName);    // 獲取和SQL語句同樣順序的填充參數Fields    List<field> sqlParamFields = ReflectUtil.getSqlParamFields(dataList.get(0).getClass());    // 最終更新結果條數    int result = 0;    int parameterIndex = 1;// SQL填充參數開始位置為1    // 執行錯誤的對象    List<object> errorsRecords = new ArrayList</object><object>(batchNum);//指定數組大小    // 計數器,batchNum提交后內循環累計次數    int innerCount = 0;    try {      connnection = this.getConnection(dbName);      // 設置非自動提交      connnection.setAutoCommit(false);      preparedStatement = connnection.prepareStatement(sql);      // 當前操作的對象      Object object = null;      int totalRecordCount = dataList.size();      for (int current = 0; current < totalRecordCount; current++) {        innerCount++;        object = dataList.get(current);       parameterIndex = 1;// 開始參數位置為1       for(Field field : sqlParamFields) {       // 放入insert語句對應sql參數          preparedStatement.setObject(parameterIndex++, field.get(object));       }       errorsRecords.add(object);        preparedStatement.addBatch();        // 達到批量次數就提交一次        if (innerCount >= batchNum || current >= totalRecordCount - 1) {          // 執行batch操作          preparedStatement.executeBatch();          preparedStatement.clearBatch();          // 提交          connnection.commit();          // 記錄提交成功條數          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 {      // 關閉      if (preparedStatement != null) {       preparedStatement.clearBatch();        preparedStatement.close();      }      if (connnection != null)        connnection.close();    }  }

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

在mysql下的開發環境下測試,5萬條數據大概13秒。

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

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對VeVb武林網的支持。


注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美性色视频在线| 夜色77av精品影院| 久久视频在线观看免费| 久久久久久亚洲精品不卡| 91色p视频在线| 国产精品美女久久久久久免费| 亚洲一级片在线看| 日韩在线免费视频| 亚洲精品99久久久久中文字幕| 久久久精品999| 亚洲一区二区久久久久久久| 亚洲aaaaaa| 尤物九九久久国产精品的分类| 精品国产999| 国产精品v日韩精品| 精品中文字幕视频| 国产精品aaaa| 亚洲成**性毛茸茸| 亚洲四色影视在线观看| 久久久国产精品亚洲一区| 一区二区成人av| 欧美日韩精品在线观看| 欧美成人激情视频| 亚洲图片欧美午夜| 欧美激情国产高清| 96精品久久久久中文字幕| 黑人巨大精品欧美一区二区一视频| 97精品国产97久久久久久春色| 亚洲国产99精品国自产| 国产精品视频1区| 精品日本高清在线播放| 日韩一区二区精品视频| 欧美国产日韩精品| 国产99视频在线观看| 疯狂做受xxxx欧美肥白少妇| 国产成人一区二区在线| 国产精品中文字幕久久久| 午夜精品99久久免费| 欧美日在线观看| 久久久久久香蕉网| 亚洲性生活视频在线观看| 88xx成人精品| 久久精品国产亚洲一区二区| 欧美另类老女人| 国产91成人video| 成人福利视频网| 亚洲欧美国产精品va在线观看| 国产精品美女网站| 欧美一区深夜视频| 日韩欧美在线观看视频| 国产精品久久久久久久久久久久久久| 久久精品国产综合| 国产成人精品午夜| 国产精品久久久久久久久久久新郎| 国产成人精品综合久久久| 色噜噜狠狠狠综合曰曰曰| 日本91av在线播放| 亚洲网站在线播放| 精品久久久久久中文字幕| 一本色道久久综合亚洲精品小说| 欧美成人性生活| 国产精品久久久久久久久久久不卡| 精品日韩视频在线观看| 久久福利视频网| 91久久精品久久国产性色也91| 日韩电影中文 亚洲精品乱码| 国产精品成人久久久久| 不卡伊人av在线播放| 亚洲人成在线观看| 国产一区二区三区中文| 亚洲精品永久免费| 91精品国产777在线观看| 国产精品日韩欧美综合| 欧美国产第一页| 在线观看欧美日韩| 国产精品久久9| 久久久精品在线| 国产69久久精品成人| 国产精品久久久久久久久久三级| 黑人巨大精品欧美一区二区一视频| 亚洲精品日韩激情在线电影| 国产精品福利小视频| 亚洲国产另类久久精品| 亚洲国产小视频| 69视频在线免费观看| 欧美精品在线极品| 久久久久这里只有精品| 中文字幕久热精品视频在线| 夜夜嗨av色综合久久久综合网| 超薄丝袜一区二区| 操日韩av在线电影| 精品调教chinesegay| 国产91久久婷婷一区二区| www.日韩.com| 国产精品伦子伦免费视频| 97人人爽人人喊人人模波多| 国产专区欧美专区| 国产精品日韩电影| 国产精品视频一区国模私拍| 亚洲国产精品小视频| 日韩一区在线视频| 亚洲欧美激情一区| 国产精品久久77777| 中文在线资源观看视频网站免费不卡| 亚洲综合日韩中文字幕v在线| 98精品国产高清在线xxxx天堂| 亚洲欧美国产va在线影院| 亚洲japanese制服美女| 久久精品99久久香蕉国产色戒| 国产69精品久久久| 欧洲成人免费aa| 狠狠综合久久av一区二区小说| 午夜精品一区二区三区视频免费看| 在线中文字幕日韩| 中国china体内裑精亚洲片| 久久久综合免费视频| 午夜免费在线观看精品视频| 日韩欧美国产激情| 亚洲aaa激情| 亚洲美女av在线| 久久久久成人精品| 欧美韩国理论所午夜片917电影| 美女福利精品视频| 这里只有视频精品| 亚洲天堂成人在线| 久久亚洲精品毛片| 91在线中文字幕| 日韩电视剧免费观看网站| 88国产精品欧美一区二区三区| 亚洲男人的天堂在线播放| 国产精品久久久久久久久久小说| 国产精品九九久久久久久久| 亚洲www永久成人夜色| 午夜精品免费视频| 庆余年2免费日韩剧观看大牛| 国产精品久久久久久久av大片| 98午夜经典影视| 国产专区精品视频| 中文字幕亚洲无线码在线一区| 精品一区二区三区电影| 97久久超碰福利国产精品…| 91精品视频免费观看| 亚洲福利视频专区| 国产精品h在线观看| 在线看日韩欧美| 国产成人免费av| 欧美激情精品久久久久久大尺度| 久久久精品欧美| 欧美午夜宅男影院在线观看| 国产精品美乳在线观看| 全色精品综合影院| 久久99久国产精品黄毛片入口| 国产美女久久精品香蕉69| 国产精品一区二区3区| 国产精品福利在线观看网址| 韩国精品久久久999| xxxxx成人.com| 亚洲国产私拍精品国模在线观看| 国产欧美日韩免费看aⅴ视频| 色yeye香蕉凹凸一区二区av| 欧美整片在线观看| 日韩在线观看免费全集电视剧网站| 国产一级揄自揄精品视频| 色先锋资源久久综合5566|