Index: src/com/ibatis/sqlmap/engine/mapping/result/loader/EnhancedLazyResultLoader.java =================================================================== --- src/com/ibatis/sqlmap/engine/mapping/result/loader/EnhancedLazyResultLoader.java (revision 774245) +++ src/com/ibatis/sqlmap/engine/mapping/result/loader/EnhancedLazyResultLoader.java (working copy) @@ -108,9 +118,14 @@ } else if (targetType.isArray() || ClassInfo.isKnownType(targetType)) { return ResultLoader.getResult(client, statementName, parameterObject, targetType); } else { - return Enhancer.create(targetType, this); + return Enhancer.create(getProxyType(), this); } } + + private Class getProxyType() { + Class resultType = client.getMappedStatement(statementName).getResultMap().getResultClass(); + return resultType != null ? resultType : targetType; + } public Object invoke(Object o, Method method, Object[] objects) throws Throwable { if ("finalize".hashCode() == method.getName().hashCode() @@ -118,15 +133,11 @@ return null; } else { loadObject(); - if (resultObject != null) { - try { - return method.invoke(resultObject, objects); - } catch (Throwable t) { - throw ClassInfo.unwrapThrowable(t); - } - } else { - return null; - } + try { + return method.invoke(resultObject, objects); + } catch (Throwable t) { + throw ClassInfo.unwrapThrowable(t); + } } } @@ -135,6 +146,11 @@ try { loaded = true; resultObject = ResultLoader.getResult(client, statementName, parameterObject, targetType); + if (resultObject == null) { + throw new LazyLoadingException("Lazy loaded object is no longer available. Statement '" + + statementName + "' with parameter '" + parameterObject + "'.", + statementName, parameterObject); + } } catch (SQLException e) { throw new RuntimeException("Error lazy loading result. Cause: " + e, e); } Index: src/com/ibatis/sqlmap/engine/mapping/result/loader/LazyLoadingException.java =================================================================== --- src/com/ibatis/sqlmap/engine/mapping/result/loader/LazyLoadingException.java (revision 0) +++ src/com/ibatis/sqlmap/engine/mapping/result/loader/LazyLoadingException.java (revision 0) @@ -0,0 +1,40 @@ +package com.ibatis.sqlmap.engine.mapping.result.loader; + +/** + * Thrown if a error occurs during lazy loading of an object. For example + * if a lazy loaded object should be loaded which is already deleted. + */ +public class LazyLoadingException extends RuntimeException { + + private String statement; + private Object parameterObject; + + public LazyLoadingException() { + } + + public LazyLoadingException(String msg, String statement, Object parameterObject) { + this(msg); + this.statement = statement; + this.parameterObject = parameterObject; + } + + public LazyLoadingException(String message, Throwable cause) { + super(message, cause); + } + + public LazyLoadingException(String message) { + super(message); + } + + public LazyLoadingException(Throwable cause) { + super(cause); + } + + public String getStatement() { + return statement; + } + + public Object getParameterObject() { + return parameterObject; + } +} Index: test/com/ibatis/sqlmap/WholePartTest.java =================================================================== --- test/com/ibatis/sqlmap/WholePartTest.java (revision 0) +++ test/com/ibatis/sqlmap/WholePartTest.java (revision 0) @@ -0,0 +1,96 @@ +package com.ibatis.sqlmap; + +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; + +import com.ibatis.sqlmap.engine.mapping.result.loader.LazyLoadingException; + +import testdomain.Part; +import testdomain.Whole; + +public class WholePartTest extends BaseSqlMapTest { + + // SETUP & TEARDOWN + + protected void setUp() throws Exception { + initSqlMap("com/ibatis/sqlmap/maps/WholePart-SqlMap.xml", null); + initScript("scripts/whole-part-relation-init.sql"); + } + + public void testWholePartRelationWithParameterObject() throws SQLException { + Whole whole = (Whole) sqlMap.queryForObject("select-whole-parameter-object", 1); + assertEquals("Whole 1", whole.getName()); + List parts = whole.getParts(); + assertEquals(2, parts.size()); + assertEquals("Part 1", parts.get(0).getName()); + assertEquals("Part 2", parts.get(1).getName()); + } + + public void testWholePartRelationEmptyWithParameterObject() throws SQLException { + Whole whole = (Whole) sqlMap.queryForObject("select-whole-parameter-object", 2); + assertEquals("Whole 2", whole.getName()); + List parts = whole.getParts(); + assertEquals(0, parts.size()); + } + + public void testPartWholeRelationWithParameterObject() throws SQLException { + Part part = (Part) sqlMap.queryForObject("select-part-parameter-object", 1); + assertEquals("Part 1", part.getName()); + assertEquals("Whole 1", part.getWhole().getName()); + } + + public void testPartWholeRelationEmptyWithParameterObject() throws SQLException { + Part part = (Part) sqlMap.queryForObject("select-part-parameter-object", 3); + assertEquals("Part 3", part.getName()); + assertNull(part.getWhole()); + } + + + public void testWholePartRelationWithParameterMap() throws SQLException { + Whole whole = (Whole) sqlMap.queryForObject("select-whole-parameter-map", Collections.singletonMap("id", 1)); + assertEquals("Whole 1", whole.getName()); + List parts = whole.getParts(); + assertEquals(2, parts.size()); + assertEquals("Part 1", parts.get(0).getName()); + assertEquals("Part 2", parts.get(1).getName()); + } + + public void testWholePartRelationEmptyWithParameterMap() throws SQLException { + Whole whole = (Whole) sqlMap.queryForObject("select-whole-parameter-map", Collections.singletonMap("id", 2)); + assertEquals("Whole 2", whole.getName()); + List parts = whole.getParts(); + assertEquals(0, parts.size()); + } + + public void testPartWholeRelationWithParameterMap() throws SQLException { + Part part = (Part) sqlMap.queryForObject("select-part-parameter-map", Collections.singletonMap("id", 1)); + assertEquals("Part 1", part.getName()); + assertEquals("Whole 1", part.getWhole().getName()); + } + + public void testPartWholeRelationEmptyWithParameterMap() throws SQLException { + Part part = (Part) sqlMap.queryForObject("select-part-parameter-map", Collections.singletonMap("id", 3)); + assertEquals("Part 3", part.getName()); + assertNull(part.getWhole()); + } + + public void testDeletedLazyLoadedRelation() throws SQLException { + Part part = (Part) sqlMap.queryForObject("select-part-parameter-object", 1); + Whole whole = part.getWhole(); + assertNotNull(whole); + + // now delete the relation to "whole" + sqlMap.delete("delete-whole", 1); + + try { + // trigger lazy loading + whole.getId(); + fail("whole should not be selectable"); + } catch (LazyLoadingException expected) { + assertEquals("select-whole-parameter-object", expected.getStatement()); + assertEquals(1, expected.getParameterObject()); + } + } + +} Index: test/com/ibatis/sqlmap/engine/mapping/result/loader/EnhancedLazyResultLoaderTest.java =================================================================== --- test/com/ibatis/sqlmap/engine/mapping/result/loader/EnhancedLazyResultLoaderTest.java (revision 0) +++ test/com/ibatis/sqlmap/engine/mapping/result/loader/EnhancedLazyResultLoaderTest.java (revision 0) @@ -0,0 +1,89 @@ +package com.ibatis.sqlmap.engine.mapping.result.loader; + +import java.sql.SQLException; +import java.util.Collections; + +import junit.framework.TestCase; + +import com.ibatis.sqlmap.engine.impl.SqlMapClientImpl; +import com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate; +import com.ibatis.sqlmap.engine.mapping.result.ResultMap; +import com.ibatis.sqlmap.engine.mapping.statement.SelectStatement; + +public class EnhancedLazyResultLoaderTest extends TestCase { + + public void testNullParameterObjectReturnsNullObject() throws SQLException { + SqlMapClientImpl client = setupMockSqlMapClientImpl(); + EnhancedLazyResultLoader loader = new EnhancedLazyResultLoader(client, "select", null, TestBean.class); + Object object = loader.loadResult(); + assertNull(object); + } + + public void testEmptyParameterMapReturnsNullObject() throws SQLException { + SqlMapClientImpl client = setupMockSqlMapClientImpl(); + EnhancedLazyResultLoader loader = new EnhancedLazyResultLoader(client, "select", Collections.emptyMap(), TestBean.class); + Object object = loader.loadResult(); + assertNull(object); + } + + public void testProxyType() throws SQLException { + SqlMapClientImpl client = setupMockSqlMapClientImpl(); + EnhancedLazyResultLoader loader = new EnhancedLazyResultLoader(client, "select", 42, TestBeanInterface.class); + Object object = loader.loadResult(); + assertTrue(object instanceof TestBeanInterface); + assertTrue(object instanceof TestBean); + } + + /** + * Mock a {@link SqlMapClientImpl} which fails if {@link SqlMapClientImpl#queryForObject(String, Object) + * is called. + */ + private SqlMapClientImpl setupMockSqlMapClientImpl() { + SqlMapExecutorDelegate delegate = new SqlMapExecutorDelegate(); + + SelectStatement selectStatement = new SelectStatement(); + selectStatement.setId("select"); + ResultMap resultMap = new ResultMap(delegate); + resultMap.setResultClass(TestBean.class); + selectStatement.setResultMap(resultMap); + + delegate.addMappedStatement(selectStatement); + SqlMapClientImpl client = new SqlMapClientImpl(delegate ) { + @Override + public Object queryForObject(String id, Object paramObject) throws SQLException { + if (id.equals("select")) { + if (paramObject instanceof Integer) { + TestBean bean = new TestBean(); + bean.setId((Integer) paramObject); + return bean; + } + } + fail(); + return null; + } + }; + return client; + } + + public interface TestBeanInterface { + int getId(); + void setId(int id); + } + + /** + * Test bean for {@link EnhancedLazyResultLoader}. + */ + public static class TestBean implements TestBeanInterface { + private int id; + public TestBean() { + } + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + } + + +} Index: test/com/ibatis/sqlmap/maps/WholePartRelation.xml =================================================================== --- test/com/ibatis/sqlmap/maps/WholePartRelation.xml (revision 0) +++ test/com/ibatis/sqlmap/maps/WholePartRelation.xml (revision 0) @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DELETE FROM whole WHERE id = #value# + + \ No newline at end of file