Java Ninja Chronicles By Norris Shelton

Things I learned in the pursuit of code

This is an example of setting up a Springframework project with Maven for dependency management and building with Junit for unit tests. In your pom.xml, you will need the artifacts for Spring and JUnit. This file is located in your root directory. You can get this location via the ${basedir} maven expression.

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>norris.shelton</groupId>
    <artifactId>spring3txJunit4</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>Spring3 Transactions JUnit4</name>


    <properties>
        <spring.version>3.0.5.RELEASE</spring.version>
        <slf4j.version>1.6.1</slf4j.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>jsr250-api</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2</version>
        </dependency>

        <!-- 1.3 is the version for JDK 5, 1.4 is for JDK 6 -->
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.3</version>
        </dependency>
        <!--MS SQL Server driver-->
        <dependency>
            <groupId>net.sourceforge.jtds</groupId>
            <artifactId>jtds</artifactId>
            <version>1.2.5</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8.2</version>
            <scope>test</scope>
        </dependency>


        <!-- LogBack -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>0.9.27</version>
        </dependency>

        <!--hijack log4j-->
        <!--NOTE Saying it is provided so it will not be packaged in my builds if a dependency is using it-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
            <version>${slf4j.version}</version>
            <scope>provided</scope>
        </dependency>

        <!--hijack commons logging-->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>spring3txJunit4</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.7.1</version>
                <configuration>
                    <!--<parallel>methods</parallel>-->
                    <!--<parallel>classes</parallel>-->
                    <!--<parallel>both</parallel>-->
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Create a class that can be injected by spring. The @Repository annotation is what identifies this as an injectible class. @Repository is a specialization of @Component stereotype. If you annotate your class with @Repository, then spring will automatically translate exceptions in your persistence layer. This java source files are in ${basedir}/src/main/java/norris/shelton/spring3junit4.

package norris.shelton.spring3junit4;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;


/**
 * Data access object for the application data.  This is a spring @Repository class and will receive automatic exception
 * translation.
 * @author Norris Shelton
 * @see Repository
 * @see JdbcTemplate
 * @see NamedParameterJdbcTemplate
 */
@Repository
public class ApplicationDao {
    private static Logger log = LoggerFactory.getLogger(ApplicationDao.class);
    /** These are just the fields that would be inserted (e.g. no id). */
    private static final String SQL_INSERT_FIELDS =
        " name, description, entity_ids, acronym, aka1, aka2, aka3, status, helpGroup, version, date_updated, vendor, approved, display ";

    /** these are the fields that would be selected from. */
    private static final String SQL_FIELDS =
    " id, " + SQL_INSERT_FIELDS;

    /** SQL select statement. */
    private static final String SQL_SELECT_FROM =
        " SELECT " + SQL_FIELDS + " FROM dirApp ";

    /** SQL order by clause. */
    private static final String SQL_ORDER_BY = " ORDER BY status DESC, name ";

    /**
     * Inject the spring object with this name.  This is a jsr-250 annotation.  You can accomplish nearly the same thing
     * with @Autowired to find the objects of the correct type along with @Qualifier("name") to indicate which specific
     * bean you want.
     */
    @Resource(name = "bhsAuxCmsJdbcTemplate")
    private JdbcTemplate jdbcTemplate;

    /**
     * Inject the spring object with this name.  This is a jsr-250 annotation.  You can accomplish nearly the same thing
     * with @Autowired to find the objects of the correct type along with @Qualifier("name") to indicate which specific
     * bean you want.
     */
    @Resource(name = "bhsAuxCmsNamedParameterJdbcTemplate")
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    /**
     * Get an application by id.
     * @param id the application
     * @return the desired application
     * @see JdbcTemplate#queryForObject(String, Object[], RowMapper)   
     */
    public Application get(int id) {
        // use spring jdbc template to query database using a prepared statement and a user provided row mapper to
        // return a domain object
        return jdbcTemplate.queryForObject(SQL_SELECT_FROM + " WHERE id = ? " + SQL_ORDER_BY,
                                  new Integer[]{id},
                                  new ApplicationRowMapper());
    }

    /**
     * Get all applications.
     * @return list of application objects
     * @see JdbcTemplate#query(String, RowMapper) 
     */
    public List<Application> getAll() {
        // use spring jdbc template to query database using a user provided row mapper to return a list of domain
        // objects
        return jdbcTemplate.query(SQL_SELECT_FROM + SQL_ORDER_BY,
                                  new ApplicationRowMapper());
    }


