Oracle Advanced Queing, JMS Publish/Subscribe, Weblogic et Message-Driven Beans

Traditionnellement, les applications qui s’appuient sur des bases de données, exécutent des requêtes pour répondre aux questions de leurs utilisateurs. Toutefois, ces systèmes d’information évoluent :

  • les bases de données sont désormais alimentées au fil de l’eau plutôt que par des batchs ou des travaux planifiés
  • les utilisateurs s’attendent de plus en plus à ce qu’on leur pousse l’information alors qu’ils allaient la chercher autrefois.

Pour répondre à ces évolutions de manière efficace, les applications de bases de données mutent. Alors qu’elles passaient parfois un temps important à faire du « Polling » de données dans des tables, elles s’appuient désormais sur des mécanismes de publish/subscribe. Il faut dire que ceux-ci sont extrêmement virtueux :

  • ils ont fait leur preuve, depuis plusieurs milliers d’années, alors qu’IBM dominait encore le monde de l’informatique
  • ils réduisent les temps de latence ; les notifications des clients, même en nombre, deviennent quasi-instantanées
  • ils nécessitent bien moins de ressources que de sonder systématiquement toutes les données depuis parfois des dizaines de programmes
  • ils sont capables de monter facilement en charge, du fait de leur nature asynchrone

Avec l’arrivée annoncée d’une nouvelle génération d’applications web qui s’appuient sur les websockets, il est probable que tout cela ait encore plus de sens.

Cet article illustre ce type d’approche à l’aide d’une file d’attente Oracle Advanced Queuing (AQ) sur laquelle sont publiés des messages JMS qui déclenchent un EJB de type Message Driven Bean dans Weblogic. Il présente les différents aspects d’implémentation et de paramétrage de ces composants…

Résumé

Le schéma ci-dessous présente le fonctionnement de l’architecture publish/subscribe de l’article :

La configuration consiste en :

  • Une file d’attente AQ dans la base de données, capable d’échanger des messages JMS. Cette file d’attente est appelée Topic dans le contexte JMS, car elle peut avoir plusieurs subscribers (souscripteurs).
  • Un module JMS dans Weblogic qui se connecte à la file d’attente AQ en tant que serveur JMS étranger. Celui-ci s’appuie sur une Datasource JDBC créée préalablement et passée en paramètre au connecteur
  • Un EJB de type Message Driven Bean lié via des annotations et la configuration JNDI de Weblogic à la base de données et à la file d’attente.

Lorsqu’un évènement est détecté en base de données, le programme associé met un message JMS dans la file d’attente. Le message déclenche automatiquement l’EJB MDB avec le message. Celui-ci peut être utilisé pour documenter l’évènement et les informations associées.

File d’attente AQ et messages JMS

Côté paramètrage, commencez par créer un schéma, une table de type « multi consumers » qui pourra stocker le « Topic ». La file associée permet d’échanger des messages JMS (ici javax.jms.TextMessage). Vous aurez besoin de démarrer la file d’attente pour terminer. Voici un exemple de script réalisant l’ensemble de ces opérations :

connect / as sysdba

create user demo
identified by demo
default tablespace users;

grant connect, resource, aq_user_role to demo;
grant execute on dbms_aqadm to demo;
grant execute on dbms_aq to demo;
grant execute on dbms_aqin to demo;
grant execute on dbms_aqjms to demo;

connect demo/demo

begin
dbms_aqadm.create_queue_table(
queue_table =>'myQueueTable',
queue_payload_type =>'sys.aq$_jms_text_message',
multiple_consumers =>true);
end;
/

begin
dbms_aqadm.create_queue(
queue_name=>'myqueue',
queue_table=>'myqueuetable');
end;
/

begin
dbms_aqadm.start_queue(queue_name=>'myqueue');
end;
/

Créer le module et le serveur JMS

Une fois la base de données paramétrée, vous pouvez créer le module JMS et le foreign server JMS qui permet de se connecter au Topic JMS supporté par AQ ainsi que les identifiants JNDI correspondants.

  • Etape 1 : Créez une datasource JDBC

Vous créerez une source de données Oracle ainsi qu’un alias JNDI correspondant ; la datasource est une source XA et elle est déployée sur la cible serveur ou cluster sur laquelle on exécutera l’EJB ; n’oubliez pas de tester la source de données :

