Storing Files as URLs with Hibernate UserType
March 25th, 2009 by PaulStoring Files in databases as a BLOB always seemed inherently daft to me. I wanted to be able to store user generated Files as URLs in the database, and let an HTTP Server serve them, so why is Hibernate storing BLOBs? Hibernates UserType allows conversion between ungainly classes such as java.net.URL or java.io.File and the somewhat pale and pasty datatypes of the database world.
At Less Rain we produce a lot of projects with User generated content, and a lot of dynamically generated images which need to be stored. Uploading files, and move them to an appropriate place on the server is one task, but storing that information and meta-data about the image (caption, height, width) and associating the file with a user's profile requires a database. We've been working with Hibernate Annotations for sometime, and love the flexibility it gives us in creating Domain Driven Designs, but the standard mapping for hibernate for Files is somewhat disappointing - they are stored as a BLOB in the database.
I've often heard of people storing files as BLOBs in the database, but could never understand why. Databases are good at storing and retrieving information via queries. Many of our projects feature quite high contention for database connections, and the last thing we want to do is tie them up delivering a few hundred K of user generated imagery for every second request. Thats what file systems and web servers are designed to do.
We were able to make good use of Hibernate UserType to resolve this little dilemma, our first thought was to to store the path of the file in the Database, however an eclectic mix of developer machines, a dev server and a production server with various Windows, and Linux Distros guaranteed that the path of the File would never remain consistent. We opted instead to store the URL of the file as a java.net.URL instance, after all the files would be served by an HTTP Server. A small configurable utility class allowed us to effortlessly resolve the URLs to file paths at creation, and back again if we needed to manipulate the File.
The User Type needs to be declared at the top of the Java file:
JAVA:
... import com.blah; @TypeDefs( { @TypeDef( name="URLUserType", typeClass = com.lessrain.util.hibernate.URLUserType.class) } ) ...
Our annotated Mapping looked like this:
and here is the code for the URLUserType:
- package com.lessrain.util.hibernate;
- import java.io.Serializable;
- import java.net.MalformedURLException;
- import java.net.URL;
- import java.sql.PreparedStatement;
- import java.sql.ResultSet;
- import java.sql.SQLException;
- import java.sql.Types;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- import org.hibernate.Hibernate;
- import org.hibernate.usertype.UserType;
- public class URLUserType implements UserType {
- static final Log log = LogFactory.getLog(URLUserType.class);
- public URLUserType() {
- }
- public int[] sqlTypes() {
- }
- @SuppressWarnings("unchecked")
- public Class returnedClass() {
- }
- return (x == y) || (x != null && y != null && (x.equals(y)));
- }
- URL url = null;
- try {
- log.error("problem creating URL from " + val);
- }
- return url;
- }
- public void nullSafeSet( PreparedStatement inPreparedStatement, Object o, int i) throws SQLException {
- String url="";
- if (val != null){
- url = val.toString();
- }
- inPreparedStatement.setString(i, url);
- }
- if (o == null) { return null; }
- URL deepCopy = null;
- try {
- log.error("Problem creating deepcopy of URL" + o.toString());
- }
- return deepCopy;
- }
- public boolean isMutable() {
- return true;
- }
- return deepCopy(cached);
- }
- }
- return deepCopy(original);
- }
- return x.hashCode();
- }
- }
The main action takes place in the nullSafeGet and nullSafeSet methods. These convert the URL to a varchar compatible String, and from a String back to a URL Object.
nullSafeSet takes our URL Object as an argument and uses it's toString method to get return a String which is Stored in the database as a simple varchar value.nullSafeGet takes the string, uses it to construct a new URL object, and return it.
For completion here is the interface of of our File to URL converter:
So thanks to the UserType we were able to let all the components do their own thing. The database doing relational mapping, the File system delivering files and the domain model expressing the logic of an image in the context of our application.



October 8th, 2009 at 3:50 pm
My god, what type of anti-spam is that?
October 8th, 2009 at 4:08 pm
One that caught 139,426 spam posts we didn’t have to filter out by hand.