    /**
     * Get all active applications.
     * @return list of active application objects
     * @see JdbcTemplate#query(String, RowMapper)
     */
    public List<Application> getDisplayable() {
        // use spring jdbc template to query database using a user provided row mapper to return a list of domain
        // objects
        return jdbcTemplate.query(SQL_SELECT_FROM + " WHERE display = 1 " + SQL_ORDER_BY,
                                  new ApplicationRowMapper());
    }

    /**
     * Get all active applications.
     * @return list of active application objects
     * @see JdbcTemplate#query(String, RowMapper)
     */
    public List<Application> getActive() {
        // use spring jdbc template to query database using a user provided row mapper to return a list of domain
        // objects
        return jdbcTemplate.query(SQL_SELECT_FROM + " WHERE status = 1 " + SQL_ORDER_BY,
                                  new ApplicationRowMapper());
    }

    /**
     * Creates a new application record.
     * @param application object to be inserted
     * @return the number of rows affected
     * @see NamedParameterJdbcTemplate#update(String, Map)
     * @see BeanPropertySqlParameterSource
     */
    public int insert(Application application) {
        // use spring named parameter jdbc template to insert data into the database using a bean property sql parameter
        // source to retrieve the property values from the domain object by bean property name
        return namedParameterJdbcTemplate.update("INSERT INTO dirApp (" + SQL_INSERT_FIELDS + ") " +
                                                 " values(:name, " +
                                                 "        :description, " +
                                                 "        :entityIds, " +
                                                 "        :acronym, " +
                                                 "        :aka1, " +
                                                 "        :aka2, " +
                                                 "        :aka3, " +
                                                 "        :status, " +
                                                 "        :helpGroup, " +
                                                 "        :version, " +
                                                 "        GETDATE(), " +
                                                 "        :vendor, " +
                                                 "        :approved, " +
                                                 "        :display) ",
                                                 new BeanPropertySqlParameterSource(application));
    }


    /**
     * Updates the given application object.
     * @param application object to be updated
     * @return the number of rows affected
     * @see NamedParameterJdbcTemplate#update(String, Map)
     * @see BeanPropertySqlParameterSource
     */
    public int update(Application application) {
        // use spring named parameter jdbc template to update the database using a bean property sql parameter source to
        // retrieve the property values from the domain object by bean property name
        return namedParameterJdbcTemplate.update("UPDATE dirApp SET name = :name, " +
                                                 "    description = :description, " +
                                                 "    entity_ids = :entityIds, " +
                                                 "    acronym = :acronym, " +
                                                 "    aka1 = :aka1, " +
                                                 "    aka2 = :aka2, " +
                                                 "    aka3 = :aka3, " +
                                                 "    status = :status, " +
                                                 "    helpGroup = :helpGroup, " +
                                                 "    version = :version, " +
                                                 "    date_updated = GETDATE(), " +
                                                 "    vendor = :vendor, " +
                                                 "    display = :display, " +
                                                 "    approved = :approved " +
                                                 " WHERE id = :id ",
                                                 new BeanPropertySqlParameterSource(application));
    }
}

Note, there are other spring APIs being used which require other classes. The missing functionality is needed by spring jdbc. Here is the model bean class.

package norris.shelton.spring3junit4;


import org.apache.commons.lang.builder.ReflectionToStringBuilder;

import java.util.Date;


/** @author Norris Shelton */
public class Application implements Comparable<Application> {
    public static final String SESSION_KEY = "norris.shelton.directories.applications.Applicatioin";
    
    private int id;
    private String name;
    private String description;
    private String entityIds;
    private String acronym;
    private String aka1;
    private String aka2;
    private String aka3;
    private boolean status;
    private String helpGroup;
    private String version;
    private Date dateUpdated;
    private String vendor;
    private boolean display = true;
    private boolean approved;

    public Application() { }

