Skonfiguruj hibernację (używając JPA), aby przechowywać Y / N dla typu Boolean zamiast 0/1

80

Czy mogę ustawić JPA / hibernację, aby utrwalać Booleantypy jako Y/N? W bazie danych (kolumna jest zdefiniowana jako varchar2(1). Obecnie przechowuje je jako 0/1. Baza danych to Oracle.

sengs
źródło

Odpowiedzi:

8

Jedyny sposób, w jaki się zorientowałem, jak to zrobić, to mieć dwie właściwości dla mojej klasy. Jeden jako wartość logiczna dla programowania API, który nie jest uwzględniony w mapowaniu. To getter i setter odwołują się do prywatnej zmiennej typu char, która jest T / N. Następnie mam inną chronioną właściwość, która jest uwzględniona w mapowaniu hibernacji, a jej metody pobierające i ustawiające odwołują się bezpośrednio do prywatnej zmiennej char.

EDYCJA: Jak już wspomniano, istnieją inne rozwiązania wbudowane bezpośrednio w Hibernate. Zostawiam tę odpowiedź, ponieważ może działać w sytuacjach, w których pracujesz ze starszym polem, które nie gra dobrze z wbudowanymi opcjami. Poza tym nie ma żadnych poważnych negatywnych konsekwencji takiego podejścia.

Spencer Ruport
źródło
1
Musiałem zrobić coś podobnego - zmieniłem typ członka z Boolean na String. W getterach i setterach (które pobierały i ustawiały Boolean) napisałem kod konwertujący Y / N na odpowiednią wartość logiczną.
sengs
@bernardn Nie, jest lepszy.
aleksander
Tutaj jest lepszym rozwiązaniem thoughts-on-java.org/hibernate-tips-how-to-map-a-boolean-to-yn
Ganesh Hoolageri
146

Hibernate ma wbudowany typ „yes_no”, który zrobi to, co chcesz. Mapuje do kolumny CHAR (1) w bazie danych.

Podstawowe mapowanie: <property name="some_flag" type="yes_no"/>

Mapowanie adnotacji (rozszerzenia hibernacji):

@Type(type="yes_no")
public boolean getFlag();
ChssPly76
źródło
28
Dla zainteresowanych istnieje również typ „true_false”, który będzie przechowywać „T” lub „F”.
Matt Solnit,
To zadziałało, ale nie mogłem go użyć, ponieważ jest to adnotacja specyficzna dla hibernacji. Dziękuję za odpowiedź. Może użyć go w innym projekcie.
sengs
dotyczy to hibernacji 4 i nowszych, to znaczy java 1.6 i nowszych. nie działa dla hibernacji 3. *
storm_buster
7
@storm_buster Hibernate 4 nie istniał, gdy udzielono tej odpowiedzi. Działa doskonale z Hibernate 3.xi java 1.5
ChssPly76
3
Jak więc umieścić 0 lub 1?
JavaTechnical
84

To jest czysty JPA bez użycia metod pobierających / ustawiających. Od 2013/2014 jest to najlepsza odpowiedź bez użycia adnotacji specyficznych dla Hibernate, ale pamiętaj, że to rozwiązanie to JPA 2.1 i nie było dostępne, gdy po raz pierwszy zadano pytanie:

@Entity
public class Person {    

    @Convert(converter=BooleanToStringConverter.class)
    private Boolean isAlive;    
    ...
}

I wtedy:

@Converter
public class BooleanToStringConverter implements AttributeConverter<Boolean, String> {

    @Override
    public String convertToDatabaseColumn(Boolean value) {        
        return (value != null && value) ? "Y" : "N";            
        }    

    @Override
    public Boolean convertToEntityAttribute(String value) {
        return "Y".equals(value);
        }
    }

Edytować:

Powyższa implementacja uwzględnia wszystko inne niż znak „Y”, w tym nullas false. Czy to jest poprawne? Niektórzy tutaj uważają to za niepoprawne i uważają, że nullbaza danych powinna być nullw Javie.

Ale jeśli powrócisz nullw Javie, da ci to, NullPointerExceptionczy twoje pole jest prymitywną wartością logiczną . Innymi słowy, o ile niektóre z pól faktycznie użyć klasy Boolean najlepiej rozważyć nulljak falsei użyj powyższej implementacji. Wtedy Hibernate nie będzie emitować żadnych wyjątków niezależnie od zawartości bazy danych.

A jeśli chcesz akceptować nulli emitować wyjątki, jeśli zawartość bazy danych nie jest ściśle poprawna, to myślę, że nie powinieneś akceptować żadnych znaków oprócz „Y”, „N” i null. Zadbaj o spójność i nie akceptuj żadnych odmian, takich jak „y”, „n”, „0” i „1”, które tylko utrudnią Ci życie. To jest bardziej rygorystyczna realizacja:

@Override
public String convertToDatabaseColumn(Boolean value) {
    if (value == null) return null;
    else return value ? "Y" : "N";
    }

@Override
public Boolean convertToEntityAttribute(String value) {
    if (value == null) return null;
    else if (value.equals("Y")) return true;
    else if (value.equals("N")) return false;
    else throw new IllegalStateException("Invalid boolean character: " + value);
    }

I jeszcze jedna opcja, jeśli chcesz zezwolić nullw Javie, ale nie w bazie danych:

@Override
public String convertToDatabaseColumn(Boolean value) {
    if (value == null) return "-";
    else return value ? "Y" : "N";
    }

@Override
public Boolean convertToEntityAttribute(String value) {
    if (value.equals("-") return null;
    else if (value.equals("Y")) return true;
    else if (value.equals("N")) return false;
    else throw new IllegalStateException("Invalid boolean character: " + value);
    }
MarcG
źródło
Konwerter pokazuje pomysł, ale oczywiście nie działa. Przetwornik wykorzystuje możliwych wartości Y, Na T. Nie jestem też pewien, czy w wyniku konwersji należy pomijać przypadek posiadania wartości null.
Matthias
@Matthias Tak, T była literówką. Naprawiłem to. Dzięki.
MarcG
Miałem problem z używaniem pola Y / N w JPQL i opublikowałem tutaj dodatkowe pytanie: stackoverflow.com/questions/39581225/ ...
Chris Williams
11

Skorzystałem z koncepcji z odpowiedzi zamieszczonej przez @marcg i świetnie działa z JPA 2.1. Jego kod nie był całkiem poprawny, więc opublikowałem moją działającą implementację. Spowoduje to przekonwertowanie Booleanpól encji na kolumnę znaków T / N w bazie danych.

Z mojej klasy encji:

@Convert(converter=BooleanToYNStringConverter.class)
@Column(name="LOADED", length=1)
private Boolean isLoadedSuccessfully;

Moja klasa konwertera:

/**
 * Converts a Boolean entity attribute to a single-character
 * Y/N string that will be stored in the database, and vice-versa
 * 
 * @author jtough
 */
public class BooleanToYNStringConverter 
        implements AttributeConverter<Boolean, String> {

    /**
     * This implementation will return "Y" if the parameter is Boolean.TRUE,
     * otherwise it will return "N" when the parameter is Boolean.FALSE. 
     * A null input value will yield a null return value.
     * @param b Boolean
     */
    @Override
    public String convertToDatabaseColumn(Boolean b) {
        if (b == null) {
            return null;
        }
        if (b.booleanValue()) {
            return "Y";
        }
        return "N";
    }

    /**
     * This implementation will return Boolean.TRUE if the string
     * is "Y" or "y", otherwise it will ignore the value and return
     * Boolean.FALSE (it does not actually look for "N") for any
     * other non-null string. A null input value will yield a null
     * return value.
     * @param s String
     */
    @Override
    public Boolean convertToEntityAttribute(String s) {
        if (s == null) {
            return null;
        }
        if (s.equals("Y") || s.equals("y")) {
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

}

Ten wariant jest również zabawny, jeśli kochasz emotikony i masz dość T / N lub T / F w swojej bazie danych. W takim przypadku kolumna bazy danych musi mieć dwa znaki zamiast jednego. Prawdopodobnie nic wielkiego.

/**
 * Converts a Boolean entity attribute to a happy face or sad face
 * that will be stored in the database, and vice-versa
 * 
 * @author jtough
 */
public class BooleanToHappySadConverter 
        implements AttributeConverter<Boolean, String> {

    public static final String HAPPY = ":)";
    public static final String SAD = ":(";

    /**
     * This implementation will return ":)" if the parameter is Boolean.TRUE,
     * otherwise it will return ":(" when the parameter is Boolean.FALSE. 
     * A null input value will yield a null return value.
     * @param b Boolean
     * @return String or null
     */
    @Override
    public String convertToDatabaseColumn(Boolean b) {
        if (b == null) {
            return null;
        }
        if (b) {
            return HAPPY;
        }
        return SAD;
    }

    /**
     * This implementation will return Boolean.TRUE if the string
     * is ":)", otherwise it will ignore the value and return
     * Boolean.FALSE (it does not actually look for ":(") for any
     * other non-null string. A null input value will yield a null
     * return value.
     * @param s String
     * @return Boolean or null
     */
    @Override
    public Boolean convertToEntityAttribute(String s) {
        if (s == null) {
            return null;
        }
        if (HAPPY.equals(s)) {
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

}
Jim Tough
źródło
Czy rozpakowanie „b.booleanValue ()” jest konieczne?
LeOn - Han Li
Z wyjątkiem NULLwłaściwości dla NULLwartości bazy danych, ta odpowiedź nie zapewnia żadnej wartości dodanej do odpowiedzi @MarKG. Najlepiej ująć tę różnicę jako komentarz.
Mohnish
Spóźniłeś się 5 lat koleś
Jim Tough,
2

Aby jeszcze lepiej odwzorować wartości logiczne na T / N, dodaj do swojej konfiguracji hibernacji:

<!-- when using type="yes_no" for booleans, the line below allow booleans in HQL expressions: -->
<property name="hibernate.query.substitutions">true 'Y', false 'N'</property>

Teraz możesz używać wartości logicznych w HQL, na przykład:

"FROM " + SomeDomainClass.class.getName() + " somedomainclass " +
"WHERE somedomainclass.someboolean = false"
MA Hogendoorn
źródło
2
To jest globalne. Nieodpowiednie dla pojedynczych nieruchomości.
maxxyme
0

Aby zrobić to w ogólny sposób JPA przy użyciu adnotacji pobierających, poniższy przykład działa w moim przypadku z Hibernate 3.5.4 i Oracle 11g. Zwróć uwagę, że odwzorowane metody pobierające i ustawiające ( getOpenedYnStringi setOpenedYnString) są metodami prywatnymi. Te metody zapewniają mapowanie, ale cały programowy dostęp do klasy wykorzystuje metody getOpenedYni setOpenedYn.

private String openedYn;

@Transient
public Boolean getOpenedYn() {
  return toBoolean(openedYn);
}

public void setOpenedYn(Boolean openedYn) {
  setOpenedYnString(toYesNo(openedYn));
}

@Column(name = "OPENED_YN", length = 1)
private String getOpenedYnString() {
  return openedYn;
}

private void setOpenedYnString(String openedYn) {
  this.openedYn = openedYn;
}

Oto klasa util z metodami statycznymi toYesNoi toBoolean:

public class JpaUtil {

    private static final String NO = "N";
    private static final String YES = "Y";

    public static String toYesNo(Boolean value) {
        if (value == null)
            return null;
        else if (value)
            return YES;
        else
            return NO;
    }

    public static Boolean toBoolean(String yesNo) {
        if (yesNo == null)
            return null;
        else if (YES.equals(yesNo))
            return true;
        else if (NO.equals(yesNo))
            return false;
        else
            throw new RuntimeException("unexpected yes/no value:" + yesNo);
    }
}
Dave Moten
źródło
-1

użycie konwerterów JPA 2.1 jest najlepszym rozwiązaniem, jednak jeśli używasz wcześniejszej wersji JPA, mogę polecić jeszcze jedno rozwiązanie (lub obejście). Utwórz wyliczenie o nazwie BooleanWrapper z 2 wartościami T i F i dodaj do niego następującą metodę, aby uzyskać opakowaną wartość public Boolean getValue() { return this == T; }:, zamapuj ją za pomocą @Enumerated (EnumType.STRING).

Ravshan Samandarov
źródło