自己开发一个Java ORM框架(5)-CRUD操作源码

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: 本文目录1. 整体思路介绍2. 注解设计与实现3. 实体类结构解析器DataTable的实现4. 通过表结构类DataTable生成sql语句5. 通过EntityOperation封装数据库实体操作6. 总结和展望

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

1. 整体思路介绍

1,先了解项目结构如下,注意所有的包都在src目录的panda.orm下,配置文件config.properties处于src目录下。

image.png

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,这样即美观又解决问题。

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
20天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
58 7
|
2天前
|
移动开发 前端开发 Java
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
JavaFX是Java的下一代图形用户界面工具包。JavaFX是一组图形和媒体API,我们可以用它们来创建和部署富客户端应用程序。 JavaFX允许开发人员快速构建丰富的跨平台应用程序,允许开发人员在单个编程接口中组合图形,动画和UI控件。本文详细介绍了JavaFx的常见用法,相信读完本教程你一定有所收获!
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
|
13天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
75 13
|
18天前
|
算法 Java API
如何使用Java开发获得淘宝商品描述API接口?
本文详细介绍如何使用Java开发调用淘宝商品描述API接口,涵盖从注册淘宝开放平台账号、阅读平台规则、创建应用并申请接口权限,到安装开发工具、配置开发环境、获取访问令牌,以及具体的Java代码实现和注意事项。通过遵循这些步骤,开发者可以高效地获取商品详情、描述及图片等信息,为项目和业务增添价值。
52 10
|
11天前
|
前端开发 Java 测试技术
java日常开发中如何写出优雅的好维护的代码
代码可读性太差,实际是给团队后续开发中埋坑,优化在平时,没有那个团队会说我专门给你一个月来优化之前的代码,所以在日常开发中就要多注意可读性问题,不要写出几天之后自己都看不懂的代码。
49 2
|
1天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
3天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
3天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
4天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
20 3
|
4天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
45 2