MyBatis動態批量插入、更新Mysql數據庫的通用實現方案


一、業務背景

  由於需要從A數據庫提取大量數據同步到B系統,采用了tomikos+jta進行分布式事務管理,先將系統數據源切換到數據提供方,將需要同步的數據查詢出來,然后再將系統數據源切換到數據接收方,進行批量的插入和更新操作,

關於數據源的切換可以參考之前的文章《spring+springMVC+Mybatis架構下采用AbstractRoutingDataSource、atomikos、JTA實現多數據源靈活切換以及分布式事務管理

 

二、批量插入的具體實現

  1.查詢需要同步的數據:

    @Autowired
    SysPersonPOMapper sysPersonPOMapper;
    
    public void dataDs(){
        //根據具體情況可以創建查詢條件或者采用自定義的Mapper進行查詢
        SysPersonPOExample sysPersonPOExample = new SysPersonPOExample();
        sysPersonPOExample.createCriteria().andIsDeleteEqualTo(false);
        //查詢需要同步的數據
        List<SysPersonPO> persons = sysPersonPOMapper.selectByExample(sysPersonPOExample);
    }

  2.將不能進行遍歷的PO實體對象轉為Map,此處有一個前提條件:MySQL中的表字段是按全大寫加下划線命名,實體類PO映射字段為對應的標准駝峰命名方式,進行PO到Map

的轉換時,會遵循這一規則。

    @Autowired
    SysPersonPOMapper sysPersonPOMapper;
    
    public void dataDs() throws Exception{
        //1.查詢需要同步的數據
        //根據具體情況可以創建查詢條件或者采用自定義的Mapper進行查詢
        SysPersonPOExample sysPersonPOExample = new SysPersonPOExample();
        sysPersonPOExample.createCriteria().andIsDeleteEqualTo(false);
        //查詢需要同步的數據
        List<SysPersonPO> persons = sysPersonPOMapper.selectByExample(sysPersonPOExample);
        
        //2.將不能進行遍歷的PO實體對象轉為Map
        
        //用於存放轉換后的對象的List
        List<Map<String,Object>> insertItems = Lists.newArrayList();
        for (SysPersonPO sysPersonPO : persons) {
            Map<String,Object> insertItem = BeanMapUtil.convertBean2MapWithUnderscoreName(sysPersonPO);
            insertItems.add(insertItem);
        }
    }

  

BeanMapUtil類,PO到Map的轉換方法,基於類反射技術:
import com.fms.common.utils.other.StringUtil;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.MethodDescriptor;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;


public class BeanMapUtil {

  @SuppressWarnings({"unchecked", "rawtypes"})
    public static Map convertBean2MapWithUnderscoreName(Object bean) throws Exception {
        Map returnMap = null;
        try {
            Class type = bean.getClass();
            returnMap = new HashMap();
            BeanInfo beanInfo = Introspector.getBeanInfo(type);
            PropertyDescriptor[] propertyDescriptors = beanInfo
                    .getPropertyDescriptors();
            for (int i = 0; i < propertyDescriptors.length; i++) {
                PropertyDescriptor descriptor = propertyDescriptors[i];
                String propertyName = descriptor.getName();
                if (!propertyName.equalsIgnoreCase("class")) {
                    Method readMethod = descriptor.getReadMethod();
                    Object result = readMethod.invoke(bean, new Object[0]);
                    returnMap.put( StringUtil.underscoreName(propertyName), result);
                }
            }
        } catch (Exception e) {
            // 解析錯誤時拋出服務器異常 記錄日志
            throw new Exception("從bean轉換為map時異常!", e);
        }
        return returnMap;
    }

}

 

 StringUtil類,標准駝峰命名與數據庫下划線命名之間轉換的方法:

public class StringUtil {

   /**
     * 將駝峰式命名的字符串轉換為下划線大寫方式。如果轉換前的駝峰式命名的字符串為空,則返回空字符串。</br>
     * 例如:HelloWorld->HELLO_WORLD
     *
     * @param name 轉換前的駝峰式命名的字符串
     * @return 轉換后下划線大寫方式命名的字符串
     */
    public static String underscoreName(String name) {
        StringBuilder result = new StringBuilder();
        if (name != null && name.length() > 0) {
            // 將第一個字符處理成大寫
            result.append(name.substring(0, 1).toUpperCase());
            // 循環處理其余字符
            for (int i = 1; i < name.length(); i++) {
                String s = name.substring(i, i + 1);
                // 在大寫字母前添加下划線
                if (s.equals(s.toUpperCase()) && !Character.isDigit(s.charAt(0))) {
                    result.append("_");
                }
                // 其他字符直接轉成大寫
                result.append(s.toUpperCase());
            }
        }
        return result.toString();
    }
}

 

 

3.編寫Mybatis映射文件fmsDataDsMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.fms.common.dao.fmsDataDsMapper">

<!-- 批量插入,傳入表名和需要插入的數據的集合 -->
    <insert id="insertDatas" parameterType="map">
       insert into ${table_name}
       <foreach collection="fields" index="field"  item="fieldVal" separator="," open="(" close=")">
                ${field}
       </foreach>
               values  
       <foreach collection="list" index="index" item="record" separator="," >
            <foreach collection="record" index="key"  item="item" separator="," open="(" close=")">
                #{item}
            </foreach>
        </foreach> 
    </insert>

