web-dev-qa-db-fra.com

Comment implémenter un auditeur de base de données dans Java

J'ai une exigence: si un enregistrement est inséré dans une table de base de données, un processus Java doit être exécuté automatiquement). Quel est le moyen le plus simple d'implémenter un écouteur de base de données?

63
Krithika Vittal

J'ai une solution pour Oracle. Vous n'avez plus besoin de créer le vôtre puisque Oracle a acheté Java il a publié un écouteur pour celui-ci. Pour autant que je sache, cela n'utilise pas la scrutation en interne, mais les notifications sont poussées vers le = Java (probablement basé sur 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 n'est qu'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();                                       
            }
        }       
    }
}

EDIT:
Vous pouvez utiliser la classe suivante pour vous inscrire: Oracle.jdbc.OracleConnectionWrapper

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

Dites 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 n'inclut pas les clauses try-catch ni la gestion des exceptions.

44
Adrian

Une réponse similaire ici: Comment créer un auditeur de base de données avec Java?

Vous pouvez le faire avec une file de messages qui prend en charge les transactions et déclenche simplement un message lorsque la transaction est validée ou (connexion fermée) pour les bases de données qui ne prennent pas en charge les notifications. C’est pour la plupart que vous devrez notifier manuellement et garder trace de ce qu’il faut notifier.

Spring fournit un support de transaction automatique pour [~ # ~] amqp [~ # ~] et [~ # ~ ] jms [~ # ~] . Une alternative plus simple que vous pouvez utiliser est AsyncEventBus de Guava mais cela ne fonctionnera que pour une seule machine virtuelle. Pour toutes les options ci-dessous, je vous recommande d'informer le reste de votre plate-forme avec une file d'attente de messages.

Option - non spécifique à la base de données et non interrogateur

Option ORM

Certaines bibliothèques comme Hibernate JPA ont des écouteurs d'entités qui facilitent les choses, mais parce qu'elles supposent qu'elles gèrent tous les fichiers CRUD.

Pour que votre compte soit normal [~ # ~] jdbc [~ # ~] , vous devrez le faire vous-même. C'est après que la connexion soit validée ou fermée que vous envoyez ensuite le message à MQ que quelque chose a été mis à jour.

Analyse JDBC

Une option compliquée pour la comptabilité consiste à envelopper/décorer votre Java.sql.DataSource Et/ou Java.sql.Connection Dans une configuration personnalisée telle que le commit() (et fermer) vous envoyez ensuite un message. Je pense que certains systèmes de mise en cache fédérés le font. Vous pouvez intercepter le SQL exécuté et analyser pour voir si c'est un INSERT ou UPDATE, mais sans analyse très compliquée et métadonnées, vous n'obtiendrez pas d'écoute au niveau de la ligne. Malheureusement, je dois admettre que c’est l’un des avantages qu’un [~ # ~] orm [~ # ~] fournit en ce qu’il sait quelle est votre mise à jour.

Option Dao

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

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

Suivez en quelque sorte la recommandation @GlenBest.

J'ai deux choses que je ferais différemment. Je voudrais externaliser la minuterie ou faire en sorte qu'un seul serveur exécute la minuterie (c.-à-d. Programmateur). Je voudrais simplement utiliser ScheduledExecutorService (il est préférable de l’emballer dans ListenerScheduledExecutorService de Guava) au lieu de Quartz (à mon humble avis, l’utilisation du quartz pour des interrogations excessives).

Pour toutes les tables que vous souhaitez surveiller, vous devez ajouter une colonne "notifiée".

Ensuite, 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 encore besoin d'interroger dans une certaine mesure pour pouvoir effectuer la plupart des opérations ci-dessus, puis envoyer le message au bus.

25
Adam Gent

Une solution générale consisterait probablement à créer un déclencheur sur la table d’intérêt, en avertissant les auditeurs des événements INSERT. Certaines bases de données ont formalisé les moyens permettant une telle notification entre processus. Par exemple:

Oracle:

Postgres:

  • L’instruction NOTIFY est un moyen simple pour une telle notification

Autres:

  • Il se peut que des mécanismes de notification similaires existent dans d'autres bases de données, à ma connaissance.
  • Vous pouvez toujours implémenter vos propres tables de files d'attente de notification d'événement en insérant un événement dans une table d'événement consommée/interrogée par un processus Java. Obtenir ce droit et cette performance peut toutefois s'avérer assez délicat.
22
Lukas Eder

Hypothèses:

  • Avoir du code portable standard est plus important que l'exécution instantanée en temps réel du programme Java. Vous souhaitez autoriser la portabilité vers d'autres technologies futures (par exemple, éviter les événements de base de données propriétaires, les déclencheurs externes). Java peut s’exécuter légèrement après l’ajout de l’enregistrement à la table (10 secondes plus tard, par exemple), c’est-à-dire que les événements planifiés + interrogation ou déclencheurs/messages/événements en temps réel sont acceptables.

  • Si plusieurs lignes sont ajoutées à la table en même temps, vous souhaitez exécuter un processus, pas plusieurs. Un déclencheur de base de données lancerait un processus Java pour chaque ligne - inapproprié).

  • La qualité de service est importante. Même en cas d'erreur fatale matérielle ou logicielle, vous souhaitez que le programme Java soit exécuté à nouveau et traite les données incomplètes.

  • Vous souhaitez appliquer des normes de sécurité strictes à votre environnement (par exemple, évitez d'avoir Java ou des commandes d'exécution directe du système d'exploitation))

  • Vous voulez minimiser le code

    1. Core Java Code standard sans dépendance de la fonctionnalité de base de données propriétaire:

      • Utilisez ScheduledExecutorService ou le planificateur Quartz (ou le planificateur de tâche cron unix ou Windows) pour exécuter un programme Java toutes les minutes (ou peut le faire toutes les 10 secondes). Cela sert à la fois de planificateur et de programme fonctionne 24 heures sur 24. Quartz peut également être déployé dans un serveur d'applications.
      • Lancez votre programme Java) pendant 1 minute (ou 10 secondes) en boucle, en interrogeant la base de données via JDBC et en dormant quelques secondes, puis en sortant.
    2. Si vous avez une application sur un serveur d'applications: Créez un bean session utilisant le service de minuterie et interrogez à nouveau la table via JDBC Session Bean Timer Service .

    3. Avoir un déclencheur de base de données qui écrit/ajoute dans un fichier. Utilisez Java 7 filewatcher pour déclencher la logique lorsque le fichier est modifié Observateur de fichiers Java 7

Il existe une autre option: utiliser 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 offre des fonctionnalités puissantes allant au-delà de vos exigences spécifiées. Il est fastidieux et 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 envoyer des objets d'événement CDI d'un conteneur d'EJB vers un conteneur Web.

9
Glen Best

Je ne sais pas dans quelle mesure cette solution répond à vos besoins, mais peut être considérée comme une option. Si vous utilisez Oracle, vous pouvez écrire un programme Java et le compiler en tant que fonction Oracle. Vous pouvez appeler votre programme Java à partir du déclencheur de post-insertion .

programme Java dans Oracle DB

1
Chandru