사용자 도구

사이트 도구


java:hibernate:usertype:stringboolean

StringBoolean Hibernate UserType

boolean을 DB상에서는 문자열로 매핑하는 Hibernate User Type. Hibernate는 기본적으로 “yes_no”와 “true_false”라는 문자열 Boolean 매핑이 이미 존재한다. 하지만 이들는 “Y/N”, “T/F” 이외의 알 수 없는 값이 들어왔을 때 오류를 발생시킨다. 특히 Empty 문자열에 대해서도 null이 아닌 에러로 처리한다.

데이터가 망가진 상태의 Legacy DB와 매핑할 필요가 있을 때는(근본적으로 데이터를 고치는게 맞지만) 문제 가능성이 있어서 따로 만든 UserType. 이 UserType은 legacy 매핑시에만 사용하고 신규 DB를 구축할 때는 절대로 저런일이 발생할 수 없도록 BOOLEAN NOT NULL 컬럼으로 만드는 것이 애초에 좋다.

한 마디로… 이런 UserType은 사용할 일을 만들지 말자.

또한, JPA Converter를 사용하게 하는게 나을 듯 하다.

코드

public class StringBooleanUserType implements UserType, ParameterizedType {
  public static final String PARAM_TRUE_VALUE = "trueValue";
  public static final String DEFAULT_TRUE_VALUE = "Y";
  public static final String PARAM_FALSE_VALUE = "falseValue";
  public static final String DEFAULT_FALSE_VALUE = "N";
 
  /**
   * DB상의 데이터가 알 수 없는 값일때(빈 문자열 포함) unknownResult는
   * "true", "false", "null"을 문자열로 기입한다.
   * "null"이 기본값이며 이때는 알 수 없는 값이 들어오면 null을 리턴하고,
   * "true", "false"로 할 경우 해당 Boolean으로 변환한다.
   */
  public static final String PARAM_UNKNOWN_RESULT = "unknownResult";
  public static final String DEFAULT_UNKNOWN_RESULT = "null";
 
  /**
   * 대소문자 무시할 지 여부를 true/false로 지정한다.
   * 기본값은 true로 대소문자를 무시하고 비교한다.
   */
  public static final String PARAM_IGNORE_CASE = "ignoreCase";
  public static final String DEFAULT_IGNORE_CASE = "true";
 
  private AbstractSingleColumnStandardBasicType type;
  private int[] sqlTypes = null;
 
  private String trueValue = null;
  private String falseValue = null;
  private Boolean unknownResult = null;
  private boolean ignoreCase = true;
 
  @Override
  public void setParameterValues(Properties parameters) {
    if (parameters == null) {
      parameters = new Properties();
    }
 
    trueValue = parameters.getProperty(PARAM_TRUE_VALUE, DEFAULT_TRUE_VALUE);
    falseValue = parameters.getProperty(PARAM_FALSE_VALUE, DEFAULT_FALSE_VALUE);
    unknownResult = populateUnknownResult(parameters.getProperty(PARAM_UNKNOWN_RESULT, DEFAULT_UNKNOWN_RESULT));
    ignoreCase = Boolean.valueOf(parameters.getProperty(PARAM_IGNORE_CASE, DEFAULT_IGNORE_CASE));
 
    populateSqlTypes();
  }
 
  Boolean populateUnknownResult(String unknownResultString) {
    if ("true".equalsIgnoreCase(unknownResultString)) {
      return Boolean.TRUE;
    } else if ("false".equalsIgnoreCase(unknownResultString)) {
      return Boolean.FALSE;
    } else if ("null".equalsIgnoreCase(unknownResultString)) {
      return null;
    }
 
    throw new IllegalArgumentException(
      format("[%s] is illegal unknownResult value. Only 'true', 'false', 'null' are allowed.",
      unknownResultString));
  }
 
  private void populateSqlTypes() {
    TypeResolver tr = new TypeResolver();
    String stringClassName = String.class.getName();
 
    // type은 org.hibernate.type.StringType 으로 사실상 고정값임.
    type = (AbstractSingleColumnStandardBasicType) tr.basic(stringClassName);
    sqlTypes = new int[] { type.sqlType() };
  }
 
  @Override
  public int[] sqlTypes() {
    return Arrays.copyOf(sqlTypes, sqlTypes.length);
  }
 