</mapper>

 

4.調用sqlsession相關API的insert方法插入數據:

    @Autowired
    SysPersonPOMapper sysPersonPOMapper;
    
    public void dataDs() throws Exception{
        //1.查詢需要同步的數據
        //根據具體情況可以創建查詢條件或者采用自定義的Mapper進行查詢
        SysPersonPOExample sysPersonPOExample = new SysPersonPOExample();
        sysPersonPOExample.createCriteria().andIsDeleteEqualTo(false);
        //查詢需要同步的數據
        List<SysPersonPO> persons = sysPersonPOMapper.selectByExample(sysPersonPOExample);
        
        //2.將不能進行遍歷的PO實體對象轉為Map
        
        //用於存放轉換后的對象的List
        List<Map<String,Object>> insertItems = Lists.newArrayList();
        for (SysPersonPO sysPersonPO : persons) {
            Map<String,Object> insertItem = BeanMapUtil.convertBean2MapWithUnderscoreName(sysPersonPO);
            insertItems.add(insertItem);
        }
        
        //3.插入數據
        insertDatas(insertItems,"sys_person");
    }
    
    
    @Autowired
    SqlSessionTemplate sqlSessionTemplate;
    private String dataDsNameSpace = "com.fms.common.dao.fmsDataDsMapper";
    private void insertDatas(List<Map<String,Object>> insertItems, String tableName){
        if (!insertItems.isEmpty()) {
            Map<String,Object> params = Maps.newHashMap();
       //這里把數據分成每1000條執行一次,可根據實際情況進行調整
int count = insertItems.size() / 1000; int yu = insertItems.size() % 1000; for (int i = 0; i <= count; i++) { List<Map<String,Object>> subList = Lists.newArrayList(); if (i == count) { if(yu != 0){ subList = insertItems.subList(i * 1000, 1000 * i + yu); }else { continue; } } else { subList = insertItems.subList(i * 1000, 1000 * (i + 1)); } params.put("table_name", tableName); params.put("fields", subList.get(0)); params.put("list", subList); sqlSessionTemplate.insert(dataDsNameSpace+".insertDatas", params); } } }

 

三 、批量更新的具體實現

  通常我們根據主鍵更新時的語句是 update  table_name set column1 = val1 , column2 = val2 [,......] where id = ?  或者使用Mybatis提供的updateByPrimaryKey接口,

  當我們希望一次性更新大量數據時,每條SQL只能執行一條更新,MySQL支持另外一種更新方式,讓我們可以實現通過一條SQL語句一次性更新多條 根據主鍵更新的記錄:

  ON DUPLICATE KEY UPDATE

  注意:這不同於在滿足where條件時將某個table中的某一字段全部更新為同一各值,而是每條記錄修改后的值都不一樣。

 

1.構建需要更新的數據,需要注意的是如果PO中存在不想要更新的字段,你有兩種處理方式,一種是將該字段的值在此處設為數據庫中原來的值,另一種方式就是在進行Map轉換時,

不將此字段構建到Map中,這只需要對convertBean2MapWithUnderscoreName方法做一些小的修改,你可以把不需要保留的字段作為參數傳給它,然后在轉換的時候過濾掉(主鍵不能省略)。

        //4.構建批量更新的數據
        //用於存放轉換后的對象的List
        List<Map<String,Object>> updateItems = Lists.newArrayList();
        for (SysPersonPO sysPersonPO : persons) {
            sysPersonPO.setCode(sysPersonPO.getCode()+"updateTest");
            Map<String,Object> updatetItem = BeanMapUtil.convertBean2MapWithUnderscoreName(sysPersonPO);
            updateItems.add(updatetItem);
        }

 

2.編寫Mybatis映射文件fmsDataDsMapper.xml

  <!-- 根據主鍵批量更新某個字段,傳入表名和需要更新的數據的集合 -->
    <insert id="updateDatas" parameterType="map">
       INSERT INTO ${table_name}
       <foreach collection="fields" index="field"  item="fieldVal" separator="," open="(" close=")">
                ${field}
       </foreach>
               VALUES  
       <foreach collection="list" index="index" item="record" separator="," >
            <foreach collection="record" index="key"  item="item" separator="," open="(" close=")">
                #{item}
            </foreach>
        </foreach> 
        ON DUPLICATE KEY UPDATE 
        <foreach collection="fields" index="field"  item="fieldVal" separator=",">
                ${field}=VALUES(${field})
       </foreach>
    </insert>

 

3.更新數據

//5.更新數據
updateDatas(updateItems,"sys_person");


private void updateDatas(List<Map<String,Object>> updateItems, String tableName){
        if (!updateItems.isEmpty()) {
            Map<String,Object> params = Maps.newHashMap();
       //這里將數據分成每1000條執行一次,可根據實際情況調整
int count = updateItems.size() / 1000; int yu = updateItems.size() % 1000; for (int i = 0; i <= count; i++) { List<Map<String,Object>> subList = Lists.newArrayList(); if (i == count) { if(yu != 0){ subList = updateItems.subList(i * 1000, 1000 * i + yu); }else { continue; } } else { subList = updateItems.subList(i * 1000, 1000 * (i + 1)); } params.put("table_name", tableName); params.put("fields", subList.get(0)); params.put("list", subList); sqlSessionTemplate.insert(dataDsNameSpace+".updateDatas", params); } } }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM