更新时间:2026-10-21 GMT+08:00
分享

数据库API(Database Data API)

场景介绍

本章节介绍用户侧应用如何接入并使用华为云RDS Supabase提供的数据库服务。

  • 传统方式:前端不直接连接数据库,而是自行搭建后端服务。为每张表手写数据访问代码并封装为接口,前端通过这些接口间接读写数据库;表越多,重复劳动越多;修改表时,接口也需同步维护,成本随业务增长而持续累积。
  • RDS Supabase:基于托管的PostgreSQL,按表结构自动生成Data API,新建一张表即可调用(首次可能有数秒缓存延迟),后续新增表同步生成;前端只需引入Supabase JavaScript SDK直连这套API即可完成增删改查(CRUD,读操作以anon身份即可,写操作需登录态),从源头消除重复劳动,应用无需自建后端接口,也不必为每张表编写数据访问代码。

这套API由PostgREST框架生成,前端SDK直连即可读写(受RLS与登录态约束),行级安全策略(RLS)负责行级访问控制。

相关概念

华为云RDS Supabase提供托管式PostgreSQL数据库,并基于表结构自动生成Data API,开发者无需手写后端接口即可完成数据增删改查;同时通过行级安全策略(RLS)实现细粒度的数据访问控制与隔离。

  • PostgreSQL数据库

    华为云RDS Supabase为您托管一个独立的PostgreSQL数据库。它是完整的PostgreSQL(非简化版),您无需自行搭建或运维数据库服务器,即可使用全部SQL能力,包括JSONB、全文搜索、视图、存储过程、触发器等高级特性,并可通过SQL可视化界面查看与管理数据。

    创建Supabase应用时,会在托管的PostgreSQL数据库实例上自动创建以下两个数据库,详见表1。

    表1 数据库说明

    数据库

    用途

    supabase_db

    应用数据库,存放业务表与业务数据。

    _supabase

    平台元数据库,存放Supabase平台自身配置,请勿修改。

  • 自动生成Data API

    创建数据库表后,Supabase会基于表结构(Schema)自动为每张表生成对应的HTTP接口,无需手写后端代码。

    这套接口由PostgREST框架生成,覆盖增删改查(CRUD)全部操作:新建一张表即获得其完整数据访问端点(首次可能有数秒缓存延迟,执行NOTIFY pgrst, 'reload schema'可立即刷新),新增表也同步生成。开发者只需关注业务逻辑,无需编写DAO层或后端接口,从源头消除重复开发。

  • 数据库角色

    表2列出RDS Supabase面向应用层、对外的数据库角色,应用与SDK据此选取访问身份。

    表2 角色说明

    角色

    说明

    anon

    匿名用户,通过API Key访问,受RLS约束。

    authenticated

    已认证用户,通过JWT访问,受RLS约束。

    service_role

    服务端角色,绕过RLS,请勿在客户端使用。

    三类角色中,anon与authenticated面向前端,均受RLS约束,区别在登录态决定的读写权限;service_role面向服务端,绕过RLS,严禁客户端使用。

  • 行级安全策略(RLS)

    行级安全策略(Row Level Security,RLS)是PostgreSQL内置的行级访问控制机制。对表启用RLS后,所有访问均须经策略放行:策略按“角色 × 操作”维度界定可见或可操作的行,未匹配策略的操作默认拒绝。RDS Supabase据此在共享数据库上实现租户与用户间的数据隔离,具体表现为anon与authenticated的访问均受RLS约束,service_role则绕过RLS。RLS策略须与GRANT权限配合使用。

  • GRANT权限

    GRANT是PostgreSQL的权限授予语句,用于将表级操作权限赋予数据库角色,决定角色能否对表执行SELECT、INSERT、UPDATE、DELETE等操作。它是表级访问控制,先于RLS生效,即角色须先经GRANT获得操作权限,再由RLS在行级把关。RDS Supabase中,authenticated默认对新建表无任何权限,须显式GRANT方可使用。

  • RLS策略与GRANT权限的关系

    RLS策略须与GRANT权限配合使用,两者缺一不可。

    表3 GRANT与RLS权限机制对比

    机制

    层级

    作用

    GRANT

    表级

    决定角色能否执行SELECT、INSERT、UPDATE、DELETE操作。

    RLS策略

    行级

    在GRANT放行后,进一步决定哪些行可见或可操作。

    执行流程:请求 → GRANT检查 → RLS策略检查 → 返回结果

    • 仅GRANT不设置RLS:启用RLS后默认拒绝所有行级访问,查询结果为空。
    • 仅RLS不设置GRANT:角色无表级操作权限,请求直接被拒绝。

