I see JPA used in a few projects, each to different degrees of success and annoyances. This is a three-part series of articles about my view of how the ideal JPA pattern should be.
There are three objectives that the pattern tries to meet: ease of use, portability and partner with the DBA.
- Ease of use entails making the API reasonably easy to understand for those using your JPA classes. This is achieved by making sure you expose typical business objects rather than exposing your internals.
- Portability is important (actually more important than efficiency) because when you’re coding, you don’t want to be tied on your enterprise database or even a full local database when you’re working and unit testing especially when a small in-memory database is readily available. You also don’t want to be tied down to the target application server if there is a leaner application server that integrates well with your IDE. The developer’s time should be more important than hardware cost.
- Partnering with the DBA is also important, they are the ones that are supposed to maintain the schema. However, the development team should be partnered with them. In other words they work together to get the database working as efficiently as possible. No one group should have full authority over the other.
Now that the objectives are set, let’s get to actually achieving them.
JPA allows developers to use many different types and some even provide vendor specific extensions to let developers do more within the JPA framework. Instead, I choose to forgo most of them. I have stated before why I would prefer portability so I will focus on the how.
To achieve portability I avoid “rich” information therefore I limit myself to the following types:
As you can see it is a pretty limited list. Some reasons why I didn’t add the others are as follows:
short is not used because some persistence frameworks and databases differ on what a they mean. If I really need to save table space, I would have it done on the DDL rather than JPA.
byte tends to be coerced to a Blob by default no matter what the length. So the only way around it is to put a
columnDefinition which is database specific. I tend to use BASE64 or hex encoded (for easier debugging)
String to represent this as this is more portable as I had noted in my answer at StackOverflow.
boolean is not used because the JPA standard does not do any conversion to whatever the underlying database is (e.g. store Y/N or 0/1 or y/n or t/f or T/F). There are vendor-specific extensions to do this, but that would violate the portability objective. As such I wrap this with a property in
@Entity and hide the logic in the
double is used primarily because I tend to work with “business” numbers so precision in the calculations would matter. As such I limit myself to
BigDecimal in those situations.
Calendar is used. This is the bane of many JPA and JDBC applications I have worked with. The main reason is JDBC drivers are not required to store timezone information so when you do operations that are time based, well all hell breaks loose. Not only that some JPA implementations do not respect that you only want the DATE or TIME components even if you use the @Temporal annotation. As such I wrap this with a property as a long if I need a timestamp but store it as an XML Schema Standard Format String (or an application specific one using
SimpleDateFormat) for time and date only with no timezone information an hide it in my
Another note on portability is in the use of primary keys. My general rule is always use int unless proven otherwise. Most database systems implement integer keys efficiently. Business keys should be on a separate column from the primary keys marked with a unique constraint.
I don’t use
long because if I ever needed the domain space of a long in a single database table I would be in trouble or it is a really large dataset (over 4,294,967,296 records) which I would’ve planned for to begin with and used a
One note I want to add about internationalization. The default behaviour of JPA is to use the database character set to store string data for
Clob. The good part is this behaviour can be overridden in three ways each with its own con:
- adding a
@Column(columnDefinition = "...")which is not portable and requires code changes.
- letting your DBA do it in the final DDL so your code is separate from the final DDL, but that gives too much extra work for the DBA
- in the orm.xml file but that would mean you have an
orm.xmlfile that is specific to each database you are supporting.
My personal preference is using the
orm.xml file as it has the least amount of impact to development as there is usually just two database systems you need to support: development and final.
In my next article which should come out in a couple of weeks, I will explain about “ease of use”