Frage zu @PreUpdate und @PrePersist

SilencerandLois

Aktives Mitglied
Hallo zusammen,
durch Recherche zum Thema Auditierung bin ich auf die Annotation @PreUpdate und @PrePersist gestoßen (wir verwenden Java EE 5).
Was ich dadurch schonmal erreichen kann ist, dass ich automatisiert ein Erstellungsdatum / Änderungsdatum in der Entität setzen kann. Soweit so gut.
Ich möchte aber nun noch zusätzlich den Benutzer in der Entität anpassen, sollte ein neuer Datensatz angelegt oder ein Update auf diesem durchgeführt werden.

Leider habe ich auf Ebene der Entität keine Möglichkeit, den Sessionkontext abzurufen, da die Entitäten bei unserer gewählten Architektur wie folgt aussehen (wir verwenden als O/R-Mapper Eclipselink):

Java:
@Entity
public class XY implements {
}


Hat jemand von euch eine Idee, wie ich nun automatisiert bei einer Neuanalge / Änderung einer Entität neben den Zeitstempeln auch noch den Benutzer setzen kann?

Besten Dank für eure Hilfe!
Martin


Den Sessionkontext hätte ich bei einer EJB-Entität wie folgt geholt:
Java:
context.getCallerPrincipal().getName()
 

KSG9|sebastian

Top Contributor
Gar nicht..dafür sind PrePersist/.. nicht gedacht.

Die Spec sagt
In general, the lifecycle method of a portable application should not invoke EntityManager or Query operations, access other entity instances, or modify relationships within the same persistence context.

Hibernate verbietet es sogar
A callback method must not invoke EntityManager or Query methods

Dafür musst du wahrscheinlich Provider-spezifische Funktionen verwenden..bei Hibernate wären das Listener (Hiberante < 4). Später wurde dann ein entsprechendes SPI eingeführt..siehe Hibernate-Dok
 

SilencerandLois

Aktives Mitglied
Dachte ich mir schon fast :-(
Gibt es eine ähnliche Funktionalität bei eclipselink?

Wo würdet ihr ansonsten das Attribut setzen? In der Business-Logik?

Danke!
 

Spitfire777

Bekanntes Mitglied
Also wenn ich so etwas implementieren müsste, würde ich es tatsächlich in der Business-Logik machen und nicht im Datenmodell.

Wenn du aber ganz dreist sein willst, könntest du das vielleicht mit einer Factory lösen, die dir den passenden Session-Context liefert. Die Factory könntest du dann in deiner PrePersist-Methode verwenden. Was ich dir aber nicht empfehle.

Denn: Wenn dein Datenmodell entscheidet, welcher Benutzer nun genau zur Entity gehört, so kann es unter Umständen sein, dass du keine Chance haben wirst, dich drüber im Falle des Falles hinweg zu setzen. Zudem der nächste, der auf den Code schaut, sich beim Debuggen einen Wolf sucht.
 
Zuletzt bearbeitet:

SilencerandLois

Aktives Mitglied
Danke Spitfire für deinen Kommentar.

Das Factory-Pattern kenn ich schon. Aber wie würdest du das Umsetzen? Damit die Factory die Session kennt, müsste diese doch ein EJB sein, in welcher die Session über
Java:
public void setSessionContext(final SessionContext sessionContext) {
}
automatisch durch JEE gesetzt wird. Wie kann ich aber dann von der Entität des Datenmodells auf die Factory-Klasse zugreifen? Oder verstehe ich deinen Lösungsansatz falsch?

Sitz gerade auf dem Schlauf :oops:
Wennsd kurz ein paar erklärende Codeschnippsel posten könntest, wäre ich dir sehr Dankbar!

Viele Grüße,
Martin

P.S.: Dein Einwand mit der engen Kopplung beim Setzen des Benutzers im Datenmodell ist berechtigt. Stellt aber bei unseren fachlichen Bedürfnissen kein Problem dar. Diese könnten sich zwar natürlcih ändern, was ich in diesem Kontext jedoch stark bezweifle.
 

KSG9|sebastian

Top Contributor
Öhm ich glaub kaum dass das funktionieren wird. Vermutlich kriegst du vom Persistenceprovider auf die Finger weil an der Stelle keine Interaktion mehr mit der Session machen darf .
Was er meint ist den EntityManager in ein Singleton ( oder besser ThreadLocal) zu packen... dann kommst aus dem PrePersist dran.
 

mackiex

Neues Mitglied
Ich weiß, es ist nicht die schönste Art von der Datenschicht auf die Session zuzugreifen.
Muss man abwägen anhand der Architektur bzw. Modularisierung.

Mit Spring Securtiy:

private String getUsername()
{
try
{
final User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return user.getUsername();
}
catch (final Exception e)
{
return "system";
}
}

Man kann das aber mit Sicherheit noch entkoppeln. Session über Interface an Bean koppeln, etc ....
 

SilencerandLois

Aktives Mitglied
Ich wollte das Problem nun mit einer ThreadLocal-Variable lösen.
Leider klappt es nicht so ganz.

Was habe ich gemacht:
1) In einem zentralen EJB-Container komme an meine Session. Hier lege ich den Benutzer nun in einer ThreadLocal-Variable ab. Durch Debuggen habe ich evaluiert, dass der Wert dort korrekt abgelegt wird. Die Methode wird auch nur ein einzigstes Mal beim Einloggen des Benutzers ausgeführt.
Java:
class BusinessObject{
    public static final ThreadLocal<String> getAdmbName = new ThreadLocal();

    ....

    getAdmbName.set(context.getCallerPrincipal().getName());

2) In meiner Entität greife ich nun in der mit @PreUpdate-Annotierten-Methode auf diese ThreadLocal-Variable zu. Ich befinde mich auch noch im selben Thread. Die Variable ist jedoch nicht gefüllt.
Java:
@Entity
class Entität {
   ...

   String getAdmbName = BusinessObject.getAdmbName.get();

   ...
}

Sollte es nicht so sein: wenn ich in einem einzelnen Thread unterwegs bin, dann kann ich bedenkenlos auf diese Variable zugreifen?


Danke!
Martin
 
Zuletzt bearbeitet:

SilencerandLois

Aktives Mitglied
Problem gelöst.
Ich weiß zwar nicht WARUM ich das Problem hatte, jedenfalls berechne ich die Thread-Local-Variable in einer anderen Klasse. Hier funktioniert nun auch der Zugriff.

Ich verstehs zwar noch nicht ganz, da es eigentlich auch vorher der selbe Thread hätte sein sollen, aber was solls. Hauptsache, es läuft nun. :toll:

Danke für die Hilfe!
Martin
 

Ähnliche Java Themen


Oben