В этом практикуме соединим две технологии: Database Change Notification и Active Data Service (ADS). Т.е. будем следить за изменениями данных в таблице БД на странице приложения в браузере.
Схема HR на Orcale XE.
На странице будет отображаться сумма зарплат по отделам. При изменении у служащего з.п., сразу увидим это изменение в сводном списке.
От БД до сервера приложений эти уведомления доставим с помощью Database Change Notification, а от сервера приложений до клиента (браузера) доставим изменения с попощью ADS. При этом все будет происходить автоматически без участия пользователя.
Часть I - Database Change Notification
public class DBChangeNotification
Здесь происходит настройка свойств уведомления и их регистрация на сервере, создание Listener, а также регистрируется запрос результат выборки которого будем мониторить. Т.е. если в таблице Eployees изменится salary, то мы получим уведомление в Listener. Одно из свойств на которое подписались - это ROWID измененной записи.
public class DCNDemoListener
В событии onDatabaseChangeNotification получим информацию об изменениях и ROWID измененной записи. Далее управление можно передать в ADS.
Часть II - ADS
Механизм доставки уведомления ADS реализован по модели - "Active Data Service with an Asynchronous Backend".
public class DepartmentManager
Все начинается с класса DepartmentManager, который расширяет - ActiveCollectionModelDecorator. В котором, основное что он делает - это вычисляет новую модель данных основанную на бине - DepartmentBackend. Сам тоже в свою очередь является зарегистрированным бином. Так же в нем создается событие ADS - ActiveDataUpdateEvent для отправки в framework.
public class DepartmentBackend
DepartmentBackend - в нем формируется начальный список отделов (Departments) в initData, который вызывается после создания бина (@PostConstruct).И запускается ждущий поток, в ожидании события об измененной записи. После получения ROWID измененной записи из startUpdateProcess, запускаем ожидающий поток, узнаем ИД отдела и далее вычисляем для него новую сумму - calcSalary(), ее и отображаем в списке на странице для указанного отдела.
public class MyActiveDataModel
Есть еще один класс - MyActiveDataModel, он используется в основном для хранения счетчиков изменений и вызова события уведомления - notifyDataChange.
Конфигурация ADS - \.adf\META-INF\adf-config.xml
файл - \.adf\META-INF\services\adf-config.properties
http\://xmlns.oracle.com/adf/activedata/config=oracle.adfinternal.view.faces.activedata.ActiveDataConfiguration$ActiveDataConfigCallback
Для работы приложения используется прямой доступ к DataSource через JNDI, поэтому на WLS надо создать DataSource с JNDI именем - jdbc/HRDS
Вид страницы со списком отделов и их суммарной з.п.
После нажатия кнопки "Start watching" начнется наблюдение за изменениями в БД.
Для имитации изменений, можно запустить PL-SQL Developer и нем внести зменения в таблицу - EMPLOYEES.
Изменив значение в поле Salary и выполнив commit, сразу увидим изменения в списке на странице в браузере, поле на мгновение будет подсвечено другим цветом, примерно так
(картинка из другого приложения).
Источники:
Database Change Notification
Using the Active Data Service with an Asynchronous Backend
Исходник
Схема HR на Orcale XE.
На странице будет отображаться сумма зарплат по отделам. При изменении у служащего з.п., сразу увидим это изменение в сводном списке.
От БД до сервера приложений эти уведомления доставим с помощью Database Change Notification, а от сервера приложений до клиента (браузера) доставим изменения с попощью ADS. При этом все будет происходить автоматически без участия пользователя.
Часть I - Database Change Notification
public class DBChangeNotification
void run() throws SQLException { // first step: create a registration on the server: Properties prop = new Properties(); // Ask the server to send the ROWIDs as part of the DCN events (small performance // cost): prop.setProperty(OracleConnection.DCN_NOTIFY_ROWIDS, "true"); //Set the DCN_QUERY_CHANGE_NOTIFICATION option for query registration with finer granularity. prop.setProperty(OracleConnection.DCN_QUERY_CHANGE_NOTIFICATION, "true"); // The following operation does a roundtrip to the database to create a new // registration for DCN. It sends the client address (ip address and port) that // the server will use to connect to the client and send the notification // when necessary. Note that for now the registration is empty (we haven't registered // any table). This also opens a new thread in the drivers. This thread will be // dedicated to DCN (accept connection to the server and dispatch the events to // the listeners). dcr = conn.registerDatabaseChangeNotification(prop); try { // add the listenerr: DCNDemoListener list = new DCNDemoListener(this); dcr.addListener(list); System.out.println("registration."); // second step: add objects in the registration: Statement stmt = conn.createStatement(); // associate the statement with the registration: ((OracleStatement) stmt).setDatabaseChangeRegistration(dcr); ResultSet rs = stmt.executeQuery("select employee_id, salary from EMPLOYEES where department_id > 10"); while (rs.next()) { }
public class DCNDemoListener
public class DCNDemoListener implements DatabaseChangeListener { @Override public void onDatabaseChangeNotification(DatabaseChangeEvent databaseChangeEvent) { System.out.println("onDatabaseChangeNotification"); QueryChangeDescription[] changeDescription = databaseChangeEvent.getQueryChangeDescription(); //System.out.println("changeDescription:"+changeDescription); for (QueryChangeDescription change: changeDescription) { TableChangeDescription[] tdescription = change.getTableChangeDescription(); for (TableChangeDescription tchange: tdescription) { RowChangeDescription[] rchange = tchange.getRowChangeDescription(); for (RowChangeDescription rcd: rchange) { System.out.println("Affected ROWID -> " + rcd.getRowid().stringValue()); dbChangeNotification.onDatabaseChange(rcd.getRowid().stringValue()); } } } }
В событии onDatabaseChangeNotification получим информацию об изменениях и ROWID измененной записи. Далее управление можно передать в ADS.
Часть II - ADS
Механизм доставки уведомления ADS реализован по модели - "Active Data Service with an Asynchronous Backend".
public class DepartmentManager
public class DepartmentManager extends ActiveCollectionModelDecorator implements IBackendListener { private MyActiveDataModel _activeDataModel = new MyActiveDataModel(); private CollectionModel _model = null; public CollectionModel getCollectionModel() { if (_model == null) { FacesContext ctx = FacesContext.getCurrentInstance(); ExpressionFactory ef = ctx.getApplication().getExpressionFactory(); ValueExpression ve = ef.createValueExpression(ctx.getELContext(), "#{DepartmentBackend}", DepartmentBackend.class); DepartmentBackend context = (DepartmentBackend)ve.getValue(ctx.getELContext()); _model = new SortableModel(context.getDepartments()); } return _model; } public void onDeptartmentUpdate(Integer rowKey, String attribue, BigDecimal value) { if (rowKey != null) { System.out.println("changeEvent "+rowKey); MyActiveDataModel asm = getMyActiveDataModel(); // start the preparation for the ADS update asm.prepareDataChange(); // create an ADS event, using an _internal_ util..... // this class is not part of the API ActiveDataUpdateEvent event = ActiveDataEventUtil.buildActiveDataUpdateEvent( ActiveDataEntry.ChangeType.UPDATE, // type asm.getCurrentChangeCount(), // changeCount new Object[] {rowKey}, // rowKey null, //insertKey (null as we don't insert stuff; new String[] {attribue}, // attribue/property name that changes new Object[] {value} // the payload for the above attribute ); // deliver the new Event object to the ADS framework asm.notifyDataChange(event); } }
Все начинается с класса DepartmentManager, который расширяет - ActiveCollectionModelDecorator. В котором, основное что он делает - это вычисляет новую модель данных основанную на бине - DepartmentBackend. Сам тоже в свою очередь является зарегистрированным бином. Так же в нем создается событие ADS - ActiveDataUpdateEvent для отправки в framework.
public class DepartmentBackend
public class DepartmentBackend { public void setListener(IBackendListener listener) { this.listener = listener; } public IBackendListener getListener() { return listener; } private IBackendListener listener; private final List<Department> departments = new ArrayList<Department>(); public List<Department> getDepartments() { return departments; } /** * Инициализировать список начальными данными * А также создать ждущий поток по отправки уведомления об изменении данных */ @PostConstruct public void initData() { System.out.println("initData begin"); am = (AppModuleImpl) ADFUtils.getApplicationModuleForDataControl("AppModuleDataControl"); ViewObject vo = am.findViewObject("DepartSalaryViewObj1"); vo.executeQuery(); Row row = vo.first(); Long i = 0L; while (row != null) { departments.add(new Department((String) row.getAttribute("DepartmentName"), i, (Integer) row.getAttribute("DepartmentId"), (BigDecimal) row.getAttribute("Salary"))); i++; row = vo.next(); } System.out.println("initData end"); dataChanger = new Runnable() { @Override public void run() { synchronized (DepartmentBackend.this) { try { System.out.println("wait"); DepartmentBackend.this.wait(); System.out.println("end wait"); // Вычислить Salary и ID в списке для измененной записи по ROWID BigDecimal salary = DepartmentBackend.this.calcSalary(); DepartmentBackend.this.changeData(id.intValue(), "salary", salary); } catch (InterruptedException e) { System.out.println(e); } } DepartmentBackend.this.isBusy = false; } }; System.out.println("start"); // start the process Thread newThread = new Thread(dataChanger); newThread.start(); } /** * Начать обновление данных в списке. * Сюда приходит вызов из DCNDemoListener (onDatabaseChangeNotification) * @param rowid */ public void startUpdateProcess(String rowid) { System.out.println("startUpdateProcess:" + rowid); this.rowid = rowid; if (!isBusy) { synchronized (this) { isBusy = true; System.out.println("notify"); this.notify(); // start the process Thread newThread = new Thread(dataChanger); newThread.start(); } } }
DepartmentBackend - в нем формируется начальный список отделов (Departments) в initData, который вызывается после создания бина (@PostConstruct).И запускается ждущий поток, в ожидании события об измененной записи. После получения ROWID измененной записи из startUpdateProcess, запускаем ожидающий поток, узнаем ИД отдела и далее вычисляем для него новую сумму - calcSalary(), ее и отображаем в списке на странице для указанного отдела.
public class MyActiveDataModel
public class MyActiveDataModel extends BaseActiveDataModel { @Override public int getCurrentChangeCount() { return changeCounter.get(); } /** * Does not much; simple increments the change counter... */ public void prepareDataChange() { changeCounter.incrementAndGet(); } /** * Delivers an <code>ActiveDataUpdateEvent</code> object to the ADS system. * * @param event the ActiveDataUpdateEvent object */ public void notifyDataChange(ActiveDataUpdateEvent event) { // delegate even to internal fireActiveDataUpdate() method fireActiveDataUpdate(event); }
Есть еще один класс - MyActiveDataModel, он используется в основном для хранения счетчиков изменений и вызова события уведомления - notifyDataChange.
Конфигурация ADS - \.adf\META-INF\adf-config.xml
<ads:adf-activedata-config xmlns="http://xmlns.oracle.com/adf/activedata/config"> <transport>long-polling</transport> <latency-threshold>10000</latency-threshold> <keep-alive-interval>10000</keep-alive-interval> <polling-interval>3000</polling-interval> <max-reconnect-attempt-time>1800000</max-reconnect-attempt-time> <reconnect-wait-time>10000</reconnect-wait-time> </ads:adf-activedata-config>
файл - \.adf\META-INF\services\adf-config.properties
http\://xmlns.oracle.com/adf/activedata/config=oracle.adfinternal.view.faces.activedata.ActiveDataConfiguration$ActiveDataConfigCallback
Для работы приложения используется прямой доступ к DataSource через JNDI, поэтому на WLS надо создать DataSource с JNDI именем - jdbc/HRDS
Вид страницы со списком отделов и их суммарной з.п.
После нажатия кнопки "Start watching" начнется наблюдение за изменениями в БД.
Для имитации изменений, можно запустить PL-SQL Developer и нем внести зменения в таблицу - EMPLOYEES.
Изменив значение в поле Salary и выполнив commit, сразу увидим изменения в списке на странице в браузере, поле на мгновение будет подсвечено другим цветом, примерно так
(картинка из другого приложения).
Источники:
Database Change Notification
Using the Active Data Service with an Asynchronous Backend
Исходник
Комментариев нет:
Отправить комментарий