Archive for November, 2009

Database integration testing / unit-testing with Spring, JPA, JUnit and Maven

November 22, 2009

The exact name for this tests is disputable. Whether it should be “database integration testing” or “unit testing”. But anyway, it is about this:
Many applications’ service layer relies heavily on database operations (through a JPA provider, for example), even though the access to the database is abstracted in a DAO layer.
An option for pure unit tests is to mock the DAO handler (using Mockito, EasyMock, etc), but in most cases this would be either a futile excercise, or it will be too complex to create well-behaving mocks.

So, using the following:
– spring 2.5.6
– hibernate entity manager
– junit 4.4
– maven2

we should achieve smooth database integration testing.
First, don’t try junit > 4.4, because spring 2.5.6 doesn’t work with it. Spring 3 will.

So, the steps.

  1. define your database access properties in .properites file and place it in src/main/resources (where the applicationContext.xml should reside as well). These properties should include: the dialect, the connection url, the username/password, the database driver.
  2. create src/test/resources, and create a properties file with the same name there, and set test-database parameters (using HSQLDB for example). Make the output folder for this source folder to be target/test-classes.
  3. in applicationContext.xml add
    <context:property-placeholder location="classpath:application.properties" />
  4. in src/test/java, in an appropriate package, create the following class:
    package com.tickets;
    
    import org.junit.runner.RunWith;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations="classpath:/applicationContext.xml")
    public abstract class BaseTest {
    
    }
    
  5. Make all your test classes extend BaseTest (or alternatively, add those annotations on all classes)
  6. For additional capabilities, transactions, autowiring, etc, refer to the Spring documentation
  7. Run your unit tests either from within your IDE, or via maven. It works both ways.
Advertisements

Deep @Embedded Hibernate entity hierarchy with Oracle

November 13, 2009

If you want to have a deep @Embeddable Hibernate entity hierarchy with Oracle – you are not welcome. Oracle has a genious unconfigurable (at least after a lot of search) limit for column names – 30 characters.
Hibernate (JPA’s actually) @Embedded and @Embeddable annotations provide the possibility to have a deep hierarcy of objects that will be represented in one table in the database. (The reasons to use such hierarchy is beyond the scope of this post.). Hibernate generates the names for the columns using all property names down the hierarcy. For instance: person_address_street_number But these names often get long and oracle says “identifier too long”. You can set @Column(name=”something”) at the bottom of the hierarchy, but it is not a rare case that one class, or one column name is found twice in the same entity (ergo table). So a duplicate column name problem appears.

The resolution, although not very beautiful, is to write a custom NamingStrategy which abbreviates the columns. A sample would look like this:

public class PrefixNamingStrategy extends DefaultComponentSafeNamingStrategy {

    private static final int MAX_ORACLE_ALLOWED_CHARS = 30;
    private static final int MAX_PART_LENGTH = 4;
    private static final int MAX_PART_SHORT_LENGTH = 3;

    @Override
    public String propertyToColumnName(String propertyName) {
        // Take the last column name before calling propertyToColumnName
        // in order to preserve casing
        String lastPropertyName = propertyName;
        if (lastPropertyName.indexOf(".") != -1) {
            lastPropertyName = lastPropertyName.substring(lastPropertyName.lastIndexOf(".") + 1);
        }

        // Getting the fully qualified, component-safe name of the column
        String columnName = super.propertyToColumnName(propertyName);

        if (columnName.length() <= MAX_ORACLE_ALLOWED_CHARS) {
            return columnName;
        }

        String result = shortenColumnName(columnName, MAX_PART_LENGTH, lastPropertyName,
                false);

        if (result.length() > MAX_ORACLE_ALLOWED_CHARS) {
            result = shortenColumnName(columnName, MAX_PART_SHORT_LENGTH,
                    lastPropertyName, true);
        }

        return result;
    }

    private String shortenColumnName(String columnName, int maxPartLength,
            String lastPropertyName, boolean shortenLast) {
        String[] parts = columnName.split("_");
        for (int i = 0; i < parts.length - 1; i++) {
            parts[i] = parts[i].substring(0, parts[i].length() < maxPartLength ? parts[i]
                    .length() : maxPartLength);
        }

        if (shortenLast) {
            parts[parts.length - 1] = getAbbreviation(lastPropertyName);
        }

        String result = "";
        for (int i = 0; i < parts.length; i++) {
            if (i != 0) {
                result += "_";
            }
            result += parts[i];
        }

        return result;
    }

    private String getAbbreviation(String string) {
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < string.length() - 1; i++) {
            if (Character.isUpperCase(string.charAt(i)) || i == 0) {
                buf.append(string.substring(i, i + MAX_PART_SHORT_LENGTH));
            }
        }

        return buf.toString();
    }
}