註解+反射+JDBC,實現一個簡易的泛型DAO接口

1、實現思路

       一、定義3個Annotation(註解):Entity、Id、Column,Entity做用於Type級別,用於標識JavaBean與數據庫表名的映射關係。Id做用於Field級別,用於標識JavaBean中ID屬性與表中ID字段的映射關係,Column做用於Field級別,用於標識JavaBean中除ID屬性外的其它屬性與表中字段的映射關係。

     二、在Dao實現類中,經過反射API得到JavaBean中註解和屬性的信息,如:表名、字段。JavaBean屬性的名稱、數據類型等信息。而後將這些信息拼接成一條SQL語句,經過JDBC的方式與數據庫交互。

2、示例代碼

一、定義一個Dao公共類,提供得到數據庫鏈接與釋放數據庫資源的接口

package dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * 提供獲取數據庫鏈接、釋放資源的接口
 */
public class JdbcDaoHelper {
	
	/**
	 * 數據庫用戶名
	 */
	private static final String USER = "test";
	
	/**
	 * 數據庫密碼 
	 */
	private static final String PASSWORD = "test";
	
	/**
	 * 鏈接數據庫的地址
	 */
	private static final String URL = "jdbc:oracle:thin:@127.0.0.1:1521:study";
	
	private static Connection conn;
	
