动态SQL数据层框架(4):DynamicSQLParser工具类和配置文件

 

这是一个简单的数据层框架,可以实现动态SQL查询、自动化事务管理、IOC和依赖注入,使用了以下技术:

1) Maven管理依赖,Github托管代码  2) DBUtils框架作为JDBC底层框架  3) JDK动态代理实现的AOP  4) 注解  5) Freemarker来做动态SQL模板的解析  6) 工厂设计模式  7) 反射和XML解析


从使用方式开始介绍,逐步深入地去理解框架的各个知识点。

框架主要分为两部分:
1) dynamic-sql-dao是框架主体
2) dynamic-sql-dao-test是一个demo含有测试用例


Github地址

https://github.com/xuguofeng/dynamic-sql-dao
https://github.com/xuguofeng/dynamic-sql-dao-test

 

文章目录

动态SQL数据层框架(0):一个基于FreeMarker和DBUtils的动态SQL框架
动态SQL数据层框架(1):DBUtils框架基础
动态SQL数据层框架(2):框架结构
动态SQL数据层框架(3):BaseDao接口和BaseDaoSupport抽象类
动态SQL数据层框架(4):DynamicSQLParser工具类和配置文件
动态SQL数据层框架(5):AOP事务管理

 

本文会介绍如何使用freemarker进行sql模板的解析

 

一、Freemarker示例

FreeMarker是一个使用Java编写的基于模板动态生成文本工具。

FreeMarker被设计用来生成HTML Web页面,在MVC模式的应用程序,可以作为显示层的模板解析工具。

在此框架中我们用它来解析动态SQL

 

1、sql.txt模板文件

在类路径下有sql.txt”文件,内容如下:

 1 select 
 2 id, name, age, phone, email, create_time from base_dao_employee 
 3 <#if map??&&(map?size>0)>
 4     where 
 5     <#if map['name']??>
 6         and name = #name# 
 7     </#if>
 8     <#if map['age']??>
 9         and age = #age# 
10     </#if>
11     <#if map['phone']??>
12         and phone = #phone# 
13     </#if>
14     <#if map['email']??>
15         and email = #email# 
16     </#if>
17 </#if>
18 order by id asc

 

  • 使用 #fieldName# 方式表示一个待注入的参数
  • 使用 <#if map??&&(map?size>0)></#if> 标签判断数据model是否存在或者含有元素
  • 使用 <#if map['name']??></#if> 标签判断是否含有某个参数,如果有在追加查询条件或更新字段

 

2、模板解析

 1 // 封装SQL查询参数
 2 Map<String, Object> map = new HashMap<String, Object>();
 3 map.put("name", "admin1");
 4 map.put("age", 28);
 5 
 6 // 数据model
 7 Map<String, Object> model = new HashMap<String, Object>();
 8 model.put("map", map);
 9 
10 // 从类路径下的sql.txt文件创建一个模板对象
11 Template t = new Template("sql", new InputStreamReader(
12         TestFreeMarker.class.getClassLoader().getResourceAsStream(
13                 "sql.txt"), "utf-8"), null);
14 
15 // 定义一个字符串输出流
16 StringWriter out = new StringWriter();
17 
18 // 使用数据model解析模板到输出流
19 t.process(model, out);
20 
21 // 转为字符串
22 String sql = out.toString();
23 
24 // SQL字符串优化
25 // 把“where and”替换为“where”
26 // 把一个或多个空白替换为一个空格
27 sql = sql.replaceAll("where\\s*and", "where").replaceAll("\\s+", " ");
28 
29 System.out.println(sql);

 

二、配置文件

1、主配置文件dynamic-sql.xml

dynamic-sql.xml是主配置文件,配置实体类sql文件存放路径。如下:

1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE dynamic-sql SYSTEM "dynamic-sql.dtd">
3 <files>
4     <file path="org/net5ijy/dao/sql/employee.xml"></file>
5     <file path="org/net5ijy/dao/sql/department.xml"></file>
6     <file path="org/net5ijy/dao/sql/student.xml"></file>
7     <file path="org/net5ijy/dao/sql/teacher.xml"></file>
8 </files>

 

<files>标签下可以配置多个<file>标签,在<file>标签里面可以使用path属性配置文件在类路径下的位置。

 

2、实体类SQL文件

通常,一个实体类的SQL配置在一个sql文件中。

  • 使用 #fieldName# 方式表示一个待注入的参数,这个参数在BaseDaoSupprt中会被替换为“?
  • 使用 <#if map??&&(map?size>0)></#if> 标签判断参数map是否存在或者含有元素
  • 使用 <#if map['name']??></#if> 标签判断是否含有某个参数,如果有在追加查询条件或更新字段

