[Hibernate] Przechowywanie typu enum

0

Jak zmusić Hibernate, by przechowywał typ enum? Osobiście myślałem, że najłatwiej byłoby wyposarzyć enum w metodę, która zwracałaby jego wartość liczbową, a później stworzyć specjalnie dla Hibernate specjalną właściwość w klasie, którą bym mapował na tabelę. Właściwośc ta zwracałaby enuma w postaci inta.

enum MyEnum
(
FIRST(1), SECOND(2);

public MyEnum(int integer)
{
   this.integer = integer;
}

public int toInt()
{
   return integer;
}

private int integer;
);
class MyBean
{
   public MyEnum getMyEnum() {...};
   public void setMyEnum(MyEnum myEnum) {...};

   // Hibernate:
   private int getMyEnumAsInteger() {getMyEnum().toInt();};
   ........
}
0

Jak wy zapisujecie enumy?

0

Wlasciwie to za kazdym razem jak pisze podobny kod to sie zastanawiam dlaczego nie ma czegos takiego domyslnie, jakiegos generycznego typu z kodem ktory by umial skladowac enumy jako varchary lub inty.

0

OMG. Przecież typ enum ma metodę ordinal(), która podaje dla każdego wyliczenia jego indeks liczony od zera i identyczny z kolejnością deklarowania samych elementów tego wyliczenia. Jest to najprostsza konwersja na typ int. A w odwrotną stronę mając wyliczenie e jakiegoś typu enum {...} używa się e.getDeclaringClass().getEnumConstants()[i] gdzie i, to właśnie indeks będący numerem wyliczenia. Za pomocą e.getDeclaringClass().getEnumConstants().length dostajemy długość wyliczenia, czyli ilość wszystkich jego elementów. Można sobie więc puścić taki kod:

public enum Autostrada { A1, A2, A3, A4, A5, A6, A7, W_BUDOWIE }
//--------
public class Test
{
	public static void main(String[] args)
	{
		Autostrada el = Autostrada.A5; //obiekt z palca

		System.out.println( "Element" + el.toString()
			+ " ma pozycję " + el.ordinal() + "/"
			+ (el.getDeclaringClass().getEnumConstants().length - 1) );
		
		int x = 4;

		Class<Autostrada> c = Autostrada.class;
		el = c.getEnumConstants()[x]; //obiekt z runtime

		System.out.println( "Element o indeksie " + x
			+ " w wyliczeniu " + c.getSimpleName()
			+ ", to będzie: " + el.toString() );
	}
}

Żeby się przekonać, że konwersja dowolnego wyliczenia na int, i z powrotem jest trywialna.

Tak więc kłania się znajomość podstaw Javy. ;-P
Warto zauważyć, że nie ma żadnych importów. To wszystko cechy języka i runtime'a.

Dodatkowo można użyć też dynamicznej tablicy (rzekomo bitowej) EnumSet<E>, która jest normalnym rozszerzalnym zbiorem typu AbstractSet<E> i Collection<E> aby móc wygodnie przechowywać stan włączenia/wyłączenia (istnienia/nieistnienia) wszystkich elementów dowolnego wyliczenia. Rzekomo miał być szybki bo operować na bitach zmiennych long (do 64 pozycji miał to być tylko jeden long), ale jak spojrzałem do źródeł to implementacja bitowa, to chyba jakaś pieśń przyszłości. Na dodatek wpakowano to do bieżącej hierarchii kolekcji Javy, co spowodowało, że do tego typu wpakowało się mnóstwo niewygodnych zaszłości. No, ale to tak na marginesie.
Ja w każdym razie zrobiłem sobie własną implementację takiego typu template w oparciu o stary i szybki BitSet. Nie mam czasu na czekanie na Javę 7. [diabel] Z użyciem wyżej podanych konwersji jest to banał.

0

Przykozaczyles, ale da sie latwiej, bez refleksji:
Autostrada.values()[i];

O ty, ze szkoda ze nie ma takiego kodu w libiach Hibernate swiadczy fakt ze sam sobie go musiales napisac, i setki (tysiace) programistow korzystajacych z Hibernate rowniez. A przeciez da sie napisac taki UserType ktory by sie dal ladnie konfigurowac, umiescic w dystrybucji, i kazdy by korzystal z jednego. Jakiekolwiek bugi bylyby wylapywane i naprawiane raz a dobrze, a nie kazdy z osobna sprawdza swoj kod. OMG, dziwne ze tego nie rozumiesz i nie widzisz slabosci rozwiazania gdzie kazdy pisze ten sam / niemal ten sam kod, nawet dosc czesto, bo dla kazdego enuma od nowa?. DRY, panie?

