Подтвердить что ты не робот

Как реализовать db-прослушиватель в Java

У меня есть требование, где, если запись вставлена ​​в таблицу db, тогда автоматически должен выполняться java-процесс. Какой самый простой способ реализовать db-прослушиватель?

4b9b3361

Ответ 1

У меня есть решение для 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();                                       
            }
        }       
    }
}

EDIT:
Вы можете использовать следующий класс для регистрации: 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

Аналогичный ответ здесь: как создать прослушиватель базы данных с помощью java?

Вы можете сделать это с очередью сообщений, которая поддерживает транзакции и просто отключить сообщение при завершении транзакции или (закрытие соединения) для баз данных, которые не поддерживают уведомления. Это по большей части вам придется вручную уведомлять и отслеживать, что уведомлять.

Spring предоставляет некоторую автоматическую поддержку транзакций для AMQP и JMS. Более простая альтернатива, которую вы можете использовать, это Guava AsyncEventBus, но это будет работать только для одной JVM. Для всех приведенных ниже вариантов я рекомендую вам сообщать остальную часть вашей платформы очереди сообщений.

Опция - Непоселение, не связанное с базой данных

Опция ORM

Некоторые библиотеки, такие как Hibernate JPA, имеют слушателей сущностей, которые делают это проще, но это потому, что они предполагают, что они управляют всем CRUDing.

Для регулярного JDBC вам придется делать свой собственный бухгалтерский учет. То есть после того, как соединение зафиксировано или закрыто, вы затем отправляете сообщение в MQ о том, что что-то обновлено.

Разбор JDBC

Один сложный вариант для ведения бухгалтерского учета заключается в том, чтобы обернуть/украсить ваш java.sql.DataSource и/или java.sql.Connection в пользовательском, например, чтобы на commit() (и закрыть) вы отправили сообщение. Я считаю, что некоторые федеративные системы кэширования делают это. Вы можете захватить выполненный SQL и проанализировать, чтобы увидеть, если его INSERT или UPDATE, но с очень сложными синтаксическими анализами и метаданными вы не получите прослушивание на уровне строк. К сожалению, я должен признать, что это одно из преимуществ, предоставляемых ORM, поскольку оно знает, что вы обновляете.

Вариант Dao

Лучший вариант, если вы не используете ORM, просто вручную отправите сообщение в DAO после того, как транзакция закрыта, что строка была обновлена. Просто убедитесь, что транзакция закрыта, прежде чем отправлять сообщение.

Опция - опрос, не связанный с базой данных

В некоторой степени следуйте рекомендациям @GlenBest.

Я пару вещей, которые я бы сделал по-другому. Я бы выделил таймер или сделал его так, чтобы только один сервер запускал таймер (т.е. планировщик). Я бы просто использовал ScheduledExecutorService (предпочтительнее обернуть его в Guava ListenerScheduledExecutorService) вместо Quartz (IMHO использует кварц для опроса super overkill).

Далеко все ваши таблицы, которые вы хотите посмотреть, должны добавить столбец "уведомленный".

Затем вы делаете что-то вроде:

// 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); }

Опция - db

С Postgres NOTIFY вам все равно нужно будет опросить в какой-то степени, чтобы вы выполнили большую часть выше, а затем отправили сообщение на автобус.

Ответ 3

Общее решение, вероятно, будет заключаться в создании триггера в интересующей таблице, уведомлении слушателей о событиях INSERT. Некоторые базы данных имеют формализованные средства для такого уведомления о процессе. Например:

Oracle:

  • DBMS_ALERT - это простое средство для такого уведомления
  • Oracle AQ/Oracle Streams обеспечивают более сложные механизмы очереди

Postgres:

  • Оператор NOTIFY является простым средством для такого уведомления

Другие:

  • В других базах данных могут быть аналогичные механизмы уведомления, о которых я не знаю.
  • Вы всегда можете реализовать собственные таблицы очередей уведомлений о событиях, вставив событие в таблицу событий, которая потребляется/обрабатывается процессом Java. Правильное и исполнительное право может быть довольно сложным.

Ответ 4

Предположения:

  • Наличие стандартного портативного кода более важно, чем мгновенное выполнение Java-программы в реальном времени. Вы хотите разрешить переносимость альтернативных будущих технологий (например, избегайте проприетарных событий БД, внешних триггеров). Процесс Java может немного работать после добавления записи в таблицу (например, через 10 секунд). то есть оба графика + опрос или триггер/сообщение/событие в реальном времени являются приемлемыми.

  • Если сразу несколько строк добавляются в таблицу, вы хотите запустить один процесс, а не много. Триггер DB запускает java-процесс для каждой строки - неуместно.

  • Важное значение имеет качество обслуживания. Даже если есть фатальная ошибка оборудования или программного обеспечения, вы хотите, чтобы java-программа снова запускалась и обрабатывала неполные данные.

  • Вы хотите применить к своей среде сильные стандарты безопасности (например, избегать непосредственного выполнения команд Java или БД)

  • Вы хотите свести к минимуму код

    • Стандартный код ядра Java без зависимости от собственной функциональности БД:

      • Используйте ScheduledExecutorService или Quartz scheduler (или задание unix cron или планировщик задач Windows) для запуска Java-программы каждую минуту (или может делать каждые 10 секунд). Это действует как планировщик и сторожевой таймер, гарантируя, что программа работает круглосуточно. Кварц также может быть развернут на сервере приложений.
      • Попробуйте выполнить java-программу всего за 1 минуту (или 10 секунд), зациклив, запросив DB через JDBC и спящий на несколько секунд, а затем, наконец, выйдя.
    • Если у вас есть приложение на сервере приложений: Создайте сеанс Bean, который использует службу таймера и снова запросит таблицу через JDBC сеанс Bean служба таймера.

    • У вас есть триггер DB, который записывает/присоединяется к файлу. Используйте java 7 filewatcher для запуска логики, когда файл изменяется Java 7 File Watcher

Существует еще один вариант: использование ESB с открытым исходным кодом с логикой запуска адаптера DB (например, Fuse или Mule или OpenAdapter), но это дает мощную функциональность, не зависящую от ваших заявленных требований, и занимает много времени и сложна для установки и изучения.

Пример таймера EJB с использованием @Schedule:

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

См. также: См. этот ответ для отправки объектов события CDI из EJB в веб-контейнер.

Ответ 5

Я не уверен, насколько это решение удовлетворяет ваши потребности, но может рассматриваться как вариант. Если вы используете oracle, то оракул вы можете написать java-программу и скомпилировать ее как функцию оракула. вы можете вызвать свою java-программу из триггера post insert.

Java-программа в Oracle DB