Java Ninja Chronicles By Norris Shelton

Things I learned in the pursuit of code

I am working on a Springframework project that accumulates data via a series of web pages. There is basically a table per page and and object per page. This makes it nice and neat 1:1. As I am going through the data, I notice that there is a spouse, an emergency contact, guardian, and a registration that have a ton of fields in common. Basically, they are people. Most of the information that is needed for a person is the same, regardless of the specialized type of person. They all need a first name, last name, address, city, state, zip …. There are eventually 18 fields that all of these objects have in common. I am a big fan of DRY and didn’t want to have a ton of the same properties/getters/setters all over the place.

I ended up with a Person that was extended by a Registration and an AttachedPerson. The AttachedPerson was extended by EmergencyContact, Guardian and Spouse.

Here is what the java code for Spouse looks like:

package com.bhsi.preregistration.spouse;


import com.bhsi.preregistration.AttachedPerson;


/** @author Norris Shelton */
public class Spouse extends AttachedPerson {

    private String middleInitial;

    public String getMiddleInitial() {
        return middleInitial;
    }

    public void setMiddleInitial(String middleInitial) {
        this.middleInitial = middleInitial;
    }
}

Spouse extends AttachedPerson.

package com.bhsi.preregistration;


/**
 * This is the class that supports every person that is attached to a registration (e.g. not the person being
 * pre-registered).
 * @author Norris Shelton
 */
public abstract class AttachedPerson extends Person {
    private int     id;
    private int     registrationId;
    private boolean addressSame;

    public int getId() {
        return id;
    }

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

    public int getRegistrationId() {
        return registrationId;
    }

    public void setRegistrationId(int registrationId) {
        this.registrationId = registrationId;
    }

    public boolean isAddressSame() {
        return addressSame;
    }

    public void setAddressSame(boolean addressSame) {
        this.addressSame = addressSame;
    }
}

AttachedPerson extends Person.

package com.bhsi.preregistration;


import java.sql.Timestamp;


/**
 * This is the base information that every person will have.
 * @author Norris Shelton
 */
public abstract class Person {
    private String    firstName;
    private String    lastName;
    private String    address;
    private String    city;
    private String    state;
    private String    zip;
    private String    homePhone;
    private String    ssn;
    private String    birthDate;
    private String    gender;
    private int       employerStatus;
    private String    employerName;
    private String    employerAddress;
    private String    employerCity;
    private int       employerState;
    private String    employerZip;
    private String    employerPhone;
    private Timestamp employerRetireDate;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getZip() {
        return zip;
    }

    public void setZip(String zip) {
        this.zip = zip;
    }

    public String getHomePhone() {
        return homePhone;
    }

    public void setHomePhone(String homePhone) {
        this.homePhone = homePhone;
    }

    public String getSsn() {
        return ssn;
    }

    public void setSsn(String ssn) {
        this.ssn = ssn;
    }

