如何使用PostgreSQL中的JSONB数据类型(一)

本系列博文讲述使用PG JSON/JSONB类型考虑的问题,这是第一篇(我尽量写的简单,让初学者跟着做很容易也能重复实验),看看不使用JSON/JSONB,仅仅使用row_to_json()函数能帮我们做什么。

JSON数据类型在当前互联网浪潮中得到了大量的采用,见下图GOOGLE指数对比JSON和XML,从全球范围来看:

蓝色表示JSON的趋势,成明显的上升趋势。

从中国范围来看,JSON搜索热度明显高于XML:

PG也从9.2开始正式引入JSON类型,那自然就问:

  1. 既然DB支持了JSON和JSON相关函数,是不是中间服务层(如PHP,JAVA)通过JDBC可以直接获取到JSON数据,而无需再用org.json和json-lib库把以前的行数据进行转换?

我们来做个实验:

运行环境:PG 9.6,WIN8.1 64,Tomcat 8.0,JDK 8

-- 创建数据库
--postgres登陆pg,使用如下sql创建数据库和用户名密码:
CREATE ROLE jsontest PASSWORD 'jsontest.dba' CREATEDB NOSUPERUSER CREATEROLE LOGIN;
CREATE DATABASE jsontestdb OWNER jsontest ENCODING 'UTF8';
-- 用jsontest登录,然后是DDL:
-- 创建一个全局的序列
DROP SEQUENCE IF EXISTS jsontest_uuid_seq CASCADE;
CREATE SEQUENCE jsontest_uuid_seq START 0;
-- Table: TUsers, 用户表
DROP TABLE IF EXISTS TUsers CASCADE;
CREATE TABLE TUsers (
id bigint DEFAULT nextval('jsontest_uuid_seq') PRIMARY KEY,-- 用户id
realName character varying(64) -- 真实姓名
)WITH ( OIDS=FALSE );
CREATE INDEX TUsers_cellphone_idx ON TUsers (realName);
-- Table: TProject,用户创建的项目表
DROP TABLE IF EXISTS TProject CASCADE;
CREATE TABLE TProject (
id bigint DEFAULT nextval('jsontest_uuid_seq') PRIMARY KEY,-- 活动id
title character varying(256) NOT NULL UNIQUE, -- 项目名称,设置为UNIQUE,避免混淆
creatorId integer DEFAULT NULL REFERENCES TUsers (id) match simple on delete SET NULL -- 活动创建的用户id
)WITH ( OIDS=FALSE );
CREATE INDEX TProject_creator_idx ON TProject( creatorId );
-- 测试数据:
insert into TUsers values(1,'test1');
insert into TUsers values(2,'test2');
insert into TUsers values(3,'test3');
insert into TProject values(1,'测试项目1',1);
insert into TProject values(2,'测试项目2',1);
insert into TProject values(3,'测试项目3',1);

DB设计图(在Eclipse用ERMaster反向生成)如下:

好,JAVA这一块,我们分多种情况来分析:

1. 数据库最多只会返回一行数据,然后转换成JSON对象:如通过用户id获得用户JSON对象

