通用DAO类

基础:

一般编写MVC的数据持久化层是使用DAO模式。所谓DAO,也就是Data Access Object(数据访问接口)。DAO与业务逻辑层传递的是一个实体对象,而这个实体对象是数据库表在程序中的一个映射。

所以对于数据库中要操作的每一张表,都要建立一个VO(实体对象)类,一个DAO接口,一个创建DAO的工厂类,以及一个DAO的实现类。而这些基本是一样的,如果程序需要访问多个表的时候,需要大量的重复劳动。就有了抽象出来的必要

思考:

首先分析DAO模式中的每一部分

VO(对象实体):这个类成员变量与数据库中每个字段一一对应,同时提供了相应是set和get方法来设置或取得数值。这个类一般是通过IDE自动生成的

DAO(数据访问接口):这个是业务逻辑层使用数据持久化层服务的一个接口

DAOFactory(工厂类):它提供了创建DAO类的一个方法,目的是更改数据持久化层实现时(比如更换数据库),不需要对业务逻辑层进行更改,只要再创建一个实现了DAO接口的类,再交给DAOFactory创建对象就可以了。为程序提供了更好的扩充性

DAOImpl(实现类):这个类实现了DAO接口。同时也是代码最多的类。

从上文分析看出,抽象DAO首先要从实现类下手。在不同的表中,DAO提供的操作基本类似,所以实现类所实现的方法基本相同,不同的地方主要有这样几个方面:

1、传递的实体类类型

2、实体类的取值赋值方法

3、SQL语句

这些问题第一个解决起来最容易,可以通过Java的泛型解决。第三个也比较容易,可以在创建时传入字段,执行时生成SQL语句。只有第二个最困难,因为并不知道实体类提供哪些方法,以及方法的返回值和参数列表,怎么样去调用未知的方法?

关键技术:

Java为我们提供了反射机制,来进行一些对象的操作。

比如通过这个方法可以让加载器加载一个类:

Class c = Class.forName(className);

得到Class对象以后,我们可以调用它的公有的无参构造方法,获得它的一个实例

Object o = c.newInstance();

也可以获得描述它所有方法的一个数组

Method[] m = c.getMethods();

也可以得到某一个方法的方法名

System.out.println(m[0].getName());
或者对一个方法的参数列表及返回值类型的描述
System.out.println(m[0].toGenericString());
通过以上方法,可以分析出哪个方法是我们想要的方法,我们怎么调用它呢
public Object invoke(Object obj, Object... args)

参数:
obj - 从中调用底层方法的对象
args - 用于方法调用的参数
返回:
使用参数 argsobj 上指派该对象所表示方法的结果

通过这个方法,在第一个参数传入一个创建的对象,2~n个参数传入调用方法的参数,就可以调用这个方法,
同时也可以接受得到Object类型的返回值。想了解更详细的内容可以参考JDK API
理解了以上的函数,我们可以动手编写这个类了
 

详细设计:

为了更规范,我们希望用户创建的VO类都有一个标志,所以我们创建一个接口,这个接口不需要实现任何方法,只起一个标记作用
package generalDAO;

/**
 * 实体类标记接口
 * 
 * @author 石莹
 *
 */
public interface VO {

}
 
因为是通过方法名的字符串来确定我们究竟要调用哪个方法,如果VO类并不是符合我们规范的类,应该抛出一个异常
package generalDAO;

/**
 * VO类不符合规范异常
 * @author 石莹
 *
 */
public class VOClassIllegalException extends Exception {

	private static final long serialVersionUID = 861316221880365983L;

	public VOClassIllegalException() {
		
	}
	
	public VOClassIllegalException(String str) {
		super(str);
	}
}
 
下面就是主要的部分,注释已经非常清楚
package generalDAO;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;

import connectionPool.ConnectionPool;


