I migrate a Spring 5/Hibernate 5 application to Jakarta EE and have an issue with the Hibernate 6 migration.
Since Hibernate 4 I used the hbm.xml and the <dynamic-component/> for adding custom fields to entities at the start of the application.
With Hibernate 6 I get the following warning:
WARN org.hibernate.orm.deprecation - HHH90000028: Support for `<hibernate-mappings/>` is deprecated [INPUT_STREAM : D:\Workspace\IntelliJ\App-JakartaEE\build-target\app-3.1.00\WEB-INF\classes\generated.hbm.xml]; migrate to orm.xml or mapping.xml, or enable `hibernate.transform_hbm_xml.enabled` for on the fly transformation
Now I tried to migrate from hbm.xml to orm.xml/mapping.xml, but I don't find an equivalent to <dynamic-component/>. I had a look on the hibernate transformation triggered by the setting hibernate.transform_hbm_xml.enabled. There I found this:
else if ( hbmAttributeMapping instanceof JaxbHbmDynamicComponentType ) {
final String name = ( (JaxbHbmDynamicComponentType) hbmAttributeMapping ).getName();
handleUnsupported(
"<dynamic-component/> mappings not supported for transformation [name=%s]",
name
);
}
So the transformation of <dynamic-component/> is not supported. Now I search for a alternative way to add custom fields to the entities at the start of the application. In the best case represented as Map like <dynamic-component/> and similar schema.
Sources
hbm.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="de.user.app.domain">
<class name="de.user.app.domain.CustomFields"
abstract="true" lazy="false" optimistic-lock="version">
<id name="id" type="java.lang.String" length="36">
<generator class="uuid2" />
</id>
<version name="version" />
</class>
<union-subclass name="de.user.app.domain.AddressCustomFields" extends="de.user.app.domain.CustomFields" lazy="false">
<dynamic-component name="fields">
<property name="notes" type="java.lang.String" lazy="false" length="60" not-null="false" />
<property name="distance" type="java.lang.Double" lazy="false" length="20" not-null="false" />
</dynamic-component>
</union-subclass>
<union-subclass name="de.user.app.domain.MeasurementCustomFields" extends="de.user.app.domain.CustomFields" lazy="false">
<dynamic-component name="fields">
<property name="name" type="java.lang.String" lazy="false" length="60" not-null="false" />
<property name="volume" type="java.lang.Double" lazy="false" length="20" not-null="false" />
</dynamic-component>
</union-subclass>
...
</hibernate-mapping>
CustomFields:
public abstract class CustomFields implements Map<String, Object> {
private static final long serialVersionUID = 1L;
private String id;
private int version;
private Map<String, Object> fields;
@Override
public int size() {
if (fields == null) {
return 0;
} else {
return fields.size();
}
}
@Override
public boolean isEmpty() {
if (fields == null) {
return true;
} else {
return fields.isEmpty();
}
}
@Override
public boolean containsKey(Object key) {
if (fields == null) {
return false;
} else {
return fields.containsKey(key);
}
}
@Override
public boolean containsValue(Object value) {
if (fields == null) {
return false;
} else {
return fields.containsValue(value);
}
}
@Override
public Object get(Object key) {
if (fields == null) {
return null;
} else {
return fields.get(key);
}
}
@Override
public Object put(String key, Object value) {
if (fields == null) {
fields = new HashMap<>();
}
return fields.put(key, value);
}
@Override
public Object remove(Object key) {
if (fields == null) {
return null;
} else {
return fields.remove(key);
}
}
@Override
public void putAll(Map<? extends String, ? extends Object> m) {
if (fields == null) {
fields = new HashMap<>();
}
fields.putAll(m);
}
@Override
public void clear() {
if (fields != null) {
fields.clear();
}
}
@Override
public Set<String> keySet() {
if (fields == null) {
return null;
} else {
return fields.keySet();
}
}
@Override
public Collection<Object> values() {
if (fields == null) {
return null;
} else {
return fields.values();
}
}
@Override
public Set<Entry<String, Object>> entrySet() {
if (fields == null) {
return null;
} else {
return fields.entrySet();
}
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37).append(id).toHashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (!(obj instanceof CustomFields)) {
return false;
}
CustomFields rhs = (CustomFields) obj;
return new EqualsBuilder().append(id, rhs.getId()).isEquals();
}
@Override
public String toString() {
return new ToStringBuilder(this).append("fields", fields).toString();
}
public Map<String, Object> getFields() {
return fields;
}
public void setFields(Map<String, Object> customFields) {
this.fields = customFields;
}
@Override
public String getId() {
return id;
}
@Override
public void setId(String id) {
this.id = id;
}
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
}