    public Application(Application application) {
        id = application.getId();
        name = application.getName();
        description = application.getDescription();
        entityIds = application.getEntityIds();
        acronym = application.getAcronym();
        aka1 = application.getAka1();
        aka2 = application.getAka2();
        aka3 = application.getAka3();
        status = application.isStatus();
        helpGroup = application.getHelpGroup();
        version = application.getVersion();
        dateUpdated = application.getDateUpdated();
        vendor = application.getVendor();
        display = application.isDisplay();
        approved = application.isApproved();
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getEntityIds() {
        return entityIds;
    }

    public void setEntityIds(String entityIds) {
        this.entityIds = entityIds;
    }

    public String getAcronym() {
        return acronym;
    }

    public void setAcronym(String acronym) {
        this.acronym = acronym;
    }

    public String getAka1() {
        return aka1;
    }

    public void setAka1(String aka1) {
        this.aka1 = aka1;
    }

    public String getAka2() {
        return aka2;
    }

    public void setAka2(String aka2) {
        this.aka2 = aka2;
    }

    public String getAka3() {
        return aka3;
    }

    public void setAka3(String aka3) {
        this.aka3 = aka3;
    }

    public boolean isStatus() {
        return status;
    }

    public void setStatus(boolean status) {
        this.status = status;
    }

    public String getHelpGroup() {
        return helpGroup;
    }

    public void setHelpGroup(String helpGroup) {
        this.helpGroup = helpGroup;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public Date getDateUpdated() {
        return dateUpdated;
    }

    public void setDateUpdated(Date dateUpdated) {
        this.dateUpdated = dateUpdated;
    }

    public String getVendor() {
        return vendor;
    }

    public void setVendor(String vendor) {
        this.vendor = vendor;
    }

    public boolean isDisplay() {
        return display;
    }

    public void setDisplay(boolean display) {
        this.display = display;
    }

    public boolean isApproved() {
        return approved;
    }

    public void setApproved(boolean approved) {
        this.approved = approved;
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) { return true; }
        if (o == null || getClass() != o.getClass()) { return false; }

        Application that = (Application) o;

        if (name != null ? !name.equals(that.name) : that.name != null) { return false; }

        return true;
    }

    @Override
    public int hashCode() {
        return name != null ? name.hashCode() : 0;
    }

    /**
     * Compares this object with the specified object for order.  Returns a negative integer, zero, or a positive
     * integer as this object is less than, equal to, or greater than the specified object.<p>
     * @param o the Object to be compared.
     * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than
     *         the specified object.
     * @throws ClassCastException if the specified object's type prevents it from being compared to this Object.
     */
    public int compareTo(Application o) {
        return name.compareTo(o.getName());
    }

    /**
     * Returns a string representation of the object. In general, the <code>toString</code> method returns a string that
     * "textually represents" this object. The result should be a concise but informative representation that is easy
     * for a person to read. It is recommended that all subclasses override this method.
     * @return a string representation of the object.
     */
    @Override
    public String toString() {
        return ReflectionToStringBuilder.toString(this);
    }
}

This is the row mapper to map the database result set to an object.

package norris.shelton.spring3junit4;


import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;


/**
 * Application row mapper
 * @author Norris Shelton
 */
public class ApplicationRowMapper implements RowMapper<Application> {
    /**
     * Implementations must implement this method to map each row of data in the ResultSet. This method should not call
     * <code>next()</code> on the ResultSet; it is only supposed to map values of the current row.
     * @param rs     the ResultSet to map (pre-initialized for the current row)
     * @param rowNum the number of the current row
     * @return the result object for the current row
     * @throws SQLException if a SQLException is encountered getting column values (that is, there's no need to catch
     *                      SQLException)
     */
    public Application mapRow(ResultSet rs, int rowNum)
    throws SQLException {
        Application application = new Application();
        application.setId(rs.getInt("id"));
        application.setName(rs.getString("name"));
        application.setDescription(rs.getString("description"));
        application.setEntityIds(rs.getString("entity_ids"));
        application.setAcronym(rs.getString("acronym"));
        application.setAka1(rs.getString("aka1"));
        application.setAka2(rs.getString("aka2"));
        application.setAka3(rs.getString("aka3"));
        application.setStatus(rs.getBoolean("status"));
        application.setHelpGroup(rs.getString("helpGroup"));
        application.setVersion(rs.getString("version"));
        application.setDateUpdated(rs.getDate("date_updated"));
        application.setVendor(rs.getString("vendor"));
        application.setDisplay(rs.getBoolean("display"));
        application.setApproved(rs.getBoolean("approved"));
        return application;
    }
}

Now that we have a spring bean, let’s test it. This is the junit unit test class. This java test source files are in ${basedir}/src/test/java/norris/shelton/spring3junit4

package norris.shelton.spring3junit4;


import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

import static org.junit.Assert.*;


/**
 * This class uses the default for the context configuration and the transaction manager.  It is set-up to work as a
 * unit test instead of an integration test.  It only has what it needs.  However, this would probably lead to some
 * duplication if common parts are not imported by the context file or specified in the context configuration.
 * @author Norris Shelton
 */
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({TransactionalTestExecutionListener.class,
                         DependencyInjectionTestExecutionListener.class})
@ContextConfiguration
@Transactional
public class TestApplicationDao {

