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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容