动态SQL数据层框架(0):一个基于FreeMarker和DBUtils的动态SQL框架
这是一个简单的数据层框架,可以实现动态SQL查询、自动化事务管理、IOC和依赖注入,使用了以下技术:
1) Maven管理依赖,Github托管代码 2) DBUtils框架作为JDBC底层框架 3) JDK动态代理实现的AOP 4) 注解 5) Freemarker来做动态SQL模板的解析 6) 工厂设计模式 7) 反射和XML解析
从使用方式开始介绍,逐步深入地去理解框架的各个知识点,最基础的demo代码就不在这里做过多讲解了,还是要在理解了需求和使用场景的前提下去学习技术,而不是单纯地去编写Hello World似的demo代码。
框架主要分为两部分:
1) dynamic-sql-dao是框架主体
2) dynamic-sql-dao-test是一个demo含有测试用例
文章目录
动态SQL数据层框架(0):一个基于FreeMarker和DBUtils的动态SQL框架
动态SQL数据层框架(1):DBUtils框架基础
动态SQL数据层框架(2):框架结构
动态SQL数据层框架(3):BaseDao接口和BaseDaoSupport抽象类
动态SQL数据层框架(4):DynamicSQLParser工具类和配置文件
动态SQL数据层框架(5):AOP事务管理
一、环境准备
1、开发、运行环境
操作系统 |
Windows 7 64位操作系统 |
JDK |
java version "1.6.0_45" |
maven |
Apache Maven 3.2.1 |
Eclipse |
Luna Service Release 2 (4.4.2) |
工作空间 |
D:\workspace\SpringCloud |
2、Github地址
https://github.com/xuguofeng/dynamic-sql-dao
https://github.com/xuguofeng/dynamic-sql-dao-test
3、从Github上clone项目
git clone https://github.com/xuguofeng/dynamic-sql-dao.git git clone https://github.com/xuguofeng/dynamic-sql-dao-test.git
然后把项目导入到Eclipse中
4、引入依赖
使用以下方式把dynamic-sql-dao引入到自己的项目中
1 <!-- 依赖dynamic-sql-dao --> 2 <dependency> 3 <groupId>org.net5ijy.dao</groupId> 4 <artifactId>dynamic-sql-dao</artifactId> 5 <version>0.0.1-SNAPSHOT</version> 6 </dependency>
二、编写代码
1、实体类
实体类按照平常方式编写即可,没有较多的要求。
需要注意:框架在进行增删改查操作时并不能维护关联关系。如果需要,可以考虑使用关联SQL查询,自定义ResultSetHandler来处理结果集。
以Employee的DAO为例,可以在sql文件中使用关联方式查询出Department信息,然后在DAO实现类中重写获取ResultSetHandler的方法(以下方法定义在BaseDaoSupport中):
1 protected ResultSetHandler<E> getResultSetHandler() { 2 return new BeanHandler<E>(this.entityClass); 3 } 4 protected ResultSetHandler<List<E>> getResultSetListHandler() { 5 return new BeanListHandler<E>(this.entityClass); 6 }
Employee.java
1 public class Employee implements Serializable { 2 3 private Integer id; 4 private String name; 5 private Integer gender; 6 private Double salary; 7 private String phone; 8 private String email; 9 private Date birthday; 10 private Date joinDate; 11 private Date createTime; 12 13 private Integer departmentId; 14 private Department department; 15 16 private Integer version; 17 18 // getter and setter 19 // equals and hashCode 20 // toString 21 }
2、DAO接口
1 import org.net5ijy.dao.bean.Employee; 2 import org.net5ijy.dao.dynamic.BaseDao; 3 4 public interface EmployeeDao extends BaseDao<Employee> { 5 6 }
在BaseDao中定义了最基础的实体增删改查方法。
1 public interface BaseDao<E> { 2 int addObject(E e); 3 boolean deleteObject(Integer id); 4 boolean updateObject(E e); 5 E getObject(Integer id); 6 E getObject(Map<String, Object> paramaters); 7 List<E> getObjects(); 8 List<E> getObjects(Map<String, Object> paramaters); 9 List<E> getObjects(Map<String, Object> params, Integer num,Integer size); 10 int count(Map<String, Object> paramaters); 11 }
3、DAO实现
1 import org.net5ijy.dao.EmployeeDao; 2 import org.net5ijy.dao.bean.Employee; 3 import org.net5ijy.dao.dynamic.BaseDaoSupport; 4 5 public class EmployeeDaoImpl extends BaseDaoSupport<Employee> implements 6 EmployeeDao { 7 8 }
如果没有特殊的需求,上面的方式就可以了
如果需要在增删改查时维护关联关系,就要重写相关方法,下面是Student的数据层实现的代码片段,它需要维护了Teacher的关系。
- 依赖注入了一个TeacherDao
- 重写了增删改查方法以维护关系,当然了相关的sql配置文件也要做相应的修改
1 public class StudentDaoImpl extends BaseDaoSupport<Student> implements StudentDao { 2 3 private TeacherDao teacherDao; 4 5 // 依赖注入 6 @Resource 7 public void setTeacherDao(TeacherDao teacherDao) { 8 this.teacherDao = teacherDao; 9 } 10 11 @Override 12 public int addObject(Student e) { 13 int id = super.addObject(e); 14 Set<Teacher> teachers = e.getTeachers(); 15 this.addStudentTeacher(id, teachers); 16 return id; 17 } 18 // 维护和Teacher的关系 19 private void addStudentTeacher(int id, Set<Teacher> teachers) { 20 for (Teacher t : teachers) { 21 super.execute("Student_add_student_teacher", id, t.getId()); 22 } 23 } 24 25 @Override 26 public boolean deleteObject(Integer id) { 27 super.execute("Student_deleteTeachersById", id); 28 return super.deleteObject(id); 29 } 30 31 @Override 32 public boolean updateObject(Student e) { 33 super.execute("Student_deleteTeachersById", e.getId()); 34 Set<Teacher> teachers = e.getTeachers(); 35 this.addStudentTeacher(e.getId(), teachers); 36 return super.updateObject(e); 37 } 38 39 @Override 40 public Student getObject(Integer id) { 41 Student student = super.getObject(id); 42 if (student != null) { 43 student.setTeachers(getTeachers(id)); 44 } 45 return student; 46 } 47 48 @Override 49 public Student getObject(Map<String, Object> paramaters) { 50 Student student = super.getObject(paramaters); 51 if (student != null) { 52 student.setTeachers(getTeachers(student.getId())); 53 } 54 return student; 55 } 56 // 根据Student对象id获取关联的Teacher集合 57 private Set<Teacher> getTeachers(int studentId) { 58 Set<Teacher> teachers = new HashSet<Teacher>(); 59 Map<String, Object> map = new HashMap<String, Object>(); 60 map.put("studentId", studentId); 61 List<Integer> ids = super.selectColumn("Student_getTeacherIdsById", map); 62 for (Integer i : ids) { 63 Teacher t = this.teacherDao.getObject(i); 64 teachers.add(t); 65 } 66 return teachers; 67 } 68 }
4、业务层事务管理
在需要使用事务的业务层实现类和相关方法上添加@Transactional注解
1 @Transactional 2 public class EmployeeServiceImpl implements EmployeeService { 3 4 private EmployeeDao employeeDao; 5 6 @Resource 7 public void setEmployeeDao(EmployeeDao employeeDao) { 8 this.employeeDao = employeeDao; 9 } 10 11 @Override 12 @Transactional 13 public void deleteEmployees(List<Integer> ids) { 14 int i = 0; 15 for (Integer id : ids) { 16 i++; 17 if (i == 2) { 18 throw new RuntimeException("测试事务管理"); 19 } 20 this.employeeDao.deleteObject(id); 21 } 22 } 23 }
5、从BeanFactory中获取对象
BeanFactory类提供一个静态方法用于获取保存在工厂中的某个接口的实现类对象
1 public static <T> T getObject(Class<T> clazz) { 2 Object bean = beans.get(clazz.getSimpleName()); 3 if (bean != null) { 4 return (T) bean; 5 } 6 return null; 7 }
所以,可以使用下面方式获取EmployeeDao的实现对象
EmployeeDao employeeDao = BeanFactory.getObject(EmployeeDao.class);
当然,也可以使用依赖注入向对象里面注入依赖的对象,需要使用set方法和@Resource注解
1 private EmployeeDao employeeDao; 2 3 @Resource 4 public void setEmployeeDao(EmployeeDao employeeDao) { 5 this.employeeDao = employeeDao; 6 }
三、编写配置
1、beans.properties工厂配置
例如
1 DepartmentDao=org.net5ijy.dao.jdbc.DepartmentDaoImpl 2 EmployeeDao=org.net5ijy.dao.jdbc.EmployeeDaoImpl 3 StudentDao=org.net5ijy.dao.jdbc.StudentDaoImpl 4 TeacherDao=org.net5ijy.dao.jdbc.TeacherDaoImpl 5 EmployeeService=org.net5ijy.service.impl.EmployeeServiceImpl
以上例子配置了需要使用BeanFactory类管理的bean
key为接口简单类名
value为对应使用的实现类全名
2、dbcp.properties数据源配置
1 driverClassName=com.mysql.jdbc.Driver 2 url=jdbc:mysql://localhost:3306/test 3 username=system 4 password=123456 5 6 initialSize=1 7 maxActive=10 8 maxIdle=5 9 minIdle=2 10 11 maxWaitMillis=-1 12 validationQuery=select 1 13 testWhileIdle=true 14 timeBetweenEvictionRunsMillis=60000 15 numTestsPerEvictionRun=3 16 minEvictableIdleTimeMillis=60000 17 softMinEvictableIdleTimeMillis=60000 18 maxConnLifetimeMillis=300000 19 removeAbandonedTimeout=300
就是在配置BasicDataSource的属性
3、dynamic-sql.xml引入实体sql文件
引入系统需要的实体类sql文件,每一个file标签配置一个实体类sql文件,path属性配置在类路径下的位置
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>
4、实体sql文件
通常,一个实体类的SQL配置在一个sql文件中。
- 使用 #fieldName# 方式表示一个待注入的参数,这个参数在BaseDaoSupprt中会被替换为“?”
- 使用 <#if map??&&(map?size>0)></#if> 标签判断参数map是否存在或者含有元素
- 使用 <#if map['name']??></#if> 标签判断是否含有某个参数,如果有在追加查询条件或更新字段
如果某个sql对应的是BaseDao中的方法,在为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) |
四、dynamic-sql-dao-test测试demo