示例如下:

  1 <sql id="Department_addObject">
  2     <![CDATA[
  3     insert into base_dao_department 
  4     (name, description, create_time, parent_department_id) 
  5     values 
  6     (#name#, #description#, #createTime#, #parentDepartmentId#)
  7     ]]>
  8 </sql>
  9 <sql id="Department_updateObject">
 10     <![CDATA[
 11     update base_dao_department set 
 12     name = #name#, description = #description#, 
 13     parent_department_id = #parentDepartmentId#, version = version+1 
 14     where id = #id# and version = #version#
 15     ]]>
 16 </sql>
 17 <sql id="Department_deleteObject">
 18     <![CDATA[
 19     delete from base_dao_department where id = ?
 20     ]]>
 21 </sql>
 22 
 23 <sql id="Department_getObject_int">
 24     <![CDATA[
 25     select 
 26     id, name, description, create_time as createTime, parent_department_id as parentDepartmentId, version 
 27     from base_dao_department where id = ?
 28     ]]>
 29 </sql>
 30 
 31 <sql id="Department_getObject_map">
 32     <![CDATA[
 33     select 
 34     id, name, description, create_time as createTime, parent_department_id as parentDepartmentId, version 
 35     from base_dao_department 
 36     <#if map??&&(map?size>0)>
 37         where 
 38         <#if map['name']??>
 39             and name like #name# 
 40         </#if>
 41         <#if map['startTime']??>
 42             and create_time >= #startTime# 
 43         </#if>
 44         <#if map['endTime']??>
 45             and create_time <= #endTime# 
 46         </#if>
 47         <#if map['parentDepartmentId']??>
 48             and parent_department_id = #parentDepartmentId# 
 49         </#if>
 50     </#if>
 51     order by id asc
 52     ]]>
 53 </sql>
 54 
 55 <sql id="Department_getObjects">
 56     <![CDATA[
 57     select 
 58     id, name, description, create_time as createTime, parent_department_id as parentDepartmentId, version 
 59     from base_dao_department order by id asc
 60     ]]>
 61 </sql>
 62 
 63 <sql id="Department_getObjects_map">
 64     <![CDATA[
 65     select 
 66     id, name, description, create_time as createTime, parent_department_id as parentDepartmentId, version 
 67     from base_dao_department 
 68     <#if map??&&(map?size>0)>
 69         where 
 70         <#if map['name']??>
 71             and name like #name# 
 72         </#if>
 73         <#if map['startTime']??>
 74             and create_time >= #startTime# 
 75         </#if>
 76         <#if map['endTime']??>
 77             and create_time <= #endTime# 
 78         </#if>
 79         <#if map['parentDepartmentId']??>
 80             and parent_department_id = #parentDepartmentId# 
 81         </#if>
 82     </#if>
 83     order by id asc
 84     ]]>
 85 </sql>
 86 
 87 <sql id="Department_getObjects_map_int_int">
 88     <![CDATA[
 89     select 
 90     id, name, description, create_time as createTime, parent_department_id as parentDepartmentId, version 
 91     from base_dao_department 
 92     <#if map??&&(map?size>0)>
 93         where 
 94         <#if map['name']??>
 95             and name like #name# 
 96         </#if>
 97         <#if map['startTime']??>
 98             and create_time >= #startTime# 
 99         </#if>
100         <#if map['endTime']??>
101             and create_time <= #endTime# 
102         </#if>
103         <#if map['parentDepartmentId']??>
104             and parent_department_id = #parentDepartmentId# 
105         </#if>
106     </#if>
107     order by id asc limit ?,?
108     ]]>
109 </sql>
110 
111 <sql id="Department_count_map">
112     <![CDATA[
113     select count(*) from base_dao_department 
114     <#if map??&&(map?size>0)>
115         where 
116         <#if map['name']??>
117             and name like #name# 
118         </#if>
119         <#if map['startTime']??>
120             and create_time >= #startTime# 
121         </#if>
122         <#if map['endTime']??>
123             and create_time <= #endTime# 
124         </#if>
125         <#if map['parentDepartmentId']??>
126             and parent_department_id = #parentDepartmentId# 
127         </#if>
128     </#if>
129     ]]>
130 </sql>
View Code

 

3、DAO方法和SQL id设置

如果要使用BaseDao中定义的通用方法,可以让DAO接口继承BaseDao接口,然后让DAO实现类继承BaseDaoSupport

 

如果这样做,在为SQL设置id时需要注意:id的前缀为实体类的简单名,后面跟固定的字符串,如下:

后缀

对应方法

_addObject

addObject(E e)

_updateObject

deleteObject(Integer id)

_deleteObject

updateObject(E e)

_getObject_int

getObject(Integer id)

_getObject_map

getObject(Map<String, Object> paramaters)

_getObjects

getObjects()

_getObjects_map

getObjects(Map<String, Object> paramaters)

_getObjects_map_int_int

getObjects(Map<String, Object> paramaters, Integer pageNum, Integer pageSize)

_count_map

count(Map<String, Object> paramaters)

 

也可以在DAO接口中自定义方法,或者在实现类里面重写BaseDaoSupport中的方法。比如:在添加Student对象时,需要维护StudentTeacher的关系,可以重写addObject方法

 1 public class StudentDaoImpl extends BaseDaoSupport<Student> implements StudentDao {
 2 
 3     private TeacherDao teacherDao = new TeacherDaoImpl();
 4 
 5     @Override
 6     public int addObject(Student e) {
 7         int id = super.addObject(e);
 8         Set<Teacher> teachers = e.getTeachers();
 9         this.addStudentTeacher(id, teachers);
10         return id;
11     }
12     private void addStudentTeacher(int id, Set<Teacher> teachers) {
13         for (Teacher t : teachers) {
14             super.execute("Student_add_student_teacher", id, t.getId());
15         }
16     }
17 }

 

  • 首先使用superaddObject方法添加Student对象
  • 然后在私有的addStudentTeacher方法中调用superexecute方法维护关联关系

 

三、DynamicSQLParser工具类

1、主要字段

1 private Configuration freeMarkerConfig = new Configuration(new Version("2.3.0"));
2 
3 /**
4 * SqlID—>freemarker Template对应关系
5 */
6 private Map<String, Template> sqlTemplates = new HashMap<String, Template>();
7 
8 private Map<String, String> sqlMap = new HashMap<String, String>();

 

  • sqlTemplates保存需要使用freemarker进行动态SQL解析的SQL id和语句模板
  • sqlMap保存不需要进行动态SQL解析的SQL id和语句

 

2、单例模式

 

使用getDynamicSQLParser方法获取单例对象

 

 

3、加载SQL模板

 1 /**
 2  * 加载SQL模板
 3  */
 4 private void initAllTemplates(Document doc) {
 5     Element root = doc.getRootElement();
 6     List<Element> files = root.elements("file");
 7     for (Element file : files) {
 8         String path = file.attributeValue("path");
 9         Document d = this.getDocument(path);
10         initOneFileTemplates(d);
11     }
12 }
13 /**
14  * 加载一个SQL模板
15  */
16 private void initOneFileTemplates(Document doc) {
17     Element root = doc.getRootElement();
18 // 获取全部sql元素
19     List<Element> sqls = root.elements("sql");
20     StringTemplateLoader loader = new StringTemplateLoader();
21 
22 // 遍历sql元素
23     for (Element sql : sqls) {
24      // 获取sql的id
25         String id = sql.attributeValue("id");
26      // 获取sql模板字符串
27         String sqlTxt = sql.getTextTrim();
28         this.sqlMap.put(id, sqlTxt); // 首先添加到sqlMap中
29 
30      // 生成freemarker模板
31         loader.putTemplate(id, sqlTxt);
32         this.freeMarkerConfig.setTemplateLoader(loader);
33         try {
34             Template template = this.freeMarkerConfig.getTemplate(id);
35             this.sqlTemplates.put(id, template);
36         } catch (TemplateNotFoundException e) {
37         } catch (MalformedTemplateNameException e) {
38         } catch (ParseException e) {
39         } catch (IOException e) {
40         }
41     }
42 }
View Code

 

4、通过id解析/获取SQL

parseSql方法需要传递三个参数:SQLid,解析动态SQL的参数Object,设置是否进行动态解析的parse标识

 

parsefalse时,直接根据idsqlMap中查找SQL,如果没有找到,会抛出一个RuntimeException

 

1 if (!parse) {
2     String sql = this.sqlMap.get(id);
3     if (sql == null) {
4         throw new RuntimeException("SQL语句" + id + "未找到");
5     }
6     return sql;
7 }

 

parsetrue时,表示需要进行动态解析,首先会根据idsqlTemplates中获取SQL模板,如果模板不存在,会抛出一个RuntimeException

如果模板存在,会使用process方法解析、替换多余的and或者“,”后返回

 1 Template template = this.sqlTemplates.get(id);
 2 if (template == null) {
 3     throw new RuntimeException("SQL模板" + id + "未找到");
 4 }
 5 StringWriter sw = new StringWriter();
 6 try {
 7     Map<String, Object> map = new HashMap<String, Object>();
 8     map.put("map", object);
 9     template.process(map, sw);
10     String dynamicSql = sw.toString().replaceAll("(where|set)\\s+(and|,)", "$1 ");
11     return dynamicSql;
12 } catch (TemplateException e) {
13     throw new RuntimeException("SQL模板" + id + "解析出错");
14 } catch (IOException e) {
15     throw new RuntimeException("SQL模板" + id + "解析出错");
16 }

 

posted @ 2018-08-18 14:55  用户不存在!  阅读(1111)  评论(0编辑  收藏  举报