72 votes

Comment implémenter un écouteur de données en Java ?

J'ai une exigence où si un enregistrement est inséré dans une table de la base de données, alors automatiquement un processus Java doit être exécuté.

47voto

Adrian Points 2320

J'ai une solution pour Oracle. Vous n'avez pas besoin de créer la vôtre puisque maintenant qu'Oracle a acheté Java, il a publié un listener pour celui-ci. Pour autant que je sache, il n'utilise pas le polling en interne, les notifications sont poussées vers le côté Java (probablement en fonction d'un déclencheur) :

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

Et vous pouvez l'implémenter comme ceci (ceci est juste un exemple) :

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

EDITAR:
Vous pouvez utiliser la classe suivante pour vous inscrire : oracle.jdbc.OracleConnectionWrapper

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

Disons que vous créez une méthode quelque part :

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

Cet exemple ne comprend pas de clauses try-catch ni de traitement des exceptions.

30voto

Adam Gent Points 15055

Une réponse similaire ici : Comment faire un écouteur de base de données avec java ?

Vous pouvez le faire avec une file d'attente de messages qui prend en charge les transactions et envoyer un message lorsque la transaction est effectuée ou (connexion fermée) pour les bases de données qui ne prennent pas en charge les notifications. Dans la plupart des cas, vous devrez notifier manuellement et garder la trace de ce qui doit être notifié.

Spring fournit un certain support de transaction automatique pour AMQP y JMS . Une alternative plus simple que vous pourriez utiliser est AsyncEventBus de Guava mais cela ne fonctionnera que pour une seule JVM. Pour toutes les options ci-dessous, je vous recommande de notifier le reste de votre plateforme avec une file de messages.

Option - Non-polling non spécifique à la base de données

Option ORM

Certaines bibliothèques comme Hibernate JPA ont des auditeurs d'entités qui rendent cela plus facile, mais c'est parce qu'ils supposent qu'ils gèrent tout le CRUDing.

Pour l'ordinaire JDBC vous devrez faire votre propre comptabilité. C'est-à-dire qu'une fois que la connexion est validée ou fermée, vous envoyez un message à MQ pour l'informer que quelque chose a été mis à jour.

Parsing JDBC

Une option compliquée pour la tenue d'un livre est d'emballer/décorer votre livre de comptes. java.sql.DataSource et/ou java.sql.Connection dans un modèle personnalisé de sorte que le commit() (et fermer) vous envoyez ensuite un message. Je crois que certains systèmes de mise en cache fédérée font cela. Vous pourriez piéger le SQL exécuté et l'analyser pour voir s'il s'agit d'un INSERT ou d'un UPDATE, mais sans une analyse très complexe et des métadonnées, vous n'obtiendrez pas d'écoute au niveau des lignes. Malheureusement, je dois admettre que c'est l'un des avantages d'un système de cache fédéré. ORM fournit en ce sens qu'il sait ce que vous mettez à jour.

Option Dao

La meilleure option si votre no L'utilisation d'un ORM consiste à envoyer manuellement dans votre DAO, après la clôture de la transaction, un message indiquant qu'une ligne a été mise à jour. Assurez-vous simplement que la transaction est clôturée avant d'envoyer le message.

Option - Interrogation de données non spécifiques à la base de données

Suivez un peu la recommandation de @GlenBest.

Il y a deux choses que je ferais différemment. J'externaliserais la minuterie ou je ferais en sorte qu'un seul serveur l'exécute (c'est-à-dire le planificateur). J'utiliserais simplement ScheduledExecutorService (de préférence en l'enveloppant dans l'enveloppe de Guava ListenerScheduledExecutorService ) au lieu de Quartz (à mon avis, l'utilisation de Quartz pour les sondages est superflue).

Pour toutes les tables que vous voulez surveiller, vous devez ajouter une colonne "notified".

Alors vous faites quelque chose comme :

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

Option - spécifique à la base de données

