SPRING技术内幕-笔记(十二)spring驱动ibatis的设计与实现

12.1设计原理
对ibatis的使用相对来说比较简单,在spring的封装中,有几个基本过程,首先需要创建sqlmapclient,这个sqlmapclient类似于在hibernate中的session,对于它的创建,在spring中设计sqlmapclientfactorybean,通过这个factorybean;哎读取sqlmapclient的配置和具体创建。在配置和创建好sqlmapclient之后,在spring中同样为sqlmapclient的使用封装了sqlmapclienttemplate,他同样作为一个模板,封装了通过sqlmapclient完成的主要操作。

12.2创建sqlmapclient
在springIOC容器中,ibatis实例通常通过sqlmapclientfactorybean来设置,在sqlmapfactorybean中完成sqlmapclient的创建,sqlmapclient是用户使用ibatis操作数据库的主要类,这个创建过程包括一些对sqlmapclient的配置过程(比如对数据源打他source的配置),以及对参数的配置,宰割创建过程是在afterpropertiesset中完成,他在依赖注入完成以后被ioc容器回调。
IOC调用回顾:

    protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
            throws Throwable {

        boolean isInitializingBean = (bean instanceof InitializingBean);
        if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
            if (logger.isDebugEnabled()) {
                logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
            }
            if (System.getSecurityManager() != null) {
                try {
                    AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
                        public Object run() throws Exception {
                            ((InitializingBean) bean).afterPropertiesSet();
                            return null;
                        }
                    }, getAccessControlContext());
                }
                catch (PrivilegedActionException pae) {
                    throw pae.getException();
                }
            }               
            else {
            //对bean注入之后进行初始化
                ((InitializingBean) bean).afterPropertiesSet();
            }
        }

        if (mbd != null) {
            String initMethodName = mbd.getInitMethodName();
            if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
                    !mbd.isExternallyManagedInitMethod(initMethodName)) {
                invokeCustomInitMethod(beanName, bean, mbd);
            }
        }
    }

SqlMapClientFactoryBean的getObject以及afterPropertiesSet方法:
public SqlMapClient getObject() {
return this.sqlMapClient;
}

    public void afterPropertiesSet() throws Exception {
        if (this.lobHandler != null) {
            // Make given LobHandler available for SqlMapClient configuration.
            // Do early because because mapping resource might refer to custom types.
            configTimeLobHandlerHolder.set(this.lobHandler);
        }

        try {
        //开始创建sqlMapClient 
            this.sqlMapClient = buildSqlMapClient(this.configLocations, this.mappingLocations, this.sqlMapClientProperties);

            // Tell the SqlMapClient to use the given DataSource, if any.
            //为sqlMapClient 设置数据源
            if (this.dataSource != null) {
                TransactionConfig transactionConfig = (TransactionConfig) this.transactionConfigClass.newInstance();
                DataSource dataSourceToUse = this.dataSource;
                if (this.useTransactionAwareDataSource && !(this.dataSource instanceof TransactionAwareDataSourceProxy)) {
                    dataSourceToUse = new TransactionAwareDataSourceProxy(this.dataSource);
                }
                transactionConfig.setDataSource(dataSourceToUse);
                transactionConfig.initialize(this.transactionConfigProperties);
                applyTransactionConfig(this.sqlMapClient, transactionConfig);
            }
        }

        finally {
            if (this.lobHandler != null) {
                // Reset LobHandler holder.
                configTimeLobHandlerHolder.remove();
            }
        }
    }

buildSqlMapClient方法的实现:

    protected SqlMapClient buildSqlMapClient(
            Resource[] configLocations, Resource[] mappingLocations, Properties properties)
            throws IOException {

        if (ObjectUtils.isEmpty(configLocations)) {
            throw new IllegalArgumentException("At least 1 'configLocation' entry is required");
        }

        SqlMapClient client = null;
        //对应对数据源以及数据源附属参数的设置
        SqlMapConfigParser configParser = new SqlMapConfigParser();
        for (Resource configLocation : configLocations) {
            InputStream is = configLocation.getInputStream();
            try {
                client = configParser.parse(is, properties);
            }
            catch (RuntimeException ex) {
                throw new NestedIOException("Failed to parse config resource: " + configLocation, ex.getCause());
            }
        }
//对映射文件的初始化。
        if (mappingLocations != null) {
            SqlMapParser mapParser = SqlMapParserFactory.createSqlMapParser(configParser);
            for (Resource mappingLocation : mappingLocations) {
                try {
                    mapParser.parse(mappingLocation.getInputStream());
                }
                catch (NodeletException ex) {
                    throw new NestedIOException("Failed to parse mapping resource: " + mappingLocation, ex);
                }
            }
        }

        return client;
    }