建表与权限配置

下面以一个产品定价场景为例说明:products(产品表)记录产品信息,product_pricings(价格表)记录各产品的计费档位与价格,两表以product_id关联(删除产品则其价格一并删除)。样例按业务身份划分访问边界:访客仅浏览,登录运营人员可管理,后端服务可运维。基于此,给出完整的建表、RLS策略与GRANT权限配置。

以下步骤为建表与权限配置的完整SQL,可在Supabase Dashboard控制台的SQL Editor中逐段或整段执行。

  1. 创建数据表。

    定义products(产品表)与product_pricings(产品定价表),二者以product_id外键关联,并在删除产品时级联删除其定价。

    -- products:产品表
    CREATE TABLE products (
      id           BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
      name         TEXT NOT NULL,
      category     TEXT,
      description  TEXT,
      created_at   TIMESTAMPTZ DEFAULT now(),
      updated_at   TIMESTAMPTZ DEFAULT now()
    );
    
    -- product_pricings:产品定价表
    CREATE TABLE product_pricings (
      id             BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
      product_id     BIGINT NOT NULL REFERENCES products(id) ON DELETE CASCADE,
      tier           TEXT NOT NULL,
      price          NUMERIC(10,2) NOT NULL,
      unit           TEXT DEFAULT '元/月',
      effective_from DATE DEFAULT current_date,
      created_at     TIMESTAMPTZ DEFAULT now()
    );

    字段说明:

    • id:主键,数据库自增。采用GENERATED BY DEFAULT以支持upsert等显式指定id的场景;官方模板默认GENERATED ALWAYS(禁止手动插入id),更为严格。
    • product_id:外键,关联products(id)。ON DELETE CASCADE表示删除产品时其定价一并删除。
    • price:NUMERIC(10,2)表示总有效数字10位、小数2位,整数部分最多8位。
    • updated_at:仅插入时取默认值;如需更新时自动刷新,须另行配置触发器。
  2. 启用行级安全(RLS)。

    启用RLS后,表的访问须经RLS策略放行,未配置策略的操作默认拒绝。

    ALTER TABLE products ENABLE ROW LEVEL SECURITY;
    ALTER TABLE product_pricings ENABLE ROW LEVEL SECURITY;

    对产品表与价格表启用RLS,相当于给两张表都“上锁”,此后谁能查看或修改哪条产品、哪个价格,不再由表本身决定,而交由下一步的RLS策略按角色放行,即产品与价格数据不再对所有访问者敞开,而是按身份逐行把关。

  3. 配置RLS策略。

    按“角色 × 操作”维度配置策略:对anon(匿名用户)仅开放只读,对authenticated(已认证用户)开放增删改查。

    -- anon(匿名用户):只读
    CREATE POLICY "anon_select_products"
      ON public.products FOR SELECT TO anon USING (true);
    
    CREATE POLICY "anon_select_product_pricings"
      ON public.product_pricings FOR SELECT TO anon USING (true);
    
    -- authenticated(已认证用户):增、删、改、查
    CREATE POLICY "authenticated_select_products"
      ON public.products FOR SELECT TO authenticated USING (true);
    
    CREATE POLICY "authenticated_select_product_pricings"
      ON public.product_pricings FOR SELECT TO authenticated USING (true);
    
    CREATE POLICY "authenticated_insert_products"
      ON public.products FOR INSERT TO authenticated WITH CHECK (true);
    
    CREATE POLICY "authenticated_insert_product_pricings"
      ON public.product_pricings FOR INSERT TO authenticated WITH CHECK (true);
    
    CREATE POLICY "authenticated_update_products"
      ON public.products FOR UPDATE TO authenticated USING (true) WITH CHECK (true);
    
    CREATE POLICY "authenticated_update_product_pricings"
      ON public.product_pricings FOR UPDATE TO authenticated USING (true) WITH CHECK (true);
    
    CREATE POLICY "authenticated_delete_products"
      ON public.products FOR DELETE TO authenticated USING (true);
    
    CREATE POLICY "authenticated_delete_product_pricings"
      ON public.product_pricings FOR DELETE TO authenticated USING (true);

    这组策略为产品表与价格表分别定义各角色可做的操作。策略均放行全部行,因此限制只在“操作类型”而非“具体行”。

    • 访客(anon)只能浏览产品与价格。
    • 登录用户(authenticated)可新增、修改、删除产品与价格。
    • service_role不在此列,因其绕过RLS,详见步骤4。
  4. 授予表级权限(GRANT)。

    GRANT决定角色能否执行某类表级操作,RLS决定可操作哪些行,二者缺一不可。

    -- anon:只读,仅授予SELECT
    GRANT SELECT ON TABLE public.products TO anon;
    GRANT SELECT ON TABLE public.product_pricings TO anon;
    
    -- authenticated:增删改查,授予全部四种操作
    GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE public.products TO authenticated;
    GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE public.product_pricings TO authenticated;
    
    -- service_role:绕过RLS,授予全部权限
    GRANT ALL ON ALL TABLES IN SCHEMA public TO service_role;
    

    各角色对商品表与价格表的权限如下表:

    表4 样例权限设计

    角色

    products(商品表)

    product_pricings(价格表)

    anon(访客)

    只读

    只读

    authenticated(登录用户)

    增删改查

    增删改查

    service_role(服务端)

    全部(绕过RLS)

    全部(绕过RLS)

    从业务角度看,商品表与价格表是面向用户展示的目录数据:访客(anon)仅可浏览商品与价格,保证目录不被随意篡改;登录的运营人员(authenticated)可上下架商品、调整价格,完成日常经营。

  5. 刷新schema缓存。

    执行以下语句可立即刷新PostgREST的schema缓存,使新建表即时可被Data API访问,无需等待周期性自动刷新。

    NOTIFY pgrst, 'reload schema';