/*
	 * 根据用户id获取用户信息
	 */
	public String getUserInfoFromId(String userId){
    	String res = null ;
    	if(null ==  userId || userId.isEmpty() ) return res;
    	Connection conn;
		Statement stm;
		ResultSet rs;
		DBPool dbp = new DBPool();
		conn = dbp.getConnection();
		try {
			stm = conn.createStatement();
			/*
			 * WITH myInfo AS (select a.id,a.realName from TUsers a where a.id = 1 )
SELECT row_to_json(b.*) from myInfo b
			 */
			String sql = "SELECT row_to_json(a.*) from TUsers a where a.id = "+userId;
			rs = stm.executeQuery(sql);
			if(rs != null && rs.next()){
				res = rs.getString(1);
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally{
			dbp.closeConnection();
		}
    	return res;
    }

上面的核心是SQL:SELECT row_to_json(a.*) from TUsers a where a.id = 1

那如果我获得的不是a.*,即不需要一整行数据(如密码不想返回)怎么办?如下会报错:

SELECT row_to_json(a.id,a.realName) from TUsers a where a.id = 1

解决办法有:

SELECT row_to_json(a.*) from (select b.id,b.realName from TUsers b where b.id = 1 ) a

或者使用WITH:

WITH myInfo AS (select a.id,a.realName from TUsers a where a.id = 1 )

SELECT row_to_json(b.*) from myInfo b

或者:

SELECT

(SELECT row_to_json(_) from (select a.id, a.realName) as _)

from

TUsers a where a.id = 1

关于这个问题可以参考:http://dba.stackexchange.com/questions/27732/set-names-to-attributes-when-creating-json-with-row-to-json

注意的是,不能用row()函数,因为无法产生别名,即正如https://www.postgresql.org/docs/9.6/static/functions-json.html里所演示的那样:

row_to_json(row(1,'foo')) {"f1":1,"f2":"foo"}

所以综合以上,建议用WITH方法,简洁明了。

public class UserWS extends HttpServlet :

@WebServlet(asyncSupported = true, urlPatterns = { "/user" })
public class UserWS extends HttpServlet {
	private static final long serialVersionUID = 1L;       

    public UserWS() {
        super();
    }
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String rtn = "";
		request.setCharacterEncoding("UTF-8");
		response.setContentType("text/json; charset=UTF-8");
		response.setCharacterEncoding("UTF-8");
		String action = request.getParameter("action");		

		if(action.equalsIgnoreCase("getuserinfo")) {//其他人通过用户ID获取用户信息
			String userid = (String) request.getParameter("userid");
			User u = new User();
			String userJson = u.getUserInfoFromId(userid);
			if(null != userJson){//存在该用户信息
				rtn = new StringBuilder()
						.append( "{\"issuccessful\":\"true\",\"data\":")
						.append((null==userJson)?"":userJson)
						.append("}")
						.toString();
			}else{
				rtn = "{\"issuccessful\":\"false\",\"errorcode\":\"NOEXIST_USER\"}";
			}
        }

		String callbackFunName = request.getParameter("callbackparam");
		if(null != callbackFunName ){
			response.getOutputStream().write(StringUtil.wrapCrossDomain(rtn,callbackFunName).getBytes("UTF-8"));
		}else{
			response.getOutputStream().write(rtn.getBytes("UTF-8"));
		}
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request,response);
	}
}

测试一下,当我们发送:http://localhost:8080/jsonTest/user?action=getuserinfo&userid=1

返回:

{
"issuccessful":"true",
"data":{
"id":1,
"realname":"test1"
}
}

2. 数据库返回多行数据:如获取用户参与的项目

WITH myProjects AS (select a.id,a.title from TProject a where a.creatorId = 1)

SELECT row_to_json(b.*) from

(SELECT array_to_json(array(select row_to_json(myProjects.*) from myProjects),false) as myProjects) b

测试一下:http://localhost:8080/jsonTest/project?action=getmyprojects&userid=1

返回:

{
"issuccessful":"true",
"data":{
"myprojects":[
{
"id":3,
"title":"测试项目3"
},
{
"id":2,
"title":"测试项目2"
},
{
"id":1,
"title":"测试项目1"
}
]
}
}

3. 返回某些表的一行数据,加上某些表的多行数据:如获取用户基本信息以及用户参与的项目

WITH myInfo AS (select id,realName from TUsers where id = 1 ), -- 一行数据

myProjects AS (select a.id,a.title from TProject a,myInfo b where a.creatorId = b.id) -- 多行数据

SELECT row_to_json(x.*) from

(

select c.*,d.* from

myInfo c,

(SELECT array_to_json(array(select row_to_json(myProjects.*) from myProjects),false) as myProjects) d -- 把多行数据生成一行一列json数组

) x

该SQL返回:

{
"myprojects":[
{
"id":3,
"title":"测试项目3"
},
{
"id":2,
"title":"测试项目2"
},
{
"id":1,
"title":"测试项目1"
}
],
"id":1,
"realname":"test1"
}

总结一下:

仅仅使用PG的row_to_json(),即可轻松返回需要的各种SQL数据。这样子使得中间层(JAVA/PHP)的代码变得简化,无需org.json和json-lib库再做一次封装,这是架构师要注意的一个重要趋势。

时间: 2024-07-28 17:06:13

如何使用PostgreSQL中的JSONB数据类型(一)的相关文章

介绍PostgreSQL中的jsonb数据类型_数据库其它

PostgreSQL 9.4 正在加载一项新功能叫jsonb,是一种新型资料,可以储存支援GIN索引的JSON 资料.换言之,此功能,在即将来临的更新中最重要的是,如果连这都不重要的话,那就把Postgres 置于文件为本数据库系统的推荐位置吧. 自从9.2开始,一个整合JSON 资料类型已经存在,带有一整套功能(例如资料产生和资料解构功能),还有9.3新增的操作者.当使用JSON 资料类型,资料的被存储成一完全一样的副本,功能还在此之上运作,还另外需要后台运作的重新分析. 这心得JSONB 资

《卸甲笔记》-PostgreSQL和Oracle的数据类型的对比系列五:其它类型

