<a name="imPZx"></a>
Foxnic-SQL (12) —— DAO 特性 : 记录与记录集
<a name="nUzwx"></a>
概述
默认情况下,JDBC 从数据库取得的是 ResultSet(游标),但是游标打开着是消耗数据库连接的,所以我们希望,打开游标取数结束后立即关闭游标。Foxnic-SQL 使用 Rcd(记录)和 RcdSet(记录集) 将游标遍历的数据取出存放。本节将详细介绍 Rcd(记录)和 RcdSet(记录集)的概念和使用方法。<br />本文中的示例代码均可在 https://gitee.com/LeeFJ/foxnic-samples 项目中找到。 <a name="mxCWK"></a>
数据结构
一般来讲,记录就类似一个 Map 的结构,记录集就是由这些 Map 组成的列表。从内存角度考虑,这种存储结构并非是最优的,原因是每个记录各自存储列名是不合适的。一组结构一致的记录没有必要各自单独存放这些结构信息,而是统一存放。<br /> 所以,Foxnic-SQL 设计了元数据(结构数据)独立,且元数据归属于一个记录集,记录集内的记录结构一致,记录也归属于记录集,并不独立存在。下面这个例子展示了记录、记录集以及元数据之间的关系:
1/** 2* 记录集、记录、元数据的关系 3*/ 4@Test 5public void demo_RcdSetRcdMeta() { 6 // 创建DAO 7 DAO dao = DBInstance.DEFAULT.dao(); 8 // 查询并获得第一个记录 9 Rcd r = dao.queryRecord("select * from sys_dict where code like ?", "%o%"); 10 // 输出记录的数据 11 System.out.println(r.toJSONObject()); 12 // 获得当前记录的所在的记录集 13 RcdSet rs = r.getOwnerSet(); 14 // 获得记录集元数据 15 QueryMetaData meta = rs.getMetaData(); 16 // 遍历元数据 17 for (int i = 0; i < meta.getColumnCount(); i++) { 18 // 获得列标签 19 String label = meta.getColumnLabel(i); 20 // 按标签取数 21 Object value = r.getValue(label); 22 // 输出 23 System.out.println(label + " = " + value); 24 } 25}
<a name="pbL8Y"></a>
获得记录集
获得记录集的途径一般来讲就是通过 DAO 对象的查询方法。虽然,RcdSet 和 Rcd 的构造函数创建,但这样做似乎没有太大意义。除了 DAO 的查询方法创建 RcdSet 外,还可以通过自身克隆和取子集的方式获得新的记录集。示例如下:
1/** 2* 获得记录集 3*/ 4@Test 5public void demo_GetRcdSet() { 6 // 创建DAO 7 DAO dao = DBInstance.DEFAULT.dao(); 8 // 方式1:查询 9 RcdSet rs = dao.queryPage("select * from sys_dict where code like ?", 50, 1, "%o%"); 10 // 方式2:克隆 11 RcdSet rs2 = rs.clone(); 12 // 方式3:子集 13 RcdSet rs3 = rs.subset(1, 3, true); 14 // 遍历 15 for (Rcd rcd : rs3) { 16 System.out.println(rcd.toJSONObject()); 17 } 18}
<a name="tH9kf"></a>
遍历记录集
取得记录集之后,最常见的操作就是遍历,Foxnic-SQL 设计了多种记录集遍历方式。首先 RcdSet 实现了 Iterable 接口,可以通过 for...each 遍历,其次是 通过 RcdSet.getRcd(i) 方法按下标遍历。RcdSet 也支持 Lambda 方式的遍历。以下是 RcdSet 使用各种方法遍历的示例代码:
1/** 2* 记录集遍历 3*/ 4@Test 5public void demo_IterateOver() { 6 // 创建DAO 7 DAO dao = DBInstance.DEFAULT.dao(); 8 RcdSet rs = dao.queryPage("select * from sys_dict where code like ?", 50, 1, "%o%"); 9 //遍历方式-1 : iterator 10 for (Rcd r : rs) { 11 System.out.println(r.toJSONObject()); 12 } 13 // 遍历方式-2 : 按行取 14 for (int i = 0; i < rs.size(); i++) { 15 Rcd r = rs.getRcd(i); 16 System.out.println(r.toJSONObject()); 17 } 18 // 遍历方式-3 : Lambda 19 rs.stream().forEach(r -> { 20 System.out.println(r.toJSONObject()); 21 }); 22 // 遍历方式-3 : Lambda 23 rs.parallelStream().forEach(r -> { 24 System.out.println(r.toJSONObject()); 25 }); 26}
<a name="xu1xa"></a>
值类型与存取值
特别值得一提的是,RcdSet 在增加新列时仅指定了列名,并未指定列的类型。这是因为,RcdSet 存储的数据实际上是动态的。当 DAO 从数据库取数时,会按从游标中取到的值存放,Rcd 类提供了多种方法获取开发人员想要的数据类型。<br /> Rcd 存取值可以使用数值下标也可以用列名,列名也有多种匹配模式。虽然 Rcd 支持多种命名方式,但建议优先选用数据库原始字段名,其次是驼峰命名。如下示例所示:
1/** 2* 取值与设置值 3* */ 4@Test 5public void demo_Value() { 6 // 创建DAO 7 DAO dao = DBInstance.DEFAULT.dao(); 8 RcdSet rs = dao.queryPage("select * from sys_dict where code like ? and create_time is not null", 50, 1, "%o%"); 9 rs.parallelStream().forEach(r->{ 10 // 取 Object 类型值,实际值类型和从数据库取出时的原始类型一致 11 Object idObj=r.getValue("id"); 12 // 指定取字符串类型 13 String idStr=r.getString("id"); 14 // 指定取 Long 类型,Rcd 会尽可能转换成 Long 类型,如果不能转换,返回 null 15 Long idLong=r.getLong("id"); 16 // 指定取 Date 类型,Rcd 会尽可能转换成 Date 类型,如果不能转换,返回 null 17 Date idDate=r.getDate("id"); 18 // 用序号取数获得更高的效率 19 idObj=r.getValue(0); 20 // 按序号设置值,无类型校验 21 r.set(0,"12345"); 22 // 按列名设置值 23 r.set("id","12345"); 24 // 设置值,同时单个单词的列名不区分大小写 25 idObj=r.getValue("id"); 26 idObj=r.getValue("ID"); 27 // 多单词列名按原始数据库命名的匹配方式 28 Date date1=r.getDate("create_time"); 29 // 多单词列名按驼峰命名的匹配方式 30 Date date2=r.getDate("createTime"); 31 // 多单词列名按驼峰命名大写或小写的匹配方式 32 Date date3=r.getDate("createtime"); 33 Date date4=r.getDate("CREATETIME"); 34 System.out.println(); 35 }); 36}
<a name="QdsDL"></a>
列控制
通过 DAO 获得的记录集其结构和查询语句相关,查询语句中有哪些列那么记录集中就会有哪些列。但事实上,开发人员可能需要对记录集的列做一些处理,例如增加新列、删除原有列等。
1/** 2* 列控制 3*/ 4@Test 5public void demo_Column() { 6 // 创建DAO 7 DAO dao = DBInstance.DEFAULT.dao(); 8 RcdSet rs = dao.queryPage("select * from sys_dict where code like ?", 50, 1, "%o%"); 9 // 输出列数 10 System.out.println("size after query " + rs.getFields().size()); 11 // 增加列 12 rs.addColumn("new_column"); 13 System.out.println("size after add " + rs.getFields().size()); 14 for (String field : rs.getFields()) { 15 System.out.println("a : " + field); 16 } 17 // 变更列名 18 rs.changeColumnLabel("new_column", "new_column_1"); 19 for (String field : rs.getFields()) { 20 System.out.println("b : " + field); 21 } 22 // 按列取数与设置 23 for (Rcd r : rs) { 24 r.set("new_column_1", IDGenerator.getNanoId()); 25 } 26 // 取数 27 for (Rcd r : rs) { 28 // 用原始列名取数 29 String value1 = r.getString("new_column_1"); 30 // 用驼峰命名取数 31 Object value2 = r.getValue("newColumn1"); 32 System.out.println("value1 = " + value1 + " ; value2=" + value2); 33 assertTrue(value2.equals(value1)); 34 } 35}
<a name="L5bEE"></a>
数据结构转换
在某些场合,我们需要将记录集转换成某些目标结构,如单列提取为Arrray、List 或 Set;或者按列提取为 Map 结构等等,这些 RcdSet 都能很好的支持。示例代码如下:
1/** 2* 数据结构转换 3*/ 4@Test 5public void demo_Structure() { 6 // 创建DAO 7 DAO dao = DBInstance.DEFAULT.dao(); 8 RcdSet rs = dao.queryPage("select * from sys_dict where code like ?", 50, 1, "%o%"); 9 // 提取单列数据为数组 10 String[] ids = rs.getValueArray("id", String.class); 11 // 提取单列数据为 List 12 List<Date> dateList = rs.getValueList("createTime", Date.class); 13 // 提取单列数据为 Set 14 Set<Date> codeSet = rs.getValueSet("code", Date.class); 15 // 提取指定列数据为 Map 16 Map<String, String> map = rs.getValueMap("id", String.class, "name", String.class); 17 // 将记录集转换成 Map 结构 18 Map<String, Rcd> rcdMap = rs.getMappedRcds("id", String.class); 19 // 将记录集进行分组 20 Map<Object, List<Rcd>> groupedMap = rs.getGroupedMap("id", "code"); 21}
<a name="ghaTM"></a>
排序、过滤、去重
RcdSet 同时也提供若干排序、过滤、去重的方法,示例代码如下:
1/** 2* 排序、过滤、去重 3*/ 4@Test 5public void demo_misc() { 6 // 创建DAO 7 DAO dao = DBInstance.DEFAULT.dao(); 8 RcdSet rs = dao.queryPage("select * from sys_dict where code like ?", 50, 1, "%o%"); 9 rs.parallelStream().forEach(r -> { 10 System.out.println(r.toJSONObject()); 11 }); 12 // 按指定字段去重 13 rs.distinct("id"); 14 // 按指定字段排序 15 rs.sort("create_time", true, true); 16 // 查找,精确匹配,返回第一行匹配的记录 17 Rcd r = rs.find("name", "机柜状态"); 18 System.out.println(r == null ? "未找到" : r.toJSONObject()); 19 // 过滤,并指定匹配操作符 20 RcdSet result = rs.filter("id", "36", FilterOperator.CONTAINS); 21 for (Rcd rcd : result) { 22 System.out.println("filter" + rcd.toJSONObject()); 23 } 24 // 增加排名列 25 rs.rank("rank", "id", true); 26 for (Rcd rcd : rs) { 27 System.out.println("rank-1 : id = " + rcd.getString("id") + " , rank = " + rcd.getInteger("rank")); 28 } 29 // 用排名值填充一个已经存在的列 30 rs.fillRankField("version", "create_time", false); 31 for (Rcd rcd : rs) { 32 System.out.println("rank-2 : id = " + rcd.getString("create_time") + " , rank = " + rcd.getInteger("version")); 33 } 34}
<a name="ftr4s"></a>
序列化
RcdSet 的序列化主要是将自身或 Rcd 转换成 JSON 对象。Foxnic-SQL 依赖了 FastJSON,示例代码如下:
1/** 2* 序列化 3*/ 4@Test 5public void demo_serialization() { 6 // 创建DAO 7 DAO dao = DBInstance.DEFAULT.dao(); 8 RcdSet rs = dao.queryPage("select * from sys_dict where code like ?", 50, 1, "%o%"); 9 // 序列化记录集为 JSONArray 每个元素为 JSONObject 10 JSONArray array1 = rs.toJSONArrayWithJSONObject(); 11 // 序列化记录集为 JSONArray 每个元素为 JSONArray 12 JSONArray array2 = rs.toJSONArrayWithJSONArray(); 13 // 序列化记录集为 JSONArray 每个元素为 JSONObject , 并指定哪些字段转入到 JSONObject 14 JSONArray array4 = rs.toJSONArrayWithJSONObject("id", "code", "name"); 15 System.out.println(array4); 16 17 // 序列化记录 18 for (Rcd r : rs) { 19 // 全字段转换 20 JSONObject object1 = r.toJSONObject(); 21 System.out.println(object1); 22 // 指定字段转换 23 JSONObject object2 = r.toJSONObject("id", "code", "name"); 24 System.out.println(object2); 25 } 26 27 // 指定列名转换规则 , DB_UPPER_CASE 表示完全和数据库一致,整体转大写 28 rs.setDataNameFormat(DataNameFormat.DB_UPPER_CASE); 29 System.out.println(rs.toJSONArrayWithJSONObject()); 30}
<a name="IKPmn"></a>
实体转换
RcdSet 除了转 JSON 对象外,也可以转换成 Java 的 Pojo 对象;转换到 Foxnic 生成的实体对象时将获得更高的性能。示例代码如下:
1/** 2* 处理实体数据 3*/ 4@Test 5public void demo_entity() { 6 // 创建DAO 7 DAO dao = DBInstance.DEFAULT.dao(); 8 RcdSet rs = dao.queryPage("select * from example_address", 50, 1); 9 // 转成实体列表 10 List<Address> addressList = rs.toEntityList(Address.class); 11 System.out.println(JSON.toJSON(addressList)); 12 // 转成分页的实体列表 13 PagedList<Address> addressPageList = rs.toPagedEntityList(Address.class); 14 System.out.println(JSON.toJSON(addressPageList)); 15 // 遍历并逐个转换 16 for (Rcd r : rs) { 17 Address address = r.toEntity(Address.class); 18 System.out.println(JSON.toJSON(address)); 19 } 20}
<a name="kET92"></a>
数据操作
开发人员可以通过 RcdSet 和 Rcd 完成数据的持久化操作。Rcd 为开发人员提供了 insert、update、save、delete 等方法,直接操纵在库数据。示例代码如下:
1/** 2* 操作数据 3*/ 4@Test 5public void demo_crud() { 6 // 创建DAO 7 DAO dao = DBInstance.DEFAULT.dao(); 8 9 String id = IDGenerator.getSnowflakeIdString(); 10 Rcd r = dao.queryRecord("select * from example_address where id=?", id); 11 boolean suc = false; 12 if (r == null) { 13 r = dao.createRecord("example_address"); 14 r.set("id", id); 15 r.set("name", "leefj"); 16 // 如果是 null 则不连入SQL语句 17 r.set("phone_number", "13444252562"); 18 r.set("address", "宁波"); 19 r.set("region_type", "国内"); 20 r.set("create_time", new Date()); 21 // 设置数据库表达式 22 r.setExpr("update_time", "now()"); 23 // 执行插入操作 24 suc=r.insert(); 25 System.out.println("数据插入"+(suc?"成功":"失败")); 26 } else { 27 r.set("update_time", new Date()); 28 r.set("update_by", "leefj"); 29 // 执行更新操作 30 suc=r.update(SaveMode.DIRTY_FIELDS); 31 System.out.println("数据更新"+(suc?"成功":"失败")); 32 } 33 suc=r.delete(); 34 System.out.println("数据删除"+(suc?"成功":"失败")); 35}
<a name="GOsQK"></a>
小结
本节主要介绍了 Foxnic-SQL 中的记录集(RcdSet)和记录(Rcd),它们为开发人员提供了诸多功能,如遍历与存取值、结构转换,过滤排序、数据操作等。记录集(RcdSet)和记录(Rcd)特别在无 Po 对象的场景下对处理数据,它的动态性也特别适合很多高级场景。 <a name="VZBg5"></a>
相关项目
https://gitee.com/LeeFJ/foxnic<br /> https://gitee.com/LeeFJ/foxnic-web<br /> https://gitee.com/lank/eam<br /> https://gitee.com/LeeFJ/foxnic-samples <a name="HPU3W"></a>