/**
 * 通用DAO类
 * 
 * 通过这个类,只需要按照命名约定编写一个实现了VO接口的实体类,就能操作数据库中相应的表
 * 方便的同时必然带来性能的降低。类的实现使用了Java的一些底层特性,可以供初学者学习
 * 
 * 使用DAO类必须遵循以下约定:
 * 
 * 1、所有实体类必须实现VO接口
 * 2、实体类的类名必须和数据库表名一致
 * 3、当实体类中同时存在getXXX()和setXXX()方法时,XXX可被识别为一个字段
 * 4、实体类中的字段名必须和数据库中的字段名一致
 * 5、主键必须使用“表名 + Id”的命名规则
 * 6、目前支持的类型:float,String,int
 * 
 * 另:使用需要传入我编写的一个简单的数据库连接池,请参考这里
 * http://blog.csdn.net/lingdushanke/archive/2010/09/11/5877163.aspx
 * 
 * (2010.9.19)
 * 
 * @author 石莹
 * @version 1.0
 *
 */
public class DAO {


	/** 数据库连接池 */
	private ConnectionPool connectionPool = null;

	/**
	 * 加载后的实体类
	 */
	@SuppressWarnings("unchecked")
	private Class voClass = null;


	/** 表名 */
	private String tableName = null;

	/** 表中的字段名 */
	private ArrayList fieldName = new ArrayList();

	/** VO类中的方法名 */
	private ArrayList methodName = new ArrayList();

	/**
	 * 构造方法
	 * @param voName VO类的类名
	 * @param connectionPool ConnectionPool类数据库连接池
	 * @throws ClassNotFoundException 类没有找到,抛出异常
	 * @throws VOClassIllegalException 如果VO类不符合规则,抛出异常
	 */
	public DAO(String voName, ConnectionPool connectionPool) 
			throws ClassNotFoundException, VOClassIllegalException {
		//加载VO类
		voClass = Class.forName(voName);

		//读取方法名
		for (Method me : voClass.getDeclaredMethods()) {
			methodName.add(me.getName());
		}

		//读取数据库表名
		StringBuffer buffer = new StringBuffer();
		int i = voName.length()-1;
		while (i >= 0 && voName.charAt(i) != '.') {
			buffer.insert(0, voName.charAt(i--));
		}
		tableName = buffer.toString();

		//获取字段名
		for (int i1=0; i1if (methodName.get(i1).startsWith("get")) {
				String f = methodName.get(i1).substring(3);
				if (methodName.contains("set" + f)) {
					fieldName.add(f);
				}
			}
		}

		//检查是否存在主键,并移到末尾
		boolean keyExists = false;
		for (i=0; iif (fieldName.get(i).compareToIgnoreCase(tableName + "Id") == 0) {
				keyExists = true;
				break;
			}
		}
		if (!keyExists) {
			throw new VOClassIllegalException();
		} else {
			fieldName.add(fieldName.remove(i));
		}

		//创建数据库连接池
		this.connectionPool = connectionPool;
	}


	/**
	 * 从表单获取所有记录
	 * @return 保存所有记录的ArrayList
	 * @throws InvocationTargetException 
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 * @throws SQLException 
	 * @throws IllegalArgumentException 
	 */
	public ArrayList getAll() throws IllegalArgumentException,
				SQLException, InstantiationException, IllegalAccessException,
				InvocationTargetException {
		String sql = "select * from " + tableName;
		return sqlQuery(sql);
	}


	/**
	 * 查询一条记录
	 * @param id 记录ID
	 * @return 表示记录的VO对象
	 * @throws InvocationTargetException 
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 * @throws SQLException 
	 * @throws IllegalArgumentException 
	 */
	public VO getById(String id) 
			throws IllegalArgumentException, SQLException, 
			InstantiationException, IllegalAccessException, 
			InvocationTargetException {
		String sql = "Select * from " + tableName + " where " + tableName + "id = '" + id + "'";
		ArrayList list = null;
		list = sqlQuery(sql);

		if (list.size() == 0) 
			return null;
		else 
			return list.get(0);
	}


	/**
	 * 插入一条数据
	 * @param o 要插入的数据
	 * @throws SQLException 
	 * @throws InvocationTargetException 
	 * @throws IllegalAccessException 
	 * @throws IllegalArgumentException 
	 */
	public void insert(VO o) 
			throws SQLException, IllegalArgumentException,
			IllegalAccessException, InvocationTargetException {
		//创建插入的SQL语句
		StringBuffer buffer = new StringBuffer();
		buffer.append("insert into " + tableName + " (");
		for (String s : fieldName) {
			buffer.append(s + " ,");
		}
		buffer.deleteCharAt(buffer.length()-1);
		buffer.append(")");
		buffer.append(" values ( ");
		for (@SuppressWarnings("unused") String s : fieldName) {
			buffer.append("? ,");
		}
		buffer.deleteCharAt(buffer.length()-1);
		buffer.append(")");
		String sql = buffer.toString();
		//执行SQL语句
		Connection conn = connectionPool.get();
		PreparedStatement pstat = conn.prepareStatement(sql);
		setValue(pstat, o);
		pstat.executeUpdate();

	}


