Hi.
I’ve updated to Hibernate 6.0.3.Final from 5.6.3.Final and got error when i tried to execute query with boolean field, that defined by Custom User Type. Hibernate query builder did not consider, that field described by Custom type and processed it just like Boolean
Example.
I have entity with field, that processed different string values to “true” or “false”. In real life i’ve used Oracle and Y/N, but H2Database successfully processed this values, so i choosed ‘#’ and ‘&’ as symbols for “true” and “false”
@Entity
@Table(name = "CS_FOO")
public class Foo {
private Long id;
private boolean flag;
@Id
@SequenceGenerator(name = "CS_SEQ", sequenceName = "CS_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CS_SEQ")
@Column(name = "ID")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column(name = "flag", updatable = false)
@Type(
value = StringBooleanType.class,
parameters = {
@org.hibernate.annotations.Parameter(name = "true", value = "#"),
@org.hibernate.annotations.Parameter(name = "false", value = "&"),
}
)
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
So there is implementation of StringBooleanType
public class StringBooleanType implements EnhancedUserType<Boolean>, ParameterizedType {
private static final String T_VALUES = "true";
private static final String F_VALUES = "false";
private static final String NULLABLE = "nullable";
private static final String CASE_SENSITIVE = "caseSensitive";
private static final String[] DEFAULT_T_VALUES = {
"TRUE",
"T",
"YES",
"Y",
"ON",
"1"
};
private static final String[] DEFAULT_F_VALUES = {
"FALSE",
"F",
"NO",
"N",
"OFF",
"0"
};
public static final String NAME = "StringBoolean";
private String[] tValues = DEFAULT_T_VALUES;
private String[] fValues = DEFAULT_F_VALUES;
private boolean caseSensitive;
private boolean nullable;
// ParameterizedType
@Override
public void setParameterValues(Properties parameters) {
String tsProperty = parameters.getProperty(T_VALUES);
if (tsProperty != null) {
tValues = split(tsProperty);
if (tValues.length == 0)
tValues = DEFAULT_T_VALUES;
}
String fsProperty = parameters.getProperty(F_VALUES);
if (fsProperty != null) {
fValues = split(fsProperty);
}
caseSensitive = "true".equalsIgnoreCase(parameters.getProperty(CASE_SENSITIVE));
nullable = "true".equalsIgnoreCase(parameters.getProperty(NULLABLE));
}
// UserType
@Override
public int getSqlType() {
return Types.VARCHAR;
}
@Override
public Class<Boolean> returnedClass() {
return Boolean.class;
}
@Override
public boolean equals(Boolean obj1, Boolean obj2) {
if (obj1 == obj2)
return true;
if (nullable) {
if (obj1 == null || obj2 == null)
return false;
} else {
if (obj1 == null)
return obj2.equals(Boolean.FALSE);
if (obj2 == null)
return obj1.equals(Boolean.FALSE);
}
return obj1.equals(obj2);
}
@Override
public int hashCode(Boolean obj) throws HibernateException {
return obj == null ? 0 : obj.hashCode();
}
@Override
public Boolean nullSafeGet(ResultSet result, int pos, SharedSessionContractImplementor sessionImplementor, Object o) throws HibernateException, SQLException {
String value = result.getString(pos);
return valueOf(result.wasNull() ? null : value);
}
@Override
public void nullSafeSet(PreparedStatement stmt, Boolean value, int index, SharedSessionContractImplementor sessionImplementor) throws HibernateException, SQLException {
if (value == null) {
stmt.setNull(index, Types.VARCHAR);
} else {
stmt.setString(index, toString(value));
}
}
@Override
public Boolean deepCopy(Boolean value) {
return value;
}
@Override
public boolean isMutable() {
return false;
}
@Override
public Serializable disassemble(Boolean value) {
return value;
}
@Override
public Boolean assemble(Serializable cached, Object owner) {
return (Boolean) cached;
}
// EnhancedUserType
@Override
public String toSqlLiteral(Boolean value) {
String text = toString(value);
return text == null ? "NULL" : "'" + text + "'";
}
@Override
public Boolean fromStringValue(CharSequence sequence) throws HibernateException {
return valueOf(sequence);
}
// Privates
private Boolean valueOf(CharSequence source) {
if (source == null) {
return nullable ? null : Boolean.FALSE;
}
String text = (String) source;
if (caseSensitive) {
for (String t : tValues) {
if (t.equals(text)) {
return Boolean.TRUE;
}
}
if (nullable) {
for (String f : fValues) {
if (f.equals(text)) {
return Boolean.FALSE;
}
}
return null;
}
} else {
for (String t : tValues) {
if (t.equalsIgnoreCase(text)) {
return Boolean.TRUE;
}
}
if (nullable) {
for (String f : fValues) {
if (f.equalsIgnoreCase(text)) {
return Boolean.FALSE;
}
}
return null;
}
}
return Boolean.FALSE;
}
@Override
public String toString(Boolean value) {
if (value == null) {
return nullable ? null : fValues[0];
}
return value ? tValues[0] : fValues[0];
}
public static String[] split(String text) {
StringTokenizer tokenizer = new StringTokenizer(text, " \t,;|");
List<String> list = new ArrayList<>();
while (tokenizer.hasMoreTokens()) {
list.add(tokenizer.nextToken());
}
return list.toArray(new String[0]);
}
}
Now, i want to select items, that have different values of field flag:
query = s.createQuery("select f from Foo f where f.flag", Foo.class);
or
query = s.createQuery("select f from Foo f where not(f.flag)", Foo.class);
I expect, that query analyzer will handle field’s type and tried to convert it by provided methods (for 5.6.3 worked version was f.flag=1 or f.flag=0)
But currently it raised exception “Wrong number” for my production Oracle and Data conversion error converting “CHARACTER VARYING to BOOLEAN” for H2
Best regards. Alexey