Home > Software Development > Custom User Types with JPA and Spring

Custom User Types with JPA and Spring

February 7, 2012 Leave a comment Go to comments

Thanks to Spring and JPA, one can easily and intuitively bind plain old Java objects to HTML forms, HTTP requests, and database tables. This beloved combination eliminates the tedium and droves of boiler plate code associated with developing data management applications. These tools support all Java primitives, wrappers, and even several high-level types natively. Fortunately, it is easy to add support for your own custom types.

In this example, I want to create a custom type called PhoneNumber. This type simply wraps a string representation of a phone number and provides a number of convenient methods and properties (e.g. accessing the area code) as well as validation (e.g. ensures the number is valid and converts a variety of formats into a canonical representation). Ideally, I would like to be able to:

  • Define PhoneNumber properties in my JPA Entities,
  • Get automatic validation with Bean Validation,
  • Get data binding and error binding with Spring.

Persistence

The JPA’s official answer to custom user types is the Embeddable (called a component in Hibernate terminology). Embeddable classes are merged into the tables of their containing entities and have no independent lifecycle. We could start by implementing PhoneNumber as an Embeddable:

@Embeddable
public class PhoneNumber {
	@Size(max = 20)
	private String value;

	public PhoneNumber(String value) {
		this.value = value;
	}

	public PhoneNumber() {
	}

	public String getValue() {
		return value;
	}

	public void setValue(String value) {
		this.value = value;
	}
}

Unfortunately, this has several major drawbacks:

  • The containing entity will have a column named “value”. Not only is this unintelligible, it prevents an entity from having multiple PhoneNumbers without explicitly defining the column name for each property.
  • Any queries that use phone numbers will have to dereference the value property and compare it with a String, not another PhoneNumber.
  • All validation failures will be related to value property of PhoneNumber.

These limitations force the consumer of PhoneNumber to understand its internal model which breaks encapsulation. For small internal project, this may be fine. It is far from ideal for making reusable types.

The JPA specification does not provide an alternative solution. However, if we are using Hibernate as our JPA implementation, we can take advantage of Hibernate’s support for user types. Let us start by creating a new implementation of PhoneNumber:

public class PhoneNumber implements Serializable {
	private String value;

	public PhoneNumber(String value) {
		this.value = value;
	}

	@Override
	public String toString() {
		return value;
	}

	@Override
	public boolean equals(Object object) {
		if (!(object instanceof PhoneNumber))
			return false;
		PhoneNumber that = (PhoneNumber) object;
		return value != null && that.value != null && value.equals(that.value);
	}

	@Override
	public int hashCode() {
		return value.hashCode();
	}

	public String getValue() {
		return value;
	}

	public void setValue(String value) {
		this.value = value;
	}
}

The next step is to create a UserType class that will be responsible for mapping database columns to and from instances of PhoneNumber. Here are a couple of important points:

  • While it is possible to map a single type to multiple columns (i.e. a composite type) I would strongly recommend the use of Embeddable instead. PhoneNumber is a special situation where we have a class with a single String representation that feels like a primitive.
  • It is much easier to handle and more efficient to work with immutable user-defined types. As such, our PhoneNumber implementation is immutable. While it is possible and not much more difficult to support mutable types, one should really only use this strategy for “primitive” concepts, which are almost always immutable.
public class PhoneNumberType implements UserType {
	@Override
	public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
		String value = (String) StringType.INSTANCE.get(rs, names[0], session);
		if (value == null)
			return null;
		else
			return new PhoneNumber(value);
	}

	@Override
	public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
		if (value == null)
			StringType.INSTANCE.set(st, null, index, session);
		else
			StringType.INSTANCE.set(st, value.toString(), index, session);
	}

	@Override
	public int[] sqlTypes() {
		return new int[] { VarcharTypeDescriptor.INSTANCE.getSqlType() };
	}

	@Override
	public Class<PhoneNumber> returnedClass() {
		return PhoneNumber.class;
	}

	@Override
	public boolean equals(Object x, Object y) throws HibernateException {
		return x.equals(y);
	}

	@Override
	public int hashCode(Object x) throws HibernateException {
		return x.hashCode();
	}

	@Override
	public Object deepCopy(Object value) throws HibernateException {
		return value;
	}

	@Override
	public boolean isMutable() {
		return false;
	}

	@Override
	public Serializable disassemble(Object value) throws HibernateException {
		return (Serializable) value;
	}

	@Override
	public Object assemble(Serializable cached, Object owner) throws HibernateException {
		return cached;
	}

	@Override
	public Object replace(Object original, Object target, Object owner) throws HibernateException {
		return original;
	}
}