  @Override
  public Class returnedClass() {
    return Boolean.class;
  }
 
  @Override
  public boolean equals(Object x, Object y) {
    return Objects.equals(x, y);
  }
 
  @Override
  public int hashCode(Object x) {
    return Objects.hashCode(x);
  }
 
  @Override
  public Object deepCopy(Object value) {
    return value;
  }
 
  @Override
  public boolean isMutable() {
    return false;
  }
 
  @Override
  public Serializable disassemble(Object value) {
    return (Serializable) value;
  }
 
  @Override
  public Object assemble(Serializable cached, Object owner) {
    return cached;
  }
 
  @Override
  public Object replace(Object original, Object target, Object owner) {
    return original;
  }
 
  @Override
  public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws SQLException {
    String stringBooleanValue = (String) type.get(rs, names[0], session);
 
    if (stringBooleanValue == null) {
      log.trace("Found [{}] as column [{}] original value [{}]", null, names[0], stringBooleanValue);
      return null;
    }
 
    if (stringEqualsWithCaseCheck(trueValue, stringBooleanValue)) {
      log.trace("Found [{}] as column [{}] original value [{}]", true, names[0], stringBooleanValue);
      return true;
    }
 
    if (stringEqualsWithCaseCheck(falseValue, stringBooleanValue)) {
      log.trace("Found [{}] as column [{}] original value [{}]", false, names[0], stringBooleanValue);
      return false;
    }
 
    log.trace("Found [{}] as column [{}] original value [{}]", unknownResult, names[0], stringBooleanValue);
    return unknownResult;
  }
 
  private boolean stringEqualsWithCaseCheck(String value1, String value2) {
    if (ignoreCase) {
      return StringUtils.equalsIgnoreCase(value1, value2);
    }
    return StringUtils.equals(value1, value2);
  }
 
  /**
   * 데이터베이스로 값을 저장하기 위해 Boolean을 문자열로 변환.
   */
  @Override
  public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws SQLException {
    String columnValue = null;
 
    if (Boolean.TRUE.equals(value)) {
      columnValue = trueValue;
    } else if (Boolean.FALSE.equals(value)) {
      columnValue = falseValue;
    }
    log.trace("binding parameter [{}] as [{}] - [{}] original value [{}]", index, JdbcTypeNameMapper.getTypeName(sqlTypes[0]), columnValue, value);
 
    st.setObject(index, columnValue);
  }
}

설정

@TypeDefs({
    @TypeDef(name = "loose_yes_no", typeClass = StringBooleanUserType.class,
        parameters = {
            @Parameter(name = StringBooleanUserType.PARAM_TRUE_VALUE, value = "Y"),
            @Parameter(name = StringBooleanUserType.PARAM_FALSE_VALUE, value = "N"),
            @Parameter(name = StringBooleanUserType.PARAM_IGNORE_CASE, value = "true"),
            @Parameter(name = StringBooleanUserType.PARAM_UNKNOWN_RESULT, value = "null")
        }
    ),
    @TypeDef(name = "primitive_loose_yes_no", typeClass = StringBooleanUserType.class,
        parameters = {
            @Parameter(name = StringBooleanUserType.PARAM_TRUE_VALUE, value = "true"),
            @Parameter(name = StringBooleanUserType.PARAM_FALSE_VALUE, value = "false"),
            @Parameter(name = StringBooleanUserType.PARAM_IGNORE_CASE, value = "false"),
            @Parameter(name = StringBooleanUserType.PARAM_UNKNOWN_RESULT, value = "false")
        }
    )
})
  • PARAM_TRUE_VALUE : true에 대응하는 문자열
  • PARAM_FALSE_VALUE : false에 대응하는 문자열
  • PARAM_IGNORE_CASE : DB에서 값 읽을 때 대소문자 무시여부. 단, WHERE 조건으로 줄 때는 딱 지정된 문자열로 비교 조건을 날리므로 대소문자가 일치해야 한다.
  • PARAM_UNKNOWN_RESULT : 알 수 없는 값(빈 문자열 등)에 대해서 true, false, null 로 지정하여 해당 값을 리턴한다.
java/hibernate/usertype/stringboolean.txt · 마지막으로 수정됨: 2020/08/12 13:40 저자 kwon37xi