Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Null Object pattern with a MyBatis TypeHandler

I have been trying to make a custom TypeHandler in MyBatis so that for a null column in the database, MyBatis returns an implementation of a Null Object pattern instead of having a null in the domain class.

After googling for help, I reached the excelent project mybatis-koans, namely the koan 19 that addresses exactly this issue with the same approach I am using, i.e., extending BaseTypeHandler<T> (is abstract). At this point, I have a concrete TypeHandler similar to the EmailTypeHandler in that koan:

/**
 * Acts as a factory method to return the appropriate implementation of an Email.
 * Returns a Null object if the email value in the database was null/empty
 */
public class EmailTypeHandler extends BaseTypeHandler<Email> {

  @Override
  public Email getNullableResult(ResultSet rs, String colName) throws SQLException {
    return createEmail(rs.getString(colName));
  }

  @Override
  public Email getNullableResult(ResultSet rs, int colNum) throws SQLException {
    return createEmail(rs.getString(colNum));
  }


  private Email createEmail(String s) {
    System.out.println(s);
    if (s == null || s.equals("")) {
      return new NullEmail();
    } else {
      return new EmailImpl(s);
    }
  }

  @Override
  public Email getNullableResult(CallableStatement arg0, int arg1) throws SQLException {
    return null;
  }

  @Override
  public void setNonNullParameter(PreparedStatement ps, int colNum,
                                  Email e, JdbcType t) throws SQLException {
  }
}

Unfortunately, it seems that the author (midpeter444) is facing the same problem: when the value in the DB is null, null is still returned instead of the object crafted in the concrete TypeHandler.

like image 240
jalopaba Avatar asked Feb 14 '13 12:02

jalopaba


2 Answers

I saw the solution right after posting the question. In BaseTypeHandler code:

[...]
  public T getResult(ResultSet rs, String columnName) throws SQLException {
    T result = getNullableResult(rs, columnName);
    if (rs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }

  public T getResult(ResultSet rs, int columnIndex) throws SQLException {
    T result = getNullableResult(rs, columnIndex);
    if (rs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }

  public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
    T result = getNullableResult(cs, columnIndex);
    if (cs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }

  public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;

  public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;

  public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;
[...]

Clearly, BaseTypeHandler is going to return null when the value in the database is null, as cs.WasNull() is true in that case. Therefore, the solution is to create a concrete TypeHandler<T> returning the appropiate implementation (in this case, a NullObject implmentation when the value in the database is null) without subclassing BaseTypeHandler.

EDITED (folowing quux00's comment): Of course, we can just override getResult method of BaseTypeHandler, given that the functionality provided by its method setParameters can be useful.

like image 118
jalopaba Avatar answered Oct 21 '22 03:10

jalopaba


Thanks for your answer! It helped me understand my problem and how to overcome it. I wanted a result to never be null.

By you posting the code for BaseTypeHandler, it confirmed that null was being returned depending on ResultSet.wasNull and not on the result itself returned from getNullableResult. So I overrode getResult in the concrete subclass of BaseTypeHandler. In the custom implementations of getResult, super.getResult is called, and then null is checked for. Below is an example:

public Money getResult ( ResultSet rs, String columnName ) throws SQLException
{
    Money result = super.getResult ( rs, columnName );
    return result == null ? Money.ZERO : result;
}

public Money getResult ( ResultSet rs, int columnIndex ) throws SQLException
{
    Money result = super.getResult ( rs, columnIndex );
    return result == null ? Money.ZERO : result;
}

public Money getResult ( CallableStatement cs, int columnIndex ) throws SQLException
{
    Money result = super.getResult ( cs, columnIndex );
    return result == null ? Money.ZERO : result;
}
like image 23
Cris B Avatar answered Oct 21 '22 04:10

Cris B