Avec Postgres NOTIFY vous aurez toujours besoin d'interroger dans une certaine mesure, donc vous ferez la plupart des choses ci-dessus, puis vous enverrez le message au bus.

24voto

Lukas Eder Points 48046

Une solution générale consisterait probablement à créer un déclencheur sur la table d'intérêt, notifiant les auditeurs éventuels sur les éléments suivants INSERT événements. Certaines bases de données ont formalisé des moyens pour cette notification inter-processus. Par exemple :

Oracle :

Postgres :

  • El NOTIFY La déclaration est un moyen simple pour une telle notification

Autres :

  • Il existe peut-être des mécanismes de notification similaires dans d'autres bases de données, mais je n'en ai pas connaissance.
  • Vous pouvez toujours mettre en œuvre vos propres tables de file d'attente de notification d'événements en insérant un événement dans une table d'événements, qui est consommée / interrogée par un processus Java. Cependant, il peut être assez difficile d'obtenir un résultat correct et performant.

10voto

Glen Best Points 11455

Hypothèses :

  • Disposer d'un code standard portable est plus important que l'exécution instantanée en temps réel du programme java. Vous voulez permettre la portabilité vers une technologie alternative future (par exemple, éviter les événements propriétaires de la base de données, les déclencheurs externes). Le processus Java peut s'exécuter un peu après l'ajout d'un enregistrement à la table (par exemple, 10 secondes plus tard). Autrement dit, il est possible d'utiliser soit un programme + une interrogation, soit un déclencheur/message/événement en temps réel.

  • Si plusieurs lignes sont ajoutées à la table en une seule fois, vous souhaitez exécuter un seul processus, et non plusieurs. Un déclencheur DB lancerait un processus java pour chaque ligne - inapproprié.

  • La qualité du service est importante. Même en cas d'erreur matérielle ou logicielle fatale, vous voulez que le programme java s'exécute à nouveau et traite les données incomplètes.

  • Vous souhaitez appliquer des normes de sécurité strictes à votre environnement (par exemple, éviter que java ou DB n'exécute directement des commandes du système d'exploitation).

  • Vous voulez minimiser le code

    1. Code standard Java de base sans dépendance à l'égard d'une fonctionnalité de base de données propriétaire :

      • Utilisez ScheduledExecutorService ou le planificateur Quartz (ou unix cron job ou Windows task scheduler) pour exécuter un programme java toutes les minutes (ou toutes les 10 secondes). Cela agit à la fois comme un planificateur et un chien de garde, garantissant que le programme fonctionne 24 heures sur 24. Quartz peut également être déployé dans un serveur d'applications.
      • Faites tourner votre programme java pendant une minute (ou 10 secondes), en boucle, en interrogeant la base de données via JDBC et en dormant pendant quelques secondes, puis en sortant finalement.
    2. Si vous avez une application dans un appserver : Créez un Session Bean qui utilise le Timer Service et interrogez à nouveau la table via JDBC. Session Bean Timer Service .

    3. Avoir un déclencheur DB qui écrit/applique dans un fichier. Utilisez java 7 filewatcher pour déclencher la logique lorsque le fichier change. Java 7 File Watcher

Il existe une autre option : l'utilisation d'un ESB open source avec une logique de déclenchement d'adaptateur de base de données (par exemple, Fuse ou Mule ou OpenAdapter), mais cela donne des fonctionnalités puissantes qui vont au-delà de vos exigences déclarées, et cela prend du temps et est complexe à installer et à apprendre.

Exemple de minuterie EJB utilisant @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
    }
}

Voir aussi : Voir cette réponse pour l'envoi d'objets d'événements CDI de l'EJB au conteneur Web.

1voto

Chandru Points 156

Je ne sais pas dans quelle mesure cette solution répond à votre besoin mais elle peut être considérée comme une option. Si vous utilisez oracle, vous pouvez écrire un programme java et le compiler comme une fonction oracle. Vous pouvez appeler votre programme java à partir du déclencheur post insertion.

Programme Java dans oracle DB

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X