又一个使用annotation的orm的实现,非常无聊的东西。
不过实现过程中,思考了一下,感觉从select出发的orm往往是非常难用的。再简单的系统,其select语句也会出现复杂的内外连接,分组等查询逻辑。
但是在数据存储部分,也就是upate ,insert,delete部分,逻辑往往是非常简单的,使用orm会得到比较好的效果。
然后,要反思一下DAO模式,在通常理解的DAO模式中,数据的读取和写入是放在同一个DAO类当中的,在实际开发中,select相关的findXXX方法好getXXXX方法非常多,但是update,insert,add,save等方法个数都比较少,个人认为开发DAO或者说数据访问层程序的时候,应该讲DAO分为写入DAO和读取DAO,同时可以考虑在此实现读写分离,写入一个库,读取多个库,读加缓冲池,写直接写入,利用数据库机制表同步,以提高系统的伸缩性。
而且根据@1哥的答案,写入操作本身就是一个非常慢的动作,在写入时加入反射,犹如大象身上的虱子,相对磁盘来说,反射的消耗应该可以忽略。
然后,顺便对于NutzDAO进行一下小小的批评,作为DAO中间件,有些事情做了太多,反而不太好,比如引入所谓的whereClause的条件查询,真实业务中的select的SQL非常复杂,用简单的条件封装,很难满足真实开发需要的。
要知道hibernate一开始也很好用,但是面临的需求多了,边界划分不清,代码就冗肿了,慢慢就没法用了。
下面贴一点关键代码:
insert相关类:
//////////////////////
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package com.kamike.db.generic; import java.lang.reflect.Field; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; /** * * @author THiNk * @param <T> */ public class GenericInsert<T> { protected T t; protected ArrayList<GenericColumn> columns; protected HashMap<String, Object> data; protected ArrayList<Field> fields; protected String tableName; public GenericInsert(T t) { this.t = t; tableName = t.getClass().getAnnotation(TableName.class).value(); Field[] fs = t.getClass().getDeclaredFields(); columns = new ArrayList<>(); for (Field field : fs) { FieldName fieldName = field.getAnnotation(FieldName.class); if (fieldName != null) { String fname; if ("".equals(fieldName.value())) { fname = field.getName(); } else { fname = fieldName.value(); } GenericColumn col = new GenericColumn(); col.setName(fname); col.setField(field); if (field.getType() == int.class) { col.setType(GenericType.Int); col.setIntValue(GenericReflect.getInt(field, t)); columns.add(col); } else if (field.getType() == int.class) { col.setType(GenericType.Double); col.setDoubleValue(GenericReflect.getDouble(field, t)); columns.add(col); } else if (field.getType() == String.class) { col.setType(GenericType.String); col.setStrValue(GenericReflect.getString(field, t)); columns.add(col); } else if (field.getType() == long.class) { col.setType(GenericType.Long); col.setLongValue(GenericReflect.getLong(field, t)); columns.add(col); } else if (field.getType() == Date.class) { col.setType(GenericType.Timestamp); col.setTimestampValue(new Timestamp(GenericReflect.getDate(field, t).getTime())); columns.add(col); } else if (field.getType() == boolean.class) { col.setType(GenericType.Boolean); col.setBooleanValue(GenericReflect.getBoolean(field, t)); columns.add(col); } } } } public String sql() { StringBuffer buffer = new StringBuffer(); buffer.append("insert into "); buffer.append(tableName); buffer.append("( "); StringBuffer values = new StringBuffer(); for (Iterator<GenericColumn> it = columns.iterator(); it.hasNext();) { GenericColumn column = it.next(); buffer.append(column.getName()); values.append("?"); if (it.hasNext()) { buffer.append(","); values.append(","); } } buffer.append(")"); buffer.append(" values("); buffer.append(values); buffer.append(") "); return buffer.toString(); } public int bind(PreparedStatement ps) throws SQLException { if (ps == null) { return 0; } int i = 1; for (Iterator<GenericColumn> it = columns.iterator(); it.hasNext();) { GenericColumn column = it.next(); switch (column.getType()) { case Int: ps.setInt(i, column.getIntValue()); break; case Long: ps.setLong(i, column.getLongValue()); break; case Double: ps.setDouble(i, column.getDoubleValue()); break; case Boolean: ps.setBoolean(i, column.getBooleanValue()); break; case Timestamp: ps.setTimestamp(i, column.getTimestampValue()); break; case String: ps.setString(i, column.getStrValue()); break; } i++; } return i; } }
反射工具类:
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package com.kamike.db.generic; import java.lang.reflect.Field; import java.util.Date; /** * * @author THiNk */ public class GenericReflect { public static Object get(Field field, Object obj) { try { if (!field.isAccessible()) { field.setAccessible(true); } return null == obj ? null : field.get(obj); } catch (Exception e) { return null; } } public static String getString(Field field, Object obj) { try { if (!field.isAccessible()) { field.setAccessible(true); } return null == obj ? null : (String) field.get(obj); } catch (Exception e) { return null; } } public static int getInt(Field field, Object obj) { try { if (!field.isAccessible()) { field.setAccessible(true); } return null == obj ? null : (int) field.get(obj); } catch (Exception e) { return 0; } } public static double getDouble(Field field, Object obj) { try { if (!field.isAccessible()) { field.setAccessible(true); } return null == obj ? null : (double) field.get(obj); } catch (Exception e) { return 0d; } } public static long getLong(Field field, Object obj) { try { if (!field.isAccessible()) { field.setAccessible(true); } return null == obj ? null : (long) field.get(obj); } catch (Exception e) { return 0l; } } public static Date getDate(Field field, Object obj) { try { if (!field.isAccessible()) { field.setAccessible(true); } return null == obj ? null : (Date) field.get(obj); } catch (Exception e) { return new Date(System.currentTimeMillis()); } } public static boolean getBoolean(Field field, Object obj) { try { if (!field.isAccessible()) { field.setAccessible(true); } return null == obj ? null : (boolean) field.get(obj); } catch (Exception e) { return false; } } public static void set(Object obj, Field field, String value) { try { if (!field.isAccessible()) { field.setAccessible(true); } field.set(obj, value); } catch (Exception e) { } } public static void set(Object obj, Field field, int value) { try { if (!field.isAccessible()) { field.setAccessible(true); } field.set(obj, value); } catch (Exception e) { } } public static void set(Object obj, Field field, boolean value) { try { if (!field.isAccessible()) { field.setAccessible(true); } field.set(obj, value); } catch (Exception e) { } } public static void set(Object obj, Field field, Date value) { try { if (!field.isAccessible()) { field.setAccessible(true); } field.set(obj, value); } catch (Exception e) { } } public static void set(Object obj, Field field, long value) { try { if (!field.isAccessible()) { field.setAccessible(true); } field.set(obj, value); } catch (Exception e) { } } public static void set(Object obj, Field field, double value) { try { if (!field.isAccessible()) { field.setAccessible(true); } field.set(obj, value); } catch (Exception e) { } } }
表格的列处理的实体对象
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package com.kamike.db.generic; import java.lang.reflect.Field; import java.sql.Timestamp; import java.util.Date; /** * * @author THiNk */ public class GenericColumn { private String name; private GenericType type; private Field field; private Object value; private String strValue; private int intValue; private double doubleValue; private Timestamp timestampValue; private long longValue; private boolean booleanValue; /** * @return the name */ public String getName() { return name; } /** * @param name the name to set */ public void setName(String name) { this.name = name; } /** * @return the type */ public GenericType getType() { return type; } /** * @param type the type to set */ public void setType(GenericType type) { this.type = type; } /** * @return the field */ public Field getField() { return field; } /** * @param field the field to set */ public void setField(Field field) { this.field = field; } /** * @return the value */ public Object getValue() { return value; } /** * @param value the value to set */ public void setValue(Object value) { this.value = value; } /** * @return the strValue */ public String getStrValue() { return strValue; } /** * @param strValue the strValue to set */ public void setStrValue(String strValue) { this.strValue = strValue; } /** * @return the intValue */ public int getIntValue() { return intValue; } /** * @param intValue the intValue to set */ public void setIntValue(int intValue) { this.intValue = intValue; } /** * @return the doubleValue */ public double getDoubleValue() { return doubleValue; } /** * @param doubleValue the doubleValue to set */ public void setDoubleValue(double doubleValue) { this.doubleValue = doubleValue; } /** * @return the timestampValue */ public Timestamp getTimestampValue() { return timestampValue; } /** * @param timestampValue the timestampValue to set */ public void setTimestampValue(Timestamp timestampValue) { this.timestampValue = timestampValue; } /** * @return the longValue */ public long getLongValue() { return longValue; } /** * @param longValue the longValue to set */ public void setLongValue(long longValue) { this.longValue = longValue; } /** * @return the booleanValue */ public boolean getBooleanValue() { return booleanValue; } /** * @param booleanValue the booleanValue to set */ public void setBooleanValue(boolean booleanValue) { this.booleanValue = booleanValue; } }
支持类型的枚举
package com.kamike.db.generic; /** * * @author THiNk */ public enum GenericType { String, Int, Long, Boolean, Double, Timestamp }
下面是几个annotation的定义
package com.kamike.db.generic; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * * @author THiNk */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) @Documented public @interface Id { String value() default "id"; }
package com.kamike.db.generic; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * * @author THiNk */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface FieldName { String value() default ""; }package com.kamike.db.generic; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TableName { String value() default ""; }
下面贴一下,修改和删除的实现类:
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package com.kamike.db.generic; import java.lang.reflect.Field; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; /** * * @author THiNk * @param <T> */ public class GenericUpdate<T> { protected T t; protected ArrayList<GenericColumn> columns; protected ArrayList<GenericColumn> ids; protected String tableName; public GenericUpdate(T t) { this.t = t; tableName = t.getClass().getAnnotation(TableName.class).value(); Field[] fs = t.getClass().getDeclaredFields(); columns = new ArrayList<>(); for (Field field : fs) { FieldName fieldName = field.getAnnotation(FieldName.class); if (fieldName != null) { String fname; if ("".equals(fieldName.value())) { fname = field.getName(); } else { fname = fieldName.value(); } GenericColumn col = new GenericColumn(); col.setName(fname); col.setField(field); if (field.getType() == int.class) { col.setType(GenericType.Int); col.setIntValue(GenericReflect.getInt(field, t)); columns.add(col); Id id = field.getAnnotation(Id.class); if (id != null) { ids.add(col); } } else if (field.getType() == int.class) { col.setType(GenericType.Double); col.setDoubleValue(GenericReflect.getDouble(field, t)); columns.add(col); Id id = field.getAnnotation(Id.class); if (id != null) { ids.add(col); } } else if (field.getType() == String.class) { col.setType(GenericType.String); col.setStrValue(GenericReflect.getString(field, t)); columns.add(col); Id id = field.getAnnotation(Id.class); if (id != null) { ids.add(col); } } else if (field.getType() == long.class) { col.setType(GenericType.Long); col.setLongValue(GenericReflect.getLong(field, t)); columns.add(col); Id id = field.getAnnotation(Id.class); if (id != null) { ids.add(col); } } else if (field.getType() == Date.class) { col.setType(GenericType.Timestamp); col.setTimestampValue(new Timestamp(GenericReflect.getDate(field, t).getTime())); columns.add(col); Id id = field.getAnnotation(Id.class); if (id != null) { ids.add(col); } } else if (field.getType() == boolean.class) { col.setType(GenericType.Boolean); col.setBooleanValue(GenericReflect.getBoolean(field, t)); columns.add(col); Id id = field.getAnnotation(Id.class); if (id != null) { ids.add(col); } } } } } public String sql() { StringBuffer buffer = new StringBuffer(); buffer.append("update "); buffer.append(tableName); buffer.append(" set "); for (Iterator<GenericColumn> it = columns.iterator(); it.hasNext();) { GenericColumn column = it.next(); buffer.append(column.getName()); buffer.append("=?"); if (it.hasNext()) { buffer.append(", "); } } buffer.append(" where "); for (Iterator<GenericColumn> it = ids.iterator(); it.hasNext();) { GenericColumn column = it.next(); buffer.append(column.getName()); buffer.append("=? "); if (it.hasNext()) { buffer.append(" and "); } } return buffer.toString(); } public String rawSql() { StringBuffer buffer = new StringBuffer(); buffer.append("update "); buffer.append(tableName); buffer.append(" set "); for (Iterator<GenericColumn> it = columns.iterator(); it.hasNext();) { GenericColumn column = it.next(); buffer.append(column.getName()); buffer.append("=?"); if (it.hasNext()) { buffer.append(", "); } } return buffer.toString(); } public int bind(PreparedStatement ps) throws SQLException { if (ps == null) { return 0; } int i = 1; for (Iterator<GenericColumn> it = columns.iterator(); it.hasNext();) { GenericColumn column = it.next(); switch (column.getType()) { case Int: ps.setInt(i, column.getIntValue()); break; case Long: ps.setLong(i, column.getLongValue()); break; case Double: ps.setDouble(i, column.getDoubleValue()); break; case Boolean: ps.setBoolean(i, column.getBooleanValue()); break; case Timestamp: ps.setTimestamp(i, column.getTimestampValue()); break; case String: ps.setString(i, column.getStrValue()); break; } i++; } for (Iterator<GenericColumn> it = ids.iterator(); it.hasNext();) { GenericColumn column = it.next(); switch (column.getType()) { case Int: ps.setInt(i, column.getIntValue()); break; case Long: ps.setLong(i, column.getLongValue()); break; case Double: ps.setDouble(i, column.getDoubleValue()); break; case Boolean: ps.setBoolean(i, column.getBooleanValue()); break; case Timestamp: ps.setTimestamp(i, column.getTimestampValue()); break; case String: ps.setString(i, column.getStrValue()); break; } i++; } return i; } public int rawBind (PreparedStatement ps) throws SQLException { if (ps == null) { return 0; } int i = 1; for (Iterator<GenericColumn> it = columns.iterator(); it.hasNext();) { GenericColumn column = it.next(); switch (column.getType()) { case Int: ps.setInt(i, column.getIntValue()); break; case Long: ps.setLong(i, column.getLongValue()); break; case Double: ps.setDouble(i, column.getDoubleValue()); break; case Boolean: ps.setBoolean(i, column.getBooleanValue()); break; case Timestamp: ps.setTimestamp(i, column.getTimestampValue()); break; case String: ps.setString(i, column.getStrValue()); break; } i++; } return i; } }
删除:
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package com.kamike.db.generic; import java.lang.reflect.Field; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; /** * * @author THiNk * @param <T> */ public class GenericDelete<T> { protected T t; protected ArrayList<GenericColumn> ids; protected String tableName; public GenericDelete(T t) { this.t = t; tableName = t.getClass().getAnnotation(TableName.class).value(); Field[] fs = t.getClass().getDeclaredFields(); ids = new ArrayList<>(); for (Field field : fs) { FieldName fieldName = field.getAnnotation(FieldName.class); if (fieldName != null) { String fname; if ("".equals(fieldName.value())) { fname = field.getName(); } else { fname = fieldName.value(); } GenericColumn col = new GenericColumn(); col.setName(fname); col.setField(field); if (field.getType() == int.class) { col.setType(GenericType.Int); col.setIntValue(GenericReflect.getInt(field, t)); ids.add(col); } else if (field.getType() == int.class) { col.setType(GenericType.Double); col.setDoubleValue(GenericReflect.getDouble(field, t)); ids.add(col); } else if (field.getType() == String.class) { col.setType(GenericType.String); col.setStrValue(GenericReflect.getString(field, t)); ids.add(col); } else if (field.getType() == long.class) { col.setType(GenericType.Long); col.setLongValue(GenericReflect.getLong(field, t)); ids.add(col); } else if (field.getType() == Date.class) { col.setType(GenericType.Timestamp); col.setTimestampValue(new Timestamp(GenericReflect.getDate(field, t).getTime())); ids.add(col); } else if (field.getType() == boolean.class) { col.setType(GenericType.Boolean); col.setBooleanValue(GenericReflect.getBoolean(field, t)); ids.add(col); } } } } public String sql() { StringBuffer buffer = new StringBuffer(); buffer.append("delete from "); buffer.append(tableName); buffer.append(" where "); for (Iterator<GenericColumn> it = ids.iterator(); it.hasNext();) { GenericColumn id = it.next(); buffer.append(id.getName()); buffer.append("=? "); if (it.hasNext()) { buffer.append(" and "); } } return buffer.toString(); } public int bind(PreparedStatement ps) throws SQLException { if (ps == null) { return 0; } int i = 1; for (Iterator<GenericColumn> it = ids.iterator(); it.hasNext();) { GenericColumn column = it.next(); switch (column.getType()) { case Int: ps.setInt(i, column.getIntValue()); break; case Long: ps.setLong(i, column.getLongValue()); break; case Double: ps.setDouble(i, column.getDoubleValue()); break; case Boolean: ps.setBoolean(i, column.getBooleanValue()); break; case Timestamp: ps.setTimestamp(i, column.getTimestampValue()); break; case String: ps.setString(i, column.getStrValue()); break; } i++; } return i; } }
最终的DAO实现:
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package com.kamike.db; import com.kamike.db.generic.GenericDelete; import com.kamike.db.generic.GenericInsert; import com.kamike.db.generic.GenericUpdate; import com.kamike.kami.MySQLBucketWriter; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.logging.Level; import java.util.logging.Logger; public abstract class GenericWriter<T> { //Protected protected String tableName; protected Transaction ts; protected GenericWriter(Transaction ts, String tableName) { this.tableName = tableName; this.ts= ts; } public boolean add(T t) { if (ts == null) { return false; } // String uuid= UUID.randomUUID().toString(); GenericInsert<T> insert = new GenericInsert<>(t); int success = 0; PreparedStatement ps = null; try { ps = ts.preparedStatement(insert.sql()); insert.bind(ps); success = ps.executeUpdate(); } catch (Exception e) { ts.rollback(); System.out.println(this.getClass().getName() + e.toString()); return false; } finally { try { if (ps != null) { ps.close(); ps = null; } } catch (SQLException ex) { Logger.getLogger(MySQLBucketWriter.class.getName()).log(Level.SEVERE, null, ex); } } return true; } public boolean delete(T t) { if (ts == null) { return false; } // String uuid= UUID.randomUUID().toString(); GenericDelete<T> delete = new GenericDelete<>(t); int success = 0; PreparedStatement ps = null; try { ps = ts.preparedStatement(delete.sql()); delete.bind(ps); success = ps.executeUpdate(); } catch (Exception e) { ts.rollback(); System.out.println(this.getClass().getName() + e.toString()); return false; } finally { try { if (ps != null) { ps.close(); ps = null; } } catch (SQLException ex) { Logger.getLogger(MySQLBucketWriter.class.getName()).log(Level.SEVERE, null, ex); } } return true; } public boolean edit(T t) { if (ts == null) { return false; } // String uuid= UUID.randomUUID().toString(); GenericUpdate<T> update = new GenericUpdate<>(t); int success = 0; PreparedStatement ps = null; try { ps = ts.preparedStatement(update.sql()); update.bind(ps); success = ps.executeUpdate(); } catch (Exception e) { ts.rollback(); System.out.println(this.getClass().getName() + e.toString()); return false; } finally { try { if (ps != null) { ps.close(); ps = null; } } catch (SQLException ex) { Logger.getLogger(MySQLBucketWriter.class.getName()).log(Level.SEVERE, null, ex); } } return true; } }
最后补一下Transaction的实现,这里实现方法有很多,这里给出的Transaction只是其中一种:
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package com.kamike.db; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author THiNk */ public class Transaction { protected Connection con; protected String dbName; protected boolean rollback; protected int originalTransactionIsolationLevel; public Transaction(String dbName) { this.dbName = dbName; } protected void init() { this.rollback = false; try { con = MultiDbInst.getInstance().getDatabase(dbName).getSingleConnection(); con.setAutoCommit(false); } catch (SQLException ex) { Logger.getLogger(MySQLTransaction.class.getName()).log(Level.SEVERE, null, ex); } } protected void setTransactionIsolationLevel(int transactionIsolationLevel) { try { if (con != null) { DatabaseMetaData dbmt = con.getMetaData(); if (dbmt.supportsTransactions()) { if (dbmt.supportsTransactionIsolationLevel(transactionIsolationLevel)) { originalTransactionIsolationLevel = con.getTransactionIsolation(); con.setTransactionIsolation(transactionIsolationLevel); } } } } catch (SQLException ex) { Logger.getLogger(MySQLTransaction.class.getName()).log(Level.SEVERE, null, ex); } } protected void resetTransactionIsolationLevel() { try { if (con != null) { DatabaseMetaData dbmt = con.getMetaData(); if (dbmt.supportsTransactions()) { if (dbmt.supportsTransactionIsolationLevel(originalTransactionIsolationLevel)) { con.setTransactionIsolation(originalTransactionIsolationLevel); } } } } catch (SQLException ex) { Logger.getLogger(MySQLTransaction.class.getName()).log(Level.SEVERE, null, ex); } } public void save() { try { if (this.rollback) { con.rollback(); } else { con.commit(); } } catch (SQLException ex) { Logger.getLogger(MySQLTransaction.class.getName()).log(Level.SEVERE, null, ex); } finally { try { if (con != null) { con.setAutoCommit(true); con.close(); con = null; } } catch (SQLException ex) { Logger.getLogger(MySQLTransaction.class.getName()).log(Level.SEVERE, null, ex); } } } /** * 如果忘记关闭连接池,那么对象自动销毁的时候,也需要归还链接 */ public void finalize() { try { if (con != null) { con.close(); con = null; } } catch (SQLException ex) { Logger.getLogger(MySQLTransaction.class.getName()).log(Level.SEVERE, null, ex); } try { super.finalize(); } catch (Throwable ex) { Logger.getLogger(MySQLTransaction.class.getName()).log(Level.SEVERE, null, ex); } } /** * @return the ps */ public PreparedStatement preparedStatement(String sql) throws SQLException { return con.prepareStatement(sql, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); } /** * @param rollback the rollback to set */ public void rollback() { this.rollback = true; } }
最后,再次感谢@1哥