Foxnic-SQL (12) —— DAO 特性 : 记录与记录集

LeeFJ
• 阅读 426

Foxnic-SQL (12) —— DAO 特性 : 记录与记录集

概述

  默认情况下,JDBC 从数据库取得的是 ResultSet(游标),但是游标打开着是消耗数据库连接的,所以我们希望,打开游标取数结束后立即关闭游标。Foxnic-SQL 使用 Rcd(记录)和 RcdSet(记录集) 将游标遍历的数据取出存放。本节将详细介绍 Rcd(记录)和 RcdSet(记录集)的概念和使用方法。
本文中的示例代码均可在 https://gitee.com/LeeFJ/foxnic-samples 项目中找到。

数据结构

  一般来讲,记录就类似一个 Map 的结构,记录集就是由这些 Map 组成的列表。从内存角度考虑,这种存储结构并非是最优的,原因是每个记录各自存储列名是不合适的。一组结构一致的记录没有必要各自单独存放这些结构信息,而是统一存放。
  所以,Foxnic-SQL 设计了元数据(结构数据)独立,且元数据归属于一个记录集,记录集内的记录结构一致,记录也归属于记录集,并不独立存在。下面这个例子展示了记录、记录集以及元数据之间的关系:

/**
* 记录集、记录、元数据的关系
*/
@Test
public void demo_RcdSetRcdMeta() {
    // 创建DAO
    DAO dao = DBInstance.DEFAULT.dao();
    // 查询并获得第一个记录
    Rcd r = dao.queryRecord("select * from sys_dict where code like ?", "%o%");
    // 输出记录的数据
    System.out.println(r.toJSONObject());
    // 获得当前记录的所在的记录集
    RcdSet rs = r.getOwnerSet();
    // 获得记录集元数据
    QueryMetaData meta = rs.getMetaData();
    // 遍历元数据
    for (int i = 0; i < meta.getColumnCount(); i++) {
        // 获得列标签
        String label = meta.getColumnLabel(i);
        // 按标签取数
        Object value = r.getValue(label);
        // 输出
        System.out.println(label + " = " + value);
    }
}

获得记录集

  获得记录集的途径一般来讲就是通过 DAO 对象的查询方法。虽然,RcdSet 和 Rcd 的构造函数创建,但这样做似乎没有太大意义。除了 DAO 的查询方法创建 RcdSet 外,还可以通过自身克隆和取子集的方式获得新的记录集。示例如下:

/**
* 获得记录集
*/
@Test
public void demo_GetRcdSet() {
    // 创建DAO
    DAO dao = DBInstance.DEFAULT.dao();
    // 方式1:查询
    RcdSet rs = dao.queryPage("select * from sys_dict where code like ?", 50, 1, "%o%");
    // 方式2:克隆
    RcdSet rs2 = rs.clone();
    // 方式3:子集
    RcdSet rs3 = rs.subset(1, 3, true);
    // 遍历
    for (Rcd rcd : rs3) {
        System.out.println(rcd.toJSONObject());
    }
}

遍历记录集

  取得记录集之后,最常见的操作就是遍历,Foxnic-SQL 设计了多种记录集遍历方式。首先 RcdSet 实现了 Iterable 接口,可以通过 for...each 遍历,其次是 通过 RcdSet.getRcd(i) 方法按下标遍历。RcdSet 也支持 Lambda 方式的遍历。以下是 RcdSet 使用各种方法遍历的示例代码:

/**
* 记录集遍历
*/
@Test
public void demo_IterateOver() {
    // 创建DAO
    DAO dao = DBInstance.DEFAULT.dao();
    RcdSet rs = dao.queryPage("select * from sys_dict where code like ?", 50, 1, "%o%");
    //遍历方式-1 : iterator
    for (Rcd r : rs) {
        System.out.println(r.toJSONObject());
    }
    // 遍历方式-2 : 按行取
    for (int i = 0; i < rs.size(); i++) {
        Rcd r = rs.getRcd(i);
        System.out.println(r.toJSONObject());
    }
    // 遍历方式-3 : Lambda
    rs.stream().forEach(r -> {
        System.out.println(r.toJSONObject());
    });
    // 遍历方式-3 : Lambda
    rs.parallelStream().forEach(r -> {
        System.out.println(r.toJSONObject());
    });
}

值类型与存取值

  特别值得一提的是,RcdSet 在增加新列时仅指定了列名,并未指定列的类型。这是因为,RcdSet 存储的数据实际上是动态的。当 DAO 从数据库取数时,会按从游标中取到的值存放,Rcd 类提供了多种方法获取开发人员想要的数据类型。
  Rcd 存取值可以使用数值下标也可以用列名,列名也有多种匹配模式。虽然 Rcd 支持多种命名方式,但建议优先选用数据库原始字段名,其次是驼峰命名。如下示例所示:

