You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ibatis.apache.org by Oleg Shpak <ol...@rap-x.com> on 2005/08/11 15:39:44 UTC

Proxy target type for Lazy loading

Hello,

I'm having a problem with iBatis which I'm keen to solve by patching
iBatis (other solutions exist, but they are a bit cumbersome).

In short, I propose to modify loadResult() method in
com.ibatis.sqlmap.engine.mapping.result.loader.EnhancedLazyResultLoader,
so that is creates more "honest" proxies, proxies which base on lazy
loading statement's resultMap result class, rather than just on method
return type.
---------------------------------------------------------------------------------------------
    public Object loadResult() throws SQLException {
      if (DomTypeMarker.class.isAssignableFrom(targetType)) {
        return ResultLoader.getResult(client, statementName,
parameterObject, targetType);
      } else if (Collection.class.isAssignableFrom(targetType)) {
        return Enhancer.create(Object.class, INTERFACES, this);
      } else if (targetType.isArray() ||
ClassInfo.isKnownType(targetType)) {
        return ResultLoader.getResult(client, statementName,
parameterObject, targetType);
      } else {
        // NEW CODE STARTS
        Class type;
        try {
            ResultMap resultMap =
                client.getDelegate()
                  .getMappedStatement(statementName).getResultMap();
            type = resultMap.getResultClass();
        } catch (SqlMapException e) {
            type = targetType;
        }
        return Enhancer.create(type, this);
        // NEW CODE ENDS
        // old code was
       //   return Enhancer.create(targetType, this);
      }
    }
---------------------------------------------------------------------------------------------



The problem

When a proxy is created for an object, target type is determined using
the property method return type. It is possible that the actual return
value of a method is a subclass of the return type (especially if the
return type is an interface).

// public interfaces
interface A {...}

interface B {
    A getA();
    ...
}

// implementation classes
class A1 implements A { ...}
class B1 implements B {
    private A1 a;
    public A getA(){
       return a; //an instance of A1
    }
   ...
}

// result maps

<resultMap id="A" class="A1">
...
</resultMap>
<resultMap id="B" class="B1">
   <result property="a" column="a_id" select="getA"/>
...
</resultMap>

<select id="getA" resultMap="A">
...
</select>

Here I configured lazy loading for B1.getA. The proxy which is being
generated is based on type A, not on type A1. My DAO class for type A
actually returns instances of type A1. Generally it is OK for it (DAO
class) to consume objects of type A, but in some cases, when it needs to
modify some implementation details it has to cast them down to A1. This
is where proxies cause a ClassCastException. "Honest" proxies don't.


What do you think?
Probably I put my code not into the right place, or it, probably, should
be optionally turned on/off.


Regards,
Oleg




Re: Proxy target type for Lazy loading

Posted by Clinton Begin <cl...@gmail.com>.
Technically it seems sound. It's certainly not as pure as a real proxy based 
on the interface, as there's no way we can assert that the resultmap matches 
the interface. So you'd have to be sure that you unit test appropriately.

The only technical caveat is that you would be REQUIRED to use CGLIB. There 
would be no option, becasue you'd always be creating dynamic proxies for 
concrete classes.

The other thing is a question of simple design practices. If an interface 
contract only guarantees a return value of type A, then you're really making 
an assumption when you cast it to A1. A better design would really be to 
either change the interface to include all functionality that you need, or 
change the return type. That's being more honest with yourself. Ask: "what 
is this interface really hiding if I'm just going to cast out of it anyway?"

IMHO using the result map is less "honest", because you're really "guessing" 
at types by casting them. 

Cheers,
Clinton

On 8/11/05, Oleg Shpak <ol...@rap-x.com> wrote:
> 
> Hello,
> 
> I'm having a problem with iBatis which I'm keen to solve by patching
> iBatis (other solutions exist, but they are a bit cumbersome).
> 
> In short, I propose to modify loadResult() method in
> com.ibatis.sqlmap.engine.mapping.result.loader.EnhancedLazyResultLoader,
> so that is creates more "honest" proxies, proxies which base on lazy
> loading statement's resultMap result class, rather than just on method
> return type.
> 
> ---------------------------------------------------------------------------------------------
> public Object loadResult() throws SQLException {
> if (DomTypeMarker.class.isAssignableFrom(targetType)) {
> return ResultLoader.getResult(client, statementName,
> parameterObject, targetType);
> } else if (Collection.class.isAssignableFrom(targetType)) {
> return Enhancer.create(Object.class, INTERFACES, this);
> } else if (targetType.isArray() ||
> ClassInfo.isKnownType(targetType)) {
> return ResultLoader.getResult(client, statementName,
> parameterObject, targetType);
> } else {
> // NEW CODE STARTS
> Class type;
> try {
> ResultMap resultMap =
> client.getDelegate()
> .getMappedStatement(statementName).getResultMap();
> type = resultMap.getResultClass();
> } catch (SqlMapException e) {
> type = targetType;
> }
> return Enhancer.create(type, this);
> // NEW CODE ENDS
> // old code was
> // return Enhancer.create(targetType, this);
> }
> }
> 
> ---------------------------------------------------------------------------------------------
> 
> 
> 
> The problem
> 
> When a proxy is created for an object, target type is determined using
> the property method return type. It is possible that the actual return
> value of a method is a subclass of the return type (especially if the
> return type is an interface).
> 
> // public interfaces
> interface A {...}
> 
> interface B {
> A getA();
> ...
> }
> 
> // implementation classes
> class A1 implements A { ...}
> class B1 implements B {
> private A1 a;
> public A getA(){
> return a; //an instance of A1
> }
> ...
> }
> 
> // result maps
> 
> <resultMap id="A" class="A1">
> ...
> </resultMap>
> <resultMap id="B" class="B1">
> <result property="a" column="a_id" select="getA"/>
> ...
> </resultMap>
> 
> <select id="getA" resultMap="A">
> ...
> </select>
> 
> Here I configured lazy loading for B1.getA. The proxy which is being
> generated is based on type A, not on type A1. My DAO class for type A
> actually returns instances of type A1. Generally it is OK for it (DAO
> class) to consume objects of type A, but in some cases, when it needs to
> modify some implementation details it has to cast them down to A1. This
> is where proxies cause a ClassCastException. "Honest" proxies don't.
> 
> 
> What do you think?
> Probably I put my code not into the right place, or it, probably, should
> be optionally turned on/off.
> 
> 
> Regards,
> Oleg
> 
> 
> 
>