Database Change Notification的作用就是當資料庫中的被監聽table變化時(包括資料和表結構的更改),自動嚮應用程式發出一個通知。這對讀多寫少應用的緩衝更新,以及避免輪詢資料庫很有用。
需要3個步驟:
1. 註冊。
2. 用一個查詢表示監聽哪個(些)表的修改通知。
3. 響應。
在11g中,可以對查詢結果集監聽,而不是對所有記錄的改變觸發通知事件。
下面的代碼在java1.6,ojdbc6.jar驅動, oracle10g r2環境下通過運行。
<%@ page language="java" contentType="text/html;charset=GBK" isELIgnored="false"%>
<%@ page import="javax.naming.InitialContext, java.util.*, java.math.BigDecimal, javax.sql.DataSource, java.sql.*, oracle.sql.*, oracle.jdbc.*, oracle.jdbc.dcn.*" %>
<%!
Connection getConnection(){
try{
InitialContext ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("auditDatasource");
ctx.close();
Connection conn=ds.getConnection();
return conn;
}catch(Exception e){
throw new RuntimeException(e.getMessage());
}
}
%>
<% String type=request.getParameter("type"); %>
<%
if("register".equals(type)){
Connection conn = getConnection();
OracleConnection oconn = (OracleConnection)conn.unwrap(OracleConnection.class);
DatabaseChangeRegistration dcr = (DatabaseChangeRegistration)application.getAttribute("DatabaseChangeRegistration");
if(dcr!=null){ //撤銷以前的註冊
try{
oconn.unregisterDatabaseChangeNotification(dcr);
}catch(java.sql.SQLException e){} //避免ORA-29970: 指定的註冊 ID 不存在
application.removeAttribute("DatabaseChangeRegistration");
System.out.println("unregisterDatabaseChangeNotification, regId="+dcr.getRegId());
dcr=null;
}
Properties prop = new Properties();
//ROWIDs may be rolled-up if the total shared memory consumption due to ROWIDs is too large (exceeds 1% of the dynamic shared pool size), OR if too many rows were modified
prop.setProperty(OracleConnection.DCN_NOTIFY_ROWIDS,"true"); //要取得更改記錄的rowid
prop.setProperty(OracleConnection.DCN_IGNORE_DELETEOP,"true"); //忽略delete
//default: prop.setProperty(OracleConnection.NTF_LOCAL_TCP_PORT, 47632); //這裡可設定程式的tcp監聽連接埠,預設是47632
//Specifies the time in seconds after which the registration will be automatically expunged by the database. If 0 or NULL, then the registration persists until the client explicitly unregisters it.
prop.setProperty(OracleConnection.NTF_TIMEOUT, "3600"); //設定逾時,這裡是1個小時,屆時資料庫和磁碟機的資源將自動釋放。如果為0或不設定,則永不到期,直到程式停止監聽,當資料庫發送更新通知時,因沒有監聽連接埠,資料庫隨後釋放資源。
//prop.setProperty(OracleConnection.NTF_QOS_PURGE_ON_NTFN, "true"); //在第一次通知後,註冊就作廢撤銷
dcr = oconn.registerDatabaseChangeNotification(prop); //註冊
System.out.println("registerDatabaseChangeNotification, regId="+dcr.getRegId()); //列印註冊ID,在oracle裡 select * from dba_CHANGE_NOTIFICATION_REGS 可以看到記錄。
application.setAttribute("DatabaseChangeRegistration", dcr);
try
{
dcr.addListener(new DatabaseChangeListener(){ //增加事件監聽
public void onDatabaseChangeNotification(DatabaseChangeEvent e){
DatabaseChangeEvent.EventType etype = e.getEventType();
System.out.println("receive "+ etype+" event, RegId="+ e.getRegId());
if(etype != DatabaseChangeEvent.EventType.OBJCHANGE) return; //如果不是資料改變事件,如表結構更改或刪表,則返回。
TableChangeDescription[] tcds = e.getTableChangeDescription();
for( TableChangeDescription tcd : tcds){
System.out.println("changed table="+ tcd.getTableName());
java.util.EnumSet<TableChangeDescription.TableOperation> tops = tcd.getTableOperations(); //獲得表操作類型
for( TableChangeDescription.TableOperation top: tops)
System.out.println("... TableOperation="+ top);
RowChangeDescription[] rcds = tcd.getRowChangeDescription();
for(RowChangeDescription rcd: rcds){ //獲得更改的記錄描述,包括更改類型insert, update, delete,以及rowid.
System.out.println("... RowOperation=" + rcd.getRowOperation()+", rowid="+rcd.getRowid().stringValue());
}
}
}
}
);
Statement stmt = oconn.createStatement();
((OracleStatement)stmt).setDatabaseChangeRegistration(dcr);
ResultSet rs = stmt.executeQuery("select 1 from RPT_CHENJUN_DDJH_ERR_CODE where 1=2"); //這裡表示關聯RPT_CHENJUN_DDJH_ERR_CODE表的變更事件。
//while (rs.next()) {}
for(String tableName: dcr.getTables())
System.out.println("DatabaseChangeRegistration ON "+tableName);
rs.close();
stmt.close();
}
catch(SQLException ex)
{
if(oconn != null)
oconn.unregisterDatabaseChangeNotification(dcr);
throw ex;
}
finally
{
try
{conn.close();}catch(Exception innerex){ innerex.printStackTrace(); }
}
}
%>
<% if("unregister".equals(type)){
int regId = Integer.parseInt( request.getParameter("regId"));
System.out.println("unregister regId="+regId);
Connection conn = getConnection();
try{
OracleConnection oconn = (OracleConnection)conn.unwrap(OracleConnection.class);
//oconn.unregisterDatabaseChangeNotification(regId); //不支援的特性
//NPE, bug!
DatabaseChangeRegistration dcr = oconn.getDatabaseChangeRegistration(regId);
oconn.unregisterDatabaseChangeNotification(dcr); //The registration will be destroyed in the server and in the driver
}finally{
try{conn.close();}catch(Exception innerex){ innerex.printStackTrace(); }
}
}
%>
<%
if("insert".equals(type)){
Connection conn =null;
try{
conn = getConnection();
OracleConnection oconn = (OracleConnection)conn.unwrap(OracleConnection.class);
oconn.setAutoCommit(false);
Statement stmt2 = oconn.createStatement();
stmt2.executeUpdate("insert into RPT_CHENJUN_DDJH_ERR_CODE values (-21, '',null)", Statement.RETURN_GENERATED_KEYS);
ResultSet autoGeneratedKey = stmt2.getGeneratedKeys();
if(autoGeneratedKey.next())
System.out.println("inserted one row with ROWID="+autoGeneratedKey.getString(1));
stmt2.close();
oconn.commit();
oconn.close();
}
catch(SQLException ex) { ex.printStackTrace(); }
finally{
if(conn!=null) conn.close();
}
}
%>