0

Nie o to chodzi. Zamiana na enum i z powrotem jest trywialna, a kontener przechowujący wyliczenia (EnumSet<E>) jest, jest to zupełnie wystarczające i powiązane z całą resztą standardowych klas Javy.
Natomiast co do użycia values() i value(), to oczywiście masz rację. Podałem tamtą wersję z tego powodu, że za pomocą takiej techniki jest zaimplementowany EnumSet<E> i jednak przeszło to na mnie... :-/
Wracając do tematu - skoro nie ma żadnego problemu z natychmiastowym przechodzeniem z dowolnego enum na int i odwrotnie, to problem przestaje istnieć bo można składować je jako int wszędzie ewentualnie jako EnumSet. Krótko mówiąc - jest kod generyczny przechowujący enum i jest on standardowy. Kolega po prostu próbuje wyważać otwarte drzwi.

Natomiast napisałem sobie dodatkowy kod sam tylko z powodu obecnej implementacji i jako osobisty test czy umiem zrobić coś generycznego lepiej i chodzące jeszcze szybciej niż produkt zapewne dobrze opłacanych programistów z Suna. :-)

0

Stary wątek, ale pewnie często oglądany 'z wyszukiwarek'. Zarysowany problem zdaje się już nie istnieje w Hibernate3, bo istnieje tu EnumType i wygląda na to, że jak property jest typu enumerycznego, to Hibernate wie, co z nim zrobić. Sam siędopasouje, o ile widziałem kod, do typu kolumny. Jeśli mapowana kolumna w bazie jest numeryczna to przy zapisie używa Enum.ordinal(), jak jest kolumna znakowa, to wykorzystuje Enum.name(). I kłopot z głowy. Można go próbować zmusić, żeby inaczej działał - EnumType implementuje ParametrizedType itp. sprawy.

0

Przykładzik z użyciem adnotacji hibernate 3.x. Zwrócić uwagę na rozwiązanie problemu
niejawnego nadawania ordinali w enumach - zmuszamy się do tego, by ordinale były podawane jawnie.


public enum AvailabilityStatusEnum {
	DISABLED(0), // Moduł wyłączony, niedostępny
	IN_TESTING(1), // Moduł wdrożony testowo, dostępny tylko dla użytkowników testowych (rola ROLE_TESTER)
	IN_PRODUCTION(2), // Moduł całkowicie wdrożony i udostępniony wszystkim użytkownikom
	;

	private AvailabilityStatusEnum(int id) {
		// Wstawka zabezpieczająca przed pomieszaniem identyfikatorów stałych
		EnumValidator.checkOrdinal(this, id);
	}
	
}

//...
// Mapowanie (proste ...)
@Entity
@Table(name = "MODULE", schema = "MAIN")
public class Module {
	private AvailabilityStatusEnum astatus;

	@Column(name = "ASTATUS")
	public AvailabilityStatusEnum getAStatus() {
		return this.astatus;
	}

	public void setAStatus(AvailabilityStatusEnum avStatus) {
		this.astatus = avStatus;		
	}

}

//I jeszcze funkcja narzędziowa - pilnuje, by ktoś przez omyłkę nie wcisnął nowej stałej
//do enuma pomiędzy już istniejace - przy dodawaniu enumów ordinale deklarujemy jawnie.
public class EnumValidator {
	
	public static void checkOrdinal(Enum e, int ord) {
		// Wstawka zabezpieczająca przed pomieszaniem identyfikatorów stałych
		if (ord != e.ordinal())
                        throw new IllegalArgumentException(
                                "Nieprawidłowa deklaracja enuma, przesunięcia w identyfikatorach (id(" + ord + ") != ordinal("+e.ordinal()+")");		
	}

}
0

Ale w jakim celu podawać to id skoro nie jest zapisywane? (AvailabilityStatusEnum nie ma pola id).

Enumy można zapisywać w następujący sposób:

  1. Baz żadnej adnotacji lub z adnotacją Enumerated(ORDINAL) - w takiej sytuacji do bazy będzie zapisywany ordinal()
  2. Z adnotacją Enumerated(STRING) - do bazy pójdzie wynik name() - kolumna musi być varcharem - jest to mnie efektywne, ale lepiej widać w bazie enumy
  3. Implementując UserType i używając adnotacji http://docs.jboss.org/hibernate/stable/annotations/api/org/hibernate/annotations/Type.html - dzięki tej metodzie możemy mapować enumy np. do id, które nie są równe ordinal()

1 użytkowników online, w tym zalogowanych: 0, gości: 1