• 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); } } }
  • 相关阅读:
    jumpserver3.0跳板机
    hdu 1723 Distribute Message (DP)
    Java中 filter()函数的用法
    java实现广度优先算法
    CentOS7图形界面和命令行界面的切换快捷键 从图形界面进入命令行:ctl+alt+F2 从命令行返回图形界面:ctl+alt+F1
    ConcurrentModificationException异常原因和解决方法
    获取父节点下所有子节点集合,查询数据库,递归查询。或者这不查询数据库递归查询
    java递归获取某个父节点下面的所有子节点
    DOM给任意一个父节点,遍历出这个父节点下所有后代节点 深度有优先
    Java递归树(构建和收集子孙节点二种方式)
  • 原文地址:https://www.cnblogs.com/LiQ0116/p/7389049.html
Copyright © 2020-2023  润新知