设计模式-模板模式实际应用
背景描述:
最近在做个需求,将一个报表数据从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层,抽象类的定义,然后就是子类的实现。
模板父类:定义抽象方法(一个或多个),在非抽象方法中调用抽象方法 以及编写其它相同的业务逻辑
模板子类:继承抽象父类,实现抽象方法
调用: 声明父类对象的对象,调用父类的非抽象方法(即模板方法)即可。
posted on
浙公网安备 33010602011771号