最近在學習JAVA,在連接Oracle數據庫的時候開始是沒有使用框架的。然後在練習當中遇到了一個煩惱那就是好多對數據庫的增刪該查操作都一遍遍的重複。就像這樣:
public void save(Emp emp){
Connection conn = null;
try{
conn = DBUtil.getConnection();
String sql =
"INSERT INTO emps_myzzw VALUES("
+ "emps_seq_myzzw.NEXTVAL,"
+ "?,?,?,?,?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, emp.getEname());
ps.setString(2, emp.getJob());
ps.setInt(3, emp.getMgr());
ps.setDate(4, emp.getHiredate());
ps.setDouble(5, emp.getSal());
ps.setDouble(6, emp.getComm());
ps.setInt(7, emp.getDeptno());
ps.executeUpdate();
}catch(SQLException e){
e.printStackTrace();
throw new RuntimeException("",e);
}finally{
DBUtil.close(conn);
}
}
每個類有一個Dao,這樣要重複很多次,而且每一次對數據庫的連接都要trycatch。
當繼承了這個類後,只需要執行父類的work方法即可:
public void save(Cost c){
String sql = "INSERT INTO cost_myzzw VALUES "
+"(cost_seq_myzzw.NEXTVAL,?,?,?,?,'1',?,sysdate,null,?)";
work(Cost.class,false,sql,null,
c.getName(),
(Object)c.getBaseDuration(),
(Object)c.getBaseCost(),
(Object)c.getUnitCost(),
c.getDescr(),
c.getCostType());
}
public List<Cost> findAll(){
String sql =
"SELECT * FROM cost_myzzw "+
"ORDER BY cost_id";
List<Cost> list = work(Cost.class,true,sql,null);
return list;
}
正好最近看了Core Java,於是就想到了用Reflect來寫一個簡化這些操作的框架。
首先是如果執行的SQL需要傳參怎麼辦?
以前的操作是
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, emp.getEname());
ps.setString(2, emp.getJob());
ps.setInt(3, emp.getMgr());
ps.setDate(4, emp.getHiredate());
ps.setDouble(5, emp.getSal());
ps.setDouble(6, emp.getComm());
ps.setInt(7, emp.getDeptno());
這種方法必然不通用,所以我想用Reflect調用PreparedStatement的set方法。我先把它的所有方法存在一個實例域的HashMap中:
private Map<String,Method> methods;
然後需要在初始化塊當中得到所有的方法:
//方法的名字當做Key。
{
methods=new HashMap<>();
Method[] ms = PreparedStatement.class.getDeclaredMethods();
for(Method m:ms){
methods.put(m.getName(), m);
}
}
這樣就可以用Reflect給從連接得到的PreparedStatement實例賦值了:
private void evaluate(PreparedStatement ps, Object... params)
throws IllegalAccessException, InvocationTargetException, SQLException {
for (int i = 0; i < params.length; i++) {
if (params[i] != null) {
Class<? extends Object> cl = params[i].getClass();
String name = cl.getSimpleName();
Method m = getMethod(cl, name);
m.invoke(ps, i + 1, params[i]);
} else {
ps.setObject(i + 1, params[i]);
}
}
}
調用的時候需要判斷如果是插入操作需不需要返回主鍵:
private PreparedStatement getPrepareStatement(String sql, String[] IRV, Connection conn) throws SQLException {
if (IRV == null) {
return conn.prepareStatement(sql);
} else {
return conn.prepareStatement(sql, IRV);
}
}
然後又有三種情況:
- 查詢操作有返回值
- 插入操作有返回值
- 不需要返回值的Update語句
第一種情況,需要把得到的ResultSet解析成相應對象的實例,在以前的Dao當中是類似於這樣來解析固定的對象的:
Emp e = new Emp();
e.setEmpno(rs.getInt("empno"));
e.setComm(rs.getDouble("comm"));
e.setDeptno(rs.getInt("deptno"));
e.setEname(rs.getString("ename"));
e.setHiredate(rs.getDate("hiredate"));
e.setJob(rs.getString("job"));
e.setMgr(rs.getInt("mgr"));
e.setSal(rs.getDouble("sal"));
這樣的話每一個對象就要寫一遍,看着一大堆set,set也煩。
我們已經有每一個類了,所以就可以用Reflect直接創建類的對象並賦值,要創建那個類是通過參數傳進來的。
private <T> List<T> transReToOb(ResultSet rs, Class cl) {
List<T> list = new ArrayList<>();
try {
while (rs.next()) {
Object obj = cl.newInstance();
Field[] fileds = cl.getDeclaredFields();
AccessibleObject.setAccessible(fileds, true);
evaluateObj(rs, obj, fileds);
list.add((T) obj);
}
} catch (InstantiationException |
IllegalAccessException |
SecurityException |
SQLException e ) {
e.printStackTrace();
} catch (IllegalArgumentException |
InvocationTargetException e) {
e.printStackTrace();
}
return list;
}
這裏又要把ResultSet 的所有方法放到一個Map裏,方法是一樣的,需要注意的是裏面有很多重載的方法,我們只要參數類型爲String的所以在初始化塊中對Map進行初始化時要排除其他類型:
//初始化放ResultSet方法的HashMap
{
resultSetmethods=new HashMap<>();
Method[] ms = ResultSet.class.getDeclaredMethods();
for(Method m:ms){
Class[] cls = m.getParameterTypes();
if(cls.length>0&&cls[0]==String.class){ //只有參數類型是String的才保留
resultSetmethods.put(m.getName(), m);
}
}
}
然後就可以對參數進行賦值的操作了,具體過程是遍歷方法的每一個Field,得到它的類型,然後找到Map裏參數是這個類型的方法,在調用Map裏的方法得到ResultSet裏的值然後賦給Field。傳給ResultSet方法的表的列名是field的名字把所有大寫字母前加一個'_'。例如empName對應的列名就是emp_Name,Oracle是不區分大小寫的,所以這樣就可以了。
private void evaluateObj(ResultSet rs, Object obj, Field[] fileds)
throws IllegalAccessException, InvocationTargetException {
for (Field field : fileds) {
StringBuffer name = new StringBuffer(field.getName());
parseFieldName(name);
Class cl1 = field.getType();
Method m = getMethod(cl1);
field.set(obj, m.invoke(rs, name.toString()));
}
}
然後把實例添加到list裏返回就可以了。
第二種情況需要返回一個插入操作時生成的鍵值,這個我沒有想到用Reflect怎麼做,所以就把它寫成一個虛方法讓繼承的類來實現,然後條用的就是重寫的方法:
public abstract <T> List<T> transGeneratedKeys(ResultSet rs) throws SQLException;
在子類中類似這樣重寫:
public List<Role> transGeneratedKeys(ResultSet rs) throws SQLException {
List<Role> list = new ArrayList<>();
rs.next();
Role r = new Role();
r.setRoleId(new Integer(rs.getString(1)));
list.add(r);
return list;
}
然後就可以返回一個相應類的實例,獲取需要的字段就可以了。
第三種情況直接執行ps即可。這樣只要每一個類的DAO只要繼承這個類,就可以非常非常簡單的執行曾刪改查了@!
博主的個人博客:blog.leezw.net
另附上全部代碼:
package net.leezw.newjdbc;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
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 java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import util.DBUtil;
/**
* 處理大部分簡單的SQL增刪改查
*
* @author tarena
*
*/
abstract class GeneralSQL {
/**
* PreparedStatem的方法集合
*/
private Map<String,Method> methods;
private Map<String,Method> resultSetmethods;
{
methods=new HashMap<>();
Method[] ms = PreparedStatement.class.getDeclaredMethods();
for(Method m:ms){
methods.put(m.getName(), m);
}
}
{
resultSetmethods=new HashMap<>();
Method[] ms = ResultSet.class.getDeclaredMethods();
for(Method m:ms){
Class[] cls = m.getParameterTypes();
if(cls.length>0&&cls[0]==String.class){
resultSetmethods.put(m.getName(), m);
}
}
}
public static void main(String...params) throws SQLException{
//for test;
}
/**
* 通過傳入的參數執行SQL,並返回List結果。
* 執行插入操作的時候IRV需傳入空的String數組或者需要返回的主鍵列名。
* 執行其他操作時IRV可以指定爲null。
* @param hasReturn 是否有返回值,返回值包含兩種情況 1、執行查詢操作。2、執行插入操作,但是需要返回主鍵。
* @param sql 執行的SQL語句。
* @param IRV InsetResultValue 執行插入操作並需要返回的主鍵列,String數組。
* @param params SQL語句中的參數,必須按順序傳入。
* @return 指定類型的List集合。
*/
@SuppressWarnings("rawtypes")
public <T> List<T> work(Class cl1, boolean hasReturn, String sql, String[] IRV, Object... params) {
Connection conn = null;
try {
try {
conn = DBUtil.getConnection();
ResultSet rs = null;
PreparedStatement ps = getPrepareStatement(sql, IRV, conn);
evaluate(ps, params);
if (hasReturn) {
if (IRV == null) {
rs = ps.executeQuery();
return transReToOb(rs, cl1);
} else {
ps.executeUpdate();
rs = ps.getGeneratedKeys();
return transGeneratedKeys(rs);
}
} else {
ps.executeUpdate();
return null;
}
} finally {
DBUtil.close(conn);
}
} catch (SecurityException | IllegalArgumentException | IllegalAccessException
| InvocationTargetException e) {
e.printStackTrace();
throw new RuntimeException("", e);
}catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException("", e);
}
}
private PreparedStatement getPrepareStatement(String sql, String[] IRV, Connection conn) throws SQLException {
if (IRV == null) {
return conn.prepareStatement(sql);
} else {
return conn.prepareStatement(sql, IRV);
}
}
private void evaluate(PreparedStatement ps, Object... params)
throws IllegalAccessException, InvocationTargetException, SQLException {
for (int i = 0; i < params.length; i++) {
if (params[i] != null) {
Class<? extends Object> cl = params[i].getClass();
String name = cl.getSimpleName();
Method m = getMethod(cl, name);
m.invoke(ps, i + 1, params[i]);
} else {
ps.setObject(i + 1, params[i]);
}
}
}
private Method getMethod(Class<? extends Object> cl, String name) {
if (cl == Integer.class) {
return methods.get("setInt");
} else {
return methods.get("set" + name);
}
}
/**
* 重寫此方法,用於將特殊類型的Dao中,把ResultSet結果封裝成相應類型實例集合。
* @param rs SQL的執行結果集。
* @return 指定類型的List集合。
* @throws SQLException
*/
// public abstract <T> List<T> transResultSetToObject(ResultSet rs) throws SQLException;
/**
* 重寫此方法,用於封裝插入操作的SQL執行後返回的主鍵值。可以封裝成基本類型集合。
* @param rs 用於插入操作的SQL返回的主鍵集。
* @return 任意類型的List集合。
* @throws SQLException
*/
public abstract <T> List<T> transGeneratedKeys(ResultSet rs) throws SQLException;
@SuppressWarnings({ "unchecked", "rawtypes" })
private <T> List<T> transReToOb(ResultSet rs, Class cl) {
List<T> list = new ArrayList<>();
try {
while (rs.next()) {
Object obj = cl.newInstance();
Field[] fileds = cl.getDeclaredFields();
AccessibleObject.setAccessible(fileds, true);
evaluateObj(rs, obj, fileds);
list.add((T) obj);
}
} catch (InstantiationException |
IllegalAccessException |
SecurityException |
SQLException e ) {
e.printStackTrace();
} catch (IllegalArgumentException |
InvocationTargetException e) {
e.printStackTrace();
}
return list;
}
@SuppressWarnings("rawtypes")
private void evaluateObj(ResultSet rs, Object obj, Field[] fileds)
throws IllegalAccessException, InvocationTargetException {
for (Field field : fileds) {
StringBuffer name = new StringBuffer(field.getName());
parseFieldName(name);
Class cl1 = field.getType();
Method m = getMethod(cl1);
field.set(obj, m.invoke(rs, name.toString()));
}
}
@SuppressWarnings("rawtypes")
private Method getMethod(Class cl1) {
if (cl1 == Integer.class) {
return resultSetmethods.get("getInt");
} else {
return resultSetmethods.get("get" + cl1.getSimpleName());
}
}
private void parseFieldName(StringBuffer name) {
for (int i = 0; i < name.length(); i++) { if (i > 0) {
char ch = name.charAt(i);
if (Character.isUpperCase(ch)) {
name.insert(i, "_");
i++;
}
}
}
}
}