For some entities we need to keep loads (thousands) of detached enties permanently in memory. Many of their attributes are from a limited set of strings (though not limited enough to put it into an enumeration). Is it possible to have hibernate use String.intern for those attributes to save space?
Ideally that should work via an annotation I could put on each of those attributes, or something easily changeable, without confusing the source code too much by this implementation concern.
The method intern() creates an exact copy of a String object in the heap memory and stores it in the String constant pool. Note that, if another String with the same contents exists in the String constant pool, then a new object won't be created and the new reference will point to the other String.
The intern() method creates an exact copy of a string that is present in the heap memory and stores it in the String constant pool if not already present. If the string is already present, it returns the reference. The intern() method helps to save memory space and reuse it efficiently at the cost of time.
String Interning is a method of storing only one copy of each distinct String Value, which must be immutable. By applying String. intern() on a couple of strings will ensure that all strings having the same contents share the same memory.
intern() in Java. All compile-time constant strings in Java are automatically interned using this method. String interning is supported by some modern object-oriented programming languages, including Java, Python, PHP (since 5.4), Lua,Julia and .
As you suggest yourself, it's perfectly doable with a JPA attribute converter. You could do it on a general level by using autoApply = true
on the converter, or you could do it on a field by field level with the @Convert
annotation.
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
@Converter(autoApply = true)
public class StringInternConverter implements AttributeConverter<String, String> {
@Override
public String convertToDatabaseColumn(String attribute) {
return attribute;
}
@Override
public String convertToEntityAttribute(String dbData) {
return dbData != null? dbData.intern(): null;
}
}
Tested with Hibernate 5.2.10 and works like a charm!
1) You can use property access for the critical properties and intern the strings in the setters:
public void setFoo(String foo) {
this.foo = foo != null ? foo.intern() : null;
}
2) If the above solution is tedious (you may have lots of such String
properties), then you could register a Hibernate interceptor and intern all of the String
fields using reflection:
for (Field field : getDeclaredFields(entity)) {
if (!isStaticOrFinal(field)) {
field.setAccessible(true);
Object value = field.get(entity);
if (value instanceof String) {
field.set(entity, ((String) value).intern());
}
}
}
private List<Field> getDeclaredFields(Object object) {
List<Field> result = new ArrayList<Field>(Arrays.asList(object.getClass().getDeclaredFields()));
for (Class<?> superclass = object.getClass().getSuperclass(); superclass != null; superclass = superclass.getSuperclass()) {
result.addAll(Arrays.asList(superclass.getDeclaredFields()));
}
return result;
}
private boolean isStaticOrFinal(Field field) {
return ((Modifier.STATIC | Modifier.FINAL) & field.getModifiers()) != 0;
}
You can execute this in the onSave
and onLoad
interceptor methods.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With