为了账号安全,请及时绑定邮箱和手机立即绑定

Panda ORM源码-基于注解和反射的ORM增删改查实现

标签:
Java MySQL

本篇介绍Panda ORM的核心功能(即添加简单注解即可对实体进行增删改查操作功能)的设计与实现。

一,整体思路介绍

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语句并执行。

二,注解设计与实现

将列分为三种,主键列(需要标记是否自增长,因为关系到生成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参数表示外键指向的表的列名
}

三,实体类结构解析器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));
                    }
                }
            }
        }
    }

四,通过表结构类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;
    }
}

五,通过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();
        }
    }
}

六,总结和展望

总结就是,针对普通项目,真的挺好用,基本不用写很多代码:直接由数据库自动生成实体java代码,然后添加@Key和@ForeignKey外键,重新生成get/set方法后,就直接可以用EntityOperation进行增删改查,简直太快了,反正我是没见过更快的,有的话请告诉俺。

展望,对于更加复杂的查询请求,比如分组、比如按某一列查询,初步的想法是提供几个方法,一个是selectBySql直接使用原生sql查询,一个是selectByAppendSql写好了外键相关的表关联,直接写其他部分,当然可以在SqlFactory拓展一番。最后,也可以写一个UserOperation继承EntityOperation,这样 public Object selectOne就可以写成public User selectOne而且可以把拓展的方法写在里面,基本的方法直接调用EntityOperation,这样即美观又解决问题。

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
软件工程师
手记
粉丝
1.5万
获赞与收藏
1523

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消