Ausgehend vom Artikel “Temporal Patterns” habe ich eine generische Klasse TemporalRecord<T>
entworfen, die einen Wert mit Beobachtungs- und Ereigniszeitpunkt versieht.
Diese Klasse möchte ich gerne als Embeddable
weiterverwenden. Probleme habe ich nun mit dem Mapping des eingebetteten Wertes, weil ich nicht weiß, wie ich das Mapping für Enums und Strings definieren soll.
Wie kann ich für den eingebetteten Wert das Mapping deklarieren?
Die Klasse sieht so aus:
import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import static com.google.common.base.Preconditions.checkNotNull;
@MappedSuperclass
@Access(AccessType.FIELD)
public class TemporalRecord<T> implements Serializable {
private static final long serialVersionUID = 8327763668012454656L;
@Embedded // funktioniert für Basistypen und Embeddables problemlos, für Strings mit Einschränkungen (Länge immer 255), für Enums gar nicht
private T value;
@Column(nullable = false)
private LocalDateTime recordDate;
@Column(nullable = false)
private LocalDateTime actualDate;
protected TemporalRecord() {
}
public TemporalRecord(@Nonnull T value) {
this(value, LocalDateTime.now());
}
public TemporalRecord(@Nonnull T value, @Nonnull LocalDateTime actualDate) {
this(value, actualDate, LocalDateTime.now());
}
public TemporalRecord(@Nonnull T value, @Nonnull LocalDateTime actualDate, @Nonnull LocalDateTime recordDate) {
this.value = checkNotNull(value);
this.actualDate = checkNotNull(actualDate);
this.recordDate = checkNotNull(recordDate);
}
public T getValue() {
return value;
}
public LocalDateTime getRecordDate() {
return recordDate;
}
public LocalDateTime getActualDate() {
return actualDate;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TemporalRecord)) return false;
TemporalRecord that = (TemporalRecord) o;
return actualDate.equals(that.actualDate) && recordDate.equals(that.recordDate) && value.equals(that.value);
}
@Override
public int hashCode() {
int result = value.hashCode();
result = 31 * result + recordDate.hashCode();
result = 31 * result + actualDate.hashCode();
return result;
}
}```
So benutze ich die Klasse dann in etwa:
```import javax.persistence.Embeddable;
@Embeddable
public class TestEmbeddableRecord extends TemporalRecord<String> {
private static final long serialVersionUID = 6045969975091246866L;
}```
```import javax.persistence.*;
@Entity
public class TestEntity {
@Id
private Long id;
@Embedded
private TestEmbeddableRecord record;
}```
In diesem Fall generiert hibernate folgendes:
[sql]create table TestEntity (id bigint not null, actualDate datetime not null, recordDate datetime not null, hash integer not null, value varchar(255), primary key (id)) ENGINE=InnoDB[/sql]
Bei einem Integer sieht es so aus (alles richtig):
[sql]create table TestEntity (id bigint not null, actualDate datetime not null, recordDate datetime not null, value integer not null, primary key (id)) ENGINE=InnoDB[/sql]
Für ein Enum sieht das dann so aus (value fehlt):
[sql]create table TestEntity (id bigint not null, actualDate datetime not null, recordDate datetime not null, primary key (id)) ENGINE=InnoDB[/sql]
Ich sehe gerade, dass bei Strings auch noch eine `hash`-Spalte generiert wird, die zuviel ist.
*** Edit ***
Das müsste so etwas wie `@AttributeOverride` nur für Vererbung sein, dabei aber alle JPA-Annotationen zulassen.
*** Edit ***
Funktionierende (aber unschöne) Lösung: das Feld `value` in `TemporalRecord` weglassen, die Klasse abstrakt machen, einen abstrakten, protected setter deklarieren und `getValue` ebenfalls abstrakt machen.
Gibt es eine schönere Lösung?