/**
* 取值与设置值
* */
@Test
public void demo_Value() {
    // 创建DAO
    DAO dao = DBInstance.DEFAULT.dao();
    RcdSet rs = dao.queryPage("select * from sys_dict where code like ? and create_time is not null", 50, 1, "%o%");
    rs.parallelStream().forEach(r->{
        // 取 Object 类型值,实际值类型和从数据库取出时的原始类型一致
        Object idObj=r.getValue("id");
        // 指定取字符串类型
        String idStr=r.getString("id");
        // 指定取 Long 类型,Rcd 会尽可能转换成 Long 类型,如果不能转换,返回 null
        Long idLong=r.getLong("id");
        // 指定取 Date 类型,Rcd 会尽可能转换成 Date 类型,如果不能转换,返回 null
        Date idDate=r.getDate("id");
        // 用序号取数获得更高的效率
        idObj=r.getValue(0);
        // 按序号设置值,无类型校验
        r.set(0,"12345");
        // 按列名设置值
        r.set("id","12345");
        // 设置值,同时单个单词的列名不区分大小写
        idObj=r.getValue("id");
        idObj=r.getValue("ID");
        // 多单词列名按原始数据库命名的匹配方式
        Date date1=r.getDate("create_time");
        // 多单词列名按驼峰命名的匹配方式
        Date date2=r.getDate("createTime");
        // 多单词列名按驼峰命名大写或小写的匹配方式
        Date date3=r.getDate("createtime");
        Date date4=r.getDate("CREATETIME");
        System.out.println();
    });
}

列控制

  通过 DAO 获得的记录集其结构和查询语句相关,查询语句中有哪些列那么记录集中就会有哪些列。但事实上,开发人员可能需要对记录集的列做一些处理,例如增加新列、删除原有列等。

/**
* 列控制
*/
@Test
public void demo_Column() {
    // 创建DAO
    DAO dao = DBInstance.DEFAULT.dao();
    RcdSet rs = dao.queryPage("select * from sys_dict where code like ?", 50, 1, "%o%");
    // 输出列数
    System.out.println("size after query " + rs.getFields().size());
    // 增加列
    rs.addColumn("new_column");
    System.out.println("size after add " + rs.getFields().size());
    for (String field : rs.getFields()) {
        System.out.println("a : " + field);
    }
    // 变更列名
    rs.changeColumnLabel("new_column", "new_column_1");
    for (String field : rs.getFields()) {
        System.out.println("b : " + field);
    }
    // 按列取数与设置
    for (Rcd r : rs) {
        r.set("new_column_1", IDGenerator.getNanoId());
    }
    // 取数
    for (Rcd r : rs) {
        // 用原始列名取数
        String value1 = r.getString("new_column_1");
        // 用驼峰命名取数
        Object value2 = r.getValue("newColumn1");
        System.out.println("value1 = " + value1 + " ;  value2=" + value2);
        assertTrue(value2.equals(value1));
    }
}

数据结构转换

  在某些场合,我们需要将记录集转换成某些目标结构,如单列提取为Arrray、List 或 Set;或者按列提取为 Map 结构等等,这些 RcdSet 都能很好的支持。示例代码如下:

/**
* 数据结构转换
*/
@Test
public void demo_Structure() {
    // 创建DAO
    DAO dao = DBInstance.DEFAULT.dao();
    RcdSet rs = dao.queryPage("select * from sys_dict where code like ?", 50, 1, "%o%");
    // 提取单列数据为数组
    String[] ids = rs.getValueArray("id", String.class);
    // 提取单列数据为 List
    List<Date> dateList = rs.getValueList("createTime", Date.class);
    // 提取单列数据为 Set
    Set<Date> codeSet = rs.getValueSet("code", Date.class);
    // 提取指定列数据为 Map
    Map<String, String> map = rs.getValueMap("id", String.class, "name", String.class);
    // 将记录集转换成 Map 结构
    Map<String, Rcd> rcdMap = rs.getMappedRcds("id", String.class);
    // 将记录集进行分组
    Map<Object, List<Rcd>> groupedMap = rs.getGroupedMap("id", "code");
}

排序、过滤、去重

  RcdSet 同时也提供若干排序、过滤、去重的方法,示例代码如下:

