本篇介绍Panda ORM的核心功能(即添加简单注解即可对实体进行增删改查操作功能)的设计与实现。
1. 整体思路介绍
1,先了解项目结构如下,注意所有的包都在src目录的panda.orm下,配置文件config.properties处于src目录下。
2, annotation下是自定义注解,主要用于为实体类的列添加主键、外键相关的附加信息。这样Panda ORM运行的时候就知道实体类哪些列是主键、外键对应列了。
3,database包下数据库相关的类,其中核心是DataTable类,DataTable类通过实体类的类别实例化,通过反射读取实体了的class信息,DataTable最终保存的是实体类对应的表结构信息。
4,operation包下的IOperation定义了所有数据库操作类应该实现的方法,EntityOperation通过实体类的class来实例化,可以产生一个针对该实体类对象的增删改查操作的对象。EntityOperation就是通过DataTable读取实体类表结构信息,然后进行操作的时候翻译DataTable中的结构信息为对应sql语句执行即可。
5,exception是异常类,就是自定义了几种异常,比较简单不再详述。util下都是些工具、日志类,不属于本篇核心内容也不影响理解,不再展开讲解。
总之,通过注解描述实体-表对应关系,通过DataTable记录实体结构信息,通过EntityOperation将结构信息结合操作请求翻译为sql语句并执行。
2. 注解设计与实现
将列分为三种,主键列(需要标记是否自增长,因为关系到生成insert语句时是否写入主键),外键列(需要根据注解找到外键指向的表的列),普通列(没有注解)。
具体代码如下:
package panda.orm.annotation; //自定义枚举类型,仅用于标记主键列是否自增长,注意注解不支持Boolean类型做参数,所以用此下策 public enum AutoIncrement { TRUE,FALSE; } package panda.orm.annotation; import java.lang.annotation.*; @Target(ElementType.FIELD)//注解用于描述列 @Retention(RetentionPolicy.RUNTIME)//注解运行时可用 @Documented//可以java doc public @interface Key { public AutoIncrement value() default AutoIncrement.TRUE;//注解参数AutoIncrement,默认值是自增长 } package panda.orm.annotation; import java.lang.annotation.*; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ForeignKey { public String value() default "";//value参数表示外键指向的表的列名 }
3. 实体类结构解析器DataTable的实现
注意DataTable有三种列,分别是普通列、主键列、外键列,区别见代码(省略get set方法):
package panda.orm.database; public class Column {//普通列 //列名称 protected String name; //列值 protected String value; } public class KeyColumn extends Column{ //主键列多了个自增长属性 protected AutoIncrement autoIncrement; } public class ForeignKeyColumn extends Column{ //外键列包含主表名和主表外键对应列名 protected String foreignTableName; protected String foreignColumnName; }
重点来了就是DataTable类是如何读取实体类结构并转化为表结构的,如下:
public class DataTable { //该变量保存表对应的实体的类的信息 protected Class objClass; //表结构变量 protected String tableName;//表名 protected KeyColumn keyColumn=new KeyColumn();//主键列信息 protected Set<ForeignKeyColumn> fkeyColumns=new HashSet<ForeignKeyColumn>();//外键列信息 protected Set<Column> columns=new HashSet<Column>();//普通列信息 //构造函数,通过实体类结构初始化表结构 public DataTable(Class objClass){ this.objClass=objClass; tableName=objClass.getSimpleName(); Field[] fields = objClass.getDeclaredFields();//实体类的属性列表 for(Field field:fields){ if(field.isAnnotationPresent(Key.class)){ keyColumn=new KeyColumn(); keyColumn.setName(field.getName());//获取列名 Key mkey = (Key) field.getAnnotation(Key.class); keyColumn.setAutoIncrement(mkey.value());//获取是否自增长 }else if(field.isAnnotationPresent(ForeignKey.class)){ ForeignKeyColumn fkeyColumn=new ForeignKeyColumn(); ForeignKey fkey = (ForeignKey) field.getAnnotation(ForeignKey.class); fkeyColumn.setName(field.getName());//获取列名 fkeyColumn.setForeignTableName(field.getType().getSimpleName());//外键对应的主表名 fkeyColumn.setForeignColumnName(fkey.value());//外键对应的主表的列 fkeyColumns.add(fkeyColumn); }else{ Column column=new Column(); column.setName(field.getName()); columns.add(column); } } } }
上面这个表保存了表名、列名信息,但是有些操作,比如新增一个实体、修改一个实体,还需要实体对应的列的列值信息,所以还有一个方法DataTable.setValue如下:
//对于add和update操作,还需要表内各个列的值的信息 public void setValue(Object obj)throws Exception{ Class objClass=obj.getClass(); if(!objClass.getSimpleName().equals(tableName)){ throw new ObjectNotMatchException("实体与类别不匹配",this.getClass().getName(),"实体与类别不匹配",objClass.getSimpleName(),tableName); } Field[] fields = objClass.getDeclaredFields(); for(Field field:fields){ field.setAccessible(true); if(field.isAnnotationPresent(Key.class)){//主键列 keyColumn.setValue((String)field.get(obj)); }else if(field.isAnnotationPresent(ForeignKey.class)){ //外键列逻辑较为复杂,关键是要去取外键对应主表的列值 ForeignKey fkey = (ForeignKey) field.getAnnotation(ForeignKey.class); Object foreignObj=field.get(obj); Field foreignField=foreignObj.getClass().getDeclaredField(fkey.value()); foreignField.setAccessible(true); for(ForeignKeyColumn c:this.fkeyColumns){ if(c.getName().equals(field.getName())){ c.setValue((String)foreignField.get(foreignObj)); } } }else{ for(Column c:this.columns){//多个普通列 if(c.getName().equals(field.getName())){ c.setValue((String)field.get(obj)); } } } } }
4. 通过表结构类DataTable生成sql语句
通过一个简单的工厂类生成一般语句即可,常用的如下,这部分逻辑比较简单未加注释
package panda.orm.database; import panda.orm.annotation.AutoIncrement; public class SqlFactory { public static String CreateSelectPageSql(DataTable table,int offset,int rows){ StringBuilder sb=new StringBuilder(); sb.append("select * from "+table.getTableName()); for(ForeignKeyColumn f:table.getFkeyColumns()){ //为防止外键所在表即为本表,添加此业务逻辑 sb.append(","+f.getForeignTableName()+" as foreign_"+f.getForeignTableName()); } sb.append(" where 1=1 "); for(ForeignKeyColumn f:table.getFkeyColumns()){ sb.append(" and "+table.getTableName()+"."+f.getName()+"=foreign_"+f.getForeignTableName()+"."+f.getForeignColumnName()); } sb.append(" order by "+table.getKeyColumn().getName()+" limit "+offset+","+rows); return sb.toString(); } public static String CreateSelectAllSql(DataTable table){ StringBuilder sb=new StringBuilder(); sb.append("select * from "+table.getTableName()); for(ForeignKeyColumn f:table.getFkeyColumns()){ sb.append(","+f.getForeignTableName()+" as foreign_"+f.getForeignTableName()); } sb.append(" where 1=1 "); for(ForeignKeyColumn f:table.getFkeyColumns()){ sb.append(" and "+table.getTableName()+"."+f.getName()+"=foreign_"+f.getForeignTableName()+"."+f.getForeignColumnName()); } return sb.toString(); } public static String CreateSelectOneSql(DataTable table,String key){ StringBuilder sb=new StringBuilder(); sb.append("select * from "+table.getTableName()); for(ForeignKeyColumn f:table.getFkeyColumns()){ sb.append(","+f.getForeignTableName()+" as foreign_"+f.getForeignTableName()); } sb.append(" where 1=1 "); for(ForeignKeyColumn f:table.getFkeyColumns()){ sb.append(" and "+table.getTableName()+"."+f.getName()+"=foreign_"+f.getForeignTableName()+"."+f.getForeignColumnName()); } sb.append(" and "+table.getKeyColumn().getName()+"='"+key+"'"); return sb.toString(); } public static String CreateAddSql(DataTable table){ StringBuilder sb=new StringBuilder(); sb.append("insert into "+table.getTableName()+"("); if(table.getKeyColumn().getAutoIncrement()==AutoIncrement.FALSE){ sb.append(table.getKeyColumn().getName()); } for(Column c:table.getColumns()){ sb.append(c.getName()+","); } for(Column c:table.getFkeyColumns()){ sb.append(c.getName()+","); } sb.deleteCharAt(sb.length()-1); sb.append(")values("); if(table.getKeyColumn().getAutoIncrement()==AutoIncrement.FALSE){ sb.append(table.getKeyColumn().getValue()); } for(Column c:table.getColumns()){ sb.append("'"+c.getValue()+"',"); } for(Column c:table.getFkeyColumns()){ sb.append("'"+c.getValue()+"',"); } sb.deleteCharAt(sb.length()-1); sb.append(")"); return sb.toString(); } public static String CreateUpdateSql(DataTable table){ StringBuilder sb=new StringBuilder(); sb.append("update "+table.getTableName()+" set "); for(Column c:table.getColumns()){ sb.append(c.getName()+"='"+c.getValue()+"',"); } for(Column c:table.getFkeyColumns()){ sb.append(c.getName()+"='"+c.getValue()+"',"); } sb.deleteCharAt(sb.length()-1); sb.append(" where "+table.getKeyColumn().getName()+"='"+table.getKeyColumn().getValue()+"'"); return sb.toString(); } public static String CreateSelectCountSql(DataTable table){ String sql="select count("+table.getKeyColumn().getName()+") as count from "+table.getTableName(); return sql; } public static String CreateDeleteSql(DataTable table,String key){ String sql="delete from "+table.getTableName()+" where "+table.getKeyColumn().getName()+"='"+key+"'"; return sql; } }
5. 通过EntityOperation封装数据库实体操作
package panda.orm.operation; import java.lang.reflect.Field; import java.sql.ResultSet; import java.util.ArrayList; import java.util.Date; import java.util.List; import panda.orm.annotation.ForeignKey; import panda.orm.database.DataTable; import panda.orm.database.MySQLHandler; import panda.orm.database.SqlFactory; import panda.orm.exception.BaseException; import panda.orm.exception.SqlExcuteException; public class EntityOperation implements IOperation{ protected DataTable table; public EntityOperation(Class objClass){ table=new DataTable(objClass); } @Override public int selectCount() { MySQLHandler hand=new MySQLHandler(); ResultSet rs=null; int re=0; String sql=SqlFactory.CreateSelectCountSql(table); try { rs=hand.query(sql); while(rs.next()){ re=rs.getInt("count"); } return re; } catch (Exception ex) { new SqlExcuteException(ex.getMessage(),this.getClass().getName(),"sql执行异常",sql); return 0; }finally{ hand.sayGoodbye(); } } @Override public Object selectOne(String key) { MySQLHandler hand=new MySQLHandler(); ResultSet rs=null; Object one=null; String sql=SqlFactory.CreateSelectOneSql(table,key); try { rs=hand.query(sql); while(rs.next()){ one=objectFromSelectResult(rs); } return one; } catch (Exception ex) { new SqlExcuteException(ex.getMessage(),this.getClass().getName(),"sql执行异常",sql); return null; }finally{ hand.sayGoodbye(); } } public Object objectFromSelectResult(ResultSet rs) throws Exception{ Object obj=table.getObjClass().newInstance(); Field[] fields = table.getObjClass().getDeclaredFields(); for(Field field:fields){ field.setAccessible(true); if(field.isAnnotationPresent(ForeignKey.class)){ ForeignKey fkey = (ForeignKey) field.getAnnotation(ForeignKey.class); String className=field.getType().toString().replace("class ", ""); Object foreignObj=Class.forName(className).newInstance(); field.set(obj, foreignObj); Field[] fkeyFields=foreignObj.getClass().getDeclaredFields(); for(Field fkeyField:fkeyFields){ fkeyField.setAccessible(true); if(!fkeyField.isAnnotationPresent(ForeignKey.class)){ String foreignColumnName="foreign_"+Class.forName(className).getSimpleName()+"."+fkeyField.getName(); fkeyField.set(foreignObj, rs.getString(foreignColumnName)); } } }else{ field.set(obj, rs.getString(field.getName())); } } return obj; } @Override public List selectAll() { MySQLHandler hand=new MySQLHandler(); ResultSet rs=null; ArrayList list=new ArrayList(); String sql=SqlFactory.CreateSelectAllSql(table); try { rs=hand.query(sql); while(rs.next()){ Object one=objectFromSelectResult(rs); list.add(one); } return list; } catch (Exception ex) { new SqlExcuteException(ex.getMessage(),this.getClass().getName(),"sql执行异常",sql); return null; }finally{ hand.sayGoodbye(); } } @Override public List selectPage(int offset, int rows) { MySQLHandler hand=new MySQLHandler(); ResultSet rs=null; ArrayList list=new ArrayList(); String sql=SqlFactory.CreateSelectPageSql(table,offset,rows);; try { rs=hand.query(sql); while(rs.next()){ Object one=objectFromSelectResult(rs); list.add(one); } return list; } catch (Exception ex) { new SqlExcuteException(ex.getMessage(),this.getClass().getName(),"sql执行异常",sql); return null; }finally{ hand.sayGoodbye(); } } @Override public int delete(String key) { MySQLHandler hand=new MySQLHandler(); String sql=SqlFactory.CreateDeleteSql(table,key); try { int re=hand.execute(sql); return re; } catch (Exception ex) { new SqlExcuteException(ex.getMessage(),this.getClass().getName(),"sql执行异常",sql); return 0; }finally{ hand.sayGoodbye(); } } @Override public int add(Object obj) { MySQLHandler hand=new MySQLHandler(); String sql=""; try { table.setValue(obj); sql=SqlFactory.CreateAddSql(table); int re=hand.execute(sql); return re; } catch (Exception ex) { new SqlExcuteException(ex.getMessage(),this.getClass().getName(),"sql执行异常",sql); return 0; }finally{ hand.sayGoodbye(); } } @Override public int update(Object obj) { MySQLHandler hand=new MySQLHandler(); String sql=""; try { table.setValue(obj); sql=SqlFactory.CreateUpdateSql(table); int re=hand.execute(sql); return re; } catch (Exception ex) { new SqlExcuteException(ex.getMessage(),this.getClass().getName(),"sql执行异常",sql); return 0; }finally{ hand.sayGoodbye(); } } }
6. 总结和展望
总结就是,针对普通项目,真的挺好用,基本不用写很多代码:直接由数据库自动生成实体java代码,然后添加@Key和@ForeignKey外键,重新生成get/set方法后,就直接可以用EntityOperation进行增删改查,简直太快了,反正我是没见过更快的,有的话请告诉俺。
展望,对于更加复杂的查询请求,比如分组、比如按某一列查询,初步的想法是提供几个方法,一个是selectBySql直接使用原生sql查询,一个是selectByAppendSql写好了外键相关的表关联,直接写其他部分,当然可以在SqlFactory拓展一番。最后,也可以写一个UserOperation继承EntityOperation,这样 public Object selectOne就可以写成public User selectOne而且可以把拓展的方法写在里面,基本的方法直接调用EntityOperation,这样即美观又解决问题。