Issue
To make the mapping between hibernate and my database work, I have this mapping :
<property name="userRolesV2" column="user_roles_v2">
<type name="io.markethero.repository.CommaSeparatedGenericEnumType">
<param name="enumClassName">io.markethero.model.UserLoginRoleV2</param>
<param name="collectionClassName">java.util.Set</param>
</type>
</property>
The idea is to directly get the Collection in my field class instead of doing the mapping each time.
For example, the field in the class could be a Set, a List, or a Queue. In the database, the value is like "enumValue1,enumValue2,enumValue3".
To do that, my class CommaSeparatedGenericEnumType is like this:
public class CommaSeparatedGenericEnumType implements UserType, ParameterizedType {
private Class enumClass = null;
private Class targetCollection = null;
public void setParameterValues(Properties params) {
String enumClassName = params.getProperty("enumClassName");
String collectionClassName = params.getProperty("collectionClassName");
if (enumClassName == null) {
throw new MappingException("enumClassName parameter not specified");
}
if (collectionClassName == null) {
throw new MappingException("collectionClassName parameter not specified");
}
try {
this.enumClass = Class.forName(enumClassName);
} catch (ClassNotFoundException e) {
throw new MappingException("enumClass " + enumClassName + " not found", e);
}
try {
this.targetCollection = Class.forName(collectionClassName);
} catch (ClassNotFoundException e) {
throw new MappingException("targetCollection " + collectionClassName + " not found", e);
}
}
@Override
public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
String commaSeparatedValues = rs.getString(names[0]);
List<Object> result = new ArrayList<>();
if (!rs.wasNull()) {
String[] enums = commaSeparatedValues.split(",");
for (String string : enums) {
result.add(Enum.valueOf(enumClass, string));
}
}
return result;
}
@Override
@SuppressWarnings("unchecked")
public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
if (null == value) {
st.setNull(index, Types.VARCHAR);
} else {
List<Object> enums = (List) value;
StringBuilder sb = new StringBuilder("");
for (Object each : enums) {
sb.append(each.toString()).append(",");
}
if (sb.toString().isEmpty()) {
st.setNull(index, Types.VARCHAR);
} else {
String commaSeparatedIds = sb.toString().substring(0, sb.toString().length() - 1);
st.setString(index, commaSeparatedIds);
}
}
}
}
I would like to be able to parametrize which collection nullSafeGet and nullSafeSet are going to use, because for now, it's only working with a list.
Thank you!
Solution
Maybe my question was asked was oddly, but here what I did:
- For the getter, I used the factory pattern already implemented by Spring :
CollectionFactory
.
@Override
public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
String commaSeparatedValues = rs.getString(names[0]);
Collection<Object> result = CollectionFactory.createCollection(this.targetCollection, 50);
if (!rs.wasNull()) {
String[] enums = commaSeparatedValues.split(",");
for (String string : enums) {
try {
result.add(Enum.valueOf(enumClass, string));
} catch (IllegalArgumentException e) {
throw new MappingException("[CommaSeparatedGenericEnumType::nullSafeGet] No such enum value"+ string +"for enum : " + enumClass, e);
}
}
}
return result;
}
- For the setter, I used the reflection API.
public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.VARCHAR);
} else {
if (value instanceof Collection) {
try {
StringBuilder sb = new StringBuilder("");
Constructor<?> c = value.getClass().getConstructor(Collection.class);
Collection<Object> enums = (Collection<Object>) c.newInstance((Collection<Object>) value);
for (Object each : enums) {
sb.append(each.toString()).append(",");
}
String commaSeparatedIds = sb.substring(0, sb.toString().length() - 1);
if (sb.length() > 0) {
st.setString(index, commaSeparatedIds);
} else {
st.setNull(index, Types.VARCHAR);
}
} catch (NoSuchMethodException e) {
throw new MappingException("[CommaSeparatedGenericEnumType::nullSafeSet] No such constructor found for class : " + value.getClass(), e);
} catch (InstantiationException e) {
throw new MappingException("[CommaSeparatedGenericEnumType::nullSafeSet] Class : " + value.getClass() + " cannot be instantiate ", e);
} catch (IllegalAccessException e) {
throw new MappingException("[CommaSeparatedGenericEnumType::nullSafeSet] You cannot access to constructor of class : " + value.getClass(), e);
} catch (InvocationTargetException e) {
throw new MappingException("[CommaSeparatedGenericEnumType::nullSafeSet] InvocationTargetException for class : " + value.getClass(), e);
}
} else {
st.setNull(index, Types.VARCHAR);
throw new IllegalArgumentException();
}
}
}
Answered By - Paul Serre
Answer Checked By - Katrina (JavaFixing Volunteer)