From time to time when we develop applications, our application is not running on its own. Chances are the application is always interacting with other applications.
Let's assume you have this requirement for your application: your application has a page to display data from a database table, but another application will update the same table from outside of your application's context. What is the best approach to synchronize your application with the data that are modified from another application?
There are the following ways to achieve such requirement:
1) Your application keeps waiting on possible data change; this is not recommended, because your application acts like it is blocked, it won't respond to other requests. Use this approach if you have to. Like your application has to wait for certain change to proceed. For example your application is a payment gateway, it redirects your end user to a bank page for the end user to do some online transaction. Your application has to wait for the end user to finish and process the bank transaction response in your application.
2) Your application add a 'Refresh' button onto your page, ask your end user to click on this refresh button to refresh data update onto your screen; using this approach the user experience is largely relying on if end user is OK to keep pressing button to refresh application data.
3) Your application initialize a thread to poll the database table at certain frequency ( for example poll database table every 30 seconds ); this approach will save end user from keep clicking 'Refresh' button, but the down size is your application might have to refresh page each time the poll happens. Some user might complain that your page flashes or flickers. Another big drawback is polling database may cause database performance issue.
4) Your application continues after displaying data; you will still have some logic after the data changes are pushed to your application and your application can process such data changes at the back ground while still servicing other requests.
As you can see approach 4) above using automatic data push gives your application more flexibility in a cleaner way. Every other approach might be OK in a case by case scenario.
Fortunately there is a simple approach to achieve auto data push in ADF using Oracle database and ADF Active Data Service - ADS technology. The following are the steps to implement this from scratch.
Grant 'change notification' to your database user
In my sample application I am using scott database user, log into your database and issue the following SQL command:
grant change notification to scott
You need to grant such permission as sys or system or any user having DBA privilege.
Code your database change listener
Your ADF code must have a database change listener registered to listen on the backend database table. The following code snippet is an example:
private void startListenForDBChanges() throws Exception {
OracleConnection conn = connect();
Properties prop = new Properties();
prop.setProperty(OracleConnection.DCN_NOTIFY_ROWIDS, "true");
dcr = conn.registerDatabaseChangeNotification(prop);
try {
dbChangeListener = (new DatabaseChangeListener() {
public void onDatabaseChangeNotification(DatabaseChangeEvent dce) {
String rowId =
dce.getTableChangeDescription()[0].getRowChangeDescription()[0].getRowid().stringValue();
System.out.println("Changed row id : " + rowId);
String changeDetail = formChangeDetail(rowId);
listener.onDBChangeListener(changeDetail);
}
});
dcr.addListener(dbChangeListener);
System.err.println(dcr + " " + dbChangeListener);
Statement stmt = conn.createStatement();
((OracleStatement)stmt).setDatabaseChangeRegistration(dcr);
ResultSet rs =
stmt.executeQuery("SELECT ename, job, sal, comm FROM bonus");
rs.close();
stmt.close();
conn.close();
} catch (SQLException ex) {
if (conn != null) {
conn.unregisterDatabaseChangeNotification(dcr);
conn.close();
}
throw ex;
}
}
OracleConnection conn = connect();
Properties prop = new Properties();
prop.setProperty(OracleConnection.DCN_NOTIFY_ROWIDS, "true");
dcr = conn.registerDatabaseChangeNotification(prop);
try {
dbChangeListener = (new DatabaseChangeListener() {
public void onDatabaseChangeNotification(DatabaseChangeEvent dce) {
String rowId =
dce.getTableChangeDescription()[0].getRowChangeDescription()[0].getRowid().stringValue();
System.out.println("Changed row id : " + rowId);
String changeDetail = formChangeDetail(rowId);
listener.onDBChangeListener(changeDetail);
}
});
dcr.addListener(dbChangeListener);
System.err.println(dcr + " " + dbChangeListener);
Statement stmt = conn.createStatement();
((OracleStatement)stmt).setDatabaseChangeRegistration(dcr);
ResultSet rs =
stmt.executeQuery("SELECT ename, job, sal, comm FROM bonus");
rs.close();
stmt.close();
conn.close();
} catch (SQLException ex) {
if (conn != null) {
conn.unregisterDatabaseChangeNotification(dcr);
conn.close();
}
throw ex;
}
}
In the above code, the "SELECT ename, job, sal, comm FROM bonus" is to let your code listen on ename, job, sal and comm columns of bonus table. Any changes related to thse 4 columns will be intercepted by your database change listener.
Construct your change notification message
Your can have a method to construct the change notification message that might be displayed onto your application page.
private String formChangeDetail(String rowId) {
PreparedStatement stmt = null;
ResultSet rs = null;
String ename = "";
String job = "";
BigDecimal sal = null;
BigDecimal comm = null;
OracleConnection connection = connect();
try {
stmt = connection.prepareStatement("SELECT ename, job, sal, comm FROM bonus WHERE ROWID=?");
stmt.setString(1, rowId);
rs = stmt.executeQuery();
while (rs.next()) {
ename = rs.getString(1);
job = rs.getString(2);
sal = rs.getBigDecimal(3);
comm = rs.getBigDecimal(4);
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
rs.close();
stmt.close();
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
System.err.println("Cannot close connection.");
}
}
return "Emp name: " + ename + " job: " + job + " salary: " + sal + " commission: " + comm + ".";
}
PreparedStatement stmt = null;
ResultSet rs = null;
String ename = "";
String job = "";
BigDecimal sal = null;
BigDecimal comm = null;
OracleConnection connection = connect();
try {
stmt = connection.prepareStatement("SELECT ename, job, sal, comm FROM bonus WHERE ROWID=?");
stmt.setString(1, rowId);
rs = stmt.executeQuery();
while (rs.next()) {
ename = rs.getString(1);
job = rs.getString(2);
sal = rs.getBigDecimal(3);
comm = rs.getBigDecimal(4);
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
rs.close();
stmt.close();
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
System.err.println("Cannot close connection.");
}
}
return "Emp name: " + ename + " job: " + job + " salary: " + sal + " commission: " + comm + ".";
}
Code your AdsController class
Your AdsController java class should have this declaration:
public class AdsController extends BaseActiveDataModel implements DBChangeListener {}
You will register your AdsController class in your application same as you do as backing bean class.
Add active model statement in your AdsController constructor method:
public AdsController() {
ActiveModelContext vActiveModelContext =
ActiveModelContext.getActiveModelContext();
Object[] vKeyPath = new String[0];
vActiveModelContext.addActiveModelInfo(this, vKeyPath, "Status");
}
ActiveModelContext vActiveModelContext =
ActiveModelContext.getActiveModelContext();
Object[] vKeyPath = new String[0];
vActiveModelContext.addActiveModelInfo(this, vKeyPath, "Status");
}
Important: "Status" is the name of your change notification event name. When I was first implemented ADS, I was really confused by what the 3rd parameter means in addActiveModelInfo(), it is just a name. You should use such name consistently in your application. Later in triggerActiveDataUpdateEvent() we will use this same name again. You need to make sure they are the same.
Override startActiveData method in AdsController
protected void startActiveData(Collection<Object> coll, int i) {
System.err.println("Starting active data thread for: " + this);
dbPushController = new DBPushController();
try {
dbPushController.addChangeListener(this);
} catch (Exception e) {
e.printStackTrace();
}
}
System.err.println("Starting active data thread for: " + this);
dbPushController = new DBPushController();
try {
dbPushController.addChangeListener(this);
} catch (Exception e) {
e.printStackTrace();
}
}
In the startActiveData method, you add change listener into your ADS controller class.
Override onDBChangeListener method in AdsController
public void onDBChangeListener(String changeDetail) {
triggerActiveDataUpdateEvent(changeDetail);
}
triggerActiveDataUpdateEvent(changeDetail);
}
...
public void triggerActiveDataUpdateEvent(String changeDetail) {
counter.incrementAndGet();
System.err.println("Trigger active data update event: " +
counter.get() + " " + this);
ActiveDataUpdateEvent event =
ActiveDataEventUtil.buildActiveDataUpdateEvent(ActiveDataEntry.ChangeType.UPDATE,
counter.get(),
new String[0], null,
new String[] { "Status" },
new Object[] { changeDetail });
fireActiveDataUpdate(event);
j++;
}
counter.incrementAndGet();
System.err.println("Trigger active data update event: " +
counter.get() + " " + this);
ActiveDataUpdateEvent event =
ActiveDataEventUtil.buildActiveDataUpdateEvent(ActiveDataEntry.ChangeType.UPDATE,
counter.get(),
new String[0], null,
new String[] { "Status" },
new Object[] { changeDetail });
fireActiveDataUpdate(event);
j++;
}
Since we declared AdsController as implement DBChangeListener, we have to implement onDBChangeListener() method, in it we fire the active data update event. Notice "Status" in bold in triggerActiveDataUpdateEvent() method, it is the same data change name we used in AdsController constructor method.
That's pretty much it. You need to define a connection in your ADF application model project. I am using scott and bonus table for this sample.
Please see the screenshot of running this application below.
When application first runs, no data in scott.bonus table yet
Now add a record for SMITH and give SMITH 1.5% commission
Now modify SMITH's commission from 1.5% to 2%
At last we add 5% commission to Jones
Congratulations! You now know all it about to implement Oracle ADF automatic data push via ADS and database change listener!
Please use the following link to download the source code of this sample:
Data Push Sample Source Code
Please use the following link to download the source code of this sample:
Data Push Sample Source Code