	/**
	 * 更新一条记录
	 * @param o 要更新的VO对象,其中主键保存要更新的主键,其它值替代数据库中的当前值
	 * @throws SQLException 
	 * @throws InvocationTargetException 
	 * @throws IllegalAccessException 
	 * @throws IllegalArgumentException 
	 */
	public void update(VO o) 
			throws IllegalArgumentException, 
				IllegalAccessException, InvocationTargetException,
				SQLException {
		//创建插入的SQL语句
		StringBuffer buffer = new StringBuffer();
		buffer.append("update " + tableName + " set ");
		for (int i=0; i = ? ,");
		}
		buffer.deleteCharAt(buffer.length()-1);
		buffer.append(" where " + fieldName.get(fieldName.size()-1) + " = '?'");
		String sql = buffer.toString();
		//执行SQL语句
		Connection conn = connectionPool.get();
		PreparedStatement pstat = conn.prepareStatement(sql);
		setValue(pstat, o);
		pstat.executeUpdate();
	}
	
	/**
	 * 从数据库删除一条记录
	 * @param id 记录的ID
	 * @throws SQLException
	 */
	public void delete(String id) throws SQLException {
		String sql = "delete from " + tableName + " where " + tableName + "id = '" + id + "'";
		System.out.println(sql);
		sqlUpdate(sql);
	}


	/**
	 * 从结果集向对象设置值
	 * @param rs 从数据库中查询得到的结果集
	 * @return 结果集设置到的对象
	 * @throws InstantiationException 
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 * @throws InvocationTargetException
	 * @throws SQLException
	 */
	protected VO getValue(ResultSet rs) 
	throws InstantiationException, IllegalAccessException, 
	IllegalArgumentException, InvocationTargetException, 
	SQLException {
		VO o = (VO) voClass.newInstance();
		//每次得到一个字段名
		for (String s : fieldName) {
			//计数器
			int i=0;
			//从方法名里查找当前字段的set方法
			for (i=0; iif (methodName.get(i).equals("set" + s))
					break;
			}
			//得到找到的方法名的Method对象
			Method me = voClass.getMethods()[i];
			//获得当前对象set方法的描述信息
			String gstr = me.toGenericString();
			//解析描述信息,得到参数类型
			for (i=0; iif (gstr.charAt(i) == '(') {
					i++;
					break;
				}
			}
			StringBuffer buffer = new StringBuffer();
			while (i < gstr.length() && gstr.charAt(i) != ')') {
				buffer.append(gstr.charAt(i++));
			}
			String rstr = buffer.toString();
			//根据参数类型,调用结果集不同的方法获取值
			if (rstr.equals("java.lang.String")) {
				me.invoke(o, rs.getString(s));
			} else if (rstr.equals("int")) {
				me.invoke(o, rs.getInt(s));
			} else if (rstr.equals("float") || rstr.equals("double")) {
				me.invoke(o, rs.getFloat(s));
			}
		}
		return o;
	}