	/**
	 * 得到一個數據庫鏈接對象
	 * @return java.sql.Connection實例
	 */
	public static Connection getConnection() {
		try {
			if (conn == null) {
				Class.forName("oracle.jdbc.OracleDriver");
				conn = DriverManager.getConnection(URL, USER, PASSWORD);
			} else {
				return conn;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return conn;
	}
	
	/**
	 * 釋放數據庫資源
	 */
	public static void release(PreparedStatement ps,ResultSet rs) {
		try {
			if (conn != null) {
				conn.close();
				conn = null;
			}
			if (ps != null) {
				ps.close();
				ps = null;
			}
			if (rs != null) {
				rs.close();
				rs = null;
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

二、定義一個泛型Dao接口GenericDao<T>

package dao;

import java.util.List;
import java.util.Map;

public interface GenericDao<T> {
	
	public void save(T t) throws Exception;
	
	public void delete(Object id,Class<T> clazz) throws Exception;
	
	public void update(T t) throws Exception;
	
	public T get(Object id,Class<T> clazz) throws Exception;
	
	/**
	 * 根據條件查詢
	 * @param sqlWhereMap key:條件字段名 value:條件字段值
	 * @param clazz
	 * @return
	 * @throws Exception
	 */
	public List<T> findAllByConditions(Map<String,Object> sqlWhereMap,Class<T> clazz) throws Exception;
	
}

三、定義GenericDao<T>接口JDBC實現類JdbcGenericDaoImpl<T>

package dao;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import annotation.Column;
import annotation.Entity;
import annotation.Id;
import exception.NotFoundAnnotationException;

/**
 * 泛型DAO的JDBC實現
 * @author 楊信
 * @version 1.0
 */
public class JdbcGenericDaoImpl<T> implements GenericDao<T> {
	
	//表的別名
	private static final String TABLE_ALIAS = "t";

	@Override
	public void save(T t) throws Exception {
		Class<?> clazz = t.getClass();
		//得到表名
		String tableName = getTableName(clazz);
		//得到字段
		StringBuilder fieldNames = new StringBuilder();		//字段名
		List<Object> fieldValues = new ArrayList<Object>();	//字段值
		StringBuilder placeholders = new StringBuilder();	//佔位符
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			PropertyDescriptor pd = new PropertyDescriptor(field.getName(),t.getClass());
			if (field.isAnnotationPresent(Id.class)) {
				fieldNames.append(field.getAnnotation(Id.class).value()).append(",");
				fieldValues.add(pd.getReadMethod().invoke(t));
			} else if(field.isAnnotationPresent(Column.class)) {
				fieldNames.append(field.getAnnotation(Column.class).value()).append(",");
				fieldValues.add(pd.getReadMethod().invoke(t));
			}
			placeholders.append("?").append(",");
		}
		//刪除最後一個逗號
		fieldNames.deleteCharAt(fieldNames.length()-1);
		placeholders.deleteCharAt(placeholders.length()-1);
		
		//拼接sql
		StringBuilder sql = new StringBuilder("");
		sql.append("insert into ").append(tableName)
		   .append(" (").append(fieldNames.toString())
		   .append(") values (").append(placeholders).append(")") ;
		PreparedStatement ps = JdbcDaoHelper.getConnection().prepareStatement(sql.toString());
		//設置SQL參數佔位符的值
		setParameter(fieldValues, ps, false);
		//執行SQL
		ps.execute();
		JdbcDaoHelper.release(ps, null);
		
		System.out.println(sql + "\n" + clazz.getSimpleName() + "添加成功!");
	}


	@Override
	public void delete(Object id,Class<T> clazz) throws Exception {
		//得到表名
		String tableName = getTableName(clazz);
		//得到ID字段名和值
		String idFieldName = "";
		boolean flag = false;
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			if(field.isAnnotationPresent(Id.class)) {
				idFieldName = field.getAnnotation(Id.class).value();
				flag = true;
				break;
			}
		}
		if (!flag) {
			throw new NotFoundAnnotationException(clazz.getName() + " object not found id property.");
		}
		
		//拼裝sql
		String sql = "delete from " + tableName + " where " + idFieldName + "=?";
		PreparedStatement ps = JdbcDaoHelper.getConnection().prepareStatement(sql);
		ps.setObject(1, id);
		//執行SQL
		ps.execute();
		JdbcDaoHelper.release(ps,null);
		
		System.out.println(sql + "\n" + clazz.getSimpleName() + "刪除成功!");
	}

	@Override
	public void update(T t) throws Exception {
		Class<?> clazz = t.getClass();
		//得到表名
		String tableName = getTableName(clazz);
		//得到字段
		List<Object> fieldNames = new ArrayList<Object>();	//字段名
		List<Object> fieldValues = new ArrayList<Object>();	//字段值
		List<String> placeholders = new ArrayList<String>();//佔位符
		String idFieldName = "";
		Object idFieldValue = "";
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			PropertyDescriptor pd = new PropertyDescriptor(field.getName(),t.getClass());
			if (field.isAnnotationPresent(Id.class)) {
				idFieldName = field.getAnnotation(Id.class).value();
				idFieldValue = pd.getReadMethod().invoke(t);
			} else if(field.isAnnotationPresent(Column.class)) {
				fieldNames.add(field.getAnnotation(Column.class).value());
				fieldValues.add(pd.getReadMethod().invoke(t));
				placeholders.add("?");
			}
		}
		//ID做爲更新條件,放在集合中的最後一個元素
		fieldNames.add(idFieldName);
		fieldValues.add(idFieldValue);
		placeholders.add("?");
		
		//拼接sql
		StringBuilder sql = new StringBuilder("");
		sql.append("update ").append(tableName).append(" set ");
		int index = fieldNames.size() - 1;
		for (int i = 0; i < index; i++) {
			sql.append(fieldNames.get(i)).append("=").append(placeholders.get(i)).append(",");
		}
		sql.deleteCharAt(sql.length()-1).append(" where ").append(fieldNames.get(index)).append("=").append("?");
		
		//設置SQL參數佔位符的值
		PreparedStatement ps = JdbcDaoHelper.getConnection().prepareStatement(sql.toString());
		setParameter(fieldValues, ps, false);
		
		//執行SQL
		ps.execute();
		JdbcDaoHelper.release(ps, null);
		
		System.out.println(sql + "\n" + clazz.getSimpleName() + "修改爲功.");
	}

	@Override
	public T get(Object id,Class<T> clazz) throws Exception {
		String idFieldName = "";
		Field[] fields = clazz.getDeclaredFields();
		boolean flag = false;
		for (Field field : fields) {
			if (field.isAnnotationPresent(Id.class)) {
				idFieldName = field.getAnnotation(Id.class).value();
				flag = true;
				break;
			} 
		}
		
		if (!flag) {
			throw new NotFoundAnnotationException(clazz.getName() + " object not found id property.");
		}
		
		//拼裝SQL
		Map<String,Object> sqlWhereMap = new HashMap<String, Object>();
		sqlWhereMap.put(TABLE_ALIAS + "." + idFieldName, id);
		
		List<T> list = findAllByConditions(sqlWhereMap, clazz);
		return list.size() > 0 ? list.get(0) : null;
	}

	@Override
	public List<T> findAllByConditions(Map<String,Object> sqlWhereMap,Class<T> clazz) throws Exception {
		List<T> list = new ArrayList<T>();
		String tableName = getTableName(clazz);
		String idFieldName = "";
		//存儲全部字段的信息
		//經過反射得到要查詢的字段
		StringBuffer fieldNames = new StringBuffer();
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			String propertyName = field.getName();
			if (field.isAnnotationPresent(Id.class)) {
				idFieldName = field.getAnnotation(Id.class).value();
				fieldNames.append(TABLE_ALIAS + "." + idFieldName)
						  .append(" as ").append(propertyName).append(",");
			} else if (field.isAnnotationPresent(Column.class)) {
				fieldNames.append(TABLE_ALIAS + "." + field.getAnnotation(Column.class).value())
						  .append(" as ").append(propertyName).append(",");
			}
		}
		fieldNames.deleteCharAt(fieldNames.length()-1);
		
		//拼裝SQL
		String sql = "select " + fieldNames + " from " + tableName + " " + TABLE_ALIAS;
		PreparedStatement ps = null;
		List<Object> values = null;
		if (sqlWhereMap != null) {
			List<Object> sqlWhereWithValues = getSqlWhereWithValues(sqlWhereMap);
			if (sqlWhereWithValues != null) {
				//拼接SQL條件
				String sqlWhere = (String)sqlWhereWithValues.get(0);
				sql += sqlWhere;
				//獲得SQL條件中佔位符的值
				values = (List<Object>) sqlWhereWithValues.get(1);
			}
		} 
		
		//設置參數佔位符的值
		if (values != null) {
			ps = JdbcDaoHelper.getConnection().prepareStatement(sql);
			setParameter(values, ps, true);
		} else {
			ps = JdbcDaoHelper.getConnection().prepareStatement(sql);
		}
		
		
		//執行SQL
		ResultSet rs = ps.executeQuery();
		while(rs.next()) {
			T t = clazz.newInstance();
			initObject(t, fields, rs);
			list.add(t);
		}
		
		//釋放資源
		JdbcDaoHelper.release(ps, rs);
		
		System.out.println(sql);
		return list;
	}
	
	/**
	 * 根據結果集初始化對象
	 */
	private void initObject(T t, Field[] fields, ResultSet rs)
			throws SQLException, IntrospectionException,
			IllegalAccessException, InvocationTargetException {
		for (Field field : fields) {
			String propertyName = field.getName();
			Object paramVal = null;
			Class<?> clazzField = field.getType();
			if (clazzField == String.class) {
				paramVal = rs.getString(propertyName);
			} else if (clazzField == short.class || clazzField == Short.class) {
				paramVal = rs.getShort(propertyName);
			} else if (clazzField == int.class || clazzField == Integer.class) {
				paramVal = rs.getInt(propertyName);
			} else if (clazzField == long.class || clazzField == Long.class) {
				paramVal = rs.getLong(propertyName);
			} else if (clazzField == float.class || clazzField == Float.class) {
				paramVal = rs.getFloat(propertyName);
			} else if (clazzField == double.class || clazzField == Double.class) {
				paramVal = rs.getDouble(propertyName);
			} else if (clazzField == boolean.class || clazzField == Boolean.class) {
				paramVal = rs.getBoolean(propertyName);
			} else if (clazzField == byte.class || clazzField == Byte.class) {
				paramVal = rs.getByte(propertyName);
			} else if (clazzField == char.class || clazzField == Character.class) {
				paramVal = rs.getCharacterStream(propertyName);
			} else if (clazzField == Date.class) {
				paramVal = rs.getTimestamp(propertyName);
			} else if (clazzField.isArray()) {
				paramVal = rs.getString(propertyName).split(",");	//以逗號分隔的字符串
			} 
			PropertyDescriptor pd = new PropertyDescriptor(propertyName,t.getClass());
			pd.getWriteMethod().invoke(t, paramVal);
		}
	}
	
	/**
	 * 根據條件,返回sql條件和條件中佔位符的值
	 * @param sqlWhereMap key:字段名 value:字段值
	 * @return 第一個元素爲SQL條件,第二個元素爲SQL條件中佔位符的值
	 */
	private List<Object> getSqlWhereWithValues(Map<String,Object> sqlWhereMap) {
		if (sqlWhereMap.size() <1 ) return null;
		List<Object> list = new ArrayList<Object>();
		List<Object> fieldValues = new ArrayList<Object>();
		StringBuffer sqlWhere = new StringBuffer(" where ");
		Set<Entry<String, Object>> entrySets = sqlWhereMap.entrySet();
		for (Iterator<Entry<String, Object>> iteraotr = entrySets.iterator();iteraotr.hasNext();) {
			Entry<String, Object> entrySet = iteraotr.next();
			fieldValues.add(entrySet.getValue());
			Object value = entrySet.getValue();
			if (value.getClass() == String.class) {
				sqlWhere.append(entrySet.getKey()).append(" like ").append("?").append(" and ");
			} else {
				sqlWhere.append(entrySet.getKey()).append("=").append("?").append(" and ");
			}
		}
		sqlWhere.delete(sqlWhere.lastIndexOf("and"), sqlWhere.length());
		list.add(sqlWhere.toString());
		list.add(fieldValues);
		return list;
	}
	
	
	/**
	 * 得到表名
	 */
	private String getTableName(Class<?> clazz) throws NotFoundAnnotationException {
		if (clazz.isAnnotationPresent(Entity.class)) {
			Entity entity = clazz.getAnnotation(Entity.class);
			return entity.value();
		} else {
			throw new NotFoundAnnotationException(clazz.getName() + " is not Entity Annotation.");
		}
	}
	
	/**
	 * 設置SQL參數佔位符的值
	 */
	private void setParameter(List<Object> values, PreparedStatement ps, boolean isSearch)
			throws SQLException {
		for (int i = 1; i <= values.size(); i++) {
			Object fieldValue = values.get(i-1);
			Class<?> clazzValue = fieldValue.getClass();
			if (clazzValue == String.class) {
				if (isSearch) 
					ps.setString(i, "%" + (String)fieldValue + "%");
				else
					ps.setString(i,(String)fieldValue);
					
			} else if (clazzValue == boolean.class || clazzValue == Boolean.class) {
				ps.setBoolean(i, (Boolean)fieldValue);
			} else if (clazzValue == byte.class || clazzValue == Byte.class) {
				ps.setByte(i, (Byte)fieldValue);
			} else if (clazzValue == char.class || clazzValue == Character.class) {
				ps.setObject(i, fieldValue,Types.CHAR);
			} else if (clazzValue == Date.class) {
				ps.setTimestamp(i, new Timestamp(((Date) fieldValue).getTime()));
			} else if (clazzValue.isArray()) {
				Object[] arrayValue = (Object[]) fieldValue;
				StringBuffer sb = new StringBuffer();
				for (int j = 0; j < arrayValue.length; j++) {
					sb.append(arrayValue[j]).append("、");
				}
				ps.setString(i, sb.deleteCharAt(sb.length()-1).toString());
			} else {
				ps.setObject(i, fieldValue, Types.NUMERIC);
			}
		}
	}
}

四、定義三個註解Entity、Id、Column,生命週期保存在運行期間,以便經過反射獲取
1)、Entity
package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 數據庫表的的名稱
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Entity {

	/**
	 * 表名
	 */
	String value();
	
}
2)、Id
package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 標識數據庫字段的ID
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Id {

	/**
	 * ID的名稱
	 * @return
	 */
	String value();
	
}
3)、Column
package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 標識數據庫字段的名稱
 * @author 楊信
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
	
	/**
	 * 字段名稱
	 */
	String value();
	
	/**
	 * 字段的類型
	 * @return
	 */
	Class<?> type() default String.class;
	
	/**
	 * 字段的長度
	 * @return
	 */
	int length() default 0;

}


