设计模式-模板模式实际应用
背景描述:
最近在做个需求,将一个报表数据从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
抽象模板表的子类
(这里因为我的所有子类实现逻辑都几乎一致,只有最后调用的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信息.........
动态传入表名 执行sql
特别说明一下 truncateConProTable(String tablename) 这个方法,因为传到Dao层的是表明,在Mybatis里面记得用 ${} 去获取,而不是用 #{}去获取变量值。
原因:使用 #{} 获取的值经过了预编译,防止sql注入 会带单引号,整条sql执行会有语法问题。
使用示例(正确):
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层,抽象类的定义,然后就是子类的实现。
模板父类:定义抽象方法(一个或多个),在非抽象方法中调用抽象方法 以及编写其它相同的业务逻辑
模板子类:继承抽象父类,实现抽象方法
调用: 声明父类对象的对象,调用父类的非抽象方法(即模板方法)即可。