数据库与应用缓存同步的一个解决办法

83

1.创建java文件

创建一个静态方法,入参为对应数据库中相应表的字段以及进行的操作。

import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.util.HashMap;
import java.util.Map;
import java.net.URL;
import java.io.PrintWriter;
public class Server {
    public static void sendMsg(Integer deptno,String name,String loc,String action) {
        try {
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("deptno",deptno);
            map.put("name",name);
            map.put("loc",loc);
            map.put("action",action);
            //URL为发布服务的地址
            URL url = new URL("http://127.0.0.1:8080/*.jsonRequest");
            //建立连接
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setConnectTimeout(15000);
            connection.setReadTimeout(60000);
            connection.setDoOutput(true);
            connection.setDoInput(true);
            //设置请求头内容
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("Content-Type", "application/json");
            connection.setRequestProperty("X-Service-Id", "demo.pubService");
            connection.setRequestProperty("X-Service-Method", "pubService");
            connection.setRequestProperty("Cookie", "tk=5f9b82e38698a7142eaa362e");
            //设置请求参数
            OutputStream os = connection.getOutputStream();
            PrintWriter pw = new PrintWriter(os);
            pw.write("['"+map.toString()+"']");
            pw.flush();
            if (HttpURLConnection.HTTP_OK == connection.getResponseCode()){
                System.out.println("请求成功");
            }
            pw.close();
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
}

2.Oracle使用loadjava命令加载Java程序

在$ORACLE_HOME/bin目录下有个loadjava命令,使用这个命令将创建的Java程序load进数据库。

image-20230410173216420

3.创建存储过程,通过存储过程来调用加载的Java程序。

说明:加载进来的类如果声明了包名如:package com.bsoft; 调用该类时需要写全类名com/bsoft/类名

CREATE OR REPLACE PROCEDURE server_test
--定义参数及类型
(
    DNUM number,
    DNAME varchar2,
    LOC varchar2,
    ACTION VARCHAR2
) AS
LANGUAGE JAVA NAME
--类.方法名(入参1类型,入参2类型...)
'Server.sendMsg(java.lang.Integer,java.lang.String,java.lang.String,java.lang.String)';

4.创建触发器

CREATE OR REPLACE TRIGGER "SERVER_TRIGGER"
--在增删改操作后触发
AFTER DELETE OR INSERT OR UPDATE OF
--需要操作的数据库及其字段
"DEPTNO", "DNAME", "LOC" ON "ROOT"."TEST"
REFERENCING OLD AS "OLD" NEW AS "NEW" FOR EACH ROW
BEGIN
    --插入操作
   IF INSERTING THEN
        server_test(:new.DEPTNO,:new.DNAME,:new.LOC,'insert');
        dbms_output.put_line('insert success');
    --更新操作
    ELSIF UPDATING THEN
        server_test(:new.DEPTNO,:new.DNAME,:new.LOC,'update');
        dbms_output.put_line('update success');
    --删除操作
    ELSIF DELETING THEN
        server_test(:new.DEPTNO,:new.DNAME,:new.LOC,'delete');
        dbms_output.put_line('delete success');
    END IF;
END server_trigger;

5.创建发布服务

编写推送更新信息到消息队列的服务,在应用中将数据库通过http推送过来的更新消息,发布到消息队列中,让所有需要更新缓存的地方接受更新消息

package ctd;
 
import ctd.net.broadcast.support.BroadcastInstance;
import ctd.util.annotation.RpcService;
 
public class PubService {
    @RpcService
    public void pubService(String info) {
        //topic设置为topic_表名
        BroadcastInstance.getPublisher().publish("topic_tablename", info);
    }
}

此服务为oracle触发器通过http调用jsonRequester请求调用,因此需要权限验证。可单独分配一个token为权限,或者将此服务暂时关闭权限验证

关闭服务权限验证:spring配置mvc_authentication属性

spring-pub-sub.xml

<?xml version="1.0" encoding="UTF-8"?>
<s:beans xmlns:s="http://www.springframework.org/schema/beans"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.bsoft.com.cn/schema/ssdev"
         xsi:schemaLocation="http://www.springframework.org/schema/beans
                http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.bsoft.com.cn/schema/ssdev
                        http://www.bsoft.com.cn/schema/ssdev/ssdev.xsd">
    <s:bean class="ctd.PubService" id="pubService"/>
    <service ref="pubService">
        <property name="mvc_authentication" value="false"/>
    </service>
   </s:beans>

6.创建订阅服务

新建订阅的服务,接收数据库修改所推动过来的信息,以此来更新对应需要更新的缓存

package ctd;
 
import ctd.net.broadcast.Observer;
import ctd.net.broadcast.support.BroadcastInstance;
import ctd.util.annotation.RpcService;
import java.util.HashMap;
import java.util.Map;
 
public class SubService {
    @RpcService
    public void subService() {
        BroadcastInstance.getSubscriber().attach("topic_tablename", new Observer<String>() {
            @Override
            public void onMessage(String message) {
              System.out.println(message);
            }
        });
    }
}

说明: 在Oracle中发送Http请求,需要在SQL Plus输入为用户提升权限的命令 。

​ 第一个listen的是数据库地址,第二个connect的是发送http请求的地址。

sql>exec dbms_java.grant_permission( 'ROOT', 'SYS:java.net.SocketPermission', '127.0.0.1:1521', 'listen,resolve' );
sql>exec dbms_java.grant_permission( 'ROOT', 'SYS:java.net.SocketPermission', '127.0.0.1:8080', 'connect,resolve' );

整个流程如图:

image-20230410173552000

第五步的权限校验为公司专有,本文章记录下来只是提供一个Oracle表中数据和应用缓存不一致的解决方案。通常出现在多个系统使用同一个库,某个系统变更了字典中的某些数据造成其它系统的缓存和表中的数据不一致问题。