<< What is this dynamic language hype all about? | Home | Dojo for beginners >>

iBATIS: constructor based bean mapping

constructor based injection

Sadly iBATIS (version 2.3) does not allow constructor injection for mapping columns to bean properties. Thus it is quite difficult to use immutable objects with iBATIS. Normally I don't like immutable objects that much but sometimes you just have to deal with them. Note that the following applies only to the java version of iBATIS. iBATIS.NET is able to do constructor based bean mapping.

In the following snippets I'll be using a custom TypeHandlerCallback for mapping my immutable objects to the database.

sqlmap-config.xml: resultmap mapping: typeHandler: MyImmutableObject:



Re: iBATIS: constructor based bean mapping

Great. Thank you!

Re: iBATIS: constructor based bean mapping

The implementation given in the blog post seems to require each javaType to be explicitly set when an injected TypeHandler is used.

Here is an alternate implementation that wires the injected TypeHandlers in such away that iBATIS's normal support for javaType inference will still function:



import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.core.io.Resource;
import org.springframework.orm.ibatis.SqlMapClientFactoryBean;

import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.extensions.TypeHandlerCallback;
import com.ibatis.sqlmap.engine.impl.SqlMapClientImpl;
import com.ibatis.sqlmap.engine.mapping.result.ResultMap;
import com.ibatis.sqlmap.engine.mapping.result.ResultMapping;
import com.ibatis.sqlmap.engine.type.CustomTypeHandler;
import com.ibatis.sqlmap.engine.type.TypeHandler;


/**
 * Extends Spring's SqlMapClientFactoryBean to support injecting managed iBATIS
 * TypeHandlerCallbacks.
 * 
 * It is recommended to declare the type handler both in the iBATIS sqlMapConfig XML
 * configuration, as well as in the Spring application context. This will allow
 * iBATIS to support inferring the javaType used in result maps. The corresponding
 * type handler will be instantiated twice: first by iBATIS, outside of the
 * application context, and second by Spring, via the application context.
 * 
 * Type handlers declared in the sqlMapConfig must support being instantiated with a
 * no-arg constructor without causing an exception, but need not be in a working
 * state; the Spring-managed type handlers will be installed immediately following
 * construction of the SqlMapClient.
 * 
 * Alternatively, it is possible to omit the type handler declaration in the
 * sqlMapConfig, but the javaType will then need to specified for each property
 * expected to use a managed type handler.
 */
public class CustomSqlMapClientFactoryBean extends SqlMapClientFactoryBean {

    private static final Logger logger = LoggerFactory.getLogger(CustomSqlMapClientFactoryBean.class);
   
    private Map<String, TypeHandlerCallback> handlerCallbacks;
   
   
    public CustomSqlMapClientFactoryBean() {
        super();
    }
   
   
    public void setTypeHandlerCallbacks(Map<String, TypeHandlerCallback> handlerCallbacks) {
        this.handlerCallbacks = handlerCallbacks;
    }
   
    @Override
    protected SqlMapClient buildSqlMapClient(Resource[] configLocations, Resource[] mappingLocations, Properties properties)
            throws IOException {
        SqlMapClient client = super.buildSqlMapClient(configLocations, mappingLocations, properties);
        installTypeHandlers((SqlMapClientImpl) client);
        return client;
    }

    private void installTypeHandlers(SqlMapClientImpl client) {
        if (handlerCallbacks != null) {
            Map<String, TypeHandler> handlersByJavaType = createTypeHandlersFromCallbacks();
            Map<Class<? extends TypeHandler>, TypeHandler> handlersByHandlerClass = mapByHandlerClass(handlersByJavaType);
           
            // Register the type handlers with the TypeHandlerFactory
            for (String javaTypeName : handlersByJavaType.keySet()) {
                TypeHandler handler = handlersByJavaType.get(javaTypeName);
                Class<?> javaType;
                try {
                    javaType = Class.forName(javaTypeName);
                } catch (ClassNotFoundException ex) {
                    throw new BeanInitializationException(
                            "Initializing the type handler for the type "+javaTypeName+" failed. " +
                            "The class "+javaTypeName+" was not found.", ex
                    );
                }
               
                logger.info("Registering the handler "+handler.getClass()+" with the iBATIS TypeHandlerFactory");
                client.delegate.getTypeHandlerFactory().register(javaType, handler);
            }
           
            // Replace the type handlers bound to the existing result set mappers
            for (Iterator<?> i = client.getDelegate().getResultMapNames(); i.hasNext();) {
                String name = (String) i.next();
                ResultMap map = client.getDelegate().getResultMap(name);
               
                logger.debug("Installing handlers for the ResultMap "+name);

                for (ResultMapping rm : map.getResultMappings()) {
                    logger.debug("Looking for handler to inject for the column "+rm.getColumnName() +"...");
                    TypeHandler injectedHandler = null;
                   
                    Class<? extends TypeHandler> typeHandlerClass = null;
                    if (rm.getTypeHandler() != null) {
                        typeHandlerClass = rm.getTypeHandler().getClass();
                    }
                   
                    String javaTypeClassName = null;
                    if (rm.getJavaType() != null) {
                        javaTypeClassName = rm.getJavaType().getName();
                    }
                   
                   
                    if (javaTypeClassName != null && handlersByJavaType.containsKey(javaTypeClassName)) {
                        injectedHandler = handlersByJavaType.get(javaTypeClassName);
                    } else if (typeHandlerClass != null && handlersByHandlerClass.containsKey(typeHandlerClass)) {
                        injectedHandler = handlersByHandlerClass.get(typeHandlerClass);
                    }
                   
                    if (injectedHandler != null) {
                        logger.info("Adding handler "+typeHandlerClass+" for "+rm.getColumnName()+" on the ResultMap "+name);
                        rm.setTypeHandler(injectedHandler);
                    }
                }
            }
        }
    }

    private Map<String, TypeHandler> createTypeHandlersFromCallbacks() {
        Map<String, TypeHandler> handlers = new HashMap<String, TypeHandler>();
        for (String key : handlerCallbacks.keySet()) {
            handlers.put(key, new CustomTypeHandler(handlerCallbacks.get(key)));
        }
        return handlers;
    }
   
    private Map<Class<? extends TypeHandler>, TypeHandler> mapByHandlerClass(Map<String, TypeHandler> handlers) {
        Map<Class<? extends TypeHandler>, TypeHandler> results = new HashMap<Class<? extends TypeHandler>, TypeHandler>();
        for (TypeHandler handler : handlers.values()) {
            results.put(handler.getClass(), handler);
        }
        return results;
     }
   
}

Add a comment Send a TrackBack