Name:            AQJMSOracleDS
JNDI Name: jdbc/AQJMSOracleDS
Database : Oracle
Database Driver: Oracle's Driver (Thin XA) for Service connections; (...)
Database Name:
Host Name:
Port:
Database User Name: demo
Password: demo
Confirm Password: demo
Targets : AdminServer
  • Etape 2 : Créez un module JMS

Vous créerez ensuite un module JMS que vous déploierez sur la cible serveur ou cluster sur laquelle on exécutera l’EJB.

*Name:           AQJMSModule
Descriptor File Name:
Location In Domain:
Targets : AdminServer
Would you like to add resources to this JMS system module? NO
  • Etape 3 : Créez une resource JMS de type « Foreign Server »

Ajoutez une resource de type « Foreign Server » et déployez-le sur la cible serveur ou cluster sur laquelle on exécutera l’EJB :

Create a New JMS System Module Resource
Foreign Server
*Name: AQJMSForeignServer
Targets : Servers |X| AdminServer
  • Etape 4 : Modifiez la Context Factory Initiale

Allez dans le menu « Configuration | General » du foreign server, modifiez les propriétés ci-dessous en prenant soin de vérifier que la Datasource utilisée est celle créée à l’étape 1.

JNDI Initial Context Factory: oracle.jms.AQjmsInitialContextFactory
JNDI Properties: datasource=jdbc/AQJMSOracleDS
|X| Default Targeting Enabled
  • Etape 5 : Ajoutez une Connection Factory

Allez dans le menu « Configuration | Connection Factories » du foreign server et ajoutez une connection factory de type TopicConnectionFactory. Prenez soin de positionnez un alias JNDI local qui sera référencé dans notre EJB MDB :

*Name: AQJMSForeignConnectionFactory
Local JNDI Name: jms/aq/mycf
Remote JNDI Name: TopicConnectionFactory
  • Etape 6 : Ajoutez une Destination qui pointe vers le Topic

Allez dans le menu « Configuration | Destinations »,  du foreign server et ajoutez une Destination. Nommez l’alias JNDI remote Topics/X où X est le nom de la file d’attente AQ. Prenez soin de positionnez un alias JNDI local qui sera référencé dans notre EJB MDB :

*Name: AQJMSForeignDestination
Local JNDI Name: jms/aq/myqueue
Remote JNDI Name: Topics/myqueue
  • Etape 7 : Redémarrez Weblogic

Vous trouverez les détails de la configuration de la connexion AQ JMS dans weblogic dans la section ci-dessous de la documentation :

Oracle® Fusion Middleware Configuring and Managing JMS for Oracle WebLogic Server
12c Release 1 (12.1.1)
7 Interoperating with Oracle AQ JMS
Configuring WebLogic Server to Interoperate with AQ JMS

Vous pouvez vérifier la configuration en affichant la structure JNDI locale du votre serveur ou cluster Weblogic ; dans ce
cas, il s’agit de la configuration du serveur AdminServer; l’arbre présente la connection factory et la file d’attente comme ci-dessous :

Créer et déployer un EJB MDB

Le développement de l’EJB Message Driven Bean est au-delà de cet article. Vous recollerez sans doute facilement les morceaux… Notez bien, en particulier, que les alias des Connection Factory et destinations JMS sont celles créées précédemment. Voici le fichier Java correspondant :

package arkzoyd.demo;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

@MessageDriven(mappedName = "jms/aq/myqueue", activationConfig = {
@ActivationConfigProperty(propertyName = "connectionFactoryJndiName",
propertyValue = "jms/aq/mycf"),
@ActivationConfigProperty(propertyName = "acknowledgeMode",
propertyValue = "Auto-acknowledge"),
@ActivationConfigProperty(propertyName = "destinationType",
propertyValue = "javax.jms.Topic"),
@ActivationConfigProperty(propertyName = "subscriptionDurability",
propertyValue = "Durable"),
@ActivationConfigProperty(propertyName = "clientId",
propertyValue = "LogMessage"),
@ActivationConfigProperty(propertyName = "subscriptionName",
propertyValue = "LogMessage")
})
public class LogMessage implements MessageListener {

public LogMessage() {
}

@Override
public void onMessage (Message message) {
try {
BufferedWriter out = new BufferedWriter(
new FileWriter("/tmp/messages.out",
true));
SimpleDateFormat formatter = new SimpleDateFormat(
"dd/MM/yyyy HH:mm:ss");

out.write(formatter.format(new Date()) + "- Message (" +
((TextMessage) message).getText() + ")n");
out.close();
} catch (Exception e) {
e.printStackTrace(System.err);
}
}
}

