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

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

背景描述:

最近在做个需求,将一个报表数据从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层,抽象类的定义,然后就是子类的实现。

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

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

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

原文地址:https://www.cnblogs.com/DFX339/p/12517898.html