com.haiegoo.framework.ibatis.SqlMapClientMasterSlaveTemplate.java Source code

Java tutorial

Introduction

Here is the source code for com.haiegoo.framework.ibatis.SqlMapClientMasterSlaveTemplate.java

Source

/*
 * Copyright 2002-2010 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 com.haiegoo.framework.ibatis;

import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapExecutor;
import com.ibatis.sqlmap.client.SqlMapSession;
import com.ibatis.sqlmap.client.event.RowHandler;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.jdbc.JdbcUpdateAffectedIncorrectNumberOfRowsException;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
import org.springframework.jdbc.support.JdbcAccessor;
import org.springframework.orm.ibatis.SqlMapClientCallback;
import org.springframework.orm.ibatis.SqlMapClientFactoryBean;
import org.springframework.orm.ibatis.SqlMapClientOperations;
import org.springframework.util.Assert;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Random;

/**
 * Helper class that simplifies data access via the iBATIS
 * {@link com.ibatis.sqlmap.client.SqlMapClient} API, converting checked
 * SQLExceptions into unchecked DataAccessExceptions, following the
 * <code>org.springframework.dao</code> exception hierarchy.
 * Uses the same {@link org.springframework.jdbc.support.SQLExceptionTranslator}
 * mechanism as {@link org.springframework.jdbc.core.JdbcTemplate}.
 *
 * <p>The main method of this class executes a callback that implements a
 * data access action. Furthermore, this class provides numerous convenience
 * methods that mirror {@link com.ibatis.sqlmap.client.SqlMapExecutor}'s
 * execution methods.
 *
 * <p>It is generally recommended to use the convenience methods on this template
 * for plain query/insert/update/delete operations. However, for more complex
 * operations like batch updates, a custom SqlMapClientCallback must be implemented,
 * usually as anonymous inner class. For example:
 *
 * <pre class="code">
 * getSqlMapClientTemplate().execute(new SqlMapClientCallback() {
 *     public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
 *        executor.startBatch();
 *        executor.update("insertSomething", "myParamValue");
 *        executor.update("insertSomethingElse", "myOtherParamValue");
 *        executor.executeBatch();
 *        return null;
 *     }
 * });</pre>
 *
 * The template needs a SqlMapClient to work on, passed in via the "sqlMapClient"
 * property. A Spring context typically uses a {@link SqlMapClientFactoryBean}
 * to build the SqlMapClient. The template an additionally be configured with a
 * DataSource for fetching Connections, although this is not necessary if a
 * DataSource is specified for the SqlMapClient itself (typically through
 * SqlMapClientFactoryBean's "dataSource" property).
 *
 * @author Juergen Hoeller
 * @since 24.02.2004
 * @see #execute
 * @see #setSqlMapClient
 * @see #setDataSource
 * @see #setExceptionTranslator
 * @see SqlMapClientFactoryBean#setDataSource
 * @see com.ibatis.sqlmap.client.SqlMapClient#getDataSource
 * @see com.ibatis.sqlmap.client.SqlMapExecutor
 */