/**
* 排序、过滤、去重
*/
@Test
public void demo_misc() {
    // 创建DAO
    DAO dao = DBInstance.DEFAULT.dao();
    RcdSet rs = dao.queryPage("select * from sys_dict where code like ?", 50, 1, "%o%");
    rs.parallelStream().forEach(r -> {
        System.out.println(r.toJSONObject());
    });
    // 按指定字段去重
    rs.distinct("id");
    // 按指定字段排序
    rs.sort("create_time", true, true);
    // 查找,精确匹配,返回第一行匹配的记录
    Rcd r = rs.find("name", "机柜状态");
    System.out.println(r == null ? "未找到" : r.toJSONObject());
    // 过滤,并指定匹配操作符
    RcdSet result = rs.filter("id", "36", FilterOperator.CONTAINS);
    for (Rcd rcd : result) {
        System.out.println("filter" + rcd.toJSONObject());
    }
    // 增加排名列
    rs.rank("rank", "id", true);
    for (Rcd rcd : rs) {
        System.out.println("rank-1 :  id = " + rcd.getString("id") + " , rank = " + rcd.getInteger("rank"));
    }
    // 用排名值填充一个已经存在的列
    rs.fillRankField("version", "create_time", false);
    for (Rcd rcd : rs) {
        System.out.println("rank-2 : id = " + rcd.getString("create_time") + " , rank = " + rcd.getInteger("version"));
    }
}

序列化

  RcdSet 的序列化主要是将自身或 Rcd 转换成 JSON 对象。Foxnic-SQL 依赖了 FastJSON,示例代码如下:

/**
* 序列化
*/
@Test
public void demo_serialization() {
    // 创建DAO
    DAO dao = DBInstance.DEFAULT.dao();
    RcdSet rs = dao.queryPage("select * from sys_dict where code like ?", 50, 1, "%o%");
    // 序列化记录集为 JSONArray 每个元素为 JSONObject
    JSONArray array1 = rs.toJSONArrayWithJSONObject();
    // 序列化记录集为 JSONArray 每个元素为 JSONArray
    JSONArray array2 = rs.toJSONArrayWithJSONArray();
    // 序列化记录集为 JSONArray 每个元素为 JSONObject , 并指定哪些字段转入到 JSONObject
    JSONArray array4 = rs.toJSONArrayWithJSONObject("id", "code", "name");
    System.out.println(array4);

    // 序列化记录
    for (Rcd r : rs) {
        // 全字段转换
        JSONObject object1 = r.toJSONObject();
        System.out.println(object1);
        // 指定字段转换
        JSONObject object2 = r.toJSONObject("id", "code", "name");
        System.out.println(object2);
    }

    // 指定列名转换规则 , DB_UPPER_CASE 表示完全和数据库一致,整体转大写
    rs.setDataNameFormat(DataNameFormat.DB_UPPER_CASE);
    System.out.println(rs.toJSONArrayWithJSONObject());
}

实体转换

  RcdSet 除了转 JSON 对象外,也可以转换成 Java 的 Pojo 对象;转换到 Foxnic 生成的实体对象时将获得更高的性能。示例代码如下:

/**
* 处理实体数据
*/
@Test
public void demo_entity() {
    // 创建DAO
    DAO dao = DBInstance.DEFAULT.dao();
    RcdSet rs = dao.queryPage("select * from example_address", 50, 1);
    // 转成实体列表
    List<Address> addressList = rs.toEntityList(Address.class);
    System.out.println(JSON.toJSON(addressList));
    // 转成分页的实体列表
    PagedList<Address> addressPageList = rs.toPagedEntityList(Address.class);
    System.out.println(JSON.toJSON(addressPageList));
    // 遍历并逐个转换
    for (Rcd r : rs) {
        Address address = r.toEntity(Address.class);
        System.out.println(JSON.toJSON(address));
    }
}

数据操作

  开发人员可以通过 RcdSet 和 Rcd 完成数据的持久化操作。Rcd 为开发人员提供了 insert、update、save、delete 等方法,直接操纵在库数据。示例代码如下:

/**
* 操作数据
*/
@Test
public void demo_crud() {
    // 创建DAO
    DAO dao = DBInstance.DEFAULT.dao();

    String id = IDGenerator.getSnowflakeIdString();
    Rcd r = dao.queryRecord("select * from example_address where id=?", id);
    boolean suc = false;
    if (r == null) {
        r = dao.createRecord("example_address");
        r.set("id", id);
        r.set("name", "leefj");
        // 如果是 null 则不连入SQL语句
        r.set("phone_number", "13444252562");
        r.set("address", "宁波");
        r.set("region_type", "国内");
        r.set("create_time", new Date());
        // 设置数据库表达式
        r.setExpr("update_time", "now()");
        // 执行插入操作
        suc=r.insert();
        System.out.println("数据插入"+(suc?"成功":"失败"));
    } else {
        r.set("update_time", new Date());
        r.set("update_by", "leefj");
        // 执行更新操作
        suc=r.update(SaveMode.DIRTY_FIELDS);
        System.out.println("数据更新"+(suc?"成功":"失败"));
    }
    suc=r.delete();
    System.out.println("数据删除"+(suc?"成功":"失败"));
}