While there are a lot of methods, most of them are self-explanatory and I will leave you to the JavaDoc if you want more information. The next step is to tell Hibernate that whenever we encounter a property of type PhoneNumber, we want to use PhoneNumberType to processes it. This is accomplished by creating a TypeDef annotation.

@TypeDef(defaultForType = PhoneNumber.class, typeClass = PhoneNumberType.class)

This annotation either needs to be put on package or in any Entity class. Unfortunately, putting annotations on packages is very ugly in Java and, since these annotations apply globally, putting them on a single entity feels inappropriate. Unfortunately, the solution is a bit of a hack itself.

@MappedSuperclass
@TypeDef(defaultForType = PhoneNumber.class, typeClass = PhoneNumberType.class)
public class PhoneNumberType implements UserType {
}

Putting this annotation on the PhoneNumberType seems intuitive. But, we need to make Hibernate aware of its existence. By annotating PhoneNumberType as a MappedSuperclass, it will get processed by Hibernate. However, MappedSuperclasses will not get map directly to tables. In effect, it will be ignored. I admit this is a hack. If you do not like it then feel free to put the TypeDef on a package somewhere.

Regardless of how you create the type definition, any properties of type PhoneNumber will be mapped by the column’s name to a single VARCHAR field. You can control the length of the field with a Column annotation (on the containing property) as you would any other type.

Furthermore, when constructing queries you simply compare the PhoneNumber property with instances of PhoneNumber. Presumably, you would use JPQL string literals for PhoneNumber literals. I use the criteria query API so I do not have a test of this at hand.

Data Binding

The next step is to instruct Spring how to map Strings, generally coming from user input such as forms, to PhoneNumbers. This is accomplished through Spring’s Formatter Registry API. We can do quickly create a Formatter:

public class PhoneNumberFormatter implements Formatter<PhoneNumber>{
	@Override
	public PhoneNumber parse(String text, Locale locale) throws ParseException {
		return new PhoneNumber(text);
	}

	@Override
	public String print(PhoneNumber object, Locale locale) {
		return object.toString();
	}
}

Then we register it with Spring:

@Configuration
@EnableWebMvc
public class PhoneNumberConfig extends WebMvcConfigurerAdapter {
	@Override
	public void addFormatters(FormatterRegistry registry) {
		registry.addFormatter(new PhoneNumberFormatter());
	}
}

However, we succeeded in getting Hibernate to automatically recognize the PhoneNumberType, so, we might want to make Spring automatically recognize the PhoneNumberFormatter. We can do this by annotating the Formatter thusly:

@Component
public class PhoneNumberFormatter implements Formatter<PhoneNumber> {
}

and then updating the registration process to use package scanning:

@Configuration
@EnableWebMvc
public class PhoneNumberConfig extends WebMvcConfigurerAdapter {
	@Inject
	private List<Formatter<?>> formatters;

	@Override
	public void addFormatters(FormatterRegistry registry) {
		for (Formatter<?> formatter : formatters)
			registry.addFormatter(formatter);
	}
}

Now, Spring can convert from a String to a PhoneNumber and will do so automatically any time in encounters a ModelAttribute with a property of type PhoneNumber. Furthermore, any input fields (e.g. in a form) will automatically render the PhoneNumber properties properly.

Combining the Two

Both the PhoneNumberType and the PhoneNumberFormatter serve very similar functions, work in tandem to create a new “primitive” throughout the entire stack, and have very similar implementations. We could combine the two.

@Component
@MappedSuperclass
@TypeDef(defaultForType = PhoneNumber.class, typeClass = PhoneNumberType.class)
public class PhoneNumberType implements UserType, Formatter<PhoneNumber> {
	@Override
	public PhoneNumber parse(String text, Locale locale) {
		return new PhoneNumber(text);
	}

	@Override
	public String print(PhoneNumber object, Locale locale) {
		return object.toString();
	}

	@Override
	public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
		String value = (String) StringType.INSTANCE.get(rs, names[0], session);
		if (value == null)
			return null;
		else
			return parse(value, null);
	}

	@Override
	public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
		if (value == null)
			StringType.INSTANCE.set(st, null, index, session);
		else
			StringType.INSTANCE.set(st, print((PhoneNumber) value, null), index, session);
	}

	...
}

