W przypadku pewnej jednostki Hibernacji mamy obowiązek przechowywania czasu jej utworzenia i ostatniej aktualizacji. Jak byś to zaprojektował?
Jakich typów danych używałbyś w bazie danych (zakładając MySQL, być może w innej strefie czasowej niż JVM)? Czy typy danych będą uwzględniać strefę czasową?
Jakie typy danych należałoby użyć w Javie (
Date
,Calendar
,long
, ...)?Kogo udzieliłbyś odpowiedzialności za ustawienie znaczników czasu - bazy danych, frameworka ORM (Hibernacja) lub programisty aplikacji?
Jakich adnotacji użyłbyś do mapowania (np.
@Temporal
)?
Szukam nie tylko działającego rozwiązania, ale także bezpiecznego i dobrze zaprojektowanego rozwiązania.
Możesz po prostu użyć
@CreationTimestamp
i@UpdateTimestamp
:źródło
TemporalType.DATE
w pierwszym iTemporalType.TIMESTAMP
drugim przypadku.@CreationTimestamp
i@UpdateTimestamp
jeden albo potrzebuje@Column(..., columnDefinition = "timestamp default current_timestamp")
lub wykorzystanie@PrePersist
i@PreUpdate
(te ostatnie klienci również ładnie zapewniające nie można ustawić inną wartość).nullable=false
z@Column(name = "create_date" , nullable=false)
pracyBiorąc zasoby w tym poście wraz z informacjami pobranymi z lewej i prawej strony z różnych źródeł, przyszedłem z tym eleganckim rozwiązaniem, utwórz następującą klasę abstrakcyjną
i niech wszystkie twoje byty ją rozszerzą, na przykład:
źródło
not-null property references a null or transient value: package.path.ClassName.created
@Column(name = "updated", nullable = false, insertable = false)
aby działał. Ciekawe, że ta odpowiedź, ale tak wiele upvotes ..1. Jakich typów kolumn bazy danych należy użyć
Twoje pierwsze pytanie brzmiało:
W MySQL
TIMESTAMP
typ kolumny przesuwa się z lokalnej strefy czasowej sterownika JDBC do strefy czasowej bazy danych, ale może przechowywać tylko znaczniki czasu do'2038-01-19 03:14:07.999999
, więc nie jest to najlepszy wybór na przyszłość.Lepiej więc użyj
DATETIME
zamiast tego, który nie ma tego górnego ograniczenia. JednakDATETIME
nie jest świadomy strefy czasowej. Z tego powodu najlepiej używać UTC po stronie bazy danych i używaćhibernate.jdbc.time_zone
właściwości Hibernacja.2. Jakiego typu właściwości encji należy użyć
Twoje drugie pytanie brzmiało:
Po stronie Java możesz używać Java 8
LocalDateTime
. Możesz także użyć starszej wersjiDate
, ale typy daty / godziny Java 8 są lepsze, ponieważ są niezmienne i nie rejestrują strefy czasowej w lokalnej strefie czasowej podczas ich rejestrowania.Teraz możemy również odpowiedzieć na to pytanie:
Jeśli używasz
LocalDateTime
lubjava.sql.Timestamp
do mapowania właściwości encji znacznika czasu, nie musisz jej używać,@Temporal
ponieważ HIbernate już wie, że ta właściwość ma zostać zapisana jako sygnatura czasowa JDBC.Tylko jeśli używasz
java.util.Date
, musisz określić@Temporal
adnotację:Ale jest znacznie lepiej, jeśli zamapujesz to w następujący sposób:
Jak wygenerować wartości kolumny kontroli
Twoje trzecie pytanie brzmiało:
Istnieje wiele sposobów osiągnięcia tego celu. Możesz na to pozwolić bazie danych…
W przypadku
create_on
kolumny można użyćDEFAULT
ograniczenia DDL, takiego jak:W przypadku
updated_on
kolumny można użyć wyzwalacza DB, aby ustawić wartość kolumny przyCURRENT_TIMESTAMP()
każdej modyfikacji danego wiersza.Lub użyj JPA lub Hibernacji, aby je ustawić.
Załóżmy, że masz następujące tabele bazy danych:
I każda tabela ma kolumny takie jak:
created_by
created_on
updated_by
updated_on
Korzystanie z hibernacji
@CreationTimestamp
i@UpdateTimestamp
adnotacjiHibernacja oferuje
@CreationTimestamp
i@UpdateTimestamp
adnotacje, których można użyć do zmapowania kolumncreated_on
iupdated_on
.Możesz użyć
@MappedSuperclass
do zdefiniowania klasy bazowej, która zostanie rozszerzona przez wszystkie jednostki:I wszystkie podmioty rozszerzą
BaseEntity
, w ten sposób:Jednak nawet jeśli właściwości
createdOn
iupdateOn
są ustawione przez Hibernację@CreationTimestamp
i@UpdateTimestamp
adnotacje,createdBy
iupdatedBy
wymagają rejestracji wywołania zwrotnego aplikacji, jak pokazano w poniższym rozwiązaniu JPA.Korzystanie z JPA
@EntityListeners
Możesz zawrzeć właściwości kontroli w Embeddable:
I utwórz wartość,
AuditListener
aby ustawić właściwości kontroli:Aby zarejestrować
AuditListener
, możesz użyć@EntityListeners
adnotacji JPA:źródło
datetime
ponadtimestamp
. Chcesz, aby twoja baza danych znała strefę czasową twoich znaczników czasu. Zapobiega to błędom konwersji strefy czasowej.timestsmp
Typ nie przechowuje informacje o strefie czasowej. Po prostu rozmawia z aplikacji TZ do DB TZ. W rzeczywistości chcesz przechowywać klient TZ osobno i przeprowadzić rozmowę w aplikacji przed renderowaniem interfejsu użytkownika.timestamp
jest zawsze w UTC. MySQL konwertujeTIMESTAMP
wartości z bieżącej strefy czasowej na UTC w celu przechowywania i z powrotem z UTC do bieżącej strefy czasowej w celu ich pobrania. Dokumentacja MySQL: Typy DATA, DATETIME i TIMESTAMPMożna również użyć przechwytywacza, aby ustawić wartości
Utwórz interfejs o nazwie TimeStamped, który implementują Twoje podmioty
Zdefiniuj przechwytywacz
I zarejestruj to w fabryce sesji
źródło
Dzięki rozwiązaniu Oliviera podczas aktualizacji instrukcji możesz natknąć się na:
Aby rozwiązać ten problem, dodaj updatable = false do adnotacji @Column atrybutu „utworzono”:
źródło
@Version
. Po ustawieniu encji wykonywane są dwa połączenia, jeden ma zostać zapisany, a drugi do aktualizacji. Z tego powodu miałem do czynienia z tym samym problemem. Po dodaniu@Column(updatable = false)
rozwiązałem mój problem.Dziękuję wszystkim, którzy pomogli. Po tym, jak sam przeprowadziłem badania (jestem facetem, który zadał pytanie), oto, co znalazłem najbardziej sensowne:
Typ kolumny bazy danych: agnostyczna liczba stref czasowych w milisekundach od 1970 r. Reprezentowana jako,
decimal(20)
ponieważ 2 ^ 64 ma 20 cyfr, a miejsce na dysku jest tanie; bądźmy szczerzy. PonadtoDEFAULT CURRENT_TIMESTAMP
nie użyję ani wyzwalaczy. Nie chcę magii w DB.Java typ pola:
long
. Uniksowy znacznik czasu jest dobrze obsługiwany w różnych bibliotekach,long
nie ma problemów z Y2038, arytmetyka znaczników czasu jest szybka i łatwa (głównie operator<
i operator+
, zakładając, że w obliczeniach nie są uwzględniane dni / miesiące / lata). I, co najważniejsze, zarówno prymitywne, jaklong
ijava.lang.Long
si są niezmienne - skutecznie przekazane przez wartość - w przeciwieństwie dojava.util.Date
s; Byłbym naprawdę wkurzony, gdybym znalazł coś takiego jakfoo.getLastUpdate().setTime(System.currentTimeMillis())
debugowanie kodu innej osoby.Struktura ORM powinna być odpowiedzialna za automatyczne wypełnianie danych.
Nie testowałem tego jeszcze, ale patrząc tylko na dokumenty, które zakładam, że
@Temporal
wykonają zadanie; nie jestem pewien, czy mogę użyć@Version
do tego celu.@PrePersist
i@PreUpdate
są dobrą alternatywą do kontrolowania tego ręcznie. Dodanie tego do nadtypu warstwy (wspólnej klasy bazowej) dla wszystkich encji jest uroczym pomysłem, pod warunkiem, że naprawdę chcesz znaczników czasu dla wszystkich swoich encji.źródło
W przypadku korzystania z interfejsu API sesji sesje zwrotne PrePersist i PreUpdate nie będą działać zgodnie z tą odpowiedzią .
Korzystam z metody persist () Hibernate Session w moim kodzie, więc jedynym sposobem, w jaki mogłem to zrobić, był kod poniżej i śledzenie tego postu na blogu (również zamieszczonego w odpowiedzi ).
źródło
updated.clone()
inne komponenty mogą manipulować stanem wewnętrznym (data)Teraz są też adnotacje @CreatedDate i @LastModifiedDate.
=> https://programmingmitra.blogspot.fr/2017/02/automatic-spring-data-jpa-auditing-saving-CreatedBy-createddate-lastmodifiedby-lastmodifieddate-automatically.html
(Spring framework)
źródło
Poniższy kod działał dla mnie.
źródło
protected Integer id;
tak jakprotected
w klasie rodzicielskiej, ponieważ nie mogłem tego użyć w moich testowych przypadkach jako.getId()
Dobrym podejściem jest posiadanie wspólnej klasy bazowej dla wszystkich twoich bytów. W tej klasie bazowej możesz mieć swoją własność id, jeśli jest ona powszechnie nazywana we wszystkich twoich jednostkach (wspólny projekt), twoja własność i właściwości daty ostatniej aktualizacji.
Na dzień utworzenia wystarczy zachować właściwość java.util.Date . Pamiętaj, aby zawsze inicjować go za pomocą nowej daty () .
W przypadku ostatniego pola aktualizacji możesz użyć właściwości znacznika czasu, musisz zmapować ją za pomocą @Version. Dzięki tej adnotacji właściwość zostanie automatycznie zaktualizowana przez Hibernację. Uważaj, że Hibernacja zastosuje również optymistyczne blokowanie (to dobrze).
źródło
Tylko dla wzmocnienia:
java.util.Calender
nie dotyczy znaczników czasu .java.util.Date
jest chwilowo agnostyczny dla regionalnych rzeczy, takich jak strefy czasowe. Większość baz danych przechowuje rzeczy w ten sposób (nawet jeśli wydaje się, że tak nie jest; zwykle jest to ustawienie strefy czasowej w oprogramowaniu klienta; dane są dobre)źródło
Jako typ danych w JAVA zdecydowanie zalecam użycie java.util.Date. Podczas korzystania z Kalendarza napotkałem dość nieprzyjemne problemy ze strefą czasową. Zobacz ten wątek .
Do ustawiania znaczników czasu zalecałbym użycie metody AOP lub możesz po prostu użyć wyzwalaczy na stole (w rzeczywistości jest to jedyna rzecz, dla której uważam użycie wyzwalaczy za dopuszczalne).
źródło
Możesz rozważyć zapisanie czasu jako DateTime oraz w UTC. Zazwyczaj używam DateTime zamiast Datownika ze względu na fakt, że MySql konwertuje daty na UTC i wraca do czasu lokalnego podczas przechowywania i pobierania danych. Wolałbym zachować taką logikę w jednym miejscu (warstwa biznesowa). Jestem pewien, że istnieją inne sytuacje, w których preferowane jest używanie znacznika czasu.
źródło
Mieliśmy podobną sytuację. Używaliśmy MySQL 5.7.
To działało dla nas.
źródło
@PrePersist
i@PrePersist
nie obejmują takiej sprawy.Jeśli używamy @Transactional w naszych metodach, @CreationTimestamp i @UpdateTimestamp zapiszą wartość w DB, ale zwrócą null po użyciu save (...).
W tej sytuacji załatwienie sprawiło użycie saveAndFlush (...)
źródło
Myślę, że fajniej jest nie robić tego w kodzie Java, możesz po prostu ustawić domyślną wartość kolumny w definicji tabeli MySql.
źródło