无需编程,基于甲骨文oracle数据库零代码生成CRUD增删改查RESTful API接口

简介: 通过之前一篇文章 无需编程,基于PostgreSQL零代码生成CRUD增删改查RESTful API接口 的介绍,采用抽象工厂设计模式,已经支持了大象数据库PostgreSQL。之前通过字符串拼接生成DDL SQL语句,比较繁琐。本文开始,引入了FreeMarker模版引擎,通过配置模版实现创建和修改物理表结构SQL语句,简化了大量代码,提高了效率,并且通过配置oracle数据库SQL模版,基于oracle数据库,零代码实现crud增删改查。

无需编程,基于甲骨文oracle数据库零代码生成CRUD增删改查RESTful API接口

回顾

通过之前一篇文章 无需编程,基于PostgreSQL零代码生成CRUD增删改查RESTful API接口 的介绍,采用抽象工厂设计模式,已经支持了大象数据库PostgreSQL。之前通过字符串拼接生成DDL SQL语句,比较繁琐。本文开始,引入了FreeMarker模版引擎,通过配置模版实现创建和修改物理表结构SQL语句,简化了大量代码,提高了效率,并且通过配置oracle数据库SQL模版,基于oracle数据库,零代码实现crud增删改查。

FreeMarker简介

FreeMarker是一款模板引擎: 即一种基于模板和要改变的数据,并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言, 不是像PHP那样成熟的编程语言。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算,之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据,而在模板之外可以专注于要展示什么数据。

UI界面

通过产品对象为例,无需编程,基于Oracle数据库,通过配置零代码实现CRUD增删改查RESTful API接口和管理UI。

productMeta
创建产品

table
编辑产品数据

productList
产品数据列表

Oracle SQL Developer
通过Oracle SQL Developer查询Oracle数据

定义元数据对象模型

元数据表ca_meta_table

ca_meta_table
元数据表ca_meta_table,用于记录表的基本信息。

TableEntity对象

TableEntity为“元数据表”对象,和ca_meta_table字段对应

public class TableEntity {
    private Long id;

    private String name;

    private String caption;

    private String description;

    private Timestamp createdDate;

    private Timestamp lastModifiedDate;

    private String pluralName;

    private String tableName;

    private EngineEnum engine;

    private Boolean createPhysicalTable;

    private Boolean reverse;

    private Boolean systemable;

    private Boolean readOnly;

    private List<ColumnEntity> columnEntityList;

    private List<IndexEntity> indexEntityList;
}

元数据列ca_meta_column

ca_meta_column
元数据列ca_meta_column,用于记录表字段信息,比如类型,长度,默认值等。

ColumnEntity对象

ColumnEntity为“元数据列”对象,和ca_meta_column字段对应

public class ColumnEntity {
  private Long id;

  private String name;

  private String caption;

  private String description;

  private Timestamp createdDate;

  private Timestamp lastModifiedDate;

  private Integer displayOrder;

  private DataTypeEnum dataType;

  private IndexTypeEnum indexType;

  private IndexStorageEnum indexStorage;

  private String indexName;

  private Integer length;

  private Integer precision;

  private Integer scale;

  private String defaultValue;

  private Long seqId;

  private Boolean unsigned;

  private Boolean autoIncrement;

  private Boolean nullable;

  private Boolean insertable;

  private Boolean updatable;

  private Boolean queryable;

  private Boolean displayable;

  private Boolean systemable;

  private Long tableId;
}

元数据索引ca_meta_index

ca_meta_index
元数据索引ca_meta_index,用于记录表联合索引信息,比如索引类型,名称等。

IndexEntity对象

IndexEntity为“元数据索引”对象,和ca_meta_index字段对应

public class IndexEntity {
  private Long id;

  private String name;

  private String caption;

  private String description;

  private Timestamp createdDate;

  private Timestamp lastModifiedDate;

  private IndexTypeEnum indexType;

  private IndexStorageEnum indexStorage;

  private Long tableId;

  private List<IndexLineEntity> indexLineEntityList;
}

元数据索引行ca_meta_index_line

ca_meta_index_line
元数据索引行ca_meta_index_line,用于记录表联合索引行信息,一个联合索引可以对应多个联合索引行,表示由多个字段组成。