Some people might call this tight coupling of independent concerns. That is valid. However, this setup is very conducive to abstraction making it trivial to create further primitives. The choice is yours.

Validation

Finally, we need to resolve the validation issue. When an instance of an object containing a PhoneNumber property is validated, we want any errors to originate from the PhoneNumber property, not the value property within the PhoneNumber class itself. This is actually not possible with Java Beams Validation. But, it turns out that is the wrong tool for the job anyway.

Consider another primitive: an integer. If someone attempts to submit a sequence of letters for a form field that maps to an integer property, Spring will fail when attempting to create integer and report this fact in its error tracking system. Since we are creating primitive like types, we want the same thing to happen for our PhoneNumber. If someone submits an invalid phone number, we want Spring to fail. We do not want Spring to create an invalid PhoneNumber. Since they are immutable, it does not make sense to have an invalid PhoneNumber. As such, the trick is not to use Beans Validation, but rather to implement validation in the constructor.

While this may seem tedious at first (after all, Beans Validation is so much fun). However, most primitives have either no validation or very complicated validation (one the motivations to use them in the first place). It turns out, in my use cases anyway, that Beans Validation is not incredibly useful for this situation.

So, we update the constructor of PhoneNumber with any validation. Simply throw Spring’s Assert methods will throw an IllegalArgumentException if the condition fails. This is sufficient for Spring to terminate binding and register an error.

public PhoneNumber(String value) {
	Assert.isTrue(...);
	this.value = value;
}

Conclusion

Sorry I do not have a sample project for you to download on this one. If I get a chance in the future I will create one. However, if you are using Hibernate/JPA and Spring is pretty trivial to experiment.

  1. Frisian
    March 14, 2012 at 2:23 am

    Go one step further and make your UserType generic by
    – passing the name of the class as a parameter
    – finding the String constructor by reflection
    – using this constructor to instantiate a new object

  2. March 14, 2012 at 10:02 am

    I actually do that myself. However, I felt kind of guilty suggesting that in this article. You might be interested in the fact that it is not necessary to pass the name of the class during construction converter. You can actually derive the class type from the class arguments (parameters):

    public static Class getArgumentType(Class clazz, int argumentIndex) {
    Type type = ((ParameterizedType) clazz.getGenericSuperclass()).getActualTypeArguments()[argumentIndex];
    if (type instanceof ParameterizedType)
    return (Class) ((ParameterizedType) type).getRawType();
    else
    return (Class) type;
    }

  3. Frisian
    March 21, 2012 at 4:20 am

    Works fine, but then you still have to write a UserType class for every type you have. Using Hibernate’s ParameterizedType interface allows you to pass the name of the class as a parameter, so you effectively get by with only one UserType class for all string-based types.

  4. Emeric Fillatre
    October 29, 2012 at 8:11 am

    Your post is really interesting and accurate giving my own experience.
    But I still miss one usage and I wonder if you already had the problem.
    You wrote :

    “Furthermore, when constructing queries you simply compare the PhoneNumber property with instances of PhoneNumber. Presumably, you would use JPQL string literals for PhoneNumber literals. I use the criteria query API so I do not have a test of this at hand.”

    Everything work perfectly but when I use JPA CriteriaQuery to build my database request, (using hibernate 3.6), I just cannot find a way to request on those user types. It seems like hibernate does not build the request using the given type when proceding this way (it instead use the hashcode value or the “address” representation of my value, the request ending being “select … from MyTable where MyField = ‘@123456798′”).

    Interesting thing is that if I embed the same type in a @Embeddable object and request on that last object, request is executed as expected (user type is used to build the request “select … from MyTable where MyField = ‘myTypeValue'”). But I totally loose the benefit of using user types …

    So I was just wondering if you were aware of this “problem” and know about any workaround.

  5. October 30, 2012 at 7:13 am

    I never have used custom types with JPQL. I have always used the Criteria API or, even better, QueryDSL. In those cases I can look for properties of my custom type with specified values. So, I am afraid I cannot help you. You might want to take a look at the more advanced type interfaces in Hibernate. The documentation stinks; but it might get you started. Good luck. If you figure it out, please consider submitted it back to me so I can add it to the article.

  1. No trackbacks yet.

Leave a comment