SDK相关操作

应用前端通过Supabase JavaScript SDK对接后端Data API。以下示例假定已完成SDK初始化(详见准备工作),客户端变量为supabase,且以Publishable Key(客户端密钥,旧版Anon Key)初始化、默认只读。

Data API按请求所用密钥与登录态决定以哪个数据库角色执行:客户端Publishable Key(旧版Anon Key)在未登录时对应anon角色(只读),用户登录后同一密钥对应authenticated角色(可增删改查,受RLS约束);服务端Secret Key(旧版Service Role Key)对应service_role角色(绕过RLS)。SDK以Publishable Key初始化时,未登录请求均以anon身份发起。

Publishable Key可安全用于前端,配合RLS控制可访问的行;Secret Key绕过RLS,严禁放入前端代码。查询以anon身份即可发起;插入、更新、更新插入、删除属于写操作,需以authenticated身份发起,登录凭证由用户认证流程签发,本指南不做说明。

  • 获取数据(Select)

    业务场景:用户在商品页浏览产品列表,或按分类筛选(如只看“计算”类产品)。查询属于读操作,未登录即可发起。

    // 标准写法:解构data与error,先判错再用data
    const { data, error } = await supabase.from('products').select()
    if (error) { console.error(error); return }
    // data为查询结果数组,如[{ id: 1, name: '云服务器 ECS', category: '计算', ... }]
    // 下文示例为简洁省略错误处理,实际请务必判错
    
    // 条件查询 + 指定字段
    const { data } = await supabase
      .from('products')
      .select('id, name, category')
      .eq('category', '计算')
  • 插入数据(Insert)

    业务场景:运营人员上架新商品,例如录入“云服务器ECS”、“对象存储OBS”等商品;新增价格档位(如按量、包月)则插入product_pricings,写法相同。

    以下示例假定用户已登录,客户端以authenticated身份发起。

    // 插入单行
    await supabase.from('products').insert({
      name: '云服务器 ECS', category: '计算', description: '弹性云服务器'
    })
    
    // 插入多行
    await supabase.from('products').insert([
      { name: '云服务器 ECS', category: '计算' },
      { name: '对象存储 OBS', category: '存储' }
    ])

    当前仅以Publishable Key初始化的未登录客户端为只读身份,直接发起写操作会被拒绝;要执行成功须以authenticated身份发起。

  • 更新数据(Update)

    业务场景:运营人员修改商品信息,如更新id为1的商品描述;如需调价,则更新价格表product_pricings中对应档位的价格。

    await supabase.from('products').update({ description: '更新后的描述' }).eq('id', 1) 

    update()必须搭配过滤条件,否则会更新表中所有行。

  • 更新插入(Upsert)

    业务场景:批量同步或导入商品,同一商品(按主键或指定列判重)存在则更新、不存在则新增,常见于从外部系统同步商品目录。

    冲突则更新,不冲突则插入。onConflict指定判冲突的列,默认为主键;若按非主键列判冲突,该列须先建立唯一约束。

    // 基于主键冲突:id=1存在则更新,不存在则插入
    const { data } = await supabase
      .from('products')
      .upsert({ id: 1, name: '云服务器 ECS', category: '计算' })
      .select()
    
    // 基于非主键列冲突:须先为该列建立唯一约束
    // ALTER TABLE products ADD CONSTRAINT products_name_key UNIQUE (name);
    const { data } = await supabase
      .from('products')
      .upsert({ name: '云服务器 ECS', category: '计算' }, { onConflict: 'name' })
      .select()
  • 删除数据(Delete)
    业务场景:下架指定商品,因外键级联(ON DELETE CASCADE),该商品的价格档位一并删除。
    await supabase.from('products').delete().eq('id', 1) 

    delete()必须搭配过滤条件,否则会删除表中所有行。

以下场景基于建表与权限配置的products与product_pricings两张表,演示关联查询。

PostgREST通过外键自动识别表间关系,在select()中以关联表(字段)语法嵌套,一次即可拿到关联数据,无需手写JOIN。

  • 从产品查其定价(一对多):查产品及其全部定价,子行以数组返回。
    // 查产品,并带出每条产品下的全部定价档位
    const { data } = await supabase
      .from('products')
      .select(`
        id, name, category,
        product_pricings(id, tier, price, unit)
      `)
    // data示例:[{ id: 1, name: '云服务器 ECS', category: '计算', product_pricings: [{ id: 1, tier: '包月', price: 99.00, unit: '元/月' }] }]
  • 从定价查其产品(多对一):从定价表出发,带出其所属产品。
    // 从定价表查,并带出所属产品
    const { data } = await supabase
      .from('product_pricings')
      .select('id, tier, price, products(id, name, category)')
  • 带过滤与排序的关联查询:查分类为“计算”的产品及其定价,按产品名排序。
    const { data } = await supabase
      .from('products')
      .select('id, name, product_pricings(tier, price)')
      .eq('category', '计算')
      .order('name')
  • 按子表条件反查父表(内连接):只返回存在“价格低于100”定价的产品,用“!inner”强制内连接,过滤写在嵌入列上。
    // 内连接:只返回存在价格<100定价的产品
    const { data } = await supabase
      .from('products')
      .select('id, name, product_pricings!inner(tier, price)')
      .lt('product_pricings.price', 100)

    去掉“!inner”则变为左连接:仍返回全部产品,但每条产品只展示价格低于100的定价档位。

相关文档