Tweaking Persistence with @PrePersist
June 13th, 2011 Mitch Goldstein, Consultant (email the author)
It is an interesting problem: even though the Java primitive double type has plenty of precision, math operations using this type can prove problematic when it comes to using them for precision calculations. Due to the nature of how floating point numbers are stored, they are by definition inaccurate. This is a consequence of trying to store decimals – especially fractional decimals – in a binary format.
To address some of these issues, the Java development team released the java.math.BigDecimal class. This is an immutable class that extends the abstract class java.lang.Number, which serves as the superclass of the primitive wrapper classes such as java.lang.Double and java.lang.Integer. The primary purpose of this base class is to provide methods that allow conversion among different number types.
BigDecimal is supported by the Java Persistence API (JPA), which uses Java Annotations to integrate Java objects with relational entities. This makes BigDecimal very tempting to use in lieu of floating-point primitives, since the JPA code, in conjunction with the JDBC driver, automatically converts persisted numeric values to BigDecimal when they are mapped to numeric table columns. However, this can be problematic depending on the type of database you happen to be connecting to.
The Problem
I encountered a problem where an object I wanted to persist was throwing a JDBC exception. It turned out that the problem was caused by the way the JDBC driver handles BigDecimal properties. This code illustrates this peculiarity:
BigDecimal bd = new BigDecimal(48.28);
System.out.println("toString() = " + bd.toString());
System.out.println("doubleValue() = " + bd.doubleValue());
Output: toString() = 48.280000000000001136868377216160297393798828125 doubleValue() = 48.28
Was this what you might have expected? It looks like the interpretation of the value is correct, but the string representation seems to have several decimal places of precision followed by random junk! Little did I know that this would wreak havoc in my code. Apparently, my JDBC driver converts the value to a string, which can usually, but not always, be digested. I would get a sporadic exception such as this when I tried to persist this data:
JDBCExceptionReporter:234 - Error converting data type nvarchar to decimal.
So what is the problem? Here we have a monetary value with two decimal places of precision. We want to use BigDecimal to eliminate rounding errors, and we can’t get it into the database?
The Solution
Regrettably, we don’t have control over the internal workings of JPA or the associated JDBC drivers, so we have to work with what we have. What we really need to do is to figure out how to modify the precision of the BigDecimal so it still behaves the way we want, but doesn’t cause the code in the driver to break.
Since BigDecimal is immutable, we really can’t fiddle with the values themselves. Much like the analogous String class, operations which alter values all return a fresh String object with the desired changes. There are operations that can be used to adjust the scale of the BigDecimal so that its raw string representation is not quite so cumbersome.
BigDecimal bd = new BigDecimal(48.28);
bd = bd.setScale(10, RoundingMode.HALF_EVEN);
System.out.println("toString() = " + bd.toString());
System.out.println("doubleValue() = " + bd.doubleValue());
Output: toString() = 48.2800000000 doubleValue() = 48.28
Even so – if you are using JPA to it’s fullest extent, it is quite likely that your mappings are applied not to your class’s methods, but directly to a class field, such as this declaration:
@Column (name = "PRICE")
protected BigDecimal _price;
This basically means you cannot ‘trap’ the update of this field in a method call when JPA uses reflection to update this value – how can we ensure our values are persistable if we can’t interrupt the persistance process?
The @PrePersist Annotation
The developers of JPA anticipated this issue and provided a very useful way to invoke code when an object is just about to be persisted. Using reflection, the persistence engine will look for any methods within the entity that is annotated with the @PrePersist annotation. This provides an excellent hook to either examine or modify entity values before they are persisted by JPA. In the case of the ‘price’ field above, we could add a method to tweak the value of the price to a more reasonable value before we try to insert the value in a table.
@PrePersist
protected void prePersist()
{
_price = _price.setScale(10, RoundingMode.HALF_EVEN);
}
This method will get invoked just prior to our object getting persisted, and will replace the ‘price’ value with its equivalent value, adjusting it’s scale so that it is still very precise, but will not cause problems due to string length when it is persisted to a relational store.
The second parameter of the setScale() method gives the rounding strategy that is used for the conversion. Any time the scale of a BigDecimal is modified, the user must provide the rule by which the scale will be reduced. Here I chose the ‘half-even’ method, which is also the one used by applications that support numeric analysis (such as Excel) since it provides a more even distribution of discrepancies over larger sets of data. For more information, have a look at the documentation for the java.math.RoundingMode enumeration.
Further study of the Java Persistence API will uncover many other ‘callback’ annotations that can be used to provide finer control over entity life-cycle without having to write large amounts of custom code.
Entry Filed under: Agile and Development
Pages
Categories
- Agile and Development
- Application Modernization
- Cloud Applications
- Process Integration
- Summa
- Technology + Healthcare
- Uncategorized
Most Recent Posts
- Summa Is Award Finalist at IBM’s Impact 2012
- Working with JqGrid and ASP.NET MVC - Setting up a base jqgrid parameters class
- Rebase a Slave Mercurial Repo to a Subversion Master
- The Social Enterprise Part 2 – How To Set Up Chatter In Less Than 30 Minutes
- Implement Clear Governance for BRMS
Feeds
Calendar
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
| « May | Jul » | |||||
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 | |||

5 Comments Add your own
1. Andrew Cottrell | June 17th, 2011 at 9:07 am
You are passing a float value in the constructor and that will be first cast to a double and then converted to the BigDecimal’s internal representation. That is where your imprecision is coming from.
You should always construct your BigDecimal literal values using a String, like this:
BigDecimal bd = new BigDecimal(“48.28″);
This way the internal representation will be exactly what you want.
If there is a database round-trip problem then your values are probably being handled as float or double at some point.
2. Mitch Goldstein | June 17th, 2011 at 9:47 pm
You are quite correct, Andrew. In this scenario, the construction is happening within Java Persistence and JDBC driver code. As we add more layers of abstraction to business data processing, it can become quite challenging to tweak low-level behavior.
3. Ben Northrop | June 30th, 2011 at 3:03 pm
Nice post. Was hoping you could clarify…
…are you saying that when you retrieve decimal values from your database, something (i.e. JDBC or JPA) is converting this value into a BigDecimal in a way that loses the correct precision (e.g. “48.28″ is in the database, but after retrieving it turns into “48.2823491234″)? If this is the case, then the problem isn’t really in *persisting* the data, it’s in retrieving, correct? And further, any calculations you did using these values in the app tier would be inaccurate, right?
…or are you saying that the BigDecimal is being instantiated in the app/ui layer (i.e. via user input), and then you get the problem when you try to persist it? In this case, the problem would be in how application logic instantiates the BigDecimal?
Just was hoping you could clarify. Thanks!
4. Mitch Goldstein | June 30th, 2011 at 11:54 pm
This problem occurred when I tried to persist an object with a BigDecimal property using JPA. This property was persisted using field-level mapping, which means somewhere inside the persisting method, reflection would have been used to get the value, which was subsequently converted to a String. It would have been this string that was passed along to JDBC, which failed to re-convert it back to a double value and threw the exception.
5. Dennis Gearon | December 16th, 2011 at 10:57 pm
accountants would really not like this. I can’t believe that there is still anything like this in the database world. A numberic,decimal should be sacrosanct, NEVER becoming a float anywhere along the line.
It should throw an exception if precisions don’t match.
BTW, is there a pre ‘hydration’ function? When pulling someting out of the database into an entity, a function that does something to a field before the entity is available to other code.
Leave a Comment
Some HTML allowed:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>
Trackback this post | Subscribe to the comments via RSS Feed