Vous déployerez votre EJB; La console Weblogic permet notamment de superviser l’activité de cet EJB comme vous le voyez ci-dessous :

Tester l’envoi d’un message JMS

Le script PL/SQL ci-dessous envoie un message JMS depuis la base de données :

connect demo/demo

DECLARE
v_enqueue_options DBMS_AQ.ENQUEUE_OPTIONS_T;
v_message_properties DBMS_AQ.MESSAGE_PROPERTIES_T;
v_message SYS.AQ$_JMS_TEXT_MESSAGE;
v_message_id RAW(16);
BEGIN
v_message := SYS.AQ$_JMS_TEXT_MESSAGE.CONSTRUCT;
v_message.set_text('Message -- TEST --');
dbms_aq.enqueue (
queue_name => 'myqueue',
enqueue_options => v_enqueue_options,
message_properties => v_message_properties,
payload => v_message,
msgid => v_message_id
);
COMMIT;
END;
/

L’EJB en regardant le contenu du fichier /tmp/message.out :

$ tail -f /tmp/messages.out 
09/06/2012 00:09:58- Message (date 09/06/2012 00:09:58)
09/06/2012 00:10:10- Message (date 09/06/2012 00:10:10)

Vous pouvez également supervision l’EJB dans la console Weblogic :

4 réflexions sur “Oracle Advanced Queing, JMS Publish/Subscribe, Weblogic et Message-Driven Beans”

  1. Merci gregory

    j’ai la même config ( sauf que je travaille avec les fichier xml ejb-jar et weblogic-ejb-jar.xml à la place des annotations)

    mais quand je redémarre mes MDB j’ai un message d’erreur :

    oracle.jms.AQjmsException: JMS-230: Opération interdite sur un abonnement durable avec TopicSubscriber actif>

    Le pire c’est que j’ai 2 MDB qui écoute chacun sur un topics dédié, et un seul me fait ce message d’erreur

  2. Oops… Je le refais !!!

    J’avais modifié mon EJB MDB entre temps pour le mettre en non durable ! Bref, dans ma nouvelle configuration et celle de cet article, c’est à dire avec :
    @ActivationConfigProperty(propertyName = « subscriptionDurability », propertyValue = « Durable »),

    Les messages sont conservés, y compris:
    – si on suspend l’EJB MDB et qu’on le redémarre
    – si on arrête l’application et qu’on la redémarre
    – si on arrête le serveur et qu’on le redémarre
    – si on désinstalle l’application et qu’on la réinstalle

    -Grégory

  3. Avec AQ ? Ca marche très bien pour moi dans les cas où:
    – Je suspends le MDB puis le réactive
    – J’arrête l’application et la redémarre

    Pour le test, j’ai modifié le script d’envoie des messages pour afficher l’heure d’envoi :

    DECLARE
    v_enqueue_options DBMS_AQ.ENQUEUE_OPTIONS_T;
    v_message_properties DBMS_AQ.MESSAGE_PROPERTIES_T;
    v_message SYS.AQ$_JMS_TEXT_MESSAGE;
    v_message_id RAW(16);
    BEGIN
    v_message := SYS.AQ$_JMS_TEXT_MESSAGE.CONSTRUCT;
    v_message.set_text(‘Sending; it is…’||to_char(sysdate,’DD/MM/YYYY HH24:MI:SS’));
    dbms_aq.enqueue (
    queue_name => ‘myqueue’,
    enqueue_options => v_enqueue_options,
    message_properties => v_message_properties,
    payload => v_message,
    msgid => v_message_id
    );
    COMMIT;
    END;
    /

    A- Voici le résultat du MDB lorsque celui-ci est suspendu et que je le relance :
    12/06/2012 19:14:09- Message (Sending; it is…12/06/2012 19:13:51)

    B- Voici le résultat lorsque l’application est arrêtée :
    12/06/2012 19:16:20- Message (Sending; it is…12/06/2012 19:16:14)

    Par contre, si j’arrête Weblogic et le redémarre, en effet, je perds les messages envoyés dans l’intervalle. Je regarde pourquoi…

  4. Bonjour
    le problème arrive avec les Durables subscription. Car en l’état on perd les messages entre l’arret/relance du MDB
    Avez vous testé ?

Les commentaires sont fermés.