小结

  本节主要介绍了 Foxnic-SQL 中的记录集(RcdSet)和记录(Rcd),它们为开发人员提供了诸多功能,如遍历与存取值、结构转换,过滤排序、数据操作等。记录集(RcdSet)和记录(Rcd)特别在无 Po 对象的场景下对处理数据,它的动态性也特别适合很多高级场景。

相关项目

  https://gitee.com/LeeFJ/foxnic
  https://gitee.com/LeeFJ/foxnic-web
  https://gitee.com/lank/eam
  https://gitee.com/LeeFJ/foxnic-samples

官方文档

  http://foxnicweb.com/docs/doc.html

点赞
收藏
评论区
推荐文章
风花雪月 风花雪月
1年前
sqlite远程访问的方法Python
importsqlite3打开数据库dbsqlite3.connect('\\\\192.168.1.10\共享\sjk\dbhmc.db')远程访问sqlite,共享必须要四个'\'使用cursor()方法获取操作游标cursordb.cursor()s
CuterCorley CuterCorley
3年前
Python SQLite 基本操作和经验技巧(一)
1.插入单行数据pythonimportsqlite3consqlite3.connect('xxxx/test.db')curcon.cursor()创建游标对象cur.execute("createtableStudent(SNOchar(10)UNIQUEprimarykey,Snamechar(20),Sse
Wesley13 Wesley13
3年前
Mysql 游标(cursor)
简介    游标实际上是一种能从包括多条数据记录的结果集中每次提取一条记录的机制。  游标充当指针的作用。  尽管游标能遍历结果中的所有行,但他一次只指向一行。  游标的作用就是用于对查询数据库所返回的记录进行遍历,以便进行相应的操作。语法  _DECLAR
Wesley13 Wesley13
3年前
Oracle 中使用 fetch bulk collect into 批量效率的读取游标数据
通常我们获取游标数据是用fetchsome\_cursorintovar1,var2的形式,当游标中的记录数不多时不打紧。然而自Oracle8i起,Oracle为我们提供了fetchbulkcollect来批量取游标中的数据,存中即是合理的。它能在读取游标中大量数据的时候提升效率,就像SNMP协议中,V2版比V1版新加了
Wesley13 Wesley13
3年前
oracle游标的例子
declare    cursor ca is select id_no, name from user where ym201401;begin    for cb in ca loop        update path set enamecb.name where id_nocb.id
Wesley13 Wesley13
3年前
Oracle之plsql及游标
1、赋值:赋值declarevar_namevarchar2(10):'&请输入名字';&是一个提示输入的特殊符号,会打开一个输入框var_agenumber(3):'&请输入年龄';begi
Wesley13 Wesley13
3年前
mysql游标(实际场景应用)
DECLARE@codeVARCHAR(64)声明编码变量DECLAREmy\_cursorCURSORFOR声明游标(无非就是一个列表结果集)\游标的结果集STARTselectcodefromsys\_areawherecodeli
Easter79 Easter79
3年前
SqlServer高级特性
游标用途:在数据很多的时候,如果在java代码中进行循环之后再进行更新数据,会造成频繁的连接数据库,耗费性能,所以就可以使用到游标作用:查询出来的集合直接在SQL中进行遍历在进行更新    DECLAREallUnitsCURSOR            //定义游标,For后面是集合,你为那个集合定义的游标。
Stella981 Stella981
3年前
Redis中的Scan命令踩坑记
1原本以为自己对redis命令还蛮熟悉的,各种数据模型各种基于redis的骚操作。但是最近在使用redis的scan的命令式却踩了一个坑,顿时发觉自己原来对redis的游标理解的很有限。所以记录下这个踩坑的过程,背景如下:公司因为redis服务器内存吃紧,需要删除一些无用的没有设置过期时间的key。大概有500多w的key。虽然key的数目听起来
LeeFJ LeeFJ
1年前
Foxnic-SQL (15) —— 使用记录集导入或导出Excel
很多时候,我们需要将外部Excel表中的数据导入到数据库,或是需要将某个查询结果导出到Excel文件中,对于这种简单的操作,FoxnicSQL已经内置了ExcelReader和ExcelWriter用于处理Excel数据。本文中的示例代码均可在https://gitee.com/LeeFJ/foxnicsamples项目中找到。读取Excel到RcdSetFoxnicSQL使用ExcelReader类读取Excel中某个sheet的数据,这些数据将被读取到RcdSet,通过RcdSet可以完成数据库保存等操作。在读取Excel前需要定义Excel结构,将Excel列映射到数据库字段,如下代码所示。一旦Excel数据转换成RcdSet,开发人员就可以去做其它更多额外的数据处理。