5、定義一個JavaBean,用於測試使用java

要求:sql

1)、類名必須用Entity註解標識,並指定數據庫中對應的表名數據庫

2)、Id屬性必須用Id註解標識,並指定表中所對應的字段名編程

3)、其它屬性必須用Column註解標識,並指定表中所對應的字段名數組

4)、JavaBean屬性的數據類型目前只實現了8大基本數據類型、String和這些基本類型的數組類型oracle

5)、JavaBean屬性目前沒有作字段的長度與類型的判斷,待之後改進。app

package model;

import java.util.Date;

import annotation.Column;
import annotation.Entity;
import annotation.Id;

/**
 * 圖書
 */
@Entity("t_book")	//表名
public class Book {

	/**
	 * 圖書編號
	 */
	@Id("t_isbn")
	private String isbn;

	/**
	 * 書名
	 */
	@Column("t_name")
	private String name;

	/**
	 * 做者
	 */
	@Column("t_author")
	private String author;

	/**
	 * 出版社
	 */
	@Column("t_publishing")
	private String publishing;

	/**
	 * 出版時間
	 */
	@Column(value = "t_pubdate")
	private Date pubdate;

	/**
	 * 價格
	 */
	@Column(value = "t_price")
	private double price;

	public String getIsbn() {
		return isbn;
	}

