虽然DAO模式已经有了好多的成熟的框架,但它仍然是一个比较重要的设计模式。要做一个比较合理的DAO模式,你需要对工厂模式、单例模式、模板模式、策略模式、代理模式、泛型、反射机制、输入输出、异常等知识比较熟悉。下面结合自己理解,设计一个DAO设计模式的例子,希望大家给与指正。
1、数据库连接池的工具类。
在数据库连接池的工具类中,采用了开源的DBCP数据库连接池,调用了DataSource接口,DBCP中关于Datasource的Connection采用了动态代理的方式实现,在这里只是提出,感兴趣可以查看其源码,该工具类采用可配置的方式实现的,代码如下:
package com.cvicse.utils;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
/**
* 数据库连接池操作工具类
*
*/
public class JDBCUtils {
private static DataSource myDataSource = null;
private JDBCUtils() {
}
static {
try {
Properties prop = new Properties();
//采用了类的加载获取路径下数据库的配置信息
InputStream is = JDBCUtils.class.getClassLoader()
.getResourceAsStream("dbcpconfig.properties");
prop.load(is);
myDataSource = BasicDataSourceFactory.createDataSource(prop);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
/**
* 获取数据源
*
* @return
*/
public static DataSource getDataSource() {
return myDataSource;
}
/**
* 获取连接
*
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return myDataSource.getConnection();
}
/**
* 关闭资源
* @param rs
* @param st
* @param conn
* @throws SQLException
*/
public static void free(ResultSet rs, Statement st, Connection conn)
throws SQLException {
try {
if (rs != null)
rs.close();
} catch (SQLException e) {
throw new SQLException();
} finally {
try {
if (st != null)
st.close();
} catch (SQLException e) {
throw new SQLException();
} finally {
if (conn != null)
try {
conn.close();
} catch (Exception e) {
throw new SQLException();
}
}
}
}
}
数据库配置文件的信息如下dbcpconfig.properties
#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test123
username=root
password=
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=50
#<!-- 最大空闲连接 -->
maxIdle=20
#<!-- 最小空闲连接 -->
minIdle=5
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=UTF-8
#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true
#driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=
#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
2、异常定义,用于处理DAO层的异常类,因为异常最好要在业务层进行处理,个人认为这DAO层异常应该在业务层进行处理,所以DAO层的必要异常都抛出。
package com.cvicse.dao.exception;
/**
*
* 定义DAO异常类
*
*/
public class DaoException extends Exception {
private static final long serialVersionUID = 1L;
/**
* @param message
* @param cause
*/
public DaoException(String message, Throwable cause) {
super(message, cause);
}
/**
* @param message
*/
public DaoException(String message) {
super(message);
}
}
package com.cvicse.dao.exception;
/**
* 传入参数错误异常
*
*/
public class DaoParameterException extends DaoException {
private static final long serialVersionUID = 1L;
/**
* @param message
* @param cause
*/
public DaoParameterException(String message, Throwable cause) {
super(message, cause);
}
/**
* @param message
*/
public DaoParameterException(String message) {
super(message);
}
}
3、定义要操作的pojo类,这里定义了2个pojo类
package com.cvicse.po;
/**
* 课程持久层对象
*
*/
public class Course {
private long id;
private String name;
/**
* 构造函数类
*/
public Course() {
this.id = 0;
this.name = null;
}
/**
* @param id
* @param name
*/
public Course(long id, String name) {
this.id = id;
this.name = name;
}
/**
* @return
*/
public long getId() {
return id;
}
/**
* @param id
*/
public void setId(long id) {
this.id = id;
}
/**
* @return
*/
public String getName() {
return name;
}
/**
* @param name
*/
public void setName(String name) {
this.name = name;
}
}
package com.cvicse.po;
/**
* 学生持久层对象
*/
public class Student {
private long id;
private String name;
public Student() {
this.id = 0;
this.name = null;
}
public Student(long id, String name) {
this.id = id;
this.name = name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
4、定义对象操作的DAO接口,因为面向接口编程,定义接口目的是DAO层的操作能和业务层解耦。
package com.cvicse.dao;
import java.util.List;
import com.cvicse.dao.exception.DaoException;
import com.cvicse.po.Course;
/**
* 课程DAO层接口
*
*/
public interface CourseDAO {
/**
* 获取列表
* @return
* @throws DaoException
*/
public List<Course> selectCourses() throws DaoException;
/**
* 插入记录
* @param course
* @throws DaoException
*/
public void insertCourse(Course course) throws DaoException;
}
package com.cvicse.dao;
import java.util.List;
import com.cvicse.dao.exception.DaoException;
import com.cvicse.po.Student;
public interface StudentDAO {
/**
* 查询方法
* @return
* @throws DaoException
*/
public List selectStudents() throws DaoException;
/**
* 添加方法
* @param student
* @throws DaoException
*/
public void insertStudent(Student student) throws DaoException;
/**
* 删除方法
* @param student
* @throws DaoException
*/
public void deleteStudent(Student student) throws DaoException;
/**
* 修改方法
* @param student
* @throws DaoException
*/
public void modifyStudent(Student student) throws DaoException;
}
5、定义DAO操作的模板类,将DAO层的常用操作类进行提取。
package com.cvicse.util;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import com.cvicse.dao.exception.DaoException;
import com.cvicse.dao.exception.DaoParameterException;
import com.cvicse.dao.refactor.RowMapper;
public class DaoOperateTemplate {
/**
* 查找单个记录对象
*
* @param sql
* @param args
* @param rowMapper
* @return
* @throws DaoException
*/
public Object find(String sql, Object[] args, RowMapper rowMapper)
throws DaoException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++)
ps.setObject(i + 1, args[i]);
rs = ps.executeQuery();
Object obj = null;
if (rs.next()) {
obj = rowMapper.mapRow(rs);
}
return obj;
} catch (SQLException e) {
throw new DaoException(e.getMessage(), e);
} finally {
try {
JDBCUtils.free(rs, ps, conn);
} catch (SQLException e) {
throw new DaoParameterException(e.getMessage(), e);
}
}
}
/**
* 查找多条记录对象
*
* @param sql
* @param args
* @param rowMapper
* @return
* @throws DaoException
*/
public List<Object> Query(String sql, Object[] args, RowMapper rowMapper)
throws DaoException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
List<Object> results = new ArrayList<Object>();
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++)
ps.setObject(i + 1, args[i]);
rs = ps.executeQuery();
Object obj = null;
while (rs.next()) {
obj = rowMapper.mapRow(rs);
results.add(obj);
}
return results;
} catch (SQLException e) {
throw new DaoException(e.getMessage(), e);
} finally {
try {
JDBCUtils.free(rs, ps, conn);
} catch (SQLException e) {
throw new DaoParameterException(e.getMessage(), e);
}
}
}
/**
* 更新操作
*
* @param sql
* @param args
* @param isGeneralKey
* @throws DaoException
*/
public void update(String sql, Object[] args, boolean isGeneralKey)
throws DaoException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = (isGeneralKey ? conn.prepareStatement(sql,
Statement.RETURN_GENERATED_KEYS) : conn
.prepareStatement(sql));
for (int i = 0; i < args.length; i++)
ps.setObject(i + 1, args[i]);
ps.executeUpdate();
} catch (SQLException e) {
throw new DaoException(e.getMessage(), e);
} finally {
try {
JDBCUtils.free(rs, ps, conn);
} catch (SQLException e) {
throw new DaoParameterException(e.getMessage(), e);
}
}
}
}
上面DAO通用操作类中定义接口,用于对象的转化。
package com.cvicse.dao.refactor;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @author Administrator
*
*/
public interface RowMapper {
/**
* 映射接口
* @param rs
* @return
* @throws SQLException
*/
public Object mapRow(ResultSet rs) throws SQLException;
}
6、定义具体DAO的实现,在DAO具体实现中,我们采用组合的方式引用通用类,正如设计原则中说的先考虑组合后考虑继承。所以我们在这里选择组合,而不用继承,同时继承对象的转换同样会存在问题。在每个具体DAO操作的实现类中,我们采用了策略模式。
package com.cvicse.dao.impl;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import com.cvicse.dao.CourseDAO;
import com.cvicse.dao.exception.DaoException;
import com.cvicse.dao.refactor.RowMapper;
import com.cvicse.po.Course;
import com.cvicse.util.DaoOperateTemplate;
public class CourseDAOImpl implements CourseDAO {
private DaoOperateTemplate daoTemplate = new DaoOperateTemplate();
public void insertCourse(Course course) throws DaoException {
// TODO Auto-generated method stub
String sql = "insert into course(id,name) values (?,?) ";
Object[] args = new Object[] { course.getId(), course.getName() };
daoTemplate.update(sql, args, false);
}
public List<Course> selectCourses() throws DaoException {
// TODO Auto-generated method stub
String sql = "select * from course where id=? ";
Object[] args = new Object[] { 1 };
List courseList = daoTemplate.Query(sql, args, new courseRowMapper());
return courseList;
}
/**
* 内部匿名类
*
* @author Administrator
*
*/
class courseRowMapper implements RowMapper {
public Object mapRow(ResultSet rs) throws SQLException {
Course course = new Course();
course.setId(rs.getLong("id"));
course.setName(rs.getString("name"));
return course;
}
}
}
package com.cvicse.dao.impl;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import com.cvicse.dao.StudentDAO;
import com.cvicse.dao.exception.DaoException;
import com.cvicse.dao.refactor.RowMapper;
import com.cvicse.po.Student;
import com.cvicse.util.DaoOperateTemplate;
public class StudentDAOImpl implements StudentDAO {
private DaoOperateTemplate daoTemplate = new DaoOperateTemplate();
/*
* (non-Javadoc)
*
* @see com.cvicse.dao.StudentDAO#deleteStudent(com.cvicse.po.Student)
*/
public void deleteStudent(Student student) throws DaoException {
// TODO Auto-generated method stub
String sql = "delete from user where id=?";
Object[] args = new Object[] { student.getId() };
daoTemplate.update(sql, args, false);
}
/*
* (non-Javadoc)
*
* @see com.cvicse.dao.StudentDAO#insertStudent(com.cvicse.po.Student)
*/
public void insertStudent(Student student) throws DaoException {
// TODO Auto-generated method stub
String sql = "insert into student(id,name) values (?,?) ";
Object[] args = new Object[] { student.getId(), student.getName() };
daoTemplate.update(sql, args, false);
}
public void modifyStudent(Student student) throws DaoException {
// TODO Auto-generated method stub
String sql = "update student set name=? where id=? ";
Object[] args = new Object[] { student.getName(), student.getId() };
daoTemplate.update(sql, args, false);
}
public List selectStudents() throws DaoException {
// TODO Auto-generated method stub
String sql = "select * from course where id=? ";
Object[] args = new Object[] { 1 };
List courseList = daoTemplate.Query(sql, args, new studentRowMapper());
return courseList;
}
/**
* 内部匿名类
*
* @author Administrator
*
*/
class studentRowMapper implements RowMapper {
public Object mapRow(ResultSet rs) throws SQLException {
Student student = new Student();
student.setId(rs.getLong("id"));
student.setName(rs.getString("name"));
return student;
}
}
}
7、我们定义工厂类,在定义工厂类,考虑到通用性,我们采用了反射机制加配置文件的形式来实现的。同时,在工厂模式中引入了饿汉式单例模式。
/**
*
*/
package com.cvicse.daofactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* 工厂类方法
*
*/
public class DaoFactory {
private static DaoFactory instance = new DaoFactory();//懒汉法声明对象
private static Properties pro;// 配置文件对象
private DaoFactory() {
try {
// 初始化配置文件
pro = new Properties();
// 采用类加载器方法读取配置文件信息到字节流对象,采用类加载灵活,不用写死
InputStream inputStream = DaoFactory.class.getClassLoader()
.getResourceAsStream("applicationContext.properties");
// 加载字节流对象
pro.load(inputStream);
} catch (IOException e) {
throw new ExceptionInInitializerError(e);
}
}
/**
* 单例模式获取唯一实例
*
* @return
*/
public static DaoFactory getInstance() {
return instance;
}
/**
* 根据配置文件的名字获取类的名字,采用反射机制获取其对象
*
* @param Key
* @return
*/
public Object getDAO(String Key) throws Exception {
String className = (String) pro.get(Key);
return (Class.forName(className).newInstance());
}
}
配置文件的内容如下:applicationContext.properties
courseDao=com.cvicse.dao.impl.CourseDAOImpl
studentsDao=com.cvicse.dao.impl.StudentDAOImpl
8、业务层的调用方式,这里用客户端方式模拟的。在业务层通过接口的方式调用,使得DAO层和业务层能够解耦。
package com.cvicse.Test;
import com.cvicse.dao.CourseDAO;
import com.cvicse.daofactory.DaoFactory;
/**
* @author Administrator
*
*/
public class ServiceClient {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
CourseDAO courseDao = (CourseDAO) DaoFactory.getInstance().getDAO(
"courseDao");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
总结:在这个DAO设计模式中,涉及到很多java的基础知识,同时,也涉及太多的模式。只有灵活应用,才能体会的其中的灵活。关于DAO具体实现可以采用spring的simpetempate会更能简化其中的实现。