如何在Java中实现db侦听器

时间:2012-09-27 10:07:22

标签: java database listener

我有一个要求,如果在db表中插入记录,则需要自动执行java进程。实现db侦听器的最简单方法是什么?

5 个答案:

答案 0 :(得分:42)

我有一个针对Oracle的解决方案。你不需要创建自己的,因为现在Oracle收购了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子句或任何异常处理。

答案 1 :(得分:25)

这里有一个类似的答案:How to make a database listener with java?

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

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

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

ORM选项

Hibernate JPA have entity listeners这样的库使这更容易,但因为他们认为他们管理所有的CRUD。

对于常规 JDBC ,您必须自己保管。这是在提交或关闭连接之后,然后将消息发送给MQ已更新的内容。

JDBC解析

一个复杂的簿记选项是在自定义文件中包装/装饰您的java.sql.DataSource和/或java.sql.Connection,以便在commit()(并关闭)时发送邮件。我相信一些联合缓存系统可以做到这一点。您可以捕获已执行的SQL并进行解析以查看它是INSERT还是UPDATE,但是如果没有非常复杂的解析和元数据,您将无法获得行级别监听。遗憾的是,我不得不承认这是 ORM 提供的优势之一,因为它知道你的更新。

Dao选项

如果您的使用ORM,那么最好的选择就是在关闭事务后更新行时,在DAO中手动发送消息。 在发送邮件之前,请确保交易已关闭。

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

稍微关注@GlenBest推荐。

我会做一些不同的事情。我会外化定时器或使其只有一个服务器运行定时器(即调度程序)。我只会使用ScheduledExecutorService(最好用Guava的ListenerScheduledExecutorService包裹它)而不是Quartz(恕我直言,使用石英进行轮询超级矫枉过正)。

您希望观看的所有表格都应添加“已通知”列。

然后你做了类似的事情:

// 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你仍然需要在某种程度上进行轮询,这样你就可以完成上述大部分操作,然后将消息发送到总线。

答案 2 :(得分:22)

一般解决方案可能包括在感兴趣的表上创建触发器,向任何听众通知INSERT事件。一些数据库已经为这种进程间通知提供了正式的方法。例如:

甲骨文:

Postgres的:

  • NOTIFY声明是此类通知的简单方法

其他:

  • 在其他数据库中可能存在类似的通知机制,我不知道。
  • 您始终可以通过在事件表中插入事件来实现自己的事件通知队列表,该事件表由Java进程使用/轮询。但是,获得正确和高效的工作可能会非常棘手。

答案 3 :(得分:9)

假设:

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

  • 如果一次向表中添加多行,则需要运行一个进程,而不是多个进程。数据库触发器将为每一行启动一个java进程 - 不合适。

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

  • 您希望对您的环境应用强大的安全标准(例如,避免让java或DB直接执行OS命令)

  • 您希望最小化代码

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

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

    3. 具有写入/附加到文件的数据库触发器。使用java 7 filewatcher触发文件更改Java 7 File Watcher

    4. 时的逻辑

还有另一种选择:使用带有数据库适配器触发逻辑的开源ESB(例如Fuse或Mule或OpenAdapter),但这提供了超出您规定要求的强大功能,并且安装和学习既费时又复杂。

使用@Schedule的EJB计时器示例:

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
    }
}

另请参阅:See This Answer,用于将CDI事件对象从EJB发送到Web容器。

答案 4 :(得分:1)

我不确定这种解决方案能满足您的需求,但可以考虑作为一种选择。如果您正在使用oracle然后oracle,您可以编写一个java程序并将其编译为oracle函数。你可以从post insert触发器调用你的java程序。

Java program in oracle DB