    public String getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(String birthDate) {
        this.birthDate = birthDate;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getEmployerStatus() {
        return employerStatus;
    }

    public void setEmployerStatus(int employerStatus) {
        this.employerStatus = employerStatus;
    }

    public String getEmployerName() {
        return employerName;
    }

    public void setEmployerName(String employerName) {
        this.employerName = employerName;
    }

    public String getEmployerAddress() {
        return employerAddress;
    }

    public void setEmployerAddress(String employerAddress) {
        this.employerAddress = employerAddress;
    }

    public String getEmployerCity() {
        return employerCity;
    }

    public void setEmployerCity(String employerCity) {
        this.employerCity = employerCity;
    }

    public int getEmployerState() {
        return employerState;
    }

    public void setEmployerState(int employerState) {
        this.employerState = employerState;
    }

    public String getEmployerZip() {
        return employerZip;
    }

    public void setEmployerZip(String employerZip) {
        this.employerZip = employerZip;
    }

    public String getEmployerPhone() {
        return employerPhone;
    }

    public void setEmployerPhone(String employerPhone) {
        this.employerPhone = employerPhone;
    }

    public Timestamp getEmployerRetireDate() {
        return employerRetireDate;
    }

    public void setEmployerRetireDate(Timestamp employerRetireDate) {
        this.employerRetireDate = employerRetireDate;
    }
}

This worked great until I started to write the Spring JDBC RowMapper. I was faced with the prospect of having the same properties being set amongst several RowMapper objects. I Googled “inheritance spring rowmapper” and didn’t find anything relevant. I tinkered with this and come up with the following.

We will use Spouse RowMapper as the example.

package com.bhsi.preregistration.spouse;


import com.bhsi.preregistration.AttachedPersonRowMapper;
import org.springframework.jdbc.core.RowMapper;

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


/** @author Norris Shelton */
public class SpouseRowMapper implements RowMapper<Spouse> {
    /**
     * 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 Spouse mapRow(ResultSet rs, int rowNum) throws SQLException {
        Spouse spouse = new Spouse();
        spouse.setMiddleInitial(rs.getString("spouse_middle_int"));

        return (Spouse) AttachedPersonRowMapper.mapRow(spouse, rs);
    }
}

Everything is typical Spring JDBC RowMapper until line 24. It then makes a call to the attached person row mapper.

package com.bhsi.preregistration;


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


/** @author Norris Shelton */
public class AttachedPersonRowMapper {
    /**
     * 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 attachedPerson instantiated AttachedPerson object or sub-class.
     * @param rs             the ResultSet to map (pre-initialized for 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 static AttachedPerson mapRow(AttachedPerson attachedPerson, ResultSet rs) throws SQLException {
        attachedPerson.setId(rs.getInt("id"));
        attachedPerson.setRegistrationId(rs.getInt("reg_id"));

        String columnPrefix = PersonRowMapper.getColumnPrefix(attachedPerson);

        attachedPerson.setAddressSame(rs.getBoolean(columnPrefix + "address_same"));

        return (AttachedPerson) PersonRowMapper.mapRow(attachedPerson, rs);
    }
}

I wanted to stay as close to the Spring RowMapper as possible for ease of maintenance by other developers. I tried to use Generics, but ran into Java’s Generics implementation limitation. I settled on passing in the concrete object and having the method use the object type needed for that RowMapper to perform it’s task. Then when it comes back, I have to cast it back to the concrete type. This is not as clean as i wanted it to be, but it worked and didn’t make me feel dirty and used.

Again, everything is normal until line 27. This makes a call to the person RowMapper.

package com.bhsi.preregistration;


import com.bhsi.preregistration.emergencycontact.EmergencyContact;
import com.bhsi.preregistration.guardian.Guardian;
import com.bhsi.preregistration.spouse.Spouse;

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


/** @author Norris Shelton */
public class PersonRowMapper {
    /**
     * 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 person       instantiated AttachedPerson object or sub-class.
     * @param rs           the ResultSet to map (pre-initialized for 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 static Person mapRow(Person person, ResultSet rs) throws SQLException {
        String columnPrefix = getColumnPrefix(person);
        person.setFirstName(rs.getString(columnPrefix + "first_name"));
        person.setLastName(rs.getString(columnPrefix + "last_name"));
        person.setAddress(rs.getString(columnPrefix + "address"));
        person.setCity(rs.getString(columnPrefix + "city"));
        person.setState(rs.getString(columnPrefix + "state"));
        person.setZip(rs.getString(columnPrefix + "zip"));
        person.setHomePhone(rs.getString(columnPrefix + "home_phone"));
        person.setSsn(rs.getString(columnPrefix + "ssn"));
        person.setBirthDate(rs.getString(columnPrefix + "birthdate"));
        person.setGender(rs.getString(columnPrefix + "gender"));
        person.setEmployerStatus(rs.getInt(columnPrefix + "emp_status"));
        person.setEmployerName(rs.getString(columnPrefix + "employer"));
        person.setEmployerAddress(rs.getString(columnPrefix + "employer_address"));
        person.setEmployerCity(rs.getString(columnPrefix + "employer_city"));
        person.setEmployerState(rs.getInt(columnPrefix + "employer_state"));
        person.setEmployerZip(rs.getString(columnPrefix + "employer_zip"));
        person.setEmployerPhone(rs.getString(columnPrefix + "employer_phone"));
        person.setEmployerRetireDate(rs.getTimestamp(columnPrefix + "employer_retiredate"));
        return person;
    }

    /**
     * Gets the appropriate column prefix based upon the true type of the object
     * @param person person sub-type
     * @return column prefix to add to the beginning of certain database columns
     */
    public static String getColumnPrefix(Person person) {
        String columnPrefix = "";
        if (person instanceof Guardian) {
            columnPrefix = "guar_";
        } else if (person instanceof Spouse) {
            columnPrefix = "spouse_";
        } else if (person instanceof EmergencyContact) {
            columnPrefix = "em_";
        }
        return columnPrefix;
    }

}

Just to make my life more interesting, I see that the tables have the same columns, BUT someone has decided to add a prefix to the beginning of the fields. Instead of first_name in all of the tables, spous_ was added to the beginnings of the fields in the spouse table. Luckily they consistently added the prefix to the same columns in all of the tables. I had to fall back to instanceof to get the concrete type to determine what prefix, if any to use.

There you have it. This is how I used inheritance in combination with Spring JDBC RowMapping.

February 16th, 2011

Posted In: Java, Spring

One Comment

Leave a Reply

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

WP to LinkedIn Auto Publish Powered By : XYZScripts.com