	public void setIsbn(String isbn) {
		this.isbn = isbn;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getAuthor() {
		return author;
	}

	public void setAuthor(String author) {
		this.author = author;
	}

	public String getPublishing() {
		return publishing;
	}

	public void setPublishing(String publishing) {
		this.publishing = publishing;
	}

	public Date getPubdate() {
		return pubdate;
	}

	public void setPubdate(Date pubdate) {
		this.pubdate = pubdate;
	}

	public double getPrice() {
		return price;
	}

	public void setPrice(double price) {
		this.price = price;
	}

	@Override
	public String toString() {
		return "書名: " + name + " 圖書編號: " + isbn + " 做者: " + author
				+ " 出版社: " + publishing + " 出版時間: " + pubdate
				+ " 價格: " + price;
	}
}

6、使用Junit4進行單元測試

package xml;

import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import model.Book;

import org.junit.BeforeClass;
import org.junit.Test;

import util.DateUtils;
import dao.GenericDao;
import dao.JdbcGenericDaoImpl;

/**
 * 測試泛型DAO的CRUD操做
 */
public class GenericDaoTest {
	
	private GenericDao<Book> bookDao = new JdbcGenericDaoImpl<Book>();
	
	private static InputStream is;

	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		is = XmlParserTest.class.getResourceAsStream("/books.xml");
	}
	