PostgreSQL是世界上功能最强大的开源数据库,在国内得到了越来越多机构和开发者的青睐和应用.随着PostgreSQL的应用越来越广泛,Oracle向PostgreSQL数据库的数据迁移需求也越来越多.数据库之间数据迁移的时候,首先遇到的,并且也是最重要的,就是数据类型之间的转换.下面根据自己的理解和测试,写了一些数据类型之间的差异以及迁移时的注意事项的文章,不足之处,尚请多多指教. 其它类型 Oracle的内建数据类型中,还有一些其它的类型.不能够归类到字符.数字.时间和大数据类型等.比如

《卸甲笔记》-PostgreSQL和Oracle的数据类型的对比系列四:大数据类型

PostgreSQL是世界上功能最强大的开源数据库,在国内得到了越来越多机构和开发者的青睐和应用.随着PostgreSQL的应用越来越广泛,Oracle向PostgreSQL数据库的数据迁移需求也越来越多.数据库之间数据迁移的时候,首先遇到的,并且也是最重要的,就是数据类型之间的转换.下面根据自己的理解和测试,写了一些数据类型之间的差异以及迁移时的注意事项的文章,不足之处,尚请多多指教. 大数据类型 Oracle的大数据类型主要包括三类.分别是存储在数据库内部的类型,包括BLOB, CLOB,

《卸甲笔记》-PostgreSQL和Oracle的数据类型的对比系列二:数字类型

PostgreSQL是世界上功能最强大的开源数据库,在国内得到了越来越多机构和开发者的青睐和应用.随着PostgreSQL的应用越来越广泛,Oracle向PostgreSQL数据库的数据迁移需求也越来越多.数据库之间数据迁移的时候,首先遇到的,并且也是最重要的,就是数据类型之间的转换.下面根据自己的理解和测试,写了一些数据类型之间的差异以及迁移时的注意事项的文章,不足之处,尚请多多指教. 数字类型 Oracle内建的数字类型有四种,分别是number,float,binary_float,bin

《卸甲笔记》-PostgreSQL和Oracle的数据类型的对比系列三:时间类型

PostgreSQL是世界上功能最强大的开源数据库,在国内得到了越来越多机构和开发者的青睐和应用.随着PostgreSQL的应用越来越广泛,Oracle向PostgreSQL数据库的数据迁移需求也越来越多.数据库之间数据迁移的时候,首先遇到的,并且也是最重要的,就是数据类型之间的转换.下面根据自己的理解和测试,写了一些数据类型之间的差异以及迁移时的注意事项的文章,不足之处,尚请多多指教. 日期时间类型 Oracle日期时间类型有两类,一类是日期时间类型,包括Date, Timestamp wit

PostgreSQL教程(四):数据类型详解_PostgreSQL

一.数值类型:     下面是PostgreSQL所支持的数值类型的列表和简单说明: 1. 整数类型:     类型smallint.integer和bigint存储各种范围的全部是数字的数,也就是没有小数部分的数字.试图存储超出范围以外的数值将导致一个错误.常用的类型是integer,因为它提供了在范围.存储空间和性能之间的最佳平衡.一般只有在磁盘空间紧张的时候才使用smallint.而只有在integer的范围不够的时候才使用bigint,因为前者(integer)绝对快得多.     2.

PostgreSQL学习手册(常用数据类型)

PostgreSQL学习手册(常用数据类型) 一.数值类型:     下面是PostgreSQL所支持的数值类型的列表和简单说明: 名字 存储空间 描述 范围 smallint 2 字节 小范围整数 -32768 到 +32767 integer 4 字节 常用的整数 -2147483648 到 +2147483647 bigint 8 字节 大范围的整数 -9223372036854775808 到 9223372036854775807 decimal 变长 用户声明精度,精确 无限制 nu

曾国藩的"顺势而为"在PostgreSQL中的价值体现

后人对曾国藩的一些评论:"曾国藩顺势而为 成就一番事业,并一定程度上做了一些推动历史前进的事情". "顺势而为"这个词实在是用得太好了,我们身在数字时代,更加应该懂得顺势而为的道理,比如在金融行业中,不断变化的股价:在物联网的传感器中,不断上报的数字:由数据监测不断画出的曲线等等,这些无不体现着一个字:"势". 我们来看看使用PostgreSQL数据库如何抓住这个"势"呢? 首先贴一个最小二乘法least-squares-fi

SQL Server Yukon 中的XML 数据类型

server|xml|数据|数据类型 SQL Server Yukon 中的XML 数据类型 作者:Suhil Srinivas 翻译:朱二 原文出处:http://www.c-sharpcorner.com/Longhorn/Yukon/XmlDataTypesInYukon.asp描述 这篇文章介绍了 SQL Server 最新版本 Yukon Beta1 中的新增数据类型.对任何了解 SQL Server2000 以及其他数据库的开发者来说,本文是 一个好的起点.介绍 当SQL Serve