动态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>
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对象时,需要维护Student和Teacher的关系,可以重写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 }
- 首先使用super的addObject方法添加Student对象
- 然后在私有的addStudentTeacher方法中调用super的execute方法维护关联关系
三、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 }
4、通过id解析/获取SQL
parseSql方法需要传递三个参数:SQL的id,解析动态SQL的参数Object,设置是否进行动态解析的parse标识
当parse为false时,直接根据id从sqlMap中查找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 }
当parse为true时,表示需要进行动态解析,首先会根据id从sqlTemplates中获取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 }