• 设计模式-模板模式实际应用


    设计模式-模板模式实际应用

    背景描述:

    最近在做个需求,将一个报表数据从Hadoop平台转移到TiDB,但是TiDB没有存储过程啥的,所以我们就打算用Java实现啦

    具体实现思路: sql查询完数据之后将数据插入数据库临时表中,最后再转移到结果表中

    使用模板设计模式是因为 整个报表数据使用了10+的临时表,我们对于临时表的处理步骤都一样,都是先查询出数据量,然后将数据分批插入(如果数据量过大的话)

    抽象模板类设计:

    package com.dfx.demo.service;
    
    import java.util.*;
    
    /**
     * 定义抽象类 模板方法
     */
    public abstract class OnePrimHaAbstract {
    
    
        /**
         * 得到需要插入数据库中的数据总量,用来判断数据是否过大,如果过多就分批插入数据
         * @param methodName 被调用的方法名
         * @return
         */
        public abstract Integer getInsertDataNums(String methodName) throws Exception;
    
        /**
         * 将数据插入到数据库中
         * @param methodName 被调用的方法名
         * @param start 插入数据的开始位置
         * @param end 插入数据的条数
         */
        public abstract void  insertData2DB(String methodName,Integer start,Integer end)throws Exception;
    
        /**
         * 处理数据
         * 1.查询数据量
         * 2.(分批)将数据插入到数据库中
         * @return
         */
        public Integer HaProData(String methodName,Integer maxInsertNums ) throws Exception{
            long starttime = System.currentTimeMillis();
    
            //查询出数据量,再根据数据量分批插入数据
            Integer sumdataNums =getInsertDataNums(methodName);
            List<Map<String,Integer>> limitList = getStartEndLimits(sumdataNums,maxInsertNums);
            if(limitList != null){
                for(Map<String,Integer> limitListObj : limitList){
                    System.out.println(methodName+" - limitListObj:"+limitListObj);
                    insertData2DB(methodName,limitListObj.get("start"),limitListObj.get("end"));
                }
            }
            System.out.println(methodName+" - 耗时:"+(System.currentTimeMillis()-starttime)/1000 +"s");
    
            return sumdataNums;
        }
    
        /**
         * 临时表 or 结果表 数据清空,在重新插入数据到结果表中时需要先清空结果表的数据
         * @param tablename 需要被清空的表名称 (eg: rs_Ha_Pro)
         */
        public abstract void truncateConProTable(String  tablename);
    
        /**
         *
         * 更新监控表的状态值
         * @param tablename 被更新的表名称 作为入参条件(eg: rs_Ha_Pro)
         * @param tabledatanums 更新的表对应的数据量
         * @param status 表名对应的状态值切换,正在更新 值为1,更新完毕可使用 值为0.(默认值为0)
         * @param remark 对应的表的备注信息
         */
        public abstract void updConProMonitor(String  tablename,Integer tabledatanums,Integer status,String remark);
    
    
        /**
         * 返回数据分页结果信息(用于分批插入数据到数据库中-数据量过大时)
         * @param sumNumbers  总条数
         * @param maxInsertNums 每次插入的最大的条数
         * @return 分页结果list(seqid排序标识 从小到大,start 开始位置,end 插入条数)
         */
        private  List<Map<String,Integer>> getStartEndLimits(Integer sumNumbers,Integer maxInsertNums){
    
            if(sumNumbers <= 0 ){return null;}//如果为0 则返回null
            int len = sumNumbers/maxInsertNums +1;
            int modlen = sumNumbers%maxInsertNums ;
            int start = 0;
            int end = maxInsertNums;
    
            List<Map<String,Integer>> limitList = new ArrayList<Map<String,Integer>>();
    
            //因数据量过大,需要分批插入(每 maxInsertNums 条数据作为1批数据)
            len = sumNumbers/maxInsertNums +1;
            modlen = sumNumbers%maxInsertNums ;
            start = 0;
            end = maxInsertNums;
            if(sumNumbers < maxInsertNums){end=sumNumbers;} //如果总数量小于20w则不需要分批插入
            for(int i=0;i<len;i++){
                Map<String,Integer>  limitMaps = new HashMap<String ,Integer>();
                limitMaps.put("start",start);
                limitMaps.put("end",end);
                limitMaps.put("seqid", i);
                limitList.add(limitMaps);
    //            System.out.println("第"+(i+1)+"次插入数据 limitMaps:"+limitMaps);
    //            System.out.println("第"+(i+1)+"次插入数据 limitList:"+limitList);
                if(i==(len-2)){
                    end =modlen;
                }
                start +=maxInsertNums;
    
            }
    
            // 从小到大排序
            Collections.sort(limitList,new Comparator<Map<String,Integer>>(){
                @Override
                public int compare(Map<String,Integer> map1, Map<String,Integer> map2) {
                    if( map1.get("seqid") > map2.get("seqid")){
                        return 1;
                    }
                    return -1;
                }
            });
    
            return limitList;
        }
    
    }
    

     

    抽象模板表的子类

    (这里因为我的所有子类实现逻辑都几乎一致,只有最后调用的Dao不同,也就是执行的sql不同,所以我这里只定义了一个子类,在子类里面通过switch case 处理了,可根据需要定义不同的子类,分别实现抽象方法)package com.dfx.demo.service.impl;

    import com.dfx.demo.dao.haProMapper;
    import com.dfx.demo.service.OnePrimHaAbstract;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    /**
     * 定义抽象类 模板方法
     */
    @Service
    public  class OnePrimHaAbstractImpl extends OnePrimHaAbstract {
    
    
        @Autowired
        private haProMapper haProMapper;
    
        /**
         * 得到需要插入数据库中的数据总量,用来判断数据是否过大,如果过多就分批插入数据
         * @return
         */
        public  Integer getInsertDataNums(String methodName) throws Exception{
            Integer resultNums = null;
            if(methodName != null ){
                switch(methodName){
                    case "insertTmp1": resultNums = haProMapper.getTmp1nums();
                        break;
    .........中间省略其它case ........ case "insertTmp20": resultNums = haProMapper.getTmp20nums(); break; } } return resultNums ; } /** * 将数据插入到数据库中 */ public void insertData2DB(String methodName,Integer start,Integer end)throws Exception{ if(methodName != null ){ switch(methodName){ case "insertHaProtmp1": haProMapper.insertTmp1(start,end); break;
    .............中间省略其它case................. case "insertHaProtmp20": haProMapper.insertTmp20(start,end); break; } } } @Override public void truncateConProTable(String tablename) { haProMapper.truncateConProTable(tablename); } @Override public void updConProMonitor(String tablename, Integer tabledatanums, Integer status, String remark) { haProMapper.updConProMonitor(tablename,tabledatanums,status,remark); } }

    Dao层的Mapper接口类

    package com.dfx.demo.dao;
    
    import com.sun.org.glassfish.gmbal.ParameterNames;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    
    import java.util.List;
    import java.util.Map;
    
    /**
     * 联络人数据接口
     */
    @Mapper
    public interface ContactAgentMapper {
    
     
      //获取临时表1  的数量
      public Integer getTmp1nums();
      //联络人临时表1 数据插入数据库 
      public void insertTmp1(@Param("start") Integer start, @Param("end") Integer end);
    
    ....... 中间省略临时表的数据量查询及数据插入方法定义......
    /** * 临时表 or 结果表 数据清空,在重新插入数据到结果表中时需要先清空表的数据 * @param tablename 需要被清空的表名称 */ public void truncateConOnelifeTable(@Param("tablename") String tablename); }

      

    Dao层的Mapper文件(sql)

    <?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.dfx.demo.dao.ContactAgentMapper">
    
    <!-- 临时表1 数据量查询 -->
    <select id="getTmp1nums" resultType="java.lang.Integer">
    	<![CDATA[
    		select count(*) from (
    			select
    				distinct
    				t.seqid
    				,t.name
    			from  rs_table_tmp4 t
    		)tt
    	]]>
    </select>
    
    <!-- 临时表1 数据插入 -->
    <insert id="insertTmp1" >
    	<![CDATA[
    		insert into rs_tmp1 ( `seqid`, `name`)
    			select
    			distinct
    			t.seqid
    			,t.name
    			from  rs_table_tmp4 t
    			order by t.seqid   limit #{start},#{end}
    	]]>
    </insert>
    
    .........中间省略数据其它xml信息.........
    
    <!-- 清除联络人数据 临时表或结果表信息 -->
    <delete id="truncateConOnelifeTable" parameterType="java.lang.String">
    	<![CDATA[
    		truncate  ${tablename}
    	]]>
    </delete>
    
    </mapper>
    

    动态传入表名 执行sql

    特别说明一下  truncateConProTable(String tablename) 这个方法,因为传到Dao层的是表明,在Mybatis里面记得用 ${} 去获取,而不是用 #{}去获取变量值

    原因:使用 #{} 获取的值经过了预编译,防止sql注入 会带单引号,整条sql执行会有语法问题。

    使用示例(正确):

    <delete id="truncateConProTable" parameterType="java.lang.String">
    	<![CDATA[
    		truncate  ${tablename}
    	]]>
    </delete>
    

    mybatis中的#和$的区别:

    1、#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。
    如:where username=#{username},如果传入的值是111,那么解析成sql时的值为where username="111", 如果传入的值是id,则解析成的sql为where username="id". 
    2、$将传入的数据直接显示生成在sql中。
    如:where username=${username},如果传入的值是111,那么解析成sql时的值为where username=111;
    如果传入的值是;drop table user;,则解析成的sql为:select id, username, password, role from user where username=;drop table user;
    3、#方式能够很大程度防止sql注入,$方式无法防止Sql注入。
    4、$方式一般用于传入数据库对象,例如传入表名.
    5、一般能用#的就别用$,若不得不使用“${xxx}”这样的参数,要手工地做好过滤工作,来防止sql注入攻击。
    6、在MyBatis中,“${xxx}”这样格式的参数会直接参与SQL编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用“${xxx}”这样的参数格式。所以,这样的参数需要我们在代码中手工进行处理来防止注入。
    【结论】在编写MyBatis的映射语句时,尽量采用“#{xxx}”这样的格式。若不得不使用“${xxx}”这样的参数,要手工地做好过滤工作,来防止SQL注入攻击。
    

      

    最后在Controller层按照业务逻辑顺序调用处理即可。

    Controller调用示例

    package com.dfx.demo.web;
    
    import com.dfx.demo.dao.pojo.ContactAgentPojo;
    import com.dfx.demo.service.OnePrimContactsService;
    import com.dfx.demo.service.OnePrimContactsAbstract;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 联络人数据接口
     */
    @RestController
    public class OnePrimContacts {
    
        @Autowired
        private  OnePrimHaAbstract  OnePrimHaAbstract oneAbstract;
    
        @RequestMapping("/getOnePrimContactsTemplate")
        public Integer dealContactPro(){
            Integer resultNums = null;
            try {
                resultNums = getContactPro();
            } catch(Exception e){
                System.out.println("数据获取失败,请注意检查,失败原因:"+ e);
                //在重新开始跑数据之前需要执行删除 清空所有临时表数据
               dealContactPro();//失败了就重新开始跑数
            }
            return resultNums;
        }
    
        private Integer getContactPro() throws Exception{
    
            long starttime = System.currentTimeMillis();
            //1.跑数据之前先清空所有的临时表信息
            oneAbstract.truncateConProTable("rs_table_tmp1");
    
            //2.开始执行获取临时表数据
            Integer insertTmp1 =  oneAbstract.HaProData("insertTmp1",200000);
            System.out.println("临时表1 insertTmp1"+insertTmp1);
           
    	    ....其它临时表的插入执行.....
    		
            long endtime = System.currentTimeMillis();
            System.out.println("整体方法耗时:"+(endtime-starttime)/1000 +"s");
    		
            return contactsPro;
        }
    }
    

      

    整个模板模式主要应用在Service层,抽象类的定义,然后就是子类的实现。

    模板父类:定义抽象方法(一个或多个),在非抽象方法中调用抽象方法 以及编写其它相同的业务逻辑

    模板子类:继承抽象父类,实现抽象方法

    调用: 声明父类对象的对象,调用父类的非抽象方法(即模板方法)即可。

  • 相关阅读:
    apache mina框架
    架构学习笔记
    接口抽象类
    继承是共用,不是复制
    线程安全的单例模式还需要对成员变量的set get方法设置锁么
    受检查异常要求try catch,new对象时,就会在堆中创建内存空间,创建的空间包括各个成员变量类型所占用的内存空间
    myeclipse 写java代码提示 dead code 原因
    Caused by: java.lang.ClassNotFoundException: org.apache.commons.io.FileUtils
    android内存优化发展——使用软引用
    cocos2dx3.1.1+cocosstudio+lua问题总结
  • 原文地址:https://www.cnblogs.com/DFX339/p/12517898.html
Copyright © 2020-2023  润新知