Configure Hibernate (usando JPA) para almacenar Y / N para el tipo Boolean en lugar de 0/1

80

¿Puedo configurar JPA / hibernate para persistir Booleantipos como Y/N? En la base de datos (la columna se define como varchar2(1). Actualmente los almacena como 0/1. La base de datos es Oracle.

sengs
fuente

Respuestas:

8

La única forma en que he descubierto cómo hacer esto es tener dos propiedades para mi clase. Uno como booleano para la API de programación que no está incluido en el mapeo. Es getter y setter hacen referencia a una variable char privada que es Y / N. Luego tengo otra propiedad protegida que está incluida en el mapeo de hibernación y sus captadores y definidores hacen referencia a la variable char privada directamente.

EDITAR: Como se ha señalado, hay otras soluciones que están integradas directamente en Hibernate. Dejo esta respuesta porque puede funcionar en situaciones en las que está trabajando con un campo heredado que no funciona bien con las opciones integradas. Además de eso, este enfoque no tiene consecuencias negativas graves.

Spencer Ruport
fuente
1
Tuve que hacer algo similar: cambié el tipo de miembro de Boolean a String. En los captadores y definidores (que obtuvieron y establecieron booleanos) escribí código para convertir Y / N al valor booleano correspondiente.
Sengs
@bernardn No, es el mejor.
alexander
Aquí es una solución mejor thoughts-on-java.org/hibernate-tips-how-to-map-a-boolean-to-yn
Ganesh Hoolageri
146

Hibernate tiene un tipo "yes_no" incorporado que haría lo que quieras. Se asigna a una columna CHAR (1) en la base de datos.

Mapeo básico: <property name="some_flag" type="yes_no"/>

Mapeo de anotaciones (extensiones de Hibernate):

@Type(type="yes_no")
public boolean getFlag();
ChssPly76
fuente
28
Para aquellos que estén interesados, también hay un tipo "true_false" que almacenará "T" o "F".
Matt Solnit
Esto funcionó, pero no pude usarlo porque es una anotación específica de hibernación. Gracias por la respuesta. Podría usarlo en un proyecto diferente.
sengs
esto es para hibernate 4 y posterior, eso significa para java 1.6 y posterior. no funciona para hibernate 3. *
storm_buster
7
@storm_buster Hibernate 4 no existía cuando se dio esta respuesta. Funciona perfectamente bien con Hibernate
3.xy
3
¿Cómo poner 0 o 1 entonces?
JavaTechnical
84

Esto es JPA puro sin usar getters / setters. A partir de 2013/2014, es la mejor respuesta sin utilizar anotaciones específicas de Hibernate, pero tenga en cuenta que esta solución es JPA 2.1 y no estaba disponible cuando se hizo la pregunta por primera vez:

@Entity
public class Person {    

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

Y entonces:

@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);
        }
    }

Editar:

La implementación anterior considera cualquier cosa diferente del carácter "Y", incluyendo null, como false. ¿Es eso correcto? Algunas personas aquí consideran que esto es incorrecto y creen que nullla base de datos debería estar nullen Java.

Pero si regresa nullen Java, le dará un NullPointerExceptionsi su campo es un booleano primitivo . En otras palabras, a menos que algunos de sus campos realmente usen la clase Boolean , es mejor considerar nullcomo falsey usar la implementación anterior. Entonces, Hibernate no emitirá ninguna excepción independientemente del contenido de la base de datos.

Y si desea aceptar nully emitir excepciones si el contenido de la base de datos no es estrictamente correcto, supongo que no debe aceptar ningún carácter aparte de "Y", "N" y null. Hágalo coherente y no acepte variaciones como "y", "n", "0" y "1", que solo harán su vida más difícil más adelante. Esta es una implementación más estricta:

@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);
    }

Y otra opción más, si desea permitirlo nullen Java pero no en la base de datos:

@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
fuente
El convertidor muestra la idea, pero por supuesto no funciona. El convertidor usa los posibles valores de Y, Ny T. Tampoco estoy seguro de que se deba omitir el caso de tener un valor nulo como resultado de una conversión.
Matthias
@Matthias Sí, T fue un error tipográfico. Lo arreglé. Gracias.
MarcG
Tuve un problema al usar el campo Y / N con JPQL y publiqué una pregunta de seguimiento aquí: stackoverflow.com/questions/39581225/…
Chris Williams
11

Usé el concepto de la respuesta publicada por @marcg y funciona muy bien con JPA 2.1. Su código no era del todo correcto, así que publiqué mi implementación de trabajo. Esto convertirá los Booleancampos de la entidad en una columna de caracteres Y / N en la base de datos.

De mi clase de entidad:

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

Mi clase de convertidor:

/**
 * 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;
    }

}

Esta variante también es divertida si te encantan los emoticonos y estás cansado de Y / N o T / F en tu base de datos. En este caso, la columna de su base de datos debe tener dos caracteres en lugar de uno. Probablemente no sea gran cosa.

/**
 * 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
fuente
¿Es necesario el unboxing 'b.booleanValue ()'?
LeOn - Han Li
Excepto por la NULLpropiedad para el NULLvalor de la base de datos, esta respuesta no proporciona ningún valor agregado sobre la respuesta de @MarKG. Lo mejor es capturar esa diferencia como comentario.
Mohnish
Llegas 5 años tarde chico
Jim Tough
2

Para incluso hacer un mejor mapeo booleano a Y / N, agregue a su configuración de hibernación:

<!-- 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>

Ahora puede usar booleanos en HQL, por ejemplo:

"FROM " + SomeDomainClass.class.getName() + " somedomainclass " +
"WHERE somedomainclass.someboolean = false"
MA Hogendoorn
fuente
2
Esto es global. No adecuado para propiedades singulares.
maxxyme
0

Para hacerlo de una manera JPA genérica usando anotaciones getter, el siguiente ejemplo me funciona con Hibernate 3.5.4 y Oracle 11g. Tenga en cuenta que el captador y el definidor mapeados ( getOpenedYnStringy setOpenedYnString) son métodos privados. Esos métodos proporcionan el mapeo, pero todo el acceso programático a la clase usa los métodos getOpenedYny 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;
}

Aquí está la clase util con métodos estáticos toYesNoy 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
fuente
-1

usar convertidores JPA 2.1 es la mejor solución, sin embargo, si está usando una versión anterior de JPA, puedo recomendar una solución más (o una solución alternativa). Cree una enumeración llamada BooleanWrapper con 2 valores de T y F y agregue el siguiente método para obtener el valor envuelto:, public Boolean getValue() { return this == T; }mapee con @Enumerated (EnumType.STRING).

Ravshan Samandarov
fuente