IndexLineEntity对象

IndexLineEntity“元数据索行”对象,和ca_meta_index_line字段对应

public class IndexLineEntity {
  private Long id;

  private Long columnId;

  private ColumnEntity columnEntity;

  private Long indexId;
}

定义FreeMarker模版

创建表create-table.sql.ftl

CREATE TABLE "${tableName}" (
<#list columnEntityList as columnEntity>
  <#if columnEntity.dataType == "BOOL">
    "${columnEntity.name}" NUMBER(1)<#if columnEntity.defaultValue??> DEFAULT <#if columnEntity.defaultValue == "true">1<#else>0</#if></#if><#if columnEntity.nullable != true> NOT NULL</#if><#if columnEntity_has_next>,</#if>
  <#elseif columnEntity.dataType == "INT">
    "${columnEntity.name}" INT<#if columnEntity.defaultValue??> DEFAULT ${columnEntity.defaultValue}</#if><#if columnEntity.nullable != true> NOT NULL</#if><#if columnEntity.indexType?? && columnEntity.indexType == "PRIMARY"> PRIMARY KEY</#if><#if columnEntity_has_next>,</#if>
  <#elseif columnEntity.dataType == "BIGINT">
    "${columnEntity.name}" INT<#if columnEntity.defaultValue??> DEFAULT ${columnEntity.defaultValue}</#if><#if columnEntity.nullable != true> NOT NULL</#if><#if columnEntity.indexType?? && columnEntity.indexType == "PRIMARY"> PRIMARY KEY</#if><#if columnEntity_has_next>,</#if>
  <#elseif columnEntity.dataType == "FLOAT">
    "${columnEntity.name}" FLOAT<#if columnEntity.defaultValue??> DEFAULT ${columnEntity.defaultValue}</#if><#if columnEntity.nullable != true> NOT NULL</#if><#if columnEntity_has_next>,</#if>
  <#elseif columnEntity.dataType == "DOUBLE">
    "${columnEntity.name}" REAL<#if columnEntity.defaultValue??> DEFAULT ${columnEntity.defaultValue}</#if><#if columnEntity.nullable != true> NOT NULL</#if><#if columnEntity_has_next>,</#if>
  <#elseif columnEntity.dataType == "DECIMAL">
    "${columnEntity.name}" DECIMAL<#if columnEntity.defaultValue??> DEFAULT ${columnEntity.defaultValue}</#if><#if columnEntity.nullable != true> NOT NULL</#if><#if columnEntity_has_next>,</#if>
  <#elseif columnEntity.dataType == "DATE">
    "${columnEntity.name}" DATE<#if columnEntity.defaultValue??> DEFAULT ${columnEntity.defaultValue}</#if><#if columnEntity.nullable != true> NOT NULL</#if><#if columnEntity_has_next>,</#if>
  <#elseif columnEntity.dataType == "TIME">
    "${columnEntity.name}" CHAR(8)<#if columnEntity.defaultValue??> DEFAULT ${columnEntity.defaultValue}</#if><#if columnEntity.nullable != true> NOT NULL</#if><#if columnEntity_has_next>,</#if>
  <#elseif columnEntity.dataType == "DATETIME">
    "${columnEntity.name}" DATE<#if columnEntity.defaultValue??> DEFAULT ${columnEntity.defaultValue}</#if><#if columnEntity.nullable != true> NOT NULL</#if><#if columnEntity_has_next>,</#if>
  <#elseif columnEntity.dataType == "TIMESTAMP">
    "${columnEntity.name}" TIMESTAMP<#if columnEntity.defaultValue??> DEFAULT ${columnEntity.defaultValue}</#if><#if columnEntity.nullable != true> NOT NULL</#if><#if columnEntity_has_next>,</#if>
  <#elseif columnEntity.dataType == "CHAR">
    "${columnEntity.name}" CHAR(${columnEntity.length})<#if columnEntity.defaultValue??> DEFAULT '${columnEntity.defaultValue}'</#if><#if columnEntity.nullable != true> NOT NULL</#if><#if columnEntity.indexType?? && columnEntity.indexType == "PRIMARY"> PRIMARY KEY</#if><#if columnEntity_has_next>,</#if>
  <#elseif columnEntity.dataType == "VARCHAR">
    "${columnEntity.name}" VARCHAR(${columnEntity.length})<#if columnEntity.defaultValue??> DEFAULT '${columnEntity.defaultValue}'</#if><#if columnEntity.nullable != true> NOT NULL</#if><#if columnEntity.indexType?? && columnEntity.indexType == "PRIMARY"> PRIMARY KEY</#if><#if columnEntity_has_next>,</#if>
  <#elseif columnEntity.dataType == "PASSWORD">
    "${columnEntity.name}" VARCHAR(200)<#if columnEntity.defaultValue??> DEFAULT '${columnEntity.defaultValue}'</#if><#if columnEntity.nullable != true> NOT NULL</#if><#if columnEntity_has_next>,</#if>
  <#elseif columnEntity.dataType == "ATTACHMENT">
    "${columnEntity.name}" VARCHAR(4000)<#if columnEntity.defaultValue??> DEFAULT '${columnEntity.defaultValue}'</#if><#if columnEntity.nullable != true> NOT NULL</#if><#if columnEntity_has_next>,</#if>
  <#elseif columnEntity.dataType == "TEXT">
    "${columnEntity.name}" VARCHAR(4000)<#if columnEntity.defaultValue??> DEFAULT '${columnEntity.defaultValue}'</#if><#if columnEntity.nullable != true> NOT NULL</#if><#if columnEntity_has_next>,</#if>
  <#elseif columnEntity.dataType == "LONGTEXT">
    "${columnEntity.name}" LONG<#if columnEntity.defaultValue??> DEFAULT ${columnEntity.defaultValue}</#if><#if columnEntity.nullable != true> NOT NULL</#if><#if columnEntity_has_next>,</#if>
  <#elseif columnEntity.dataType == "BLOB">
    "${columnEntity.name}" BLOB<#if columnEntity.defaultValue??> DEFAULT ${columnEntity.defaultValue}</#if><#if columnEntity.nullable != true> NOT NULL</#if><#if columnEntity_has_next>,</#if>
  <#elseif columnEntity.dataType == "LONGBLOB">
    "${columnEntity.name}" BLOB<#if columnEntity.defaultValue??> DEFAULT ${columnEntity.defaultValue}</#if><#if columnEntity.nullable != true> NOT NULL</#if><#if columnEntity_has_next>,</#if>
  <#else>
    "${columnEntity.name}" VARCHAR(200)<#if columnEntity.defaultValue??> DEFAULT ${columnEntity.defaultValue}</#if><#if columnEntity.nullable != true> NOT NULL</#if><#if columnEntity.indexType?? && columnEntity.indexType == "PRIMARY"> PRIMARY KEY</#if><#if columnEntity_has_next>,</#if>
  </#if>
</#list>
);

