本篇文章給大家分享的是有關(guān)怎么通過反射注解批量插入數(shù)據(jù),小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
創(chuàng)新互聯(lián)"三網(wǎng)合一"的企業(yè)建站思路。企業(yè)可建設(shè)擁有電腦版、微信版、手機(jī)版的企業(yè)網(wǎng)站。實(shí)現(xiàn)跨屏營銷,產(chǎn)品發(fā)布一步更新,電腦網(wǎng)絡(luò)+移動網(wǎng)絡(luò)一網(wǎng)打盡,滿足企業(yè)的營銷需求!創(chuàng)新互聯(lián)具備承接各種類型的成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作、外貿(mào)網(wǎng)站建設(shè)項(xiàng)目的能力。經(jīng)過十載的努力的開拓,為不同行業(yè)的企事業(yè)單位提供了優(yōu)質(zhì)的服務(wù),并獲得了客戶的一致好評。
批量導(dǎo)入思路
最近遇到一個需要批量導(dǎo)入數(shù)據(jù)問題。后來考慮運(yùn)用反射做成一個工具類,思路是首先定義注解接口,在bean類上加注解,運(yùn)行時(shí)通過反射獲取傳入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)行時(shí),因?yàn)槲覀冃枰谶\(yùn)行時(shí),去讀取注解參數(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ù)時(shí),需要更新的字段。
@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ù)存在時(shí),是否更新該字段 * @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時(shí)也會被忽略。
代碼如下:
@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 { /** *>的map緩存 */ private static final Map , Map > classTableBeanFieldMap = new HashMap , Map >(); // 用來按順序填充SQL參數(shù),其中存儲的Field和classTableBeanFieldMap保存同樣的順序,但數(shù)量多出ON DUPLICATE KEY UPDATE部分Field private static final Map , List > sqlParamFieldsMap = new HashMap , List >(); private ReflectUtil(){}; /** * 獲取該類上所有@TableField注解,且沒有忽略的字段的Map。 *
返回一個有序的LinkedHashMap類型 *
其中key為DB表中的字段,value為Bean類里的屬性Field對象 * @param clazz * @return */ public static MapgetTableBeanFieldMap(Class> clazz) { // 從緩存獲取 Map fieldsMap = classTableBeanFieldMap.get(clazz); if (fieldsMap == null) { fieldsMap = new LinkedHashMap (); 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)備 *
返回一個有序的ArrayList類型 *
其中key為DB表中的字段,value為Bean類里的屬性Field對象 * @param clazz * @return */ public static ListgetSqlParamFields(Class> clazz) { // 從緩存獲取 List sqlParamFields = sqlParamFieldsMap.get(clazz); if (sqlParamFields == null) { // 獲取所有參數(shù)字段 Map fieldsMap = getTableBeanFieldMap(clazz); sqlParamFields = new ArrayList (fieldsMap.size() * 2); // SQL后段ON DUPLICATE KEY UPDATE需要更新的字段 List updateParamFields = new ArrayList (); Iterator > iter = fieldsMap.entrySet().iterator(); while (iter.hasNext()) { Entry entry = (Entry ) 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 MaptableInsertSqlMap = new HashMap (); /** * 獲取插入的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 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 > iter = fieldsMap.entrySet().iterator(); while (iter.hasNext()) { Entry entry = (Entry ) 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時(shí)就提交一次。
代碼如下:
/** * 批量插入,如果主鍵一致則更新。結(jié)果返回更新記錄條數(shù)
* @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的屬性域 MapfieldsMap = 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 sqlParamFields = ReflectUtil.getSqlParamFields(dataList.get(0).getClass()); // 最終更新結(jié)果條數(shù) int result = 0; int parameterIndex = 1;// SQL填充參數(shù)開始位置為1 // 執(zhí)行錯誤的對象 List
最后,批量工具類使用例子
在MySQL下的開發(fā)環(huán)境下測試,5萬條數(shù)據(jù)大概13秒。
ListupdateDataList = new ArrayList (50000); // ...為updateDataList填充數(shù)據(jù) int result = batchJdbcTemplate.batchInsertSQL(updateDataList, 50);
以上就是怎么通過反射注解批量插入數(shù)據(jù),小編相信有部分知識點(diǎn)可能是我們?nèi)粘9ぷ鲿姷交蛴玫降?。希望你能通過這篇文章學(xué)到更多知識。更多詳情敬請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。