	/**
	 * 将一个VO对象的值设置到PreparedStatement对象中
	 * @param pstmt PreparedStatement对象
	 * @param o VO对象
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws SQLException
	 */
	protected void setValue(PreparedStatement pstmt, VO o)
	throws IllegalArgumentException, IllegalAccessException,
	InvocationTargetException, SQLException {
		for (int index=0; index//计数器
			int i=0;
			//从方法名里查找当前字段的set方法
			for (i=0; iif (methodName.get(i).equals("get" + s))
					break;
			}
			//得到找到的方法名的Method对象
			Method me = voClass.getMethods()[i];
			//获得当前对象set方法的描述信息
			String gstr = me.toGenericString();
			//解析描述信息,得到参数类型
			for (i=0; iif (gstr.charAt(i++) == ' ') {
					break;
				}
			}
			StringBuffer buffer = new StringBuffer();
			while (i//根据VO中get方法返回值调用不同函数
			if (rstr.equals("java.lang.String")) {
				String str = (String) me.invoke(o);
				pstmt.setString(index+1, "'" + str + "'");
			} else if (rstr.equals("int")) {
				Integer in = (Integer) me.invoke(o);
				pstmt.setInt(index+1, in);
			} else if (rstr.equals("float") || rstr.equals("double")) {
				Double dou = (Double) me.invoke(o);
				pstmt.setFloat(index+1, dou.floatValue());
			}
		}
	}


	/**
	 * 执行一条查询的SQL语句
	 * @param sql 要执行的SQL语句
	 * @return 得到的结果的实体类列表
	 * @throws SQLException
	 * @throws IllegalArgumentException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	public ArrayList sqlQuery(String sql) 
			throws SQLException, IllegalArgumentException, 
			InstantiationException, IllegalAccessException, 
			InvocationTargetException {
		ArrayList data = new ArrayList();
		Connection conn = connectionPool.get();
		PreparedStatement pstat = conn.prepareStatement(sql);
		ResultSet rs = pstat.executeQuery();
		while (rs.next()) {
			VO o = getValue(rs);
			data.add(o);
		}			
		connectionPool.close(conn);
		return data;
	}


	/**
	 * 执行一条用来修改的SQL语句
	 * @param sql 用来执行的SQL语句
	 * @throws SQLException
	 */
	public void sqlUpdate(String sql) throws SQLException {
		Connection conn = connectionPool.get();
		PreparedStatement pstat = conn.prepareStatement(sql);
		pstat.executeUpdate();
	}
}
 
编写完成以后打包发布
 

测试:

新建一个工程,导入connectionPool.jar和generalDAO.jar包,不用多说。
首先创建数据库,语句如下
CREATE TABLE [testVO] (
	[testVoId] [char] (10) COLLATE Chinese_PRC_CI_AS NULL ,
	[username] [char] (10) COLLATE Chinese_PRC_CI_AS NULL ,
	[password] [char] (10) COLLATE Chinese_PRC_CI_AS NULL 
) ON [PRIMARY]
GO
 
根据这个表在程序中编写一个VO类
import generalDAO.VO;


public class TestVO implements VO {

	private String testVoId;
	private String username;
	private String password;
	
	public String getTestVoId() {
		return testVoId;
	}
	public void setTestVoId(String testVoId) {
		this.testVoId = testVoId;
	}
	public String getUsername() {
		return username;
	}
	
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
}
下面编写主程序
 
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.SQLException;

import generalDAO.DAO;
import generalDAO.VOClassIllegalException;
import connectionPool.ConnectionPool;
import connectionPool.ConnectionPoolFactory;



public class test {

	/**
	 * @param args
	 * @throws VOClassIllegalException 
	 * @throws ClassNotFoundException 
	 * @throws InvocationTargetException 
	 * @throws IllegalAccessException 
	 * @throws SQLException 
	 * @throws IllegalArgumentException 
	 * @throws InstantiationException 
	 */
	public static void main(String[] args) 
		throws ClassNotFoundException, VOClassIllegalException,
		IllegalArgumentException, SQLException, IllegalAccessException, 
		InvocationTargetException, InstantiationException {
/*		ConnectionPool connectionPool = ConnectionPoolFactory.getSQLServerConnectionPool();
		connectionPool.setConnect("jdbc:sqlserver://localhost:7788;databaseName=test",
				"sa", "admin");
		DAO dao = new DAO("TestVO",connectionPool);
		
		TestVO tv = new TestVO();
		tv.setTestVoId("id");
		tv.setUsername("name");
		tv.setPassword("password");
		
		dao.insert(tv);
	*/
		
	//	dao.delete("id");
	
	/*
		TestVO tv = new TestVO();
		tv.setPassword("123");
		tv.setUsername("sss");
		tv.setTestVoId("id");
		dao.update(tv);
	*/
		
	//	System.out.println(((TestVO)dao.getById("id")).getUsername());
	}

}
测试结果需要对照SQL查询分析器来看,这里就不贴了。
可以看到通过这个类可以极大的减少代码量,但是带来了性能的降低。
 
posted @ 2010-09-20 00:07  石莹  阅读(2955)  评论(0编辑  收藏  举报