Saturday, October 25, 2008

Booleans in Hibernate

As the following helpful article (http://www.databasesandlife.com/hibernate-boolean-fields-mysql-50/) points out:

"There's a problem persisting boolean fields using Hibernate 3.2.2 to MySQL 5.0, if you allow Hibernate to generate your schema and you leave Hibernate to generate the schema in the default way". The problem is, by default, it generates a MqSQL field of type "bit(1)" - which causes Hibernate to throw:

could not insert: [com.company.MyObject] org.hibernate.exception.DataException: could not insert: [com.company.MyObject] at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:77) at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43) .... Caused by: java.sql.SQLException: Data too long for column 'my_field' at row 1 at ....

When I try to save.

That's causing my tests to fail… so what to do?

Well the article I mention above suggests mapping the field using

<column sql-type="BOOLEAN" />,

which does work.

However, I'm using annotations, so my preferred mapping for booleans is:

@Column(columnDefinition = "tinyint", nullable = false)
private boolean exampleBooleanValue;

An alternative is to use:

@Column
private Boolean exampleBooleanValue;

But I prefer to work with primitives rather than boxed primitives wherever possible.
Anyway, the first solution above works wonderfully.

Log4J Warnings

Annoyingly, when I run a junit test that is testing my Hibernate DAO objects, I get the following warnings:

log4j:WARN No appenders could be found for logger (jpt.dao.impl.test.GenericDaoImplTest). log4j:WARN Please initialize the log4j system properly.

This shouldn't be surprising, it's saying that Log4j hasn't been initialized properly. Why not? Well, It can't find my log4j.properties file. That's because hibernate needs it to be in the class path. So, I need to put log4j.properties into the classpath.

Well that's fine, and doing so does fix the problem - I just put log4j.properties into my src folder (or, in this case, specifically my test-src folder).

However, the problem is that I already have log4j.properties in the WebRoot/WEB-INF folder. When running my web-app (not the tests, but the web app itself), hibernate knows to look for it there because web.xml has:


<context-param>
<param-name>log4jConfigLocation</PARAM-NAME>
<param-value>/WEB-INF/log4j.properties</PARAM-VALUE>
</CONTEXT-PARAM>


What's more, that's exactly where I want my log4j.properties to be. Sure, I could change this to point somewhere else, but I don't want to do that. I want log4j.properties to be located in this directory for my web-app and configured just so.

So… the question is, how do I get Junit to initialize using this log4j.properties, rather than looking into src (on the classpath) and finding it missing. Well, I haven't been able to do it yet. I can manually configure log4j in my test class - but that's after the warnings are already shown. The only way I've been able to avoid those warnings is by placing log4j.properties into the classpath.
So, in the end, I've now got two versions of log4j.properties. One is in test-src and defines the logging levels etc for the debug running. One is in WebRoot/WEB-INF and defines logging levels for the webapp.

This is fine - and some might even say the best of both worlds - because I can have different default logging for my tests and my web-app. However, I still don't really like it. It could be confusing having two files named log4j.properites in the one project. What if I want to have only one!

Surely it shouldn't be THAT hard to configure my Junit test runs (that extend AbstractTransactionalDataSourceSprignContextTests) so that they search for the log4j.properties in the web-inf directory rather than on the classpath?!?!?!? Any ideas? (By the way, trying to add web-inf to the classpath doesn't work, as I'm using MyEclipse and it doesn't allow this!)

Hibernate "PreInsertEvent.getSource()" NoSuchMethodError.

I recently obtained the following error:

java.lang.NoSuchMethodError: org.hibernate.event.PreInsertEvent.getSource()Lorg/hibernate/engine/SessionImplementor;

This occurred when attempting to run a test on one of my Hibernate DAO entities.

The problem started when I updated some (but not all) of my hibernate .jar libraries, to enable the use of spring-test.jar to provide access to the helper org.springframework.test.AbstractTransactionalDataSourceSpringContextTests methods.

It turns out (http://opensource.atlassian.com/projects/hibernate/browse/HV-67) that the problem was:

Method getSource from an older version of the Hibernate Core is referenced.

It is stated within the compatibility matrix that Hibernate Validator 3.1.0.CR1 is compatible only with Hibernate Core 3.3.x version(s).Exception in thread "main" java.lang.NoSuchMethodError: org.hibernate.event.PreInsertEvent.getSource()Lorg/hibernate/engine/SessionImplementor;

That is, I was using the latest Hibernate Core (3.3.x), but didn't have the most up to date Hibernate Validator. I downloaded a new hibernate-validator.jar and everything worked again.

Problems with @Test annotations with AbstractTransactionalDataSourceSpringContextTests.

I've recently been writing tests, most of which use the Junit @Test (http://junit.org/apidocs/org/junit/Test.html) annotations to identify the methods for testing as junit tests. This works really well, and also allows me to use other great annotations like @Before.

However I decided I needed to do some integration testing of my DAOs with the database. So, to do this, I took advantage of Spring's AbstractTransactionalDataSourceSpringContextTests class (http://static.springframework.org/spring/docs/2.0.x/api/index.html?org/springframework/test/AbstractTransactionalDataSourceSpringContextTests.html).

This is great, however, I noticed that all of my test annotations for classes extended AbstractTransactionalDataSourceSpringContextTests were being ignored. This means that tests that were named "testExample" would run, but those named "doTestExample" would not - even if annotated. More to the point, something called "testExample" would run as a test even if not annotated.

The reason soon became clear. AbstractTransactionalDataSourceSpringContextTests extends the Junit.Framework.TestCase class. Now JUnit4 and JUnit3 tests really should not be mixed and they don't go together. So, because I'm extending TestCase (although I don't really want to), I need to go back to old fashioned Junit Tests, without test annotations. This is commented, briefly in:
http://www.jetbrains.net/jira/browse/IDEA-16466