public class SqlMapClientMasterSlaveTemplate extends JdbcAccessor
        implements SqlMapClientOperations, InitializingBean {

    private SqlMapClient sqlMapClientMaster;
    private SqlMapClient[] sqlMapClientSlaves;

    private Resource configLocation;
    private DataSource master;
    private DataSource[] slaves;

    /**
     * Create a new SqlMapClientTemplate.
     */
    public SqlMapClientMasterSlaveTemplate() {
    }

    /**
     * Create a new SqlMapTemplate.
     * @param sqlMapClient iBATIS SqlMapClient that defines the mapped statements
     */
    public SqlMapClientMasterSlaveTemplate(Resource configLocation, DataSource master, DataSource[] slaves) {
        this.configLocation = configLocation;
        this.master = master;
        this.slaves = slaves;
        afterPropertiesSet();
    }

    /**
     * ? SqlMapClient
     * @return
     */
    public SqlMapClient getSqlMapClientMaster() {
        return sqlMapClientMaster;
    }

    /**
     * ?? SqlMapClient
     * @return
     */
    public SqlMapClient getSqlMapClientSlave() {
        return this.sqlMapClientSlaves[Math.abs(new Random().nextInt()) % sqlMapClientSlaves.length];
    }

    /**
     * ? SqlMapClient 
     * @return
     */
    public SqlMapClient[] getSqlMapClientSlaves() {
        return sqlMapClientSlaves;
    }

    /**
     * iBATIS SqlMapClient ? "WEB-INF/sql-map-config.xml".
     * @see #setConfigLocations
     */
    public void setConfigLocation(Resource configLocation) {
        this.configLocation = configLocation;
    }

    /**
     * ??
     * @return
     */
    public void setMaster(DataSource slaves) {
        this.master = slaves;
    }

    /**
     * ??
     * @return
     */
    public void setSlave(DataSource slave) {
        this.slaves = (slave != null ? new DataSource[] { slave } : null);
    }

    /**
     * ??
     * @return
     */
    public void setSlaves(DataSource[] slaves) {
        this.slaves = slaves;
    }

    @Override
    public void afterPropertiesSet() {
        if (this.configLocation == null) {
            throw new IllegalArgumentException("Property 'configLocation' is required");
        }
        if (this.master == null) {
            throw new IllegalArgumentException("Property 'master' is required");
        }
        if (this.slaves == null || this.slaves.length <= 0) {
            throw new IllegalArgumentException("Property 'slaves' is required");
        }

        try {
            //
            SqlMapClientFactoryBean sqlMapClientMasterFactoryBean = new SqlMapClientFactoryBean();
            sqlMapClientMasterFactoryBean.setConfigLocation(configLocation);
            sqlMapClientMasterFactoryBean.setDataSource(master);
            sqlMapClientMasterFactoryBean.afterPropertiesSet();
            this.sqlMapClientMaster = sqlMapClientMasterFactoryBean.getObject();

            //
            this.sqlMapClientSlaves = new SqlMapClient[slaves.length];
            for (int i = 0; i < slaves.length; i++) {
                SqlMapClientFactoryBean sqlMapClientSlaveFactoryBean = new SqlMapClientFactoryBean();
                sqlMapClientSlaveFactoryBean.setConfigLocation(configLocation);
                sqlMapClientSlaveFactoryBean.setDataSource(slaves[i]);
                sqlMapClientSlaveFactoryBean.afterPropertiesSet();
                this.sqlMapClientSlaves[i] = sqlMapClientSlaveFactoryBean.getObject();
            }

        } catch (Exception e) {
            throw new RuntimeException("Initialize SqlMapClientMasterSlaveTemplate exception", e);
        }

        super.afterPropertiesSet();
    }

    public Object queryForObject(String statementName) throws DataAccessException {
        return queryForObject(statementName, null);
    }

    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);
            }
        }, this.getSqlMapClientSlave());
    }

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

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

    @SuppressWarnings("rawtypes")
    public List queryForList(String statementName) throws DataAccessException {
        return queryForList(statementName, null);
    }

    @SuppressWarnings("rawtypes")
    public List queryForList(final String statementName, final Object parameterObject) throws DataAccessException {

        return execute(new SqlMapClientCallback<List>() {
            public List doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                return executor.queryForList(statementName, parameterObject);
            }
        }, this.getSqlMapClientSlave());
    }

    @SuppressWarnings("rawtypes")
    public List queryForList(String statementName, int skipResults, int maxResults) throws DataAccessException {
        return queryForList(statementName, null, skipResults, maxResults);
    }

    @SuppressWarnings("rawtypes")
    public List queryForList(final String statementName, final Object parameterObject, final int skipResults,
            final int maxResults) throws DataAccessException {

        return execute(new SqlMapClientCallback<List>() {
            public List doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                return executor.queryForList(statementName, parameterObject, skipResults, maxResults);
            }
        }, this.getSqlMapClientSlave());
    }

    public void queryWithRowHandler(String statementName, RowHandler rowHandler) throws DataAccessException {
        queryWithRowHandler(statementName, null, rowHandler);
    }

    public void queryWithRowHandler(final String statementName, final Object parameterObject,
            final RowHandler rowHandler) throws DataAccessException {

        execute(new SqlMapClientCallback<Object>() {
            public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                executor.queryWithRowHandler(statementName, parameterObject, rowHandler);
                return null;
            }
        }, this.getSqlMapClientSlave());
    }

    @SuppressWarnings("rawtypes")
    public Map queryForMap(final String statementName, final Object parameterObject, final String keyProperty)
            throws DataAccessException {

        return execute(new SqlMapClientCallback<Map>() {
            public Map doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                return executor.queryForMap(statementName, parameterObject, keyProperty);
            }
        }, this.getSqlMapClientSlave());
    }

    @SuppressWarnings("rawtypes")
    public Map queryForMap(final String statementName, final Object parameterObject, final String keyProperty,
            final String valueProperty) throws DataAccessException {

        return execute(new SqlMapClientCallback<Map>() {
            public Map doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                return executor.queryForMap(statementName, parameterObject, keyProperty, valueProperty);
            }
        }, this.getSqlMapClientSlave());
    }

    public Object insert(String statementName) throws DataAccessException {
        return insert(statementName, null);
    }

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

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

    public int update(String statementName) throws DataAccessException {
        return update(statementName, null);
    }

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

        return execute(new SqlMapClientCallback<Integer>() {
            public Integer doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                return executor.update(statementName, parameterObject);
            }
        }, this.getSqlMapClientMaster());
    }

    public void update(String statementName, Object parameterObject, int requiredRowsAffected)
            throws DataAccessException {

        int actualRowsAffected = update(statementName, parameterObject);
        if (actualRowsAffected != requiredRowsAffected) {
            throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(statementName, requiredRowsAffected,
                    actualRowsAffected);
        }
    }

    public int delete(String statementName) throws DataAccessException {
        return delete(statementName, null);
    }

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

        return execute(new SqlMapClientCallback<Integer>() {
            public Integer doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                return executor.delete(statementName, parameterObject);
            }
        }, this.getSqlMapClientMaster());
    }

    public void delete(String statementName, Object parameterObject, int requiredRowsAffected)
            throws DataAccessException {

        int actualRowsAffected = delete(statementName, parameterObject);
        if (actualRowsAffected != requiredRowsAffected) {
            throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(statementName, requiredRowsAffected,
                    actualRowsAffected);
        }
    }

    /**
     * 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
     */
    public <T> T execute(SqlMapClientCallback<T> action) throws DataAccessException {
        return execute(action, this.getSqlMapClientMaster());
    }

    /**
     * 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
     */
    public <T> T execute(SqlMapClientCallback<T> action, SqlMapClient sqlMapClient) throws DataAccessException {
        Assert.notNull(action, "Callback object must not be null");
        Assert.notNull(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...

        SqlMapSession session = sqlMapClient.openSession();
        if (logger.isDebugEnabled()) {
            logger.debug("Opened SqlMapSession [" + session + "] for iBATIS operation");
        }
        Connection ibatisCon = null;

        try {
            Connection springCon = null;
            DataSource dataSource = sqlMapClient.getDataSource();
            boolean transactionAware = (dataSource instanceof TransactionAwareDataSourceProxy);

            // Obtain JDBC Connection to operate on...
            try {
                ibatisCon = session.getCurrentConnection();
                if (ibatisCon == null) {
                    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 {
                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.
        } finally {
            // Only close SqlMapSession if we know we've actually opened it
            // at the present level.
            if (ibatisCon == null) {
                session.close();
            }
        }
    }

    /**
     * If no DataSource specified, use SqlMapClient's DataSource.
     * @see com.ibatis.sqlmap.client.SqlMapClient#getDataSource()
     */
    @Override
    @Deprecated
    public DataSource getDataSource() {
        DataSource ds = super.getDataSource();
        return (ds != null ? ds : this.sqlMapClientMaster.getDataSource());
    }

    /**
     * Set the JDBC DataSource to obtain connections from.
     */
    @Override
    @Deprecated
    public void setDataSource(DataSource dataSource) {
        super.setDataSource(dataSource);
    }

}