レコードが db テーブルに挿入された場合、自動的に Java プロセスを実行する必要があるという要件があります。db リスナーを実装する最も簡単な方法は何ですか?
5 に答える
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 句や例外処理は含まれていません。
同様の回答がここにあります: How to make a database listener with java?
これは、トランザクションをサポートするメッセージ キューを使用して行うことができ、トランザクションがコミットされたとき、または通知をサポートしていないデータベースの場合 (接続が閉じられたとき) にメッセージを送信するだけです。ほとんどの場合、手動で通知し、何を通知するかを追跡する必要があります。
Spring は、 AMQPおよびJMSのいくつかの自動トランザクション サポートを提供します。使用できるより簡単な代替手段はGuava の AsyncEventBus ですが、これは 1 つの JVM でしか機能しません。以下のすべてのオプションについて、プラットフォームの残りの部分にメッセージ キューで通知することをお勧めします。
オプション - 非ポーリング 非データベース固有
ORM オプション
Hibernate JPA などの一部のライブラリには、これを簡単にするエンティティ リスナーがありますが、これは、すべての CRUD を管理することを前提としているためです。
通常のJDBCの場合は、独自の簿記を行う必要があります。つまり、接続がコミットまたはクローズされた後、何かが更新されたというメッセージを MQ に送信します。
JDBC 解析
ブックキーピングの複雑なオプションの 1 つは、メッセージを送信する (および閉じる)ように、java.sql.DataSource
および/またはjava.sql.Connection
カスタムのものでラップ/装飾することです。commit()
一部のフェデレーテッド キャッシング システムがこれを行っていると思います。実行された SQL をトラップし、解析して INSERT か UPDATE かを確認できますが、非常に複雑な解析とメタデータがなければ、行レベルのリッスンは得られません。悲しいことに、これはORMが提供する利点の 1 つであることを認めざるを得ません。ORM は更新内容を認識しています。
ダオオプション
ORM を使用しない場合の最適なオプションは、行が更新されたというトランザクションが閉じられた後に、DAO で手動でメッセージを送信することです。メッセージを送信する前に、トランザクションが終了していることを確認してください。
オプション - データベース固有でないポーリング
@GlenBestの推奨に従ってください。
私が別の方法で行うことをいくつか。タイマーを外部化するか、1 つのサーバーだけがタイマーを実行するようにします (つまり、スケジューラー)。私はQuartz(スーパーオーバーキルのポーリングにQuartzを使用するIMHO)の代わりに(ScheduledExecutorService
Guava'sでラップすることをお勧めします)を使用します。ListenerScheduledExecutorService
監視したいテーブルのほとんどすべてに、「通知済み」列を追加する必要があります。
次に、次のようなことを行います。
// 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); }
オプション - データベース固有
PostgresNOTIFY
の場合でも、ある程度ポーリングする必要があるため、上記のほとんどを実行してからメッセージをバスに送信します。
一般的な解決策は、おそらく、対象のテーブルにトリガーを作成し、INSERT
イベントについてリスナーに通知することです。一部のデータベースでは、このようなプロセス間通知の手段が形式化されています。例えば:
オラクル:
- は、その
DBMS_ALERT
ような通知のための簡単な手段です - Oracle AQ / Oracle Streamsは、より洗練されたキュー メカニズムを提供します。
ポストグル:
- ステートメントは、その
NOTIFY
ような通知のための簡単な手段です
その他:
- 私が知らない他のデータベースにも同様の通知メカニズムがあるかもしれません。
- Java プロセスによって消費/ポーリングされるイベント テーブルにイベントを挿入することにより、独自のイベント通知キュー テーブルをいつでも実装できます。ただし、これを適切に実行してパフォーマンスを向上させるのは非常に難しい場合があります。
仮定:
標準のポータブルコードを持つことは、Javaプログラムの即時リアルタイム実行よりも重要です。代替の将来のテクノロジーへの移植性を許可したい(たとえば、独自のDBイベント、外部トリガーを回避する)。Javaプロセスは、レコードがテーブルに追加された後(たとえば、10秒後)にわずかに実行できます。つまり、スケジュール+ポーリングまたはリアルタイムトリガー/メッセージ/イベントのいずれかが受け入れられます。
複数の行が一度にテーブルに追加される場合は、多くではなく1つのプロセスを実行する必要があります。DBトリガーは、行ごとにJavaプロセスを開始します-不適切です。
サービス品質は重要です。ハードウェアまたはソフトウェアの致命的なエラーが発生した場合でも、Javaプログラムを再度実行して、不完全なデータを処理する必要があります。
環境に強力なセキュリティ標準を適用したい(たとえば、JavaまたはDBにOSコマンドを直接実行させないようにする)
コードを最小限に抑えたい
プロプライエタリDB機能に依存しないコアJava標準コード:
- ScheduledExecutorServiceまたはQuartzスケジューラー(またはunix cronジョブまたはWindowsタスクスケジューラー)を使用して、Javaプログラムを1分ごとに実行します(または10秒ごとに実行できます)。これはスケジューラーとウォッチドッグの両方として機能し、プログラムが24時間実行されるようにします。Quartzはアプリサーバーにデプロイすることもできます。
- Javaプログラムをわずか1分(または10秒)実行し、ループし、JDBCを介してDBにクエリを実行し、数秒間スリープしてから、最後に終了します。
アプリサーバーにアプリがある場合:タイマーサービスを使用するセッションBeanを作成し、JDBCセッションBeanタイマーサービスを介してテーブルを再度クエリします。
ファイルへの書き込み/追加を行うDBトリガーを用意します。java 7 filewatcherを使用して、ファイルが変更されたときにロジックをトリガーしますJava 7 File Watcher
別のオプションがあります。DBアダプタートリガーロジック(Fuse、Mule、OpenAdapterなど)でオープンソースESBを使用しますが、これにより、指定された要件を超える強力な機能が提供され、インストールと学習に時間がかかり、複雑になります。
@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
}
}
関連項目:EJBからWebコンテナへのCDIイベントオブジェクトの送信については、この回答を参照してください。
このソリューションがあなたのニーズをどこまで満たしているかはわかりませんが、オプションと見なすことができます. Oracle を使用している場合は、Java プログラムを作成し、それを Oracle 関数としてコンパイルできます。post insert トリガーから Java プログラムを呼び出すことができます。