<#list columnEntityList as columnEntity>
  <#if columnEntity.indexType?? && columnEntity.indexType == "UNIQUE">
    ALTER TABLE "${tableName}" ADD CONSTRAINT "${columnEntity.indexName}" UNIQUE("${columnEntity.name}");
  </#if>

  <#if columnEntity.indexType?? && (columnEntity.indexType == "INDEX" || columnEntity.indexType == "FULLTEXT")>
    CREATE INDEX "${columnEntity.indexName}" ON "${tableName}" ("${columnEntity.name}");
  </#if>
</#list>

<#if indexEntityList??>
  <#list indexEntityList as indexEntity>
    <#if indexEntity.indexType?? && indexEntity.indexType == "UNIQUE">
      ALTER TABLE "${tableName}" ADD CONSTRAINT "${indexEntity.name}" UNIQUE(<#list indexEntity.indexLineEntityList as indexLineEntity>"${indexLineEntity.columnEntity.name}"<#if indexLineEntity_has_next>,</#if></#list>);
    </#if>

    <#if indexEntity.indexType?? && (indexEntity.indexType == "INDEX" || indexEntity.indexType == "FULLTEXT")>
      CREATE INDEX "${indexEntity.name}" ON "${tableName}" (<#list indexEntity.indexLineEntityList as indexLineEntity>"${indexLineEntity.columnEntity.name}"<#if indexLineEntity_has_next>,</#if></#list>);
    </#if>
  </#list>
</#if>

COMMENT ON TABLE "${tableName}" IS '${caption}';

<#list columnEntityList as columnEntity>
  COMMENT ON COLUMN "${tableName}"."${columnEntity.name}" IS '${columnEntity.caption}';
</#list>

模版解析SQL

首先保存元数据信息,下一步传递模版名称和元数据model,动态解析成创建表SQL语句,然后创建物理表,这样元数据和物理表就关联上了。运行时通过解析元数据动态生成insert,select,update,delete等SQL语句,零代码实现业务数据crud功能。

public String processTemplateToString(String database, String templateName, Object dataModel) {
    String str = null;
    StringWriter stringWriter = new StringWriter();
    try {
        Configuration config = new Configuration(Configuration.VERSION_2_3_31);
        config.setNumberFormat("#");
        String templateValue = getTemplate(database, templateName);
        if (templateValue == null) {
          return str;
        }

        Template template = new Template(templateName, templateValue, config);
        template.process(dataModel, stringWriter);

        str = stringWriter.getBuffer().toString().trim();
        log.info(str);
    } catch (Exception e) {
        e.printStackTrace();
        throw new BusinessException(ApiErrorCode.DEFAULT_ERROR, e.getMessage());
    }

    return str;
}

public List<String> toCreateTableSql(TableEntity tableEntity) {
  String createTableSql = processTemplateToString("create-table.sql.ftl", tableEntity);

  if (createTableSql == null) {
    throw new BusinessException(ApiErrorCode.DEFAULT_ERROR, "create-table.sql is empty!");
  }

  List<String> sqls = new ArrayList<String>();
  String[] subSqls = createTableSql.split(";");
  for (String t : subSqls) {
    String subSql = t.trim();
    if (!subSql.isEmpty()) {
      sqls.add(t);
    }
  }

  return sqls;
}

public Long create(TableDTO tableDTO) {
  TableEntity tableEntity = tableMapper.toEntity(tableDTO);
  //TODO
  Long tableId = crudService.create(TABLE_TABLE_NAME, tableEntity);
  List<String> sqlList = crudService.toCreateTableSql(tableEntity);
  for (String sql: sqlList) {
    execute(sql);
  }
  //TODO
  return tableId;
}

修改表

freemarker.png
包括表结构和索引的修改,删除等,和创建表原理类似。

application.properties

需要根据需要配置数据库连接驱动,无需重新发布,就可以切换不同的数据库。

#oracle
spring.datasource.url=jdbc:oracle:thin:@//localhost:1521/XEPDB1
spring.datasource.driverClassName=oracle.jdbc.OracleDriver
spring.datasource.username=crudapi
spring.datasource.password=crudapi
spring.datasource.initialization-mode=always
spring.datasource.schema=classpath:schema.sql

小结

本文主要介绍了crudapi支持oracle数据库实现原理,并且以产品对象为例,零代码实现了CRUD增删改查RESTful API,后续介绍更多的数据库,比如MSSQL Server,Mongodb等。

实现方式 代码量 时间 稳定性
传统开发 1000行左右 2天/人 5个bug左右
crudapi系统 0行 1分钟 基本为0

综上所述,利用crudapi系统可以极大地提高工作效率和节约成本,让数据处理变得更简单!

目录
相关文章
|
3月前
|
XML JSON API
识别这些API接口定义(http,https,api,RPC,webservice,Restful api ,OpenAPI)
本内容介绍了API相关的术语分类,包括传输协议(HTTP/HTTPS)、接口风格(RESTful、WebService、RPC)及开放程度(API、OpenAPI),帮助理解各类API的特点与应用场景。
|
5月前
|
缓存 安全 API
RESTful与GraphQL:电商API接口设计的技术细节与适用场景
本文对比了RESTful与GraphQL这两种主流电商API接口设计方案。RESTful通过资源与HTTP方法定义操作,简单直观但可能引发过度或欠获取数据问题;GraphQL允许客户端精确指定所需字段,提高灵活性和传输效率,但面临深度查询攻击等安全挑战。从性能、灵活性、安全性及适用场景多维度分析,RESTful适合资源导向场景,GraphQL则适用于复杂数据需求。实际开发中需根据业务特点选择合适方案,或结合两者优势,以优化用户体验与系统性能。
|
5月前
|
JSON 编解码 API
Go语言网络编程:使用 net/http 构建 RESTful API
本章介绍如何使用 Go 语言的 `net/http` 标准库构建 RESTful API。内容涵盖 RESTful API 的基本概念及规范,包括 GET、POST、PUT 和 DELETE 方法的实现。通过定义用户数据结构和模拟数据库,逐步实现获取用户列表、创建用户、更新用户、删除用户的 HTTP 路由处理函数。同时提供辅助函数用于路径参数解析,并展示如何设置路由器启动服务。最后通过 curl 或 Postman 测试接口功能。章节总结了路由分发、JSON 编解码、方法区分、并发安全管理和路径参数解析等关键点,为更复杂需求推荐第三方框架如 Gin、Echo 和 Chi。
|
9月前
|
人工智能 JSON 自然语言处理
AI 程序员的4个分身 | 代码生成专家+注释精灵+API集成助手+智能调试伙伴
AI 程序员的4个分身 | 代码生成专家+注释精灵+API集成助手+智能调试伙伴
425 35
|
8月前
|
XML JSON API
Understanding RESTful API and Web Services: Key Differences and Use Cases
在现代软件开发中,RESTful API和Web服务均用于实现系统间通信,但各有特点。RESTful API遵循REST原则,主要使用HTTP/HTTPS协议,数据格式多为JSON或XML,适用于无状态通信;而Web服务包括SOAP和REST,常用于基于网络的API,采用标准化方法如WSDL或OpenAPI。理解两者区别有助于选择适合应用需求的解决方案,构建高效、可扩展的应用程序。
|
8月前
|
机器学习/深度学习 设计模式 API
Python 高级编程与实战:构建 RESTful API
本文深入探讨了使用 Python 构建 RESTful API 的方法,涵盖 Flask、Django REST Framework 和 FastAPI 三个主流框架。通过实战项目示例,详细讲解了如何处理 GET、POST 请求,并返回相应数据。学习这些技术将帮助你掌握构建高效、可靠的 Web API。
|
8月前
|
PHP 数据库
【YashanDB知识库】PHP使用OCI接口使用数据库绑定参数功能异常
【YashanDB知识库】PHP使用OCI接口使用数据库绑定参数功能异常
|
11月前
|
JSON JavaScript 前端开发
深入浅出Node.js:从零开始构建RESTful API
在数字化时代的浪潮中,后端开发作为连接用户与数据的桥梁,扮演着至关重要的角色。本文将引导您步入Node.js的奇妙世界,通过实践操作,掌握如何使用这一强大的JavaScript运行时环境构建高效、可扩展的RESTful API。我们将一同探索Express框架的使用,学习如何设计API端点,处理数据请求,并实现身份验证机制,最终部署我们的成果到云服务器上。无论您是初学者还是有一定基础的开发者,这篇文章都将为您打开一扇通往后端开发深层知识的大门。
254 12
|
12月前
|
XML JSON 缓存
深入理解RESTful API设计原则与实践
在现代软件开发中,构建高效、可扩展的应用程序接口(API)是至关重要的。本文旨在探讨RESTful API的核心设计理念,包括其基于HTTP协议的特性,以及如何在实际应用中遵循这些原则来优化API设计。我们将通过具体示例和最佳实践,展示如何创建易于理解、维护且性能优良的RESTful服务,从而提升前后端分离架构下的开发效率和用户体验。
|
12月前
|
监控 安全 API
深入浅出:构建高效RESTful API的最佳实践
在数字化时代,API已成为连接不同软件和服务的桥梁。本文将带你深入了解如何设计和维护一个高效、可扩展且安全的RESTful API。我们将从基础概念出发,逐步深入到高级技巧,让你能够掌握创建优质API的关键要素。无论你是初学者还是有经验的开发者,这篇文章都将为你提供实用的指导和启示。让我们一起探索API设计的奥秘,打造出色的后端服务吧!

推荐镜像

更多