TimesTen
支持存储过程、函数等功能,但是不支持触发器。相应的,你可以使用TimesTen JMS/XLA API
来监听本地数据库中指定表的更改,并收到这些更改的实时通知。JMS/XLA的首要目的是作为触发器的高性能、异步的替代方式。
JMS/XLA
实现了JMS
接口,让TimesTen
的事务处理日志(Transaction Log API
,即XLA
)的功能可以使用在Java
应用上。
JMS/XLA 基本概念
Java
应用可以使用JMS/XLA
来接受TimesTen
的事件通知,JMS/XLA
使用JMS
的发布订阅接口来提供对XLA
更新事件的访问。
通过建立一个JMS
会话(Session
)实例来订阅更新,该实例提供与XLA
的连接,并用于创建持久订阅者(TopicSubscriber
)。你可以通过这个订阅者同步地接受和处理消息,也可以实现一个监听器(MessageListener
)异步地处理更新。
JMS/XLA
被设计为用来监听本地数据库,TimesTen
和和接收通知的应用程序必须在同一个系统上。
XLA 如何读取事务日志
当应用修改数据库之后,TimesTen
会产生一条事务日志记录来描述更改。新的日志记录总是添加到缓存区的末尾,并周期性从内存同步到磁盘文件上。应用程序可以使用XLA
读取日志,并进行过滤得到感兴趣的更改记录。
XLA 与物化视图
XLA
可以用来跟踪表和物化视图的改变,但是不能跟踪普通视图的改变。物化视图提供单个来源,让你可以从多个细节表中跟踪所选行和列的更改。没有物化视图,应用程序必须监听和过滤来自所有细节表的更新记录,包括应用并不关心的行和列。
一般来说,用于跟踪表的变化或物化视图的XLA
机制之间没有操作上的差异。但是对于异步物化视图,要意识到对于异步视图,XLA
通知的顺序不必和相关联的细节表一致,也不必和同步视图一致。例如,往一张细节表插入两条数据,在异步物化视图中可能是以相反的顺序插入的。此外,更新操作可能被XLA
报告为一个删除操作后跟一个插入操作,多个操作可能被合并为一个单一的操作。依赖精确操作顺序的应用程序不应该使用异步物化视图。
XLA 书签
XLA
书签标记着XLA
订阅者应用程序从事物日志中读取的位置,书签支持持久订阅,允许应用程序从一个主题断开连接,然后重新连接来继续从它离开的地方接收日志更新。
你可以使用一个持久的主题订阅者(TopicSubscriber
)来创建一个XLA消息的消费者,创建订阅者时用的订阅标识符被用做XLA
书签名。使用ttXlaSubscribe
和ttXlaUnsubscribe
内置的存储过程启动和通知对一个表的XLA
订阅时,显示的指定了使用的书签名。无论何时收到一个确认消息,书签重置到上一次的读取位置。
通过调用会话(Session
)对象的unsubscribe()
方法可以移除一个持久订阅,同时也会删除相关联的XLA
书签,重新连接时必须创建一个新的订阅。当订阅的书签在使用时不能被修改,要想修改一个订阅,必须先关闭消息消费者,然后取消订阅、重新订阅,最后再打开消息消费者。
注意:
Session
对象的unsubscribe()
方法会会删除书签,但是内置的ttXlaUnsubscribe
存储过程不会删除书签。此外,可以通过内置的ttXlaBookmarkCreate
和ttXlaBookmarkDelete
来创建和删除书签。unsubscribe()
方法相当于调用ttXlaUnsubscribe
和ttXlaBookmarkDelete
两个存储过程。
当XLA
在使用时,TimesTen
事务日志文件会持有这个书签,防止事务日志文件被清除,直到XLA
确认不再需要它们。
JMS/XLA 配置文件和主题
要连接到XLA
,需要建立与特定数据库对应的JMS
主题对象的连接,JMS/XLA
配置文件提供了主题名称与数据库之间的映射关系。JMS/XLA
默认配置文件为jmsxla.xml
,位于当前工作目录下。如果需要使用其它的名字或位置,需要指定环境变量并配置InitialContext
类以及类路径,当按照配置找到多个文件时,采用第一个的配置。
一个主题定义包括:主题名,连接字符串,预取值(指定一次检索多少条更新)。下面的配置文件将主题XlaDemo
映射到数据库TestDB
,如下:
<xlaconfig>
<topics>
<topic name="XlaDemo"
connectionString="DSN=TestDB"
xlaPrefetch="100" />
</topics>
</xlaconfig>
XLA 更新
应用程序接收XLA
更新作为JMS MapMessage
对象,一个MapMessage
对象包含了一组类型名/值对,对应于XLA
更新头的字段。通过getter
方法获取消息字段,可以跳过名字检索独立的字段值。所有保留的字段名一双下划线开头,如__TYPE
。
所有的更新都有一个__TYPE
字段,用来指示消息包含的更新类型。类型值为为一个Integer
,可以使用com.timesten.dataserver.jmsxla.XlaConstants
中定义的常量来进行比较。有如下支持的类型:
Type | Description |
---|---|
INSERT | A row has been added |
UPDATE | A row has been modified |
DELETE | A row has been removed |
COMMIT_ONLY | A transaction has been committed |
CREATE_TABLE | A table has been created |
DROP_TABLE | A table has been dropped |
CREATE_INDEX | An index has been created |
DROP_INDEX | An index has been dropped |
ADD_COLUMNS | New columns have been added to the table |
DROP_COLUMNS | Columns have been removed from the table |
CREATE_VIEW | A materialized view has been created |
DROP_VIEW | A materialized view has been dropped |
CREATE_SEQ | A sequence has been created |
DROP_SEQ | A sequence has been dropped |
CREATE_SYNONYM | A synonym has been created |
DROP_SYNONYM | A synonym has been dropped |
TRUNCATE | All rows in the table have been delete |
XLA 确认模式
XLA
确认机制被设计为用来确保应用程序不仅接收到消息,且正确地处理消息。确认一条更新永远将应用的XLA
书签重置为上一条记录的读取位置。避免了以前的消息被重复读取,确保当应用程序重新连接到XLA
,重用书签时只接收新的更新记录。JMS/XLA
可以自动确认XLA
更新消息,应用程序也可以显示地选择消息确认模式。创建会话时,可以指定更新如何被确认。
JMS/XLA
支持三种确认模式:
AUTO_ACKNOWLEDGE
:该模式下,当接收到更新时会自动确认。每一条消息只会被投递一次,重复的消息不会被发送,因此,当程序失败的时候,消息可能会丢失。消息的投递与确认总是相互独立的,所以JMS/XLA
不会预读取多条记录,xlaprefetch
属性将被忽略。DUPS_OK_ACKNOWLEDGE
:该模式下,更新会被自动确认,但是当程序失败的时候,重复的消息会被投递。当上一个预读取块的最后一条记录被读取时,JMS/XLA
根据xlaprefetch
属性值来预读取记录并发送确认。如果程序在读完所有预取记录之前失败,那么程序重新启动之后,预读取块的所有记录都会存在。CLIENT_ACKNOWLEDGE
:该模式下,程序负责通过调用MapMessage
实例的acknowledge()
方法来确认收到更新消息。JMS/XLA
通过xlaprefetch
属性值来预读取记录。
下例展示了如何设置确认模式:
Session session = connection.createSession (false, Session.CLIENT_ACKNOWLEDGE);
预读取更新
一次预读取多条更新记录比每次单独地从XLA
获取记录更加高效。当使用AUTO_ACKNOWLEDGE
时,更新不会被预读取,比其他模式更慢。可能的话,应该将程序设计为可以容忍重复的更新,以便可以使用DUPS_OK_ACKNOWLEDGE
,或者显示地确认更新。显示确认更新通常会产生最佳性能,只要可以避免单独确认每条消息。
确认更新
在更新消息上调用acknowledge()
可以明确地确认一个XLA
更新。确认一条消息会隐式地确认所有以前的消息。通常,你会在确认之间接收并处理多条更新消息。如果使用的是CLIENT_ACKNOWLEDGE
模式并且想在将来重用持久的订阅,在退出之前,应该调用acknowledge()
将书签重置到上一次读取的位置。
使用 JMS/XLA 模拟触发器功能
TimesTen 端配置
1、授权
Command> grant create session, create table, XLA to parkinglotuser;
2、新建 Xla 测试表
Command> create table xlatest (id number primary key, name varchar2(20));
3、创建 Xla 书签
Command> call ttXlaBookmarkCreate('bookmark');
4、订阅
Command> call ttXlaSubscribe('xlatest', 'bookmark');
Java 端程序
5、配置 jmsxla.xml
<xlaconfig>
<topics>
<!-- topic for xla demo -->
<topic name="XlaDemo"
connectionString="DSN=TestDB"
xlaPrefetch="100" />
</topics>
</xlaconfig>
6、连接 XLA 、接收并处理表的更新
import com.timesten.dataserver.jmsxla.XlaConstants;
import org.junit.Test;
import javax.jms.*;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Enumeration;
/**
* Demo which shows how to use JMS/XLA to process updates.
*/
public class AsyncJMS {
@Test
public void run() throws JMSException, NamingException {
String topicName = "XlaDemo";
String bookmark = "bookmark";
Context context = new InitialContext();
TopicConnectionFactory connFactory = (TopicConnectionFactory) context.lookup("TopicConnectionFactory");
TopicConnection conn = connFactory.createTopicConnection();
TopicSession session = conn.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(topicName);
TopicSubscriber subscriber = session.createDurableSubscriber(topic, bookmark);
MyListener listener = new MyListener();
subscriber.setMessageListener(listener);
conn.start();
System.out.println("Starting JMS XLA subscriber");
System.out.println();
System.out.println("Waiting for XLA updates on table xlatest ... ");
System.out.println("- Please use ttIsql or Level1 to modify the xlatest table");
while (true) {
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
// unsubscribe (which will delete the XLA bookmark)
session.unsubscribe(bookmark);
// close the connection
conn.close();
// ignore
e.printStackTrace();
}
}
}
/**
* Class to receive update messages.
*/
class MyListener implements MessageListener {
MyListener() {
}
/**
* This method is called when an XLA event occurs.
*
* @param msg a MapMessage describing the XLA event.
*/
public void onMessage(Message msg) {
MapMessage message = (MapMessage) msg;
if (msg == null) {
System.err.println("MyListener: update message is null");
return;
}
try {
System.out.println();
System.out.println("onMessage: got a " + message.getJMSType() + " message");
// Get the type of event (insert, update, delete, drop table, etc.).
int type = message.getInt(XlaConstants.TYPE_FIELD);
if (type == XlaConstants.INSERT) {
System.out.println("A row was inserted.");
} else if (type == XlaConstants.UPDATE) {
System.out.println("A row was updated.");
} else if (type == XlaConstants.DELETE) {
System.out.println("A row was deleted.");
} else {
// Messages are also received for DDL events such as CREATE TABLE.
// This program processes INSERT, UPDATE, and DELETE events,
// and ignores the DDL events.
return;
}
Enumeration enumeration = message.getMapNames();
while (enumeration.hasMoreElements()) {
String name = enumeration.nextElement().toString();
System.out.println("Name: " + name + ", Value: " + message.getObject(name));
}
System.out.println();
/*
* 1.type == INSERT || type == DELETE
* MapMessage的字段包括监听表的字段、及类本身的字段
* 使用 message.getXXX(field) 读取插入或删除的字段值
* 2.type == UPDATE
* MapMessage的字段包括监听表的字段(分为原始值和更新值)、及类本身的字段
* 使用 message.getXXX(field) 读取更新值
* 使用 message.getXXX(_field) 读取原始值
*
* 其中field为监听表的字段名
*/
// todo 处理更新逻辑
} catch (JMSException e) {
System.err.println("JMSException in " + this.getClass().getName());
e.printStackTrace();
} catch (Exception e) {
System.err.println("Exception in " + this.getClass().getName());
e.printStackTrace();
}
}
}
}