反射注解による一括挿入データのDB実現方法


一括インポートの考え方
最近は大量にデータを導入する必要があります。反射を利用してツール類にすることを考え、まず注解インターフェースを定義し、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にはデータベース名とテーブル名が定義されています。Retension Policy.RUNTIMEはこの注釈が実行に保存されていると表しています。動作中に注釈パラメータを読み取り、具体的なSQLを生成する必要があります。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
  /**
   *   
   * @return
   */
  String tableName() default "";
  /**
   *      
   * @return
   */
  String dbName();
}
注解インターフェースTable Fieldには、データベーステーブル名のそれぞれの具体的なフィールド名が定義されており、このフィールドが無視されているかどうか(無視するとデータベーステーブルでデフォルト値を定義して充填され、DB非nullフィールドの注釈は、Inore注をtrueに設定することができません)。udateコメントは、メインキーが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に注釈を追加する(importとset/get方法及びその他の属性を簡潔に省略するため)@TableField(fieldName = "company_id")は、companyIdフィールドがDBテーブルに対応するフィールド名を「company_id」と表し、udateTime属性の注釈はignore=trueを含み、この属性値が無視されることを示す。また、serialVersion UID属性は@Table Fieldのコメントがないため、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;
ステップ3で、注の反射工具類を読みだします。
第二ステップビーン類の注釈を読み取る反射工具類。反射率getAnnotation(TableField.class)で注釈情報を読み取り、バッチSQLのスティッチングに最適です。getTableBeanFieldMap()方法でLinked HashMapオブジェクトを生成するのは、SQLを挿入するfieldの順序を保証するためであり、その後も同じ順序でパラメータに値を割り当てることができ、転位を回避するためである。getSqlParamFields()方法も同様で、PreparedStatimentにパラメータを設定するためのものです。
コードは以下の通りです

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 "";
  }
ステップ4で、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文を取得し、sql ParameFieldsに従ってPreparedStatimentのパラメータ値を充填する。サイクルの値セットがbatch Numに到着したら一回提出します。
コードは以下の通りです

  /**
   *     ,         。          <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);
締め括りをつける
以上はこの文章の全部の内容です。本文の内容は皆さんの学習や仕事に対して一定の参考学習価値を持ってほしいです。ありがとうございます。もっと知りたいなら、下のリンクを見てください。