12.3 sqlmapclienttemplate的实现

sqlmapclienttemplate封装了对ibatis的操作,其继承关系如下:
SPRING技术内幕-笔记(十二)spring驱动ibatis的设计与实现_第1张图片

sqlmapclienttemplate是一个核心,这个类持有一个sqlmapclient,这个类是ibatis的类,是直接使用ibatis时调用的,它提供了使用ibatis的API,类似于hibernate的session,同时,sqlmapclientdaosupport中持有一个salmapclienttemplate,尽管对ibatis的操作是由sqlmapclienttemplate来完成,但应用可以通过扩展sqlmapclientdaosupport来对ibatis进行操作。spring封装ibatis操作基本上封装在sqlmapclienttemplate的设计和实现中。其设计时序图:
SPRING技术内幕-笔记(十二)spring驱动ibatis的设计与实现_第2张图片

在这个时序过程中,template会从sqlmapclient中得到session和datasource,并进行一系列的初始化过程,然后回调sqlmapclientcallback的doinsqlmapclient方法执行具体的动作,最后释放数据库连接和关闭session。
代码实现:

/** * Execute the given data access action on a SqlMapExecutor. * @param action callback object that specifies the data access action * @return a result object returned by the action, or <code>null</code> * @throws DataAccessException in case of SQL Maps errors */
     //使用SqlMapExecutor完成数据的操作
    public <T> T execute(SqlMapClientCallback<T> action) throws DataAccessException {
        Assert.notNull(action, "Callback object must not be null");
        Assert.notNull(this.sqlMapClient, "No SqlMapClient specified");

        // We always need to use a SqlMapSession, as we need to pass a Spring-managed
        // Connection (potentially transactional) in. This shouldn't be necessary if
        // we run against a TransactionAwareDataSourceProxy underneath, but unfortunately
        // we still need it to make iBATIS batch execution work properly: If iBATIS
        // doesn't recognize an existing transaction, it automatically executes the
        // batch for every single statement...
         //通过sqlMapClient完成sqlmapsession
        SqlMapSession session = this.sqlMapClient.openSession();
        if (logger.isDebugEnabled()) {
            logger.debug("Opened SqlMapSession [" + session + "] for iBATIS operation");
        }
        Connection ibatisCon = null;
        //得到数据源
        try {
            Connection springCon = null;
            DataSource dataSource = getDataSource();
            boolean transactionAware = (dataSource instanceof TransactionAwareDataSourceProxy);

            // Obtain JDBC Connection to operate on...
            try {
                ibatisCon = session.getCurrentConnection();
                if (ibatisCon == null) {
                //此处将要获取connection,如果已经在spring的事物管理之下,数据源直接拿过来使用,否则就是用
                //DataSourceUtils生成一个connection,并将得到的connnection放在spring的事物管理之下。
                    springCon = (transactionAware ?
                            dataSource.getConnection() : DataSourceUtils.doGetConnection(dataSource));
                    session.setUserConnection(springCon);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Obtained JDBC Connection [" + springCon + "] for iBATIS operation");
                    }
                }
                else {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Reusing JDBC Connection [" + ibatisCon + "] for iBATIS operation");
                    }
                }
            }
            catch (SQLException ex) {
                throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
            }

            // Execute given callback...
            try {
             //执行SqlMapClientCallback的回调函数
                return action.doInSqlMapClient(session);
            }
            catch (SQLException ex) {
                throw getExceptionTranslator().translate("SqlMapClient operation", null, ex);
            }
            finally {
                try {
                    if (springCon != null) {
                        if (transactionAware) {
                            springCon.close();
                        }
                        else {
                            DataSourceUtils.doReleaseConnection(springCon, dataSource);
                        }
                    }
                }
                catch (Throwable ex) {
                    logger.debug("Could not close JDBC Connection", ex);
                }
            }

            // Processing finished - potentially session still to be closed.
        }
        //释放datasource
        finally {
            // Only close SqlMapSession if we know we've actually opened it
            // at the present level.
            if (ibatisCon == null) {
                session.close();
            }
        }
    }

sqlmapclienttemplate的其他方法也是调用exector方法提供回调函数实现的。
比如:

    public Object queryForObject(final String statementName, final Object parameterObject)
            throws DataAccessException {

        return execute(new SqlMapClientCallback<Object>() {
            public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                return executor.queryForObject(statementName, parameterObject);
            }
        });
    }

关于datasource的关闭以及事物的绑定都已经做好了封装。

你可能感兴趣的:(ibatis,技术,配置,设计,封装)