	@Test
	public void testSave() throws Exception {
		List<Book> books = SaxHelper.saxReader(is);
		for (Book book : books) {
			bookDao.save(book);
		}
	}
	
	@Test
	public void testStudentFindAll1() throws Exception {
		System.out.println("\n-------------更新、刪除前,測試查詢全部記錄--------------------");
		List<Book> books = bookDao.findAllByConditions(null, Book.class);
		for (Book book : books) {
			System.out.println(book);
		}
	} 
	
	@Test
	public void testDelete() throws Exception {
		System.out.println("\n-------------測試刪除一條記錄--------------------");
		bookDao.delete("9787111349662",Book.class);
	}
	
	@Test
	public void testGet() throws Exception {
		System.out.println("\n-------------測試查詢一條記錄--------------------");
		Book book = bookDao.get("9787121025389", Book.class);
		System.out.println(book);
	}
	
	@Test
	public void testUpdate() throws Exception {
		System.out.println("\n-------------測試修改一條記錄--------------------");
		Book book = new Book();
		book.setIsbn("9787121025389");
		book.setName("JAVA面向對象編程");
		book.setAuthor("孫衛琴");
		book.setPublishing("電子工業出版社");
		book.setPubdate(DateUtils.string2Date("yyyy-MM-dd", "2006-07-01"));
		book.setPrice(50.6);
		bookDao.update(book);
	}
	
	@Test
	public void testStudentFindAll2() throws Exception {
		System.out.println("\n-------------更新、刪除前,測試根據條件查詢全部記錄--------------------");
		Map<String,Object> sqlWhereMap = new HashMap<String, Object>();
		//sqlWhereMap.put("t_isbn", "9787111213826");
		//sqlWhereMap.put("t_name", "Java");
		sqlWhereMap.put("t_publishing", "機械工業出版社");
		//sqlWhereMap.put("t_pubdate", new Date(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2007-01-01 12:06:00").getTime()));
		List<Book> books = bookDao.findAllByConditions(null, Book.class);
		for (Book book : books) {
			System.out.println(book);
		}
	} 

}
7、測試結果

books.xml請見http://blog.csdn.net/xyang81/article/details/7247169ide

說明:功能比較簡單,代碼寫得也比較笨拙,還有待優化,誠請指教!單元測試

相關文章
相關標籤/搜索