# Frage zu @PreUpdate und @PrePersist



## SilencerandLois (17. Jan 2014)

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):


```
@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:

```
context.getCallerPrincipal().getName()
```


----------



## KSG9|sebastian (17. Jan 2014)

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 (17. Jan 2014)

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!


----------



## SilencerandLois (20. Jan 2014)

-- Frage wurde wieder entfernt --


----------



## Spitfire777 (22. Jan 2014)

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.


----------



## SilencerandLois (22. Jan 2014)

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 

```
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 
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 (22. Jan 2014)

Ö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 (24. Jan 2014)

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 (28. Jan 2014)

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.

```
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. 

```
@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


----------



## KSG9|sebastian (28. Jan 2014)

versuchs mal mit InheritableThreadLocal...


----------



## SilencerandLois (28. Jan 2014)

Hat leider auch nichts gebracht


----------



## SilencerandLois (29. Jan 2014)

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


----------