    @Autowired
    private ApplicationDao applicationDao;

    @Test
    public void testGet() {
        Application application = applicationDao.get(1);
        commonAssertions(application);
    }

    @Test
    public void testGetAll() {
        List<Application> applications = applicationDao.getAll();
        assertNotNull(applications);
        for (Application application : applications) {
            commonAssertions(application);
        }
    }

    @Test
    public void testGetActive() {
        List<Application> applications = applicationDao.getActive();
        for (Application application : applications) {
            commonAssertions(application);
            assertTrue(application.isStatus());
        }
    }

    @Test
    public void testGetDisplayable() {
        List<Application> applications = applicationDao.getDisplayable();
        for (Application application : applications) {
            commonAssertions(application);
            assertTrue(application.isDisplay());
        }
    }

    @Test
    public void testUpdate() {
        Application application = applicationDao.get(25);
        assertNotNull("Unable to get the initial record to update");
        assertFalse("found that the aka1 value was still set from the last test run", "unused".equals(application.getAka1()));

        application.setAka1("unused");
        int numUpdated = applicationDao.update(application);
        assertEquals(1, numUpdated);
    }

    @Test
    public void testInsert() {
        Application application = new Application();
        application.setName("Test Name");

        int id = applicationDao.insert(application);
        assertTrue(id > 0);
    }


    private void commonAssertions(Application application) {
        assertNotNull(application);
        assertTrue(application.getId() > 0);
        assertNotNull(application.getName());
        //note name (application) is the only required property
        //assertNotNull(application.getDescription());
        //assertNotNull(application.getEntityIds());
        //assertNotNull(application.getAcronym());
        //assertNotNull(application.getAka1());
        //assertNotNull(application.getAka2());
        //assertNotNull(application.getAka3());
        //assertNotNull(application.isStatus());
        //assertNotNull(application.getHelpGroup());
        //assertNotNull(application.getVersion());
        //assertNotNull(application.getDateUpdated());
        //assertNotNull(application.getVendor());
    }
}

The junit unit test class has a lot of things going on.

  • @RunWith(SpringJUnit4ClassRunner.class) – the makes the junit test class a spring bean, meaning that it can be injected with other spring managed beans
  • @TestExecutionListeners({TransactionalTestExecutionListener.class, DependencyInjectionTestExecutionListener.class}) – this enables the spring DI and the TX listeners.
  • @ContextConfiguration – this tells spring where the test application context is located. It defaults to the same package as the class, with the same name as the class, followed by -context.xml.
  • @Transactional – this tells spring that the test class methods should be wrapped in transactions, with the default value of automatic rollback at the end of the method.
  • @Autowired – indicates to spring that you want an object of ApplicationDao-type to be injected.
  • @Test – tells junit that this is a method that is part of the unit test

All that is needed now is the spring context file. This file for that test class is contained in ${basedir}/src/test/resources/norris/shelton/spring3junit4/TestApplicationDao-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
                           http://www.springframework.org/schema/tx      http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

    <!--
        Scan for spring beans in this package and all sub-packages
    -->
    <context:component-scan base-package="norris.shelton"/>

    <!--
        Scan for classes needing transactions.  Defaults to using a transaction manager object named transactionManager
    -->
    <tx:annotation-driven/>

    <!--
        Instantiate a transaction manager object for the specified datasource
    -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
          p:dataSource-ref="bhsAuxCmsDataSource"/>

    <!--
        Create a pooled datasource.  On the production side, you would probably use the jee schema:
        <jee:jndi-lookup id="bhsAuxCmsDataSource" jndi-name="jdbc/some_jndi_name"/>
    -->
    <bean id="bhsAuxCmsDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
          p:driverClassName="net.sourceforge.jtds.jdbc.Driver"
          p:url="jdbc:jtds:sqlserver://server_ip/database_name"
          p:username="some_username"
          p:password="some_password"
          p:defaultAutoCommit="false"/>

    <!--
        Create a jdbc template object for the given datasource
    -->
    <bean id="bhsAuxCmsJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
          p:dataSource-ref="bhsAuxCmsDataSource"/>

    <!--
        Create a named parameter jdbc template object for the given datasource
    -->
    <bean id="bhsAuxCmsNamedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
        <constructor-arg ref="bhsAuxCmsDataSource"/>
    </bean>
</beans>

The maven project source is here

February 3rd, 2011

Posted In: Java, JUnit, Maven, Spring

Leave a Reply

Your email address will not be published. Required fields are marked *

WP to LinkedIn Auto Publish Powered By : XYZScripts.com