TimesTen поддерживает PL/SQL (процедуры, функции, пакеты и др.), но поддержка триггеров отсутствует, т.к. триггеры пагубно влияют на производительность. Но что делать, если необходимо реализовать триггерную логику?
Ответ - написать XLA приложение. Написать можно на С или Java, кому что ближе.
Ниже я буду описывать пример с использованием java :).
В документации (Java Developer's Guide) сказано:
"You can use the TimesTen JMS/XLA API (JMS/XLA) to monitor TimesTen for
changes to specified tables in a local data store and receive real-time notification of these changes. One of the purposes of JMS/XLA is to provide a high-performance, asynchronous alternative to triggers."
Т.е. вы можете написать java приложение, которое может использовать JMS/XLA API для получения сообщений (в асинхронном режиме) об изменениях в TimesTen. JMS/XLA использует JMS publish-subscribe interface для доступа к XLA изменениям. Подробнее про JMS можно почитать здесь (http://download.oracle.com/javaee/1.3/jms/tutorial).
Далее попробуем создать такое приложение.
Первоначально, создадим объекты в TimesTen.
[oracle@tt1 xla]$ ttisql dbxla Copyright (c) 1996-2010, Oracle. All rights reserved. Type ? or "help" for help, type "exit" to quit ttIsql. connect "DSN=dbxla"; Connection successful: DSN=dbxla;UID=oracle;DataStore=/u01/app/oracle/datastore/dbxla;DatabaseCharacterSet=WE8MSWIN1252;ConnectionCharacterSet=US7ASCII;DRIVER=/u01/app/oracle/product/11.2.1/TimesTen/tt1/lib/libtten.so;PermSize=32;TempSize=50;TypeMode=0;PLSQL=0;CacheGridEnable=0; (Default setting AutoCommit=1) Command> CREATE USER oratt IDENTIFIED BY oracle; User created. Command> grant create session, create table, XLA to oratt; Command> connect "DSN=dbxla;UID=oratt;PWD=oracle;"; Connection successful: DSN=dbxla;UID=oratt;DataStore=/u01/app/oracle/datastore/dbxla;DatabaseCharacterSet=WE8MSWIN1252;ConnectionCharacterSet=US7ASCII;DRIVER=/u01/app/oracle/product/11.2.1/TimesTen/tt1/lib/libtten.so;PermSize=32;TempSize=50;TypeMode=0;PLSQL=0;CacheGridEnable=0; (Default setting AutoCommit=1) con1: Command> create table xlatest ( id NUMBER NOT NULL PRIMARY KEY, > name VARCHAR2(100) ); con1: Command>
Теперь, создадим закладку (bookmark). XLA закладки используются для отметки позиции чтения в журналах транзакций. Данную закладку булем использовать для отслеживания изменений в таблице xlatest.
con1: Command> call ttXlaBookmarkCreate('bookmark'); con1: Command>
Далее, определим, изменения какой таблицы будем наблюдать. Для этого вызовем процедуру ttXlaSubscribe. В данном случае будем наблюдать за изменениями с таблицей xlatest с использованием закладки bookmark.
con1: Command> call ttXlaSubscribe('xlatest','bookmark'); con1: Command>
Далее, перейдем к настройке приложения.
Для соединения с XLA необходимо установить соединение с JMS Topic, который связан с опреденной базой данных TimesTen. Кофигурационный файл JMS/XLA обеспечивает привязку между именем топика и базой данных. По умолчанию, приложение ищет данный файл, названный jmsxla.xml, в текущей директории, но при желании можно определить другое имя и местоположение данного файла (см. документацию).
В данном случае я использую следующий файл jmsxla.xml:
<xlaconfig> <topics> <!-- topic for Xla demo --> <topic name="xlademo" connectionString="DSN=dbxla" xlaPrefetch="100" /> </topics> </xlaconfig>
Как видно, я связал имя топика xlademo с базой данных dbxla.
Теперь приступим, непосредственно, к написанию java Приложения.
Первоначально, инициализируем контекст.
Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.timesten.dataserver.jmsxla.SimpleInitialContextFactory"); InitialContext ic = new InitialContext(env);
Далее, используем JMS connection factory для соединения с XLA. После чего, вызываем метод start() у соединения для активации отправки сообщений. После этого, используя данное соединение создаем сессию.
private javax.jms.TopicConnection connection; /** JMS connection */ private TopicSession session; /** JMS session */ ... TopicConnectionFactory connectionFactory = (TopicConnectionFactory)ic.lookup("TopicConnectionFactory"); connection = connectionFactory.createTopicConnection(); ... // get Session session = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); ...Также при создании сессии необходимо указать транзакционность сессии и тип модели подтверждения (acknowledgment modes).
JMS/XLA поддерживается три модели (AUTO_ACKNOWLEDGE,DUPS_OK_ACKNOWLEDGE, CLIENT_ACKNOWLEDGE), подробнее про модели можно почитать в документации. В примере я использую первую модель и транзакционность сессии устанавливаю в значение false.
Далее, необходимо определиться с режимом получения сообщений. Возможны два варианта: синхронный и асинхронный.
В синхронном варианте, сообщения обрабатываются последовательно (одно за другим). Это означает, что пока сообщение не обработано, другое ожидает.
Для синхронного варинта - вызываем метод start() у соединения, для активации отправки сообщений, создаем Topic, затем создаем подписчика и получаем сообщения с помощью методов receive() и receiveNoWait().
connection.start(); Topic topic = session.createTopic(topicName); TopicSubscriber subscriber = session.createDurableSubscriber(topic, bookmark); .. MapMessage message = (MapMessage)subscriber.receive(); ...
В асинхронном режиме необходимо создать листенер и в нем обрабатывать сообщения.
MyListener myListener = new MyListener(outStream); Topic xlaTopic = session.createTopic(topic); TopicSubscriber subscriber = session.createDurableSubscriber(xlaTopic, bookmark); .. subscriber.setMessageListener(myListener); connection.start(); ..
Ниже представлен пример класса, реализующего синхронный режим (файл DemoXLA.java).
import java.util.Enumeration; import java.util.Hashtable; import javax.jms.JMSException; import javax.jms.MapMessage; import javax.jms.Session; import javax.jms.Topic; import javax.jms.TopicConnectionFactory; import javax.jms.TopicConnection; import javax.jms.TopicSession; import javax.jms.TopicSubscriber; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; public class DemoXLA { private TopicConnectionFactory connectionFactory; private TopicConnection connection; private TopicSession session; private Topic topic; private TopicSubscriber subscriber; public DemoXLA( String cf, String topicName, String selector) throws JMSException, NamingException { String key; Context messaging = getInitialContext(); // getting the context connectionFactory = (TopicConnectionFactory)messaging.lookup(cf); connection = connectionFactory.createTopicConnection(); connection.start(); session = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); topic = session.createTopic(topicName); subscriber = session.createDurableSubscriber(topic, selector); int i=0; while (i<10) { MapMessage message = (MapMessage)subscriber.receive(); Enumeration e = message.getMapNames(); while (e.hasMoreElements()) { key = (String)e.nextElement(); System.out.println("[ " + key + " = " + message.getObject(key) + " ]"); } System.out.println("----------------------------------------"); } session.unsubscribe(selector); subscriber.close(); session.close(); connection.stop(); } private Context getInitialContext() throws NamingException { Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.timesten.dataserver.jmsxla.SimpleInitialContextFactory"); InitialContext initialContext = new InitialContext(env); return initialContext; } public static void main(String[] args) throws JMSException, NamingException { DemoXLA demo = new DemoXLA("TopicConnectionFactory", "Level2Demo", "bookmark"); } }Ниже представлен пример классов, реализующих асинхронный режим (MyListener.java, DemoXLA2.java). MyListener.java
import java.util.Enumeration; import javax.jms.JMSException; import javax.jms.MapMessage; import javax.jms.Message; import javax.jms.MessageListener; public class MyListener implements MessageListener { public MyListener() {} public void onMessage(Message message) { MapMessage mp = (MapMessage)message; Enumeration e; try { e = mp.getMapNames(); } catch (JMSException s) { e = null; System.out.println("error 1"); } while (e.hasMoreElements()) { String key = (String)e.nextElement(); try { System.out.println("[ " + key + " = " + mp.getObject(key) + " ]"); } catch (JMSException f) { System.out.println("error 2"); } } System.out.println("----------------------------------------"); } }DemoXLA2.java
import java.util.Hashtable; import javax.jms.JMSException; import javax.jms.Session; import javax.jms.Topic; import javax.jms.TopicConnectionFactory; import javax.jms.TopicSession; import javax.jms.TopicSubscriber; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; public class DemoXLA2 { private javax.jms.TopicConnectionFactory connectionFactory; private javax.jms.TopicConnection connection; private TopicSession session; private Topic topic; private TopicSubscriber subscriber; public DemoXLA2( String cf, String topicName, String selector) throws JMSException, NamingException, InterruptedException { Context messaging = getInitialContext(); Object connectionFactoryObject = messaging.lookup(cf); connectionFactory = (TopicConnectionFactory)connectionFactoryObject; connection = connectionFactory.createTopicConnection(); MyListener myListener = new MyListener(); session = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); topic = session.createTopic(topicName); subscriber = session.createDurableSubscriber(topic, selector);
subscriber.setMessageListener(myListener); connection.start(); Thread.sleep(60000); session.unsubscribe(selector); subscriber.close(); session.close(); connection.stop(); } private Context getInitialContext() throws NamingException { Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.timesten.dataserver.jmsxla.SimpleInitialContextFactory"); InitialContext initialContext = new InitialContext(env); return initialContext; } public static void main(String[] args) throws JMSException, NamingException, InterruptedException { DemoXLA2 demo = new DemoXLA2("TopicConnectionFactory", "xlademo", "bookmark"); } }Далее, запускаем любой из примеров и попытаемся внести, изменить и удалить данные из таблицы xlatest.
Command> insert into xlatest values (2, 'w'); 1 row inserted. Command> update xlatest set name = 'test' where id=2; 1 row updated. Command> delete from xlatest; 1 row deleted. Command>Соответственно, в приложении получаем:
[ __TYPE = 10 ] [ __COMMIT = true ] [ __FIRST = true ] [ __NULLS = ] [ __TBLNAME = XLATEST ] [ __TBLOWNER = ORATT ] [ __mver = 5621355056449191939 ] [ __mtyp = null ] [ ID = 2 ] [ NAME = w ] ---------------------------------------- [ __TYPE = 11 ] [ __COMMIT = true ] [ __FIRST = true ] [ __UPDCOLS = NAME ] [ __NULLS = ] [ __TBLNAME = XLATEST ] [ __TBLOWNER = ORATT ] [ __mver = 5621355056449191942 ] [ __mtyp = null ] [ _ID = 2 ] [ ID = 2 ] [ _NAME = w ] [ NAME = test ] ---------------------------------------- [ __TYPE = 12 ] [ __COMMIT = true ] [ __FIRST = true ] [ __NULLS = ] [ __TBLNAME = XLATEST ] [ __TBLOWNER = ORATT ] [ __mver = 5621355056449191945 ] [ __mtyp = D ] [ ID = 2 ] [ NAME = test ] ----------------------------------------
Как видно, мы получили сообщения о прошедших с таблицей xlatest операциях.
Сообщения имеют следующий формат:
Системные атрибуты начинаются с двойного подчеркивания, например:
__TYPE - тип операции (Insert (10), Update(11), Delete(12)) также возможны другие типы (см. документацию). Для определения типа операции присутствуют константы.
__COMMIT - сигнализирует об завершении транзакции (если true).
__FIRST - сигнализирует о первой операции в транзакции (если true).
__TBLNAME - имя таблицы
__TBLOWNER - владелец таблицы
__NULLS - сигнализирует об атрибутах, в которых содержется значение null
__mver и __mtyp - системные атрибуты.
и т.д. (см. документацию)
Атрибуты без подчеркивания - колонки таблиц, имеющие определенные значения, например:
[ ID = 2 ]
[ NAME = test ]
Атрибуты начинающиеся на одно подчеркивание - старые значения полей (появляются при операции Update), например:
[ _ID = 2 ]
[ ID = 2 ]
[ _NAME = w ]
[ NAME = test ]
Итог
Следовательно, имея достаточно поверхностные знания по java, можно написать XLA приложение, которое может обрабатывать различные сообщения, полученнные из TimesTen. Кроме того, XLA приложение работает в асинхронном режиме, что практически не влияет на производительность Oracle TimesTen.