如何在Java中实现数据库侦听器


Answers:


47

我有一个针对Oracle的解决方案。自从Oracle购买Java以来​​,您无需创建自己的Java,它为此发布了一个监听器。据我所知,这在内部不使用轮询,而是将通知推送到Java端(可能基于某些触发器):

public interface oracle.jdbc.dcn.DatabaseChangeListener 
extends java.util.EventListener {
    void onDatabaseChangeNotification(oracle.jdbc.dcn.DatabaseChangeEvent arg0);
}

您可以像这样实现它(这只是一个示例):

public class DBListener implements DatabaseChangeListener {
    private DbChangeNotification toNotify;

    public BNSDBListener(DbChangeNotification toNotify) {
        this.toNotify = toNotify;
    }

    @Override
    public void onDatabaseChangeNotification(oracle.jdbc.dcn.DatabaseChangeEvent e) {
        synchronized( toNotify ) {
            try {
                toNotify.notifyDBChangeEvent(e); //do sth
            } catch (Exception ex) {
                Util.logMessage(CLASSNAME, "onDatabaseChangeNotification", 
                    "Errors on the notifying object.", true);
                Util.printStackTrace(ex);
                Util.systemExit();                                       
            }
        }       
    }
}

编辑:
您可以使用以下类进行注册:oracle.jdbc.OracleConnectionWrapper

public class oracle.jdbc.OracleConnectionWrapper implements oracle.jdbc.OracleConnection {...}

假设您在某处创建方法:

public void registerPushNotification(String sql) {
    oracle.jdbc.driver.OracleConnection oracleConnection = ...;//connect to db

    dbProperties.setProperty(OracleConnection.DCN_NOTIFY_ROWIDS, "true");
    dbProperties.setProperty(OracleConnection.DCN_QUERY_CHANGE_NOTIFICATION, "true");

    //this is what does the actual registering on the db end
    oracle.jdbc.dcn.DatabaseChangeRegistration dbChangeRegistration= oracleConnection.registerDatabaseChangeNotification(dbProperties);

    //now you can add the listener created before my EDIT
    listener = new DBListener(this);
    dbChangeRegistration.addListener(listener);

    //now you need to add whatever tables you want to monitor
    Statement stmt = oracleConnection.createStatement();
    //associate the statement with the registration:
    ((OracleStatement) stmt).setDatabaseChangeRegistration(dbChangeRegistration); //look up the documentation to this method [http://docs.oracle.com/cd/E11882_01/appdev.112/e13995/oracle/jdbc/OracleStatement.html#setDatabaseChangeRegistration_oracle_jdbc_dcn_DatabaseChangeRegistration_]

    ResultSet rs = stmt.executeQuery(sql); //you have to execute the query to link it to the statement for it to be monitored
    while (rs.next()) { ...do sth with the results if interested... }

    //see what tables are being monitored
    String[] tableNames = dbChangeRegistration.getTables();
    for (int i = 0; i < tableNames.length; i++) {
        System.out.println(tableNames[i]    + " has been registered.");
    }
    rs.close();
    stmt.close();
}

此示例不包括try-catch子句或任何异常处理。


2
看到这样的侦听器如何注册会很有趣!
卢卡斯·埃德

非常好。感谢您的加入!
卢卡斯·埃德

我有兴趣了解更多有关此的信息。这是j2se类型的应用程序,还是作为oracle存储过程在oracle中运行?
akaphenom 2015年

4
您有MySQl的解决方案吗?Oracle对我们来说不是一个选择,因为它太昂贵了。
JPM

@All-这种方法是否也适用于Postgres DB?
Pra_A

28

一个类似的答案在这里:如何使用Java制作数据库侦听器?

您可以使用支持事务的消息队列来执行此操作,并且在事务启动或不支持通知的数据库(关闭连接)时仅触发一条消息。在大多数情况下,您将必须手动通知并跟踪要通知的内容。

Spring为AMQPJMS提供了一些自动事务支持。可以使用的一种更简单的替代方法是Guava的AsyncEventBus,但这仅适用于一个JVM。对于以下所有选项,我建议您使用消息队列通知平台的其余部分。

选项-非轮询非数据库专用

ORM选项

一些库(例如Hibernate JPA) 具有实体侦听器,该实体侦听器使此操作变得更容易,但是那是因为它们假定它们管理所有CRUDing。

对于常规的JDBC,您必须自己记账。即,在落实或关闭连接后,您然后将消息已更新的消息发送给MQ。

JDBC解析

簿记的一个复杂选项是包装java.sql.DataSource和/或装饰您的和/或java.sql.Connection自定义的,这样commit()您就可以在打开(然后关闭)时发送消息。我相信某些联合缓存系统可以做到这一点。您可以捕获执行的SQL并进行分析,以查看它是INSERT还是UPDATE,但是如果没有非常复杂的分析和元数据,您将不会获得行级的侦听。可悲的是,我不得不承认这是ORM提供的优势之一,因为它知道您要进行的更新。

道选项

如果您使用ORM ,最好的选择是在事务关闭后仅手动在DAO中发送一条消息,即已更新了一行。在发送消息之前,只需确保事务已关闭即可。

选项-轮询非数据库特定的

遵循@GlenBest建议。

我做了几件我会做不同的事情。我将外部化计时器或将其设置为只有一台服务器运行计时器(即调度程序)。我只会使用ScheduledExecutorService(最好用Guava包装ListenerScheduledExecutorService)而不是石英(IMHO使用石英来轮询超级杀伤力)。

您想要观看的所有表中,都应该添加“ notified”列。

然后,您可以执行以下操作:

// BEGIN Transaction
List<String> ids = execute("SELECT id FROM table where notified = 'f'");
//If db not transactional either insert ids in a tmp table or use IN clause
execute("update table set notified = 't' where notified = 'f'")
// COMMIT Transaction
for (String id : ids) { mq.sendMessage(table, id); }

选项-特定于数据库

使用Postgres,NOTIFY您仍然需要进行一定程度的轮询,因此您将完成上述大部分操作,然后将消息发送到总线。


就消息队列而言,这听起来很笼统-即消息只是“嘿,我有一个已完成的事务” ...您能提供一个示例来说明如何以更细粒度的方式吗?
卢卡斯·埃德

@LukasEder我自己遇到了这个问题...我还没有找到一种简便的方法,而不是特定于数据库的方法。我刚刚添加了答案,因为我仍然认为它比轮询数据库更好。但是也许那是不正确的。但是,即使您正在轮询MQ,也是通知所有系统行已更新的最佳选择。
亚当·根特

精心制作。毕竟,可以说这确实不简单。试想一下SQL MERGE语句。完全不可能预测它将在数据库外部实际执行的操作。因此,我认为唯一真正可靠的方法毕竟是数据库中的某种轮询...
Lukas Eder 2012年

Postgres的DB特定部分在stackoverflow.com/a/39446972/873282
koppor

@All-这种方法是否也适用于Postgres DB?
Pra_A

24

一个通用的解决方案可能是在目标表上创建一个触发器,将INSERT事件通知给所有侦听器。一些数据库具有用于这种进程间通知的形式化手段。例如:

甲骨文:

Postgres:

  • NOTIFY声明是这样的通知的简单方法

其他:

  • 在其他数据库中可能有类似的通知机制,我并不知道。
  • 您始终可以通过将事件插入事件表来实现自己的事件通知队列表,该事件表由Java进程使用/轮询。但是,正确地做到这一点和表现可能很棘手。

10

假设:

  • 具有标准的可移植代码比Java程序的即时实时执行更为重要。您希望允许移植到替代的未来技术(例如,避免专有的数据库事件,外部触发器)。将记录添加到表中之后,Java进程可能会稍微运行(例如10秒后)。即时间表+轮询或实时触发/消息/事件都可以接受。

  • 如果一次将多行全部添加到表中,则您要运行一个进程,而不是很多。DB触发器将为每一行启动一个Java进程-不适当。

  • 服务质量很重要。即使存在硬件或软件致命错误,您也希望Java程序再次运行并处理不完整的数据。

  • 您想对环境应用严格的安全标准(例如,避免让Java或DB直接执行OS命令)

  • 您想最小化代码

    1. 不依赖专有数据库功能的核心Java标准代码:

      • 使用ScheduledExecutorService或Quartz调度程序(或UNIX cron作业或Windows任务调度程序)每分钟运行Java程序(或每10秒执行一次)。它既充当调度程序又充当看门狗,确保程序全天候运行。Quartz也可以部署在应用服务器中。
      • 让您的Java程序仅运行1分钟(或10秒),然后循环运行,通过JDBC查询数据库并休眠几秒钟,然后最终退出。
    2. 如果您在appserver中有app:创建一个使用Timer Service的Session Bean,然后再次通过JDBC Session Bean Timer Service查询该表。

    3. 有一个写入/附加到文件的数据库触发器。当文件更改时,使用Java 7 Filewatcher触发逻辑Java 7 File Watcher

还有另一种选择:将开放源代码ESB与DB适配器触发逻辑一起使用(例如Fuse或Mule或OpenAdapter),但这会提供超出您指定要求的强大功能,并且安装和学习非常耗时且复杂。

使用@Schedule的EJB Timer示例:

public class ABCRequest {
   // normal java bean with data from DB
}

@Singleton
public class ABCProcessor {    
    @Resource DataSource myDataSource;   
    @EJB ABCProcessor abcProcessor;
    // runs every 3 minutes 
    @Schedule(minute="*/3", hour="*")
    public void processNewDBData() {
        // run a JDBC prepared statement to see if any new data in table, put data into RequestData
        try
        {
           Connection con = dataSource.getConnection();
           PreparedStatement ps = con.prepareStatement("SELECT * FROM ABC_FEED;");
           ...
           ResultSet rs = ps.executeQuery();
           ABCRequest abcRequest
           while (rs.hasNext()) {
               // population abcRequest
           }
           abcProcessor.processABCRequest(abcRequst);
        } ...
    }    
}

@Stateless
public class class ABCProcessor {
    public void processABCRequest(ABCRequest abcRequest) {
    // processing job logic
    }
}

另请参见:有关从EJB向Web容器发送CDI事件对象的信息,请参见此答案


+1:这也是一个很好的答案,特别是因为
预先

这个时间表与使用Quartz时间表有什么不同?
Kumaran Senapathy 2014年

他们基本上做同样的事情。Quartz可以在JSE环境(基本JVM)中工作,但不是标准的(没有Quartz不能移植到另一个环境)。EJB调度程序/计时器可以在完整的JEE环境(应用服务器)中运行,但不能在“ EJB / JEE lite”环境(具有EJB lite受管Bean的JVM或servlet容器)中运行,但是它是标准的(可以移植到任何其他类似的端口环境)。
2014年

1

我不确定该解决方案可以满足您的需求,但可以考虑作为一种选择。如果使用的是oracle,则可以编写一个java程序并将其编译为oracle函数。您可以从插入后触发器触发Java程序。

Oracle DB中的Java程序


Oracle中的Java可能非常慢。我更喜欢使用DBMS_ALERTOracle AQ代替,并将任何更改通知常规Java进程...
Lukas Eder 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.