Local- / Remoteinterfaces per Konfiguration nutzen

Die üblichen Beispiele für EJB3-Programmierung sehen normalerweise vor, dass man sich bereits zur Implementationszeit für eine remote bzw. lokale Referenzierung einer Bean entscheidet. Dazu wird einfach das entsprechende Interface per Annotation mit der Bean verbunden.

@EJB
HalloRemote halloBean;
...
void greet()
{
    halloBean.sayHello();
}

Wenn jetzt beide Beans in einem EAR deployed werden, möchte man aber ggf. lieber auf das Local Interface zurückgreifen. Bei einigen Applikationservern ist das kein Unterschied. Sie merken, wenn sie einen Remote Call sparen können und rufen die entsprechende Bean direkt auf (z.B. WebSphere). Für Applikationserver die das nicht können, wäre es aber recht praktisch, wenn man beim Erstellen des EARs per Konfiguration einstellen könnte, ob eine Referenz remote oder lokal ist.

Das funktioniert mit folgendem portablen Konstrukt:

Basisinterface für Remote und Local

classdiagram

Hier erweitern einfach das Remote- sowie das Local Interface ein Basisinterface. In der aufrufenden Bean kann dann einfach das Basisinterface referenziert werden:

@EJB(name="halloBean")
Hallo halloBean;
...
void greet()
{
    halloBean.sayHello();
}

Da intern noch etwas Magie erforderlich ist mit der Auflösung der Home-Interfaces (die sind in EJB3 nur versteckt, nicht verschwunden), muss der Applikationserver noch darüber informiert werden, ob man in dem Kontext jetzt eine remote oder eine lokale Referenz wünscht. Das geht z.B. in der ejb-jar.xml

<?xml version="1.0" encoding="UTF-8"?>

<ejb-jar xmlns = "http://java.sun.com/xml/ns/javaee"
         version = "3.0"
         xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation = "http://java.sun.com/xml/ns/javaee
         http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd">

    <enterprise-beans>
        <session>
            <ejb-name>HelloCaller</ejb-name>
            <ejb-ref>
                <ejb-ref-name>halloBean</ejb-ref-name>
                <ejb-ref-type>Session</ejb-ref-type>
                <remote>test.HalloRemote</remote>
            </ejb-ref>
        </session>
    </enterprise-beans>
</ejb-jar>

oder für das lokale Interface statt ejb-ref:

            <ejb-local-ref>
                <ejb-ref-name>halloBean</ejb-ref-name>
                <ejb-ref-type>Session</ejb-ref-type>
                <local>test.HalloLocal</local>
            </ejb-local-ref>

Damit wird dann das Localinterface referenziert.

Bei einem Glassfish Applikationserver könnte man die Remote Referenz in der sun-ejb-jar.xml noch direkt auf einen anderen Applikationserver umleiten – ein WebService über RMI also:

<ejb-ref>
    <ejb-ref-name>halloBean</ejb-ref-name>
    <jndi-name>corbaname:iiop:hostip:3700#test.HalloRemote</jndi-name>
</ejb-ref>

Das ganze funktioniert natürlich analog mit der web.xml.

Ordered Lists in JPA – Do it yourself

Bei OneToMany und ManyToMany Relationen gibt es in JPA die Möglichkeit die Sortierreihenfolge festzulegen, mit der die Objekte aus der Datenbank kommen.

@OneToMany
@OrderBy("name ASC")
List<MyEntry> entries;

Dieses Konstrukt sorgt dafür, dass die MyEntry Objekte nach dem darin enthaltenen Feld „text“ in aufsteigender Reihenfolge aus der Datenbank kommen. Das funktioniert soweit auch ganz prima, bis man zu der Liste neue Elemente hinzufügt.

Ist eine Entity gelesen, hat der Programmierer die Verantwortung

Was bedeutet das? In dem Augenblick wo die Entity aus der Datenbank sortiert gelesen wurde, muss sich der Programmierer selber darum kümmern, dass eine neue Entity an der richtigen Stelle in der Liste eingefügt wird. Egal, wie die Cascade Einstellungen sind, egal, wie die Sortiereinstellungen sind. Wird eine Entity außerhalb der Sortierung (also beispielsweise an die letzte Stelle) an die Liste angefügt, bleibt sie da, solange das Objekt sich im internen Cache der JPA Implementation befindet. Das kann, wie versuche gezeigt haben, sehr lange sein. Da hilft kein find, kein close auf den Entity Manager und (zumindest bei Toplink mit Fetchtype.LAZY) auch kein refresh auf der Entity, die die Liste enthält.

Die einfachste Möglichkeit das Problem in den Griff zu kriegen ist, eine add Routine zu implementieren, die einen Comperator entsprechend der von JPA geforderten Sortierreihenfolge zur Verfügung stellt und das Objekt an der richtigen Stelle in die Liste einzufügen:

package jpatest.domain;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;

@NamedQuery(name="MyOrderedTextCollection.findFirst",
    query="select p from MyOrderedTextCollection p")
/**
 *
 * @author tschuett
 */
@Entity
public class MyOrderedTextCollection {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @OneToMany(mappedBy = "collection", cascade=CascadeType.ALL)
    @OrderBy("text ASC")
    private List<MyTextEntry> entries;

    public MyOrderedTextCollection() {
        entries = new ArrayList<MyTextEntry>();
    }

    public Long getId() {
        return id;
    }

    public List<MyTextEntry> getEntries() {
        return Collections.unmodifiableList(entries);
    }

    public void addEntry(String text) {
        if (text == null) {
            throw new NullPointerException(
                "text must not be null");
        }
        MyTextEntry entry = new MyTextEntry(this, text);
        int pos = Collections.binarySearch(entries, entry,
            new Comparator<MyTextEntry>() {
                public int compare(MyTextEntry entry0,
                  MyTextEntry entry1) {
                    return entry0.getText().compareTo(
                      entry1.getText());
            }
        });
        if (pos < 0)
        {
            pos = -(pos +1);
        }
        entries.add(pos, entry);
    }
}

Die Diskussion zu dem Thema lässt sich hier nachlesen:Diskussion in der Glassfish Mailingliste

Das vollständige Beispiel kann hier heruntergeladen werden: jpatest