From 34dc7332ea63c9cade5d07cb508b8121da4b8799 Mon Sep 17 00:00:00 2001
From: Ryan Lamore <rlamore@salesforce.com>
Date: Fri, 27 Mar 2015 12:59:42 -0700
Subject: [PATCH] Added better error reporting to help debug issues with mapper
 files

---
 .../executor/result/ResultMapException.java   | 40 +++++++++
 .../resultset/DefaultResultSetHandler.java    | 27 +++---
 .../ibatis/mapping/ParameterMapping.java      | 16 ++++
 .../apache/ibatis/mapping/ResultMapping.java  | 22 +++++
 .../factory/DefaultObjectFactory.java         |  8 +-
 .../defaults/DefaultParameterHandler.java     | 11 ++-
 .../apache/ibatis/type/BaseTypeHandler.java   | 30 ++++++-
 .../DefaultResultSetHandlerTest.java          | 68 +++++++++++----
 .../factory/DefaultObjectFactoryTest.java     | 55 ++++++++++++
 .../ibatis/reflection/factory/TestClass.java  | 31 +++++++
 .../defaults/DefaultParameterHandlerTest.java | 87 +++++++++++++++++++
 .../ibatis/type/UnknownTypeHandlerTest.java   | 74 +++++++++++++++-
 12 files changed, 431 insertions(+), 38 deletions(-)
 create mode 100644 src/main/java/org/apache/ibatis/executor/result/ResultMapException.java
 create mode 100644 src/test/java/org/apache/ibatis/reflection/factory/DefaultObjectFactoryTest.java
 create mode 100644 src/test/java/org/apache/ibatis/reflection/factory/TestClass.java
 create mode 100644 src/test/java/org/apache/ibatis/scripting/defaults/DefaultParameterHandlerTest.java

diff --git a/src/main/java/org/apache/ibatis/executor/result/ResultMapException.java b/src/main/java/org/apache/ibatis/executor/result/ResultMapException.java
new file mode 100644
index 00000000000..d2c2bc0a9c2
--- /dev/null
+++ b/src/main/java/org/apache/ibatis/executor/result/ResultMapException.java
@@ -0,0 +1,40 @@
+/**
+ *    Copyright 2009-2015 the original author or authors.
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+package org.apache.ibatis.executor.result;
+
+import org.apache.ibatis.exceptions.PersistenceException;
+
+/**
+ * @author Ryan Lamore
+ */
+public class ResultMapException extends PersistenceException {
+    private static final long serialVersionUID = 3270932060569707623L;
+
+    public ResultMapException() {
+    }
+
+    public ResultMapException(String message) {
+        super(message);
+    }
+
+    public ResultMapException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public ResultMapException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java b/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java
index 2556bc295c9..8343dc6333a 100644
--- a/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java
+++ b/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java
@@ -37,6 +37,7 @@ import org.apache.ibatis.executor.loader.ResultLoaderMap;
 import org.apache.ibatis.executor.parameter.ParameterHandler;
 import org.apache.ibatis.executor.result.DefaultResultContext;
 import org.apache.ibatis.executor.result.DefaultResultHandler;
+import org.apache.ibatis.executor.result.ResultMapException;
 import org.apache.ibatis.mapping.BoundSql;
 import org.apache.ibatis.mapping.Discriminator;
 import org.apache.ibatis.mapping.MappedStatement;
@@ -562,21 +563,27 @@ public class DefaultResultSetHandler implements ResultSetHandler {
     throw new ExecutorException("Do not know how to create an instance of " + resultType);
   }
 
-  private Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType, List<ResultMapping> constructorMappings,
-      List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) throws SQLException {
+  Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType, List<ResultMapping> constructorMappings,
+      List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) {
     boolean foundValues = false;
     for (ResultMapping constructorMapping : constructorMappings) {
       final Class<?> parameterType = constructorMapping.getJavaType();
       final String column = constructorMapping.getColumn();
       final Object value;
-      if (constructorMapping.getNestedQueryId() != null) {
-        value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
-      } else if (constructorMapping.getNestedResultMapId() != null) {
-        final ResultMap resultMap = configuration.getResultMap(constructorMapping.getNestedResultMapId());
-        value = getRowValue(rsw, resultMap);
-      } else {
-        final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();
-        value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
+      try {
+        if (constructorMapping.getNestedQueryId() != null) {
+          value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
+        } else if (constructorMapping.getNestedResultMapId() != null) {
+          final ResultMap resultMap = configuration.getResultMap(constructorMapping.getNestedResultMapId());
+          value = getRowValue(rsw, resultMap);
+        } else {
+          final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();
+          value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
+        }
+      } catch (ResultMapException e) {
+        throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
+      } catch (SQLException e) {
+        throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
       }
       constructorArgTypes.add(parameterType);
       constructorArgs.add(value);
diff --git a/src/main/java/org/apache/ibatis/mapping/ParameterMapping.java b/src/main/java/org/apache/ibatis/mapping/ParameterMapping.java
index 13a6cd033d0..3d69a8912db 100644
--- a/src/main/java/org/apache/ibatis/mapping/ParameterMapping.java
+++ b/src/main/java/org/apache/ibatis/mapping/ParameterMapping.java
@@ -199,4 +199,20 @@ public class ParameterMapping {
     return expression;
   }
 
+  @Override
+  public String toString() {
+    final StringBuilder sb = new StringBuilder("ParameterMapping{");
+    //sb.append("configuration=").append(configuration); // configuration doesn't have a useful .toString()
+    sb.append("property='").append(property).append('\'');
+    sb.append(", mode=").append(mode);
+    sb.append(", javaType=").append(javaType);
+    sb.append(", jdbcType=").append(jdbcType);
+    sb.append(", numericScale=").append(numericScale);
+    //sb.append(", typeHandler=").append(typeHandler); // typeHandler also doesn't have a useful .toString()
+    sb.append(", resultMapId='").append(resultMapId).append('\'');
+    sb.append(", jdbcTypeName='").append(jdbcTypeName).append('\'');
+    sb.append(", expression='").append(expression).append('\'');
+    sb.append('}');
+    return sb.toString();
+  }
 }
diff --git a/src/main/java/org/apache/ibatis/mapping/ResultMapping.java b/src/main/java/org/apache/ibatis/mapping/ResultMapping.java
index cc6dc70b409..b1cc567c4ed 100644
--- a/src/main/java/org/apache/ibatis/mapping/ResultMapping.java
+++ b/src/main/java/org/apache/ibatis/mapping/ResultMapping.java
@@ -280,4 +280,26 @@ public class ResultMapping {
     }
   }
 
+  @Override
+  public String toString() {
+    final StringBuilder sb = new StringBuilder("ResultMapping{");
+    //sb.append("configuration=").append(configuration); // configuration doesn't have a useful .toString()
+    sb.append("property='").append(property).append('\'');
+    sb.append(", column='").append(column).append('\'');
+    sb.append(", javaType=").append(javaType);
+    sb.append(", jdbcType=").append(jdbcType);
+    //sb.append(", typeHandler=").append(typeHandler); // typeHandler also doesn't have a useful .toString()
+    sb.append(", nestedResultMapId='").append(nestedResultMapId).append('\'');
+    sb.append(", nestedQueryId='").append(nestedQueryId).append('\'');
+    sb.append(", notNullColumns=").append(notNullColumns);
+    sb.append(", columnPrefix='").append(columnPrefix).append('\'');
+    sb.append(", flags=").append(flags);
+    sb.append(", composites=").append(composites);
+    sb.append(", resultSet='").append(resultSet).append('\'');
+    sb.append(", foreignColumn='").append(foreignColumn).append('\'');
+    sb.append(", lazy=").append(lazy);
+    sb.append('}');
+    return sb.toString();
+  }
+
 }
diff --git a/src/main/java/org/apache/ibatis/reflection/factory/DefaultObjectFactory.java b/src/main/java/org/apache/ibatis/reflection/factory/DefaultObjectFactory.java
index 277ec6275a4..cb7cdf9c8be 100644
--- a/src/main/java/org/apache/ibatis/reflection/factory/DefaultObjectFactory.java
+++ b/src/main/java/org/apache/ibatis/reflection/factory/DefaultObjectFactory.java
@@ -55,7 +55,7 @@ public class DefaultObjectFactory implements ObjectFactory, Serializable {
     // no props for default
   }
 
-  private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
+  <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
     try {
       Constructor<T> constructor;
       if (constructorArgTypes == null || constructorArgs == null) {
@@ -72,18 +72,20 @@ public class DefaultObjectFactory implements ObjectFactory, Serializable {
       return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
     } catch (Exception e) {
       StringBuilder argTypes = new StringBuilder();
-      if (constructorArgTypes != null) {
+      if (constructorArgTypes != null && !constructorArgTypes.isEmpty()) {
         for (Class<?> argType : constructorArgTypes) {
           argTypes.append(argType.getSimpleName());
           argTypes.append(",");
         }
+        argTypes.deleteCharAt(argTypes.length() - 1); // remove trailing ,
       }
       StringBuilder argValues = new StringBuilder();
-      if (constructorArgs != null) {
+      if (constructorArgs != null && !constructorArgs.isEmpty()) {
         for (Object argValue : constructorArgs) {
           argValues.append(String.valueOf(argValue));
           argValues.append(",");
         }
+        argValues.deleteCharAt(argValues.length() - 1); // remove trailing ,
       }
       throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
     }
diff --git a/src/main/java/org/apache/ibatis/scripting/defaults/DefaultParameterHandler.java b/src/main/java/org/apache/ibatis/scripting/defaults/DefaultParameterHandler.java
index 426f40c87ec..d1c720c92cd 100644
--- a/src/main/java/org/apache/ibatis/scripting/defaults/DefaultParameterHandler.java
+++ b/src/main/java/org/apache/ibatis/scripting/defaults/DefaultParameterHandler.java
@@ -28,6 +28,7 @@ import org.apache.ibatis.mapping.ParameterMode;
 import org.apache.ibatis.reflection.MetaObject;
 import org.apache.ibatis.session.Configuration;
 import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.TypeException;
 import org.apache.ibatis.type.TypeHandler;
 import org.apache.ibatis.type.TypeHandlerRegistry;
 
@@ -58,7 +59,7 @@ public class DefaultParameterHandler implements ParameterHandler {
   }
 
   @Override
-  public void setParameters(PreparedStatement ps) throws SQLException {
+  public void setParameters(PreparedStatement ps) {
     ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
     List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
     if (parameterMappings != null) {
@@ -82,7 +83,13 @@ public class DefaultParameterHandler implements ParameterHandler {
           if (value == null && jdbcType == null) {
             jdbcType = configuration.getJdbcTypeForNull();
           }
-          typeHandler.setParameter(ps, i + 1, value, jdbcType);
+          try {
+            typeHandler.setParameter(ps, i + 1, value, jdbcType);
+          } catch (TypeException e) {
+            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
+          } catch (SQLException e) {
+            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
+          }
         }
       }
     }
diff --git a/src/main/java/org/apache/ibatis/type/BaseTypeHandler.java b/src/main/java/org/apache/ibatis/type/BaseTypeHandler.java
index 7bc857324a1..43d9499943e 100644
--- a/src/main/java/org/apache/ibatis/type/BaseTypeHandler.java
+++ b/src/main/java/org/apache/ibatis/type/BaseTypeHandler.java
@@ -20,6 +20,7 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 
+import org.apache.ibatis.executor.result.ResultMapException;
 import org.apache.ibatis.session.Configuration;
 
 /**
@@ -48,13 +49,24 @@ public abstract class BaseTypeHandler<T> extends TypeReference<T> implements Typ
                 "Cause: " + e, e);
       }
     } else {
-      setNonNullParameter(ps, i, parameter, jdbcType);
+      try {
+        setNonNullParameter(ps, i, parameter, jdbcType);
+      } catch (Exception e) {
+        throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
+                "Try setting a different JdbcType for this parameter or a different configuration property. " +
+                "Cause: " + e, e);
+      }
     }
   }
 
   @Override
   public T getResult(ResultSet rs, String columnName) throws SQLException {
-    T result = getNullableResult(rs, columnName);
+    T result;
+    try {
+      result = getNullableResult(rs, columnName);
+    } catch (Exception e) {
+      throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);
+    }
     if (rs.wasNull()) {
       return null;
     } else {
@@ -64,7 +76,12 @@ public abstract class BaseTypeHandler<T> extends TypeReference<T> implements Typ
 
   @Override
   public T getResult(ResultSet rs, int columnIndex) throws SQLException {
-    T result = getNullableResult(rs, columnIndex);
+    T result;
+    try {
+      result = getNullableResult(rs, columnIndex);
+    } catch (Exception e) {
+      throw new ResultMapException("Error attempting to get column #" + columnIndex+ " from result set.  Cause: " + e, e);
+    }
     if (rs.wasNull()) {
       return null;
     } else {
@@ -74,7 +91,12 @@ public abstract class BaseTypeHandler<T> extends TypeReference<T> implements Typ
 
   @Override
   public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
-    T result = getNullableResult(cs, columnIndex);
+    T result;
+    try {
+      result = getNullableResult(cs, columnIndex);
+    } catch (Exception e) {
+      throw new ResultMapException("Error attempting to get column #" + columnIndex+ " from callable statement.  Cause: " + e, e);
+    }
     if (cs.wasNull()) {
       return null;
     } else {
diff --git a/src/test/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandlerTest.java b/src/test/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandlerTest.java
index f9340f214ae..7c59c40cdb8 100644
--- a/src/test/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandlerTest.java
+++ b/src/test/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandlerTest.java
@@ -16,20 +16,20 @@
 package org.apache.ibatis.executor.resultset;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.ResultSet;
-import java.sql.ResultSetMetaData;
-import java.sql.Statement;
-import java.sql.Types;
+import java.sql.*;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 
 import org.apache.ibatis.builder.StaticSqlSource;
 import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.executor.ExecutorException;
 import org.apache.ibatis.executor.parameter.ParameterHandler;
 import org.apache.ibatis.mapping.BoundSql;
 import org.apache.ibatis.mapping.MappedStatement;
@@ -39,7 +39,9 @@ import org.apache.ibatis.mapping.SqlCommandType;
 import org.apache.ibatis.session.Configuration;
 import org.apache.ibatis.session.ResultHandler;
 import org.apache.ibatis.session.RowBounds;
+import org.apache.ibatis.type.TypeHandler;
 import org.apache.ibatis.type.TypeHandlerRegistry;
+import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -67,18 +69,7 @@ public class DefaultResultSetHandlerTest {
   @Test
   public void shouldRetainColumnNameCase() throws Exception {
 
-    final Configuration config = new Configuration();
-    final TypeHandlerRegistry registry = config.getTypeHandlerRegistry();
-    final MappedStatement ms = new MappedStatement.Builder(config, "testSelect", new StaticSqlSource(config, "some select statement"), SqlCommandType.SELECT).resultMaps(
-        new ArrayList<ResultMap>() {
-          {
-            add(new ResultMap.Builder(config, "testMap", HashMap.class, new ArrayList<ResultMapping>() {
-              {
-                add(new ResultMapping.Builder(config, "cOlUmN1", "CoLuMn1", registry.getTypeHandler(Integer.class)).build());
-              }
-            }).build());
-          }
-        }).build();
+    final MappedStatement ms = getMappedStatement();
 
     final Executor executor = null;
     final ParameterHandler parameterHandler = null;
@@ -106,4 +97,45 @@ public class DefaultResultSetHandlerTest {
     assertEquals(Integer.valueOf(100), ((HashMap) results.get(0)).get("cOlUmN1"));
   }
 
+  @Test
+  public void shouldThrowExceptionWithColumnName() throws Exception {
+    final MappedStatement ms = getMappedStatement();
+    final RowBounds rowBounds = new RowBounds(0, 100);
+
+    final DefaultResultSetHandler defaultResultSetHandler = new DefaultResultSetHandler(null/*executor*/, ms,
+            null/*parameterHandler*/, null/*resultHandler*/, null/*boundSql*/, rowBounds);
+
+    final ResultSetWrapper rsw = mock(ResultSetWrapper.class);
+
+    final ResultMapping resultMapping = mock(ResultMapping.class);
+    final TypeHandler typeHandler = mock(TypeHandler.class);
+    when(resultMapping.getTypeHandler()).thenReturn(typeHandler);
+    when(typeHandler.getResult(any(ResultSet.class), anyString())).thenThrow(new SQLException("exception"));
+    List<ResultMapping> constructorMappings = Collections.singletonList(resultMapping);
+
+    try {
+      defaultResultSetHandler.createParameterizedResultObject(rsw, null/*resultType*/, constructorMappings,
+              null/*constructorArgTypes*/, null/*constructorArgs*/, null/*columnPrefix*/);
+      Assert.fail("Should have thrown ExecutorException");
+    } catch (Exception e) {
+      Assert.assertTrue("Expected ExecutorException", e instanceof ExecutorException);
+      Assert.assertTrue("", e.getMessage().contains("mapping: " + resultMapping.toString()));
+    }
+  }
+
+  MappedStatement getMappedStatement() {
+    final Configuration config = new Configuration();
+    final TypeHandlerRegistry registry = config.getTypeHandlerRegistry();
+    return new MappedStatement.Builder(config, "testSelect", new StaticSqlSource(config, "some select statement"), SqlCommandType.SELECT).resultMaps(
+        new ArrayList<ResultMap>() {
+          {
+            add(new ResultMap.Builder(config, "testMap", HashMap.class, new ArrayList<ResultMapping>() {
+              {
+                add(new ResultMapping.Builder(config, "cOlUmN1", "CoLuMn1", registry.getTypeHandler(Integer.class)).build());
+              }
+            }).build());
+          }
+        }).build();
+  }
+
 }
diff --git a/src/test/java/org/apache/ibatis/reflection/factory/DefaultObjectFactoryTest.java b/src/test/java/org/apache/ibatis/reflection/factory/DefaultObjectFactoryTest.java
new file mode 100644
index 00000000000..1c5388dcd82
--- /dev/null
+++ b/src/test/java/org/apache/ibatis/reflection/factory/DefaultObjectFactoryTest.java
@@ -0,0 +1,55 @@
+/**
+ *    Copyright 2009-2015 the original author or authors.
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+package org.apache.ibatis.reflection.factory;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.apache.ibatis.reflection.ReflectionException;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * DefaultObjectFactoryTest
+ *
+ * @author Ryan Lamore
+ */
+public class DefaultObjectFactoryTest {
+
+  @Test
+  public void instantiateClass() throws Exception {
+    DefaultObjectFactory defaultObjectFactory = new DefaultObjectFactory();
+    TestClass testClass = defaultObjectFactory.instantiateClass(TestClass.class,
+        Arrays.<Class<?>>asList(String.class, Integer.class), Arrays.<Object>asList("foo", 0));
+
+    Assert.assertEquals("myInteger didn't match expected", (Integer) 0, testClass.myInteger);
+    Assert.assertEquals("myString didn't match expected", "foo", testClass.myString);
+  }
+
+  @Test
+  public void instantiateClassThrowsProperErrorMsg() {
+    DefaultObjectFactory defaultObjectFactory = new DefaultObjectFactory();
+    try {
+      defaultObjectFactory.instantiateClass(TestClass.class, Collections.<Class<?>>singletonList(String.class), Collections.<Object>singletonList("foo"));
+      Assert.fail("Should have thrown ReflectionException");
+    } catch (Exception e) {
+      Assert.assertTrue("Should be ReflectionException", e instanceof ReflectionException);
+      Assert.assertTrue("Should not have trailing commas in types list", e.getMessage().contains("(String)"));
+      Assert.assertTrue("Should not have trailing commas in values list", e.getMessage().contains("(foo)"));
+    }
+  }
+
+}
diff --git a/src/test/java/org/apache/ibatis/reflection/factory/TestClass.java b/src/test/java/org/apache/ibatis/reflection/factory/TestClass.java
new file mode 100644
index 00000000000..3af8666d8cf
--- /dev/null
+++ b/src/test/java/org/apache/ibatis/reflection/factory/TestClass.java
@@ -0,0 +1,31 @@
+/**
+ *    Copyright 2009-2015 the original author or authors.
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+package org.apache.ibatis.reflection.factory;
+
+/**
+ * TestClass
+ *
+ * @author Ryan Lamore
+ */
+public class TestClass {
+  String myString;
+  Integer myInteger;
+
+  public TestClass(String myString, Integer myInteger) {
+    this.myString = myString;
+    this.myInteger = myInteger;
+  }
+}
diff --git a/src/test/java/org/apache/ibatis/scripting/defaults/DefaultParameterHandlerTest.java b/src/test/java/org/apache/ibatis/scripting/defaults/DefaultParameterHandlerTest.java
new file mode 100644
index 00000000000..ab21b445fdc
--- /dev/null
+++ b/src/test/java/org/apache/ibatis/scripting/defaults/DefaultParameterHandlerTest.java
@@ -0,0 +1,87 @@
+/**
+ *    Copyright 2009-2015 the original author or authors.
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+package org.apache.ibatis.scripting.defaults;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.ibatis.builder.StaticSqlSource;
+import org.apache.ibatis.mapping.*;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.TypeException;
+import org.apache.ibatis.type.TypeHandler;
+import org.apache.ibatis.type.TypeHandlerRegistry;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * DefaultParameterHandlerTest
+ *
+ * @author Ryan Lamore
+ */
+public class DefaultParameterHandlerTest {
+
+  @Test
+  public void setParametersThrowsProperException() throws SQLException {
+    final MappedStatement mappedStatement = getMappedStatement();
+    final Object parameterObject = null;
+    final BoundSql boundSql = mock(BoundSql.class);
+
+    TypeHandler<String> typeHandler = mock(TypeHandler.class);
+    doThrow(new SQLException("foo")).when(typeHandler).setParameter(any(PreparedStatement.class), anyInt(), anyString(), any(JdbcType.class));
+    ParameterMapping parameterMapping = new ParameterMapping.Builder(mappedStatement.getConfiguration(), "prop", typeHandler).build();
+    List<ParameterMapping> parameterMappings = Collections.singletonList(parameterMapping);
+    when(boundSql.getParameterMappings()).thenReturn(parameterMappings);
+
+    DefaultParameterHandler defaultParameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
+
+    PreparedStatement ps = mock(PreparedStatement.class);
+    try {
+      defaultParameterHandler.setParameters(ps);
+      Assert.fail("Should have thrown TypeException");
+    } catch (Exception e) {
+      Assert.assertTrue("expected TypeException", e instanceof TypeException);
+      Assert.assertTrue("", e.getMessage().contains("mapping: ParameterMapping"));
+    }
+
+  }
+
+  MappedStatement getMappedStatement() {
+    final Configuration config = new Configuration();
+    final TypeHandlerRegistry registry = config.getTypeHandlerRegistry();
+    return new MappedStatement.Builder(config, "testSelect", new StaticSqlSource(config, "some select statement"), SqlCommandType.SELECT).resultMaps(
+        new ArrayList<ResultMap>() {
+          {
+            add(new ResultMap.Builder(config, "testMap", HashMap.class, new ArrayList<ResultMapping>() {
+              {
+                add(new ResultMapping.Builder(config, "cOlUmN1", "CoLuMn1", registry.getTypeHandler(Integer.class)).build());
+              }
+            }).build());
+          }
+        }).build();
+  }
+
+}
diff --git a/src/test/java/org/apache/ibatis/type/UnknownTypeHandlerTest.java b/src/test/java/org/apache/ibatis/type/UnknownTypeHandlerTest.java
index f3daac22e6f..333d5009dbd 100644
--- a/src/test/java/org/apache/ibatis/type/UnknownTypeHandlerTest.java
+++ b/src/test/java/org/apache/ibatis/type/UnknownTypeHandlerTest.java
@@ -15,15 +15,21 @@
  */
 package org.apache.ibatis.type;
 
+import java.sql.SQLException;
+
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.spy;
 
+import org.apache.ibatis.executor.result.ResultMapException;
+import org.junit.Assert;
 import org.junit.Test;
 
 public class UnknownTypeHandlerTest extends BaseTypeHandlerTest {
 
-  private static final TypeHandler<Object> TYPE_HANDLER = new UnknownTypeHandler(new TypeHandlerRegistry());
+  private static final TypeHandler<Object> TYPE_HANDLER = spy(new UnknownTypeHandler(new TypeHandlerRegistry()));
 
   @Test
   public void shouldSetParameter() throws Exception {
@@ -50,4 +56,70 @@ public class UnknownTypeHandlerTest extends BaseTypeHandlerTest {
     assertEquals("Hello", TYPE_HANDLER.getResult(cs, 1));
   }
 
+  @Test
+  public void setParameterWithNullParameter() throws Exception {
+    TYPE_HANDLER.setParameter(ps, 0, null, JdbcType.INTEGER);
+    verify(ps).setNull(0, JdbcType.INTEGER.TYPE_CODE);
+  }
+
+  @Test
+  public void setParameterWithNullParameterThrowsException() throws SQLException {
+    doThrow(new SQLException("invalid column")).when(ps).setNull(1, JdbcType.INTEGER.TYPE_CODE);
+    try {
+      TYPE_HANDLER.setParameter(ps, 1, null, JdbcType.INTEGER);
+      Assert.fail("Should have thrown a TypeException");
+    } catch (Exception e) {
+      Assert.assertTrue("Expected TypedException", e instanceof TypeException);
+      Assert.assertTrue("Parameter index is in exception", e.getMessage().contains("parameter #1"));
+    }
+  }
+
+  @Test
+  public void setParameterWithNonNullParameterThrowsException() throws SQLException {
+    doThrow(new SQLException("invalid column")).when((UnknownTypeHandler)TYPE_HANDLER).setNonNullParameter(ps, 1, 99, JdbcType.INTEGER);
+    try {
+      TYPE_HANDLER.setParameter(ps, 1, 99, JdbcType.INTEGER);
+      Assert.fail("Should have thrown a TypeException");
+    } catch (Exception e) {
+      Assert.assertTrue("Expected TypedException", e instanceof TypeException);
+      Assert.assertTrue("Parameter index is in exception", e.getMessage().contains("parameter #1"));
+    }
+  }
+
+  @Test
+  public void getResultWithResultSetAndColumnNameThrowsException() throws SQLException {
+    doThrow(new SQLException("invalid column")).when((UnknownTypeHandler)TYPE_HANDLER).getNullableResult(rs, "foo");
+    try {
+      TYPE_HANDLER.getResult(rs, "foo");
+      Assert.fail("Should have thrown a ResultMapException");
+    } catch (Exception e) {
+      Assert.assertTrue("Expected ResultMapException", e instanceof ResultMapException);
+      Assert.assertTrue("column name is not in exception", e.getMessage().contains("column 'foo'"));
+    }
+  }
+
+  @Test
+  public void getResultWithResultSetAndColumnIndexThrowsException() throws SQLException {
+    doThrow(new SQLException("invalid column")).when((UnknownTypeHandler)TYPE_HANDLER).getNullableResult(rs, 1);
+    try {
+      TYPE_HANDLER.getResult(rs, 1);
+      Assert.fail("Should have thrown a ResultMapException");
+    } catch (Exception e) {
+      Assert.assertTrue("Expected ResultMapException", e instanceof ResultMapException);
+      Assert.assertTrue("column index is not in exception", e.getMessage().contains("column #1"));
+    }
+  }
+
+  @Test
+  public void getResultWithCallableStatementAndColumnIndexThrowsException() throws SQLException {
+    doThrow(new SQLException("invalid column")).when((UnknownTypeHandler)TYPE_HANDLER).getNullableResult(cs, 1);
+    try {
+      TYPE_HANDLER.getResult(cs, 1);
+      Assert.fail("Should have thrown a ResultMapException");
+    } catch (Exception e) {
+      Assert.assertTrue("Expected ResultMapException", e instanceof ResultMapException);
+      Assert.assertTrue("column index is not in exception", e.getMessage().contains("column #1"));
+    }
+  }
+
 }
\ No newline at end of file
-- 
GitLab