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

本文涉及的产品
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
简介: 仅仅使用PG的row_to_json(),即可轻松返回需要的各种SQL数据。这样子使得中间层(JAVA/PHP)的代码变得简化,无需org.json和json-lib库再做一次封装,这是架构师要注意的一个重要趋势。

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

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

cdc116eb7ecbfb88e1b7939e29d6be26eada620d

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

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

b9f4459a662df4ab91d0295280ef51c99bcca1dd

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反向生成)如下:

92eef4fae9092269236458894ef24e0fb960fa21

好,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库再做一次封装,这是架构师要注意的一个重要趋势。




相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
2月前
|
XML JSON 关系型数据库
PostgreSQL支持多种数据类型
PostgreSQL支持多种数据类型
116 1
|
3月前
|
SQL 关系型数据库 MySQL
SQL Server、MySQL、PostgreSQL:主流数据库SQL语法异同比较——深入探讨数据类型、分页查询、表创建与数据插入、函数和索引等关键语法差异,为跨数据库开发提供实用指导
【8月更文挑战第31天】SQL Server、MySQL和PostgreSQL是当今最流行的关系型数据库管理系统,均使用SQL作为查询语言,但在语法和功能实现上存在差异。本文将比较它们在数据类型、分页查询、创建和插入数据以及函数和索引等方面的异同,帮助开发者更好地理解和使用这些数据库。尽管它们共用SQL语言,但每个系统都有独特的语法规则,了解这些差异有助于提升开发效率和项目成功率。
367 0
|
JSON Java 关系型数据库
Spring Boot 学习研究笔记(十三) Spring Data JPA与PostgreSQL的jsonb类型集成
Spring Boot 学习研究笔记(十三) Spring Data JPA与PostgreSQL的jsonb类型集成
306 0
|
XML SQL JSON
PostgreSQL 12 文档: 第 8 章 数据类型
PostgreSQL有着丰富的本地数据类型可用。用户可以使用CREATE TYPE命令为 PostgreSQL增加新的数据类型。
161 0
|
存储 JSON NoSQL
「Postgresql架构」使用PostgreSQL中的JSONB数据类型加快操作
「Postgresql架构」使用PostgreSQL中的JSONB数据类型加快操作
|
SQL 存储 移动开发
PostgreSQL psql的使用,SQL语法,数据类型,递归SQL用法(四)|学习笔记
快速学习3 PostgreSQL psql的使用,SQL语法,数据类型,递归SQL用法(四)
 PostgreSQL psql的使用,SQL语法,数据类型,递归SQL用法(四)|学习笔记
|
SQL 关系型数据库 数据库
3 PostgreSQL psql的使用,SQL语法,数据类型,递归SQL用法(三)|学习笔记
快速学习3 PostgreSQL psql的使用,SQL语法,数据类型,递归SQL用法(三)
|
关系型数据库 分布式数据库 PolarDB
《阿里云产品手册2022-2023 版》——PolarDB for PostgreSQL
《阿里云产品手册2022-2023 版》——PolarDB for PostgreSQL
363 0
|
存储 缓存 关系型数据库
|
存储 SQL 并行计算
PolarDB for PostgreSQL 开源必读手册-开源PolarDB for PostgreSQL架构介绍(中)
PolarDB for PostgreSQL 开源必读手册-开源PolarDB for PostgreSQL架构介绍
419 0