触发器(六、instead of触发器实现视图增删改)

场景描述

开发中遇到一个情况:前台页面是一个角色对用户的批量授权,其中用户是多选,多选的结果是控件里会填入逗号分隔的用户ID字符串,比如“TOM,JERRY”,最终要分别插入2张一对多的主从表中。

--主表
CREATE TABLE T_MAIN
(GUID     VARCHAR2(32) primary key
,GRANTOR  VARCHAR2(30)
,ROLEID   VARCHAR2(10)
,CREATED  DATE
);
--从表
CREATE TABLE T_DETAIL
(GUID     VARCHAR2(32)
,GRANTEE  VARCHAR2(32)
,ROLEID   VARCHAR2(10)
);
--前台展示的的是一个视图:
CREATE OR REPLACE VIEW V_MAIN_DETAIL AS 
SELECT A.GUID,A.GRANTOR,A.ROLEID,A.CREATED,TO_CHAR(WM_CONCAT(B.GRANTEE)) GRANTEES
  FROM T_MAIN A,T_DETAIL B
  WHERE A.GUID=B.GUID
  GROUP BY A.GUID,A.GRANTOR,A.ROLEID,A.CREATED;

前台实现的做法是在主表中插入数据,然后对用户多选结果进行循环插入从表。
今天介绍一种后台实现的方式,即前台只需对视图进行增删改查。
大家知道,Oracle里的视图是不能做DML操作的,尽快Oracle做了很多改进,比如对单表视图或一对一键值关联的多表视图可以做增删改。但一对多或者多对多关联的视图还是不支持增删改操作,会报错ORA-01779: cannot modify a column which maps to a non key-preserved table
这里写图片描述

事实上,Oracle还提供一个变通方式来完成对视图的增删改操作。就是用instead of触发器。

INSTEAD OF INSERT 实现视图新增操作

以下对视图进行insert操作的触发器实现。

CREATE OR REPLACE TRIGGER TRI_V_MAIN_DETAIL
  INSTEAD OF INSERT
  ON V_MAIN_DETAIL
DECLARE
  --分解用分隔符分隔的字符串
  cursor cur_GRANTEE(v_GRANTEE varchar2,v_deli varchar2) is
    select substr(t.ca,instr(t.ca,v_deli,1,c.lv)+1,instr(t.ca,v_deli,1,c.lv+1)-(instr(t.ca,v_deli,1,c.lv)+1)) as GRANTEE
     from (select v_deli||v_GRANTEE||v_deli as ca,length(v_deli||v_GRANTEE||v_deli)-nvl(length(replace(v_deli||v_GRANTEE||v_deli,v_deli)),0)-1 as cnt from dual) t,
          (select level lv from dual connect by level<=200) c
     where c.lv<=t.cnt;
  v_guid varchar2(32);
BEGIN
  if :new.GRANTEES is null then
    raise_application_error(-20000,'没有选择被授权用户!');
  else
    select sys_guid() into v_guid
      from dual;
    --插入主表数据1条
    insert into T_MAIN(GUID,
                       GRANTOR,
                       ROLEID,
                       CREATED)
     values(v_guid,:new.grantor,:new.roleid,sysdate);
     --插入从表数据N条
     for rec_GRANTEE in cur_GRANTEE(:new.GRANTEES,',') loop
       insert into t_detail(guid,
                            grantee,
                            roleid)
         values(v_guid,rec_grantee.GRANTEE,:new.roleid);
     end loop;
  end if;
end;

再试一下INSERT操作,对视图插入1条数据,结果在T_MAIN插入1条数据,在T_DETAIL插入了2条数据。
触发器(六、instead of触发器实现视图增删改)_第1张图片
从上面我们大致可以看出来,INSTEAD OF INSERT触发器的原理实际上是捕捉到对视图的INSERT动作,然后用触发器的程序替换该insert操作。

INSTEAD OF UPDATE实现视图更新操作

同理,我们可以对视图进行update和delete操作,以下是update的触发器实现。

CREATE OR REPLACE TRIGGER TRI_V_MAIN_DETAIL_UPD
  INSTEAD OF UPDATE
  ON V_MAIN_DETAIL
DECLARE
  --分解用分隔符分隔的字符串
  cursor cur_GRANTEE(v_GRANTEE varchar2,v_deli varchar2) is
    select substr(t.ca,instr(t.ca,v_deli,1,c.lv)+1,instr(t.ca,v_deli,1,c.lv+1)-(instr(t.ca,v_deli,1,c.lv)+1)) as GRANTEE
     from (select v_deli||v_GRANTEE||v_deli as ca,length(v_deli||v_GRANTEE||v_deli)-nvl(length(replace(v_deli||v_GRANTEE||v_deli,v_deli)),0)-1 as cnt from dual) t,
          (select level lv from dual connect by level<=200) c
     where c.lv<=t.cnt;
BEGIN
  --更新主表数据
  UPDATE T_MAIN T SET
      GRANTOR=:NEW.GRANTOR,ROLEID=:NEW.ROLEID,CREATED=SYSDATE
    WHERE GUID=:NEW.GUID;

  if :new.GRANTEES!=:OLD.GRANTEES then
     --重新插入从表数据N条
     delete from t_detail where guid=:new.GUID;
     for rec_GRANTEE in cur_GRANTEE(:new.GRANTEES,',') loop
       insert into t_detail(guid,
                            grantee,
                            roleid)
         values(v_guid,rec_grantee.GRANTEE,:new.roleid);
     end loop;
  end if;
end;

执行效果如下
触发器(六、instead of触发器实现视图增删改)_第2张图片

INSTEAD OF DELETE 实现视图删除操作

视图delete触发器的实现如下:

CREATE OR REPLACE TRIGGER TRI_V_MAIN_DETAIL_DEL
  INSTEAD OF DELETE
  ON V_MAIN_DETAIL
DECLARE

BEGIN
  --删除从表数据
  DELETE FROM T_DETAIL T WHERE GUID=:OLD.GUID;
  --删除主表数据
  DELETE FROM T_MAIN T WHERE GUID=:OLD.GUID;

end;

执行效果如下:
触发器(六、instead of触发器实现视图增删改)_第3张图片

总结

尽管INSTEAD OF触发器比较好的实现了视图的增删改操作,
但我还是不提倡经常使用这种方式来处理真实业务,
原因是:业务逻辑被触发器的代码轻易改变了。
比如上面的案例中,假设前台用户选择的是DBA权限,而在触发器中完全可以被篡改成SYSDBA权限,或者执行一堆其他的操作。很容易引起前台人员和后台人员对于业务逻辑实现的混乱。

你可能感兴趣的:(PLSQL开发,PL/SQL开发)