最近在学习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++; } } } } }