预见猿份
主题
首页面试题模拟面试SQL练习在线工具关于我们老苗一对一私教学员评价
实战项目
零基础学习路线项目前置基础创新WMS项目Java微服务框架与实战云岚到家项目闪聚支付项目学成在线项目青橙电商项目JVM原理与实战调优分布式事务专题Java高频面试题MySQL从入门到精通达梦数据库从入门到实战MongoDB从入门到实战PostgreSQL从入门到实战TiDB从入门到实战Java数据结构与算法Java 并发编程(JUC)实战课程老苗一对一私教学员评价
blog我的会员
  • PostgreSQL从入门到实战|Java 程序员的进阶关系数据库课程

    • 课程介绍
    • 第1章 认识PostgreSQL与环境搭建
    • 第2章 SQL基础(MySQL对照)
    • 第3章 高级SQL与编程对象
    • 第4章 事务、锁与MVCC
    • 第5章 Java集成实战
    • 第6章 索引与性能优化
    • 第7章 MySQL迁移与日常运维







----- 到底线了 -----

第1章 认识 PostgreSQL 与环境搭建 ​

🎯 本章学习目标 ​

学完本章,你应当能够:

  1. 说清 PostgreSQL 的定位,以及它相对 MySQL 的气质差异:更贴标准、类型更丰富、扩展性更强、单机功能更「重」。
  2. 建立 PG 的层级心智:实例(Instance) → 数据库(Database) → 模式(Schema) → 表/对象,并能和 MySQL 的「实例 → 库 → 表」两级对齐,理解 schema 这一层 MySQL 基本没有。
  3. 牢记并解释一条与直觉相反的铁律:PG 中未加引号的标识符(表名/列名)会折叠为小写,加双引号才保留原样——这与达梦「转大写」相反,与 MySQL 依操作系统而异也不同。
  4. 完成环境搭建两条路:Windows 安装包 / Docker 容器;说清超级用户 postgres、默认端口 5432、pg_hba.conf 与 postgresql.conf 两大配置文件。
  5. 用 psql 命令行完成「连库 → 看结构 → 跑 SQL」三板斧,并知道 \l \dt \d 这些反斜杠命令。
  6. 用 pgJDBC 跑通第一个 Java 连接,打印数据库版本,理解 jdbc:postgresql://host:5432/db 的构成。

1.1 PostgreSQL 是什么,和 MySQL 有何气质差异 ​

PostgreSQL(官方常简称 PG,读作「P-G」,昵称 Postgres)是一个对象-关系型数据库,起源于加州大学伯克利分校,许可证是宽松的 BSD 类许可,可自由商用。它是很多场景下 MySQL 的「升级选项」,两者的整体气质对比:

维度MySQLPostgreSQL
定位气质轻量、易上手、Web 应用装机量第一功能强、严谨、贴标准、「开源里的 Oracle」
存储引擎可插拔(InnoDB/MyISAM...)单一统一引擎(+扩展),不用选引擎
类型系统常规类型 + JSON极丰富:JSONB、数组、范围、UUID、网络/几何、自定义类型
SQL 标准符合度部分(8.0 后大幅改善)非常高
扩展生态插件较少丰富:PostGIS、pg_trgm、TimescaleDB、pgvector...
复制/HA主从成熟、工具多流复制 + 逻辑复制,生态同样成熟
Java 生态极普及同样一等公民,Spring/Hibernate 支持完善

给 MySQL 同学的定心丸:你在 MySQL 里 90% 的直觉(索引加速事务、WHERE/GROUP BY/JOIN、连接池、主从)都能直接迁过来。本课程的重点是那 10% 会坑你的差异。

1.2 层级架构:比 MySQL 多了一层 Schema ​

1.2.1 结构对照图 ​

MySQL 世界                         PostgreSQL 世界
──────────                        ──────────────────────────
MySQL 实例 (mysqld, :3306)         PG 实例 (postmaster, :5432)
 ├── 数据库 db1                     ├── 数据库 postgres(默认自带)
 │    ├── 表 t_user                 ├── 数据库 mydb
 │    └── 表 t_order                │    └── 模式(schema)
 ├── 数据库 db2                      │         ├── public   ← 默认模式,建表落这里
 │    └── ...                       │         │    ├── 表 t_user
   (库下直接就是表)                │         │    └── 表 t_order
                                    │         └── shop(可自建模式做隔离)
                                    │              └── 表 ...
1
2
3
4
5
6
7
8
9
10
11
  • 实例:一个跑着的 PG 服务进程(术语叫 postmaster),管着若干数据库,监听 5432 端口(对应 MySQL 的 3306)。
  • 数据库(Database):概念和 MySQL 一致。一个实例下多个库,跨库不能直接 JOIN(和 MySQL 跨库不同,PG 跨库连查都做不到,需 dblink/扩展)。
  • 模式(Schema):⚠️ 这是 MySQL 几乎没有的一层。库下面是模式,模式下面才是表。完整定位一张表要写三段:数据库.模式.表,日常用两段 schema.table 即可。
    • 每个库自带一个 public 模式,你不指定时表就建在 public 里。
    • 模式常用于:多团队共用一个库时的命名隔离、把「系统对象」和用户对象分开。
  • search_path:类似 MySQL 的「当前库」概念,但作用在模式上。它是一个模式搜索列表,默认 "$user", public——建/查表不带前缀时,PG 按这个顺序找。Java 连接里可用 currentSchema 参数指定(第 5 章)。

心智转换:MySQL 里你习惯的「切换到某个 db」,在 PG 里更常见的是「在同一个 db 里切换到某个 schema,或用 schema 前缀限定」。连接串一次只能连一个 database。

1.2.2 用户与角色:MySQL 的 user 在 PG 里是 role ​

MySQL 有 user@host 这套账号体系。PG 把「谁能登录」和「有什么权限」统一抽象成 role(角色):

  • CREATE USER = 创建一个能登录的 role(等价 CREATE ROLE ... LOGIN)。
  • 超级用户安装时默认叫 postgres(对应 MySQL 的 root)。
  • 权限、GRANT/REVOKE 语法和 MySQL 类似,但第 7 章会讲 PG 特有的连接认证文件 pg_hba.conf——它决定「哪个 IP、用哪种认证方式、能连哪个库」,是 MySQL 没有的独立机制。

1.3 铁律:未加引号的标识符折叠为小写 ​

这是 MySQL 同学(甚至达梦同学)最容易翻车的一条,务必单独记住。

PG 对标识符(表名、列名、schema 名)的处理规则:

  • 不加引号:PG 会把它统一转成小写后再解析。
    sql
    CREATE TABLE T_User (Id INT, Name TEXT);
    -- 实际创建的是表 t_user,列 id、name
    SELECT * FROM T_USER;      -- ✅ 能查到,因为又折叠成小写 t_user
    SELECT * FROM "T_User";    -- ❌ 报错 relation "T_User" does not exist
    1
    2
    3
    4
  • 加双引号 "...":区分大小写、原样保留。
    sql
    CREATE TABLE "T_User" ("Id" INT, "Name" TEXT);   -- 真的建了一个大写表名
    SELECT "Id" FROM "T_User";                        -- 之后每次都得带引号且大小写一致
    1
    2

与 MySQL / 达梦 的关键差异:

数据库未加引号标识符后果
MySQL依操作系统(Windows 不区分、Linux 表名区分)跨平台迁移常踩坑
达梦 DM8默认折叠为大写与 PG 正好相反
PostgreSQL恒折叠为小写(跨平台一致)加引号才保留原样

实践建议(贯穿全课程):表名、列名一律用小写 + 下划线命名,永远不加双引号。这样最省心,也符合 PG 社区惯例。除非你有强需求,否则不要制造大小写混合的对象——一旦建了 "MyTable",之后每条 SQL 都得记得加引号,Java 里拼 SQL 更是灾难。

1.4 安装环境 ​

1.4.1 Windows(官方安装包) ​

  1. 到 EnterpriseDB Downloader 下载 Windows x86-64 安装包。
  2. 图形化安装向导中依次设置:安装目录、组件(勾选 PostgreSQL Server / pgAdmin 4 / Command Line Tools)、数据目录、超级用户 postgres 的密码(记住它)、端口(默认 5432,一般不改)、区域(locale)选默认。
  3. 完成后 PG 作为 Windows 服务自动运行。开始菜单里有 SQL shell (psql) 快捷方式和 pgAdmin 4。

1.4.2 Docker(推荐做实验,最干净) ​

bash
# 起了就用,数据存 named volume,超管密码 postgres
docker run -d --name pg \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_DB=mydb \
  -p 5432:5432 \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16

# 进入容器内的 psql
docker exec -it pg psql -U postgres
1
2
3
4
5
6
7
8
9
10

环境变量对照:POSTGRES_PASSWORD(超管密码,必填)、POSTGRES_DB(初始化建的库,默认 postgres)、POSTGRES_USER(默认 postgres)。数据目录在容器内是 /var/lib/postgresql/data,务必挂出来。

常用镜像 tag:postgres:16、postgres:17(大版本)、postgres:16-alpine(更瘦)。生产建议明确固定大版本,别用 latest。

1.4.3 Linux(yum/apt) ​

bash
# Debian/Ubuntu 系
sudo apt install postgresql postgresql-contrib
sudo systemctl status postgresql        # 装完即开机自启
sudo -u postgres psql                   # 用系统 postgres 账号免密切入 psql(peer 认证)
1
2
3
4

sudo -u postgres psql 能免密进入,靠的正是 pg_hba.conf 里对本地 socket 的 peer 认证(第 7 章详解)——这是 PG 和 MySQL 很不同的地方:操作系统用户到数据库用户的映射。

1.5 psql 三板斧 ​

psql 是官方命令行客户端,类比 MySQL 的 mysql 命令。

bash
psql -h localhost -p 5432 -U postgres -d mydb
# 提示符变为: postgres=#   (#=超级用户,$=普通用户)
1
2

进去后的两类操作——反斜杠命令(psql 自己的,不在 SQL 里)和 SQL 语句:

sql
\l                 -- 列出所有数据库
\c mydb            -- 切换连接到一个新库(Connect)
\dn                -- 列出当前库的所有 schema
\dt                -- 列出当前 schema(通常 public)下的所有表
\dt shop.*          -- 列出 shop 模式下的表
\d t_user          -- 描述一张表:列、类型、索引、约束(最常用)
\du                -- 列出所有角色(用户)
\encoding UTF8     -- 查看客户端编码

-- 以下为真正的 SQL
SELECT version();                              -- 查 PG 版本
SHOW server_version;                           -- 也能看版本
SHOW search_path;                              -- 当前模式搜索路径
SELECT current_database(), current_user;       -- 当前库、当前用户
CREATE TABLE demo (id serial primary key, note text);   -- 建表(serial=自增,第2章讲)
INSERT INTO demo(note) VALUES ('第一条');
SELECT * FROM demo;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

几个和 MySQL 不同的手感点:

  • SQL 必须以分号结尾,且 psql 里 \x 可切换扩展(竖排)显示结果,看宽表很清楚。
  • 想退出用 \q(对应 MySQL 的 exit/quit)。
  • \d 信息密度极高,一张表的列/类型/默认值/索引/约束一次看全,务必养成用它替代 MySQL 里 SHOW CREATE TABLE 的习惯。

1.6 跑通第一个 Java 连接 ​

1.6.1 引入 pgJDBC ​

Maven 坐标(官方驱动,纯 Java):

xml
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.7.4</version>
</dependency>
1
2
3
4
5

版本以 42.7.x 系列为准;Spring Boot 3.x 会传递引入适配好的版本,通常无需你手写死版本号。驱动类名 org.postgresql.Driver(JDBC 4.0+ 已可省略 Class.forName,Spring Boot 场景更是自动处理)。

1.6.2 最小连接 Demo ​

java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class PgFirstConnect {
    public static void main(String[] args) throws Exception {
        // URL 三段式:协议:子协议://主机:端口/数据库名
        String url = "jdbc:postgresql://localhost:5432/mydb";
        String user = "postgres";
        String pwd = "postgres";

        try (Connection conn = DriverManager.getConnection(url, user, pwd);
             Statement st = conn.createStatement();
             ResultSet rs = st.executeQuery("SELECT version(), current_database()")) {
            if (rs.next()) {
                System.out.println("连接成功,服务端版本:" + rs.getString(1));
                System.out.println("当前数据库:" + rs.getString(2));
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

跑通即说明环境 OK。对照 MySQL:URL 从 jdbc:mysql://host:3306/db 换成 jdbc:postgresql://host:5432/db,其余 JDBC 编程模型(DriverManager/Connection/Statement/ResultSet)完全一致,你会的 JDBC 可 100% 复用。

1.6.3 冒烟测试:建库建表一条龙 ​

bash
# 先建一个专供练习的库(在 psql 里执行,或用下面的命令行走)
docker exec -it pg psql -U postgres -c "CREATE DATABASE shopdb;"
docker exec -it pg psql -U postgres -d shopdb -c \
  "CREATE TABLE t_product(id serial primary key, name text, price numeric(10,2));"
docker exec -it pg psql -U postgres -d shopdb -c \
  "INSERT INTO t_product(name, price) VALUES('键盘', 199.00) RETURNING id;"
docker exec -it pg psql -U postgres -d shopdb -c "SELECT * FROM t_product;"
1
2
3
4
5
6
7

RETURNING id 是 PG(及标准 SQL)的特性:插入后直接返回生成的列,比 MySQL 里再 SELECT LAST_INSERT_ID() 优雅,第 2、5 章还会反复用到。

1.7 本章小结 ​

  1. PG 比 MySQL 多一层 schema(模式):库 → 模式 → 表,日常表默认落在 public 模式,跨库不能直接 JOIN。
  2. 铁律:未加引号标识符折叠为小写,加 "..." 才保留原样且区分大小写——命名请全程小写下划线。
  3. 用户体系是 role,超管默认 postgres;端口 5432;两大配置文件 postgresql.conf(参数)与 pg_hba.conf(认证)第 7 章展开。
  4. 环境搭建推荐 Docker(postgres:16);命令行工具是 psql,\dt \d \l 是日常三件套。
  5. Java 侧:org.postgresql:postgresql + jdbc:postgresql://host:5432/db,JDBC 编程模型与 MySQL 完全通用,RETURNING 更好用。

✏️ 课后练习 ​

  1. 用 Docker 起一个 postgres:16,进 psql 执行 \l \dn \dt,观察默认有哪些库(postgres/template0/template1)和 public 模式。
  2. 亲手复现「小写折叠」:分别执行 CREATE TABLE A_b(c int); 和 CREATE TABLE "A_B"(C int);,用 \dt 看两张表的真实名字,再尝试不带引号 SELECT,记录哪些成功哪些报错。
  3. 把 1.6 的 Java Demo 跑通,并在连接后执行 SHOW search_path; 打印当前模式搜索路径。
  4. 建一个 shop 模式,在其中建表并在 Java 里用两段名 shop.xxx 查询它,体会 schema 隔离。

📖 参考答案 ​

本章题目都是「环境+观察」类,答案给的是完整命令 + 预期回显。假设容器名 pg、超管 postgres、密码 postgres(与 1.4.2 一致);Windows 本地安装的话把 docker exec -it pg 换成 psql 直接连。

第 1 题:Docker 起 postgres:16,用 \l \dn \dt 观察默认库与 public 模式

第 1 步,起容器(首次会拉镜像,名字和 1.4.2 一致方便后面章节沿用):

bash
docker run -d --name pg -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=mydb -p 5432:5432 postgres:16
docker ps --filter name=pg --format "{{.Names}}  {{.Image}}  {{.Status}}  {{.Ports}}"
# pg  postgres:16  Up 5 seconds  0.0.0.0:5432->5432/tcp
1
2
3

第 2 步,进去看库(\l 是 list databases):

bash
docker exec -it pg psql -U postgres -c '\l'
1
text
                                                   List of databases
   Name    |  Owner   | Encoding |  Collate   |   Ctype    | ICU Locale | Locale Provider | Access privileges
-----------+----------+----------+------------+------------+------------+-----------------+---------------------------
 mydb      | postgres | UTF8     | C.utf8     | C.utf8     |            | libc            |
 postgres  | postgres | UTF8     | C.utf8     | C.utf8     |            | libc            |
 template0 | postgres | UTF8     | C.utf8     | C.utf8     |            | libc            | =c/postgres +
           |          |          |            |            |            |                 | postgres=CTc/postgres
 template1 | postgres | UTF8     | C.utf8     | C.utf8     |            | libc            | =c/postgres +
           |          |          |            |            |            |                 | postgres=CTc/postgres
(4 rows)
1
2
3
4
5
6
7
8
9
10

第 3 步,背下来这三个默认库的用途(面试与运维都用得上):

库作用能直连吗
postgres安装自带的「工作库」,运维连接默认落这能
template1建库时的克隆模板,你装在它里的东西会被新库继承能(但不建议往它里写业务对象)
template0原始空模板,用来建「编码/排序规则不同」的干净库否,datallowconn = f

验证最后一列:

sql
SELECT datname, datallowconn FROM pg_database ORDER BY 1;
--   datname   | datallowconn
-- -------------+--------------
--  mydb       | t
--  postgres    | t
--  template0  | f            ← 连不上,所以只能这么用:CREATE DATABASE x TEMPLATE template0 ENCODING 'LATIN1';
--  template1  | t
1
2
3
4
5
6
7

第 4 步,切到 mydb 看模式与表:

bash
docker exec -it pg psql -U postgres -d mydb -c '\dn'
--       List of schemas
--   Name  |       Owner
-- --------+-------------------
--  public | pg_database_owner
-- (1 row)
-- 注意两点:① 每个库自带且仅自带 public 这一个模式;② PG15 起它的 owner 是角色组 pg_database_owner,
--         而且默认**撤掉了 PUBLIC 的 CREATE 权限**——这就是很多新手用普通账号建表报
--         “permission denied for schema public” 的根因(第 7 章 GRANT 一节会修它)。

docker exec -it pg psql -U postgres -d mydb -c '\dt'
-- Did not find any relations.          ← 新库里 public 下确实一张表都没有
1
2
3
4
5
6
7
8
9
10
11
12

第 5 步,顺手把 1.2 的层级心智坐实(实例 → 库 → 模式 → 表 四级各一个查询):

sql
SELECT version();                              -- 实例(哪个服务端)
SELECT current_database(), current_user;       -- 当前库 + 当前角色(注意是 user 不是 username)
SELECT nspname FROM pg_namespace WHERE nspname NOT LIKE 'pg%';   -- 当前库里的模式列表
SELECT schemaname, tablename FROM pg_tables WHERE schemaname NOT IN ('pg_catalog','information_schema');
-- 刚建好的 mydb 里除了 public 什么都没有——这就是「库不跨、模式可隔离」的起点
1
2
3
4
5

第 2 题:亲手复现「小写折叠」,记录哪些语句成功、哪些报错

第 1 步,建两张表(一个不加引号、一个给表名加双引号):

sql
CREATE TABLE A_b(c int);          -- 表名未引号 → 存为 a_b;列名 c 未引号 → 存为 c
CREATE TABLE "A_B"(C int);        -- 表名引号 → 原样存 A_B;列名 C 未引号 → 仍然存为 c
-- NOTICE(可选观察):两张表的真实名字看 pg_class
SELECT relname FROM pg_class
 WHERE relname IN ('A_b','a_b','A_B','a_B','AB') AND relkind = 'r';
-- relname
-- ---------
--  a_b          ← 表 1 存成全小写
--  A_B          ← 表 2 存成你写的原样(只有它用了引号)
1
2
3
4
5
6
7
8
9

这一行就能看出铁律的细节:引号只保护它包住的那个标识符。同一句里 "A_B" 保留了大写,而同句未加引号的列 C 照样被折叠成 c。

第 2 步,\dt 看列表(反斜杠命令不参与折叠,参数必须是「已存好的真实名字」):

text
\dt
--       List of relations
--  Schema | Name | Type  |  Owner
-- --------+------+-------+----------
--  public | A_B  | table | postgres
--  public | a_b  | table | postgres
-- (2 rows)

\d a_b          ✅ 看得到表 1 的结构
\d "A_B"        ✅ 看得到表 2(名字含大写,给 psql 时也带上引号最稳)
\d A_b          ❌ Did not find any relation named "A_b"    ← 反斜杠命令不做小写折叠!
1
2
3
4
5
6
7
8
9
10
11

第 3 步,把四种 SELECT 写法全试一遍,结果如下图(这才是本题真正要记的东西):

sql
SELECT * FROM a_b;        ✅ 命中表 1(真实名就是 a_b)
SELECT * FROM A_b;        ✅ 命中表 1(折叠后还是 a_b)
SELECT * FROM A_B;        ✅ 仍然命中表 1!A_B 被折叠成 a_b——看着不同、实则同一个表,这就是坑
SELECT * FROM "A_B";      ✅ 命中表 2(引号强制区分大小写)
SELECT * FROM "a_b";      ✅ 命中表 1(它的真名就是小写 a_b)
SELECT * FROM "AB";       ❌ ERROR: relation "AB" does not exist   (SQLSTATE 42P01)
1
2
3
4
5
6

第 4 步,列名的对称实验(很多人只记得表名折叠,列名会坑得更狠):

sql
INSERT INTO "A_B"(C) VALUES(1);     ✅ 列 C 未引号 → 折叠成 c → 正好是表 2 的真列名
SELECT c  FROM "A_B";               ✅ → 1
SELECT "C" FROM "A_B";              ❌ ERROR: column "C" does not exist   ← 列真名是小写 c
1
2
3

对比另一种写法(建表时就给列名加了引号,以后每条 SQL 都得带着):

sql
CREATE TABLE "T_Case"("Id" int, Name text);   -- 列真名:Id(带引号)、name(不带→小写)
SELECT Id FROM "T_Case";                      ❌ column "id" does not exist
SELECT "Id" FROM "T_Case";                    ✅
SELECT name FROM "T_Case";                    ✅ 这一列反而不用引号(建表时就没加)
1
2
3
4

第 5 步,结论与做法(跟 1.3 的铁律呼应):

text
PG:未加引号 → 无条件折叠为小写(跨平台一致,不像 MySQL 看操作系统,也和达梦「折叠为大写」相反)
实际危害:不是「查不到」,而是「查错了表不报错」——A_B 与 a_b 在 PG 眼里是同一个对象,
            而带引号的 "A_B" 是**另一个**对象,迁移时表名大小写一混就会出现「数据好像没同步」的假象。
做法:表名列名一律小写下划线,永远不加双引号;真迫不得已建了大写对象,之后每条 SQL (包括 Java 里拼的)都必须带引号。
1
2
3
4

第 3 题:跑通 1.6 的 Java Demo,并打印 SHOW search_path;

第 1 步,先准备库(用第 1 题建的 mydb,确保驱动能连到它):

bash
docker exec -it pg psql -U postgres -c "SELECT 1 FROM pg_database WHERE datname='mydb';"
-- 若空集:docker exec -it pg psql -U postgres -c "CREATE DATABASE mydb;"
1
2

第 2 步,拉驱动 jar(最小路子,不建工程也能跑):

bash
mvn dependency:get -Dartifact=org.postgresql:postgresql:42.7.4
cp ~/.m2/repository/org/postgresql/postgresql/42.7.4/postgresql-42.7.4.jar ./
# Windows 上用:copy %USERPROFILE%\.m2\repository\org\postgresql\postgresql\42.7.4\postgresql-42.7.4.jar .\
1
2
3

第 3 步,完整可编译的单文件 Demo(补上 1.6 里没有的 search_path 打印):

java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class PgFirstConnect {
    public static void main(String[] args) throws Exception {
        String url = "jdbc:postgresql://localhost:5432/mydb";
        String user = "postgres";
        String pwd = "postgres";

        try (Connection conn = DriverManager.getConnection(url, user, pwd);
             Statement st = conn.createStatement()) {

            try (ResultSet rs = st.executeQuery("SELECT version(), current_database()")) {
                if (rs.next()) {
                    System.out.println("连接成功,服务端版本:" + rs.getString(1));
                    System.out.println("当前数据库:" + rs.getString(2));
                }
            }
            // 新增:查当前模式搜索路径(两种写法等价)
            try (ResultSet rs = st.executeQuery("SHOW search_path")) {
                rs.next();
                System.out.println("search_path = " + rs.getString(1));
            }
            try (ResultSet rs = st.executeQuery(
                    "SELECT current_setting('search_path'), current_user")) {
                rs.next();
                System.out.println("函数写法:" + rs.getString(1) + " | 当前角色:" + rs.getString(2));
            }
            System.out.println("驱动版本:" + conn.getMetaData().getDriverVersion());
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

第 4 步,编译并运行(Windows 的 classpath 分隔符是分号):

bash
javac -encoding UTF-8 PgFirstConnect.java
java -cp ".;postgresql-42.7.4.jar" PgFirstConnect
# Linux/macOS 用冒号:java -cp ".:postgresql-42.7.4.jar" PgFirstConnect
1
2
3

第 5 步,预期输出(对账点:search_path 默认就是那个带 "$user" 的字符串):

text
连接成功,服务端版本:PostgreSQL 16.4 on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
当前数据库:mydb
search_path = "$user", public
函数写法:"$user", public | 当前角色:postgres
驱动版本:42.7.4
1
2
3
4
5

第 6 步,把输出读明白(这一步才是本题的知识点):

  • "$user" 指「与当前登录角色同名的模式」。现在角色是 postgres,而 mydb 里没有叫 postgres 的模式,所以这一项直接落空;
  • 于是表名不带前缀时只去 public 找——这解释了为什么 1.6.3 的建表全落在 public;
  • 驱动版本能打印出来,说明 JDBC 4.0+ 自动发现驱动生效,确实不用写 Class.forName。

加分实验:把 URL 改成 jdbc:postgresql://localhost:5432/mydb?currentSchema=shop 重跑,search_path 会直接变成 shop(第 5 章靠的就是这个参数)。

第 4 题:建 shop 模式,在其中建表,并在 Java 里用两段名 shop.xxx 查询

第 1 步,建模式 + 建表 + 播种(可手算的 3 行):

sql
CREATE SCHEMA IF NOT EXISTS shop;
CREATE TABLE shop.t_book (
  id    serial PRIMARY KEY,
  title text NOT NULL,
  price numeric(10,2) NOT NULL
);
INSERT INTO shop.t_book(title, price) VALUES
  ('Java实战', 99.00), ('MySQL入门', 59.00), ('PG精解', 89.00) RETURNING id;
-- 返回 id 1、2、3
1
2
3
4
5
6
7
8
9

第 2 步,先在 psql 里把「不带前缀会失败」坐实(这就是 schema 隔离的直接证据):

sql
SELECT * FROM t_book;                 -- ❌ ERROR: relation "t_book" does not exist
SELECT * FROM shop.t_book ORDER BY id;  -- ✅ 三行都出来
--   id | title    | price
--  ----+----------+-------
--   1 | Java实战 |  99.00
--   2 | MySQL入门 | 59.00
--   3 | PG精解   |  89.00

SELECT sum(price) FROM shop.t_book;   -- → 247.00(99+59+89,手算可验)
1
2
3
4
5
6
7
8
9

第 3 步,再验「两个模式可以各有一张同名表且互不干扰」(schema 的真正价值):

sql
CREATE TABLE public.t_book (id int, note text);
INSERT INTO public.t_book VALUES (1, '我在 public');

SELECT * FROM public.t_book;   -- → 1 | 我在 public     (只有 1 行,列是 note)
SELECT * FROM shop.t_book;     -- → shop 那三行,列是 title/price
\dt shop.*                     -- 列出 shop 下的表:t_book
1
2
3
4
5
6

两张 t_book 共存不报错——MySQL 里没有这一层(它只有库级隔离,跨库 JOIN 还能写,PG 跨库连查都做不到)。

第 4 步,Java 里用两段名查(在第 3 题的工程上改一句 SQL 即可):

java
try (ResultSet rs = st.executeQuery(
        "SELECT id, title, price FROM shop.t_book WHERE price > 60 ORDER BY id")) {
    while (rs.next()) {
        System.out.println(rs.getInt("id") + " | " + rs.getString("title")
                + " | " + rs.getBigDecimal("price"));
    }
}
// 预期输出(price > 60 滤掉了 59.00 的 MySQL入门):
// 1 | Java实战 | 99.00
// 3 | PG精解   | 89.00
1
2
3
4
5
6
7
8
9
10

第 5 步,把两段名换成 currentSchema,体会「同一份代码两种配置」:

java
// 方案 A:SQL 里写全 shop.t_book(优点:不依赖连接参数,多人多环境都不得错)
// 方案 B:URL 加参数,SQL 里只写 t_book
String url = "jdbc:postgresql://localhost:5432/mydb?currentSchema=shop";
// 此时 st.executeQuery("SELECT count(*) FROM t_book") → 3(直接命中 shop 那张)
1
2
3
4

也可以不开新连接,会话内临时改:SET search_path = shop;(只对当前连接有效,连接池场景下必须设回,否则下个使用者会查到意外模式)。工程里推荐 5.1.3 的做法:URL 统一 currentSchema=shop,同时 DDL 脚本里带 shop. 前缀。

第 6 步,收尾清理(避免影响后面章节的示例):

sql
DROP SCHEMA shop CASCADE;      -- CASCADE 会连着模式里的表一起删,psql 会警告并列出将被删的对象
DROP TABLE public.t_book;
1
2
← 课程介绍第2章 SQL基础(MySQL对照) →








如果发现文档内容有错误或排版错乱,请及时联系站长老苗修改,不胜感激。联系我们
关于我们 | 隐私政策 | 豫ICP备2026003386号-4 | 豫公网安备41010202004008号
目录

本页无章节