首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 开源软件 >

解决Spring导致iBatis缓存失效有关问题

2012-08-17 
解决Spring导致iBatis缓存失效问题版本:? Spring 3.0.4(2.x版本中也存在类似问题)? iBatis 2.3.4.726(2.3.

解决Spring导致iBatis缓存失效问题

版本:
? Spring 3.0.4(2.x版本中也存在类似问题)
? iBatis 2.3.4.726(2.3.x版本都适用)

起因:
? 使用Spring管理iBatis实例,标准方式采用SqlMapClientFactoryBean创建SqlMapClient

?

<bean id="sqlMapClient" /><property name="dataSource" ref="dataSource" /><property name="mappingLocations" value="classpath*:/**/sqlmap/*SqlMap.xml" /></bean>



使用mappingLocations方式定义可以让sqlmap不必添加到sqlMapConfig.xml
即避免了如下形式

?

?

<sqlMapConfig>...  <sqlMap url="someSqlMap" resource="com/foo/xxx/someSqlMap.xml"/>...</sqlMapConfig>

?

随之而来的问题是在sqlMap中的cache无法在执行预订方法时自动flush!

<cacheModel id="mycache" type ="LRU" readOnly="false" serialize="false">  <flushInterval hours="24"/>  <flushOnExecute statement="myobj.insert"/>  <property name="cache-size" value="50" /></cacheModel>

?

在myobj.insert被执行时cache不会flush,即无报错也无提醒,彻头彻尾的坑爹,不清楚Spring开发组为什么会让这问题一直存在。

产生问题的原因网上有不少分析贴(不再重复,需要的可搜索相关帖子),原因是Spring在处理mappingLocations之前,iBatis已经完成对sqlMapConfig里注册的sqlMap的cache设置(真绕口=.="),非亲生的sqlMap被无情的抛弃了。解决办法大都建议放弃mappingLocations的方式,把sqlMap写到sqlMapConfig里——还真是够简单——或者说够偷懒。

找到原因就想办法解决吧,从SqlMapClientFactoryBean下手,扩展一份

/*** * @author Foxswily*/@SuppressWarnings("deprecation")public class MySqlMapClientFactoryBean implements FactoryBean<SqlMapClient>,        InitializingBean {    private static final ThreadLocal<LobHandler> configTimeLobHandlerHolder = new ThreadLocal<LobHandler>();    /**     * Return the LobHandler for the currently configured iBATIS SqlMapClient,     * to be used by TypeHandler implementations like ClobStringTypeHandler.     * <p>     * This instance will be set before initialization of the corresponding     * SqlMapClient, and reset immediately afterwards. It is thus only available     * during configuration.     *      * @see #setLobHandler     * @see org.springframework.orm.ibatis.support.ClobStringTypeHandler     * @see org.springframework.orm.ibatis.support.BlobByteArrayTypeHandler     * @see org.springframework.orm.ibatis.support.BlobSerializableTypeHandler     */    public static LobHandler getConfigTimeLobHandler() {        return configTimeLobHandlerHolder.get();    }    private Resource[] configLocations;    private Resource[] mappingLocations;    private Properties sqlMapClientProperties;    private DataSource dataSource;    private boolean useTransactionAwareDataSource = true;    @SuppressWarnings("rawtypes")    private Class transactionConfigClass = ExternalTransactionConfig.class;    private Properties transactionConfigProperties;    private LobHandler lobHandler;    private SqlMapClient sqlMapClient;    public MySqlMapClientFactoryBean() {        this.transactionConfigProperties = new Properties();        this.transactionConfigProperties.setProperty("SetAutoCommitAllowed", "false");    }    /**     * Set the location of the iBATIS SqlMapClient config file. A typical value     * is "WEB-INF/sql-map-config.xml".     *      * @see #setConfigLocations     */    public void setConfigLocation(Resource configLocation) {        this.configLocations = (configLocation != null ? new Resource[] { configLocation }                : null);    }    /**     * Set multiple locations of iBATIS SqlMapClient config files that are going     * to be merged into one unified configuration at runtime.     */    public void setConfigLocations(Resource[] configLocations) {        this.configLocations = configLocations;    }    /**     * Set locations of iBATIS sql-map mapping files that are going to be merged     * into the SqlMapClient configuration at runtime.     * <p>     * This is an alternative to specifying "&lt;sqlMap&gt;" entries in a     * sql-map-client config file. This property being based on Spring's     * resource abstraction also allows for specifying resource patterns here:     * e.g. "/myApp/*-map.xml".     * <p>     * Note that this feature requires iBATIS 2.3.2; it will not work with any     * previous iBATIS version.     */    public void setMappingLocations(Resource[] mappingLocations) {        this.mappingLocations = mappingLocations;    }    /**     * Set optional properties to be passed into the SqlMapClientBuilder, as     * alternative to a <code>&lt;properties&gt;</code> tag in the     * sql-map-config.xml file. Will be used to resolve placeholders in the     * config file.     *      * @see #setConfigLocation     * @see com.ibatis.sqlmap.client.SqlMapClientBuilder#buildSqlMapClient(java.io.InputStream,     *      java.util.Properties)     */    public void setSqlMapClientProperties(Properties sqlMapClientProperties) {        this.sqlMapClientProperties = sqlMapClientProperties;    }    /**     * Set the DataSource to be used by iBATIS SQL Maps. This will be passed to     * the SqlMapClient as part of a TransactionConfig instance.     * <p>     * If specified, this will override corresponding settings in the     * SqlMapClient properties. Usually, you will specify DataSource and     * transaction configuration <i>either</i> here <i>or</i> in SqlMapClient     * properties.     * <p>     * Specifying a DataSource for the SqlMapClient rather than for each     * individual DAO allows for lazy loading, for example when using     * PaginatedList results.     * <p>     * With a DataSource passed in here, you don't need to specify one for each     * DAO. Passing the SqlMapClient to the DAOs is enough, as it already     * carries a DataSource. Thus, it's recommended to specify the DataSource at     * this central location only.     * <p>     * Thanks to Brandon Goodin from the iBATIS team for the hint on how to make     * this work with Spring's integration strategy!     *      * @see #setTransactionConfigClass     * @see #setTransactionConfigProperties     * @see com.ibatis.sqlmap.client.SqlMapClient#getDataSource     * @see SqlMapClientTemplate#setDataSource     * @see SqlMapClientTemplate#queryForPaginatedList     */    public void setDataSource(DataSource dataSource) {        this.dataSource = dataSource;    }    /**     * Set whether to use a transaction-aware DataSource for the SqlMapClient,     * i.e. whether to automatically wrap the passed-in DataSource with Spring's     * TransactionAwareDataSourceProxy.     * <p>     * Default is "true": When the SqlMapClient performs direct database     * operations outside of Spring's SqlMapClientTemplate (for example, lazy     * loading or direct SqlMapClient access), it will still participate in     * active Spring-managed transactions.     * <p>     * As a further effect, using a transaction-aware DataSource will apply     * remaining transaction timeouts to all created JDBC Statements. This means     * that all operations performed by the SqlMapClient will automatically     * participate in Spring-managed transaction timeouts.     * <p>     * Turn this flag off to get raw DataSource handling, without Spring     * transaction checks. Operations on Spring's SqlMapClientTemplate will     * still detect Spring-managed transactions, but lazy loading or direct     * SqlMapClient access won't.     *      * @see #setDataSource     * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy     * @see org.springframework.jdbc.datasource.DataSourceTransactionManager     * @see SqlMapClientTemplate     * @see com.ibatis.sqlmap.client.SqlMapClient     */    public void setUseTransactionAwareDataSource(boolean useTransactionAwareDataSource) {        this.useTransactionAwareDataSource = useTransactionAwareDataSource;    }    /**     * Set the iBATIS TransactionConfig class to use. Default is     * <code>com.ibatis.sqlmap.engine.transaction.external.ExternalTransactionConfig</code>     * .     * <p>     * Will only get applied when using a Spring-managed DataSource. An instance     * of this class will get populated with the given DataSource and     * initialized with the given properties.     * <p>     * The default ExternalTransactionConfig is appropriate if there is external     * transaction management that the SqlMapClient should participate in: be it     * Spring transaction management, EJB CMT or plain JTA. This should be the     * typical scenario. If there is no active transaction, SqlMapClient     * operations will execute SQL statements non-transactionally.     * <p>     * JdbcTransactionConfig or JtaTransactionConfig is only necessary when     * using the iBATIS SqlMapTransactionManager API instead of external     * transactions. If there is no explicit transaction, SqlMapClient     * operations will automatically start a transaction for their own scope (in     * contrast to the external transaction mode, see above).     * <p>     * <b>It is strongly recommended to use iBATIS SQL Maps with Spring     * transaction management (or EJB CMT).</b> In this case, the default     * ExternalTransactionConfig is fine. Lazy loading and SQL Maps operations     * without explicit transaction demarcation will execute     * non-transactionally.     * <p>     * Even with Spring transaction management, it might be desirable to specify     * JdbcTransactionConfig: This will still participate in existing     * Spring-managed transactions, but lazy loading and operations without     * explicit transaction demaration will execute in their own auto-started     * transactions. However, this is usually not necessary.     *      * @see #setDataSource     * @see #setTransactionConfigProperties     * @see com.ibatis.sqlmap.engine.transaction.TransactionConfig     * @see com.ibatis.sqlmap.engine.transaction.external.ExternalTransactionConfig     * @see com.ibatis.sqlmap.engine.transaction.jdbc.JdbcTransactionConfig     * @see com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig     * @see com.ibatis.sqlmap.client.SqlMapTransactionManager     */    @SuppressWarnings("rawtypes")    public void setTransactionConfigClass(Class transactionConfigClass) {        if (transactionConfigClass == null                || !TransactionConfig.class.isAssignableFrom(transactionConfigClass)) {            throw new IllegalArgumentException(                    "Invalid transactionConfigClass: does not implement "                            + "com.ibatis.sqlmap.engine.transaction.TransactionConfig");        }        this.transactionConfigClass = transactionConfigClass;    }    /**     * Set properties to be passed to the TransactionConfig instance used by     * this SqlMapClient. Supported properties depend on the concrete     * TransactionConfig implementation used:     * <p>     * <ul>     * <li><b>ExternalTransactionConfig</b> supports "DefaultAutoCommit"     * (default: false) and "SetAutoCommitAllowed" (default: true). Note that     * Spring uses SetAutoCommitAllowed = false as default, in contrast to the     * iBATIS default, to always keep the original autoCommit value as provided     * by the connection pool.     * <li><b>JdbcTransactionConfig</b> does not supported any properties.     * <li><b>JtaTransactionConfig</b> supports "UserTransaction" (no default),     * specifying the JNDI location of the JTA UserTransaction (usually     * "java:comp/UserTransaction").     * </ul>     *      * @see com.ibatis.sqlmap.engine.transaction.TransactionConfig#initialize     * @see com.ibatis.sqlmap.engine.transaction.external.ExternalTransactionConfig     * @see com.ibatis.sqlmap.engine.transaction.jdbc.JdbcTransactionConfig     * @see com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig     */    public void setTransactionConfigProperties(Properties transactionConfigProperties) {        this.transactionConfigProperties = transactionConfigProperties;    }    /**     * Set the LobHandler to be used by the SqlMapClient. Will be exposed at     * config time for TypeHandler implementations.     *      * @see #getConfigTimeLobHandler     * @see com.ibatis.sqlmap.engine.type.TypeHandler     * @see org.springframework.orm.ibatis.support.ClobStringTypeHandler     * @see org.springframework.orm.ibatis.support.BlobByteArrayTypeHandler     * @see org.springframework.orm.ibatis.support.BlobSerializableTypeHandler     */    public void setLobHandler(LobHandler lobHandler) {        this.lobHandler = lobHandler;    }    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 {            this.sqlMapClient = buildSqlMapClient(this.configLocations,                    this.mappingLocations, this.sqlMapClientProperties);            // Tell the SqlMapClient to use the given DataSource, if any.            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.set(null);            }        }    }    /**     * Build a SqlMapClient instance based on the given standard configuration.     * <p>     * The default implementation uses the standard iBATIS     * {@link SqlMapClientBuilder} API to build a SqlMapClient instance based on     * an InputStream (if possible, on iBATIS 2.3 and higher) or on a Reader (on     * iBATIS up to version 2.2).     *      * @param configLocations     *            the config files to load from     * @param properties     *            the SqlMapClient properties (if any)     * @return the SqlMapClient instance (never <code>null</code>)     * @throws IOException     *             if loading the config file failed     * @throws NoSuchFieldException     * @throws SecurityException     * @throws IllegalAccessException     * @throws IllegalArgumentException     * @throws NoSuchMethodException     * @throws InvocationTargetException     * @see com.ibatis.sqlmap.client.SqlMapClientBuilder#buildSqlMapClient     */    protected SqlMapClient buildSqlMapClient(Resource[] configLocations,            Resource[] mappingLocations, Properties properties) throws IOException,            SecurityException, NoSuchFieldException, IllegalArgumentException,            IllegalAccessException, NoSuchMethodException, InvocationTargetException {        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);                }            }        }        //*************其实只改这一点而已,为了方便他人,全source贴出**************        //为了取sqlMapConfig,反射private的field        Field stateField = configParser.getClass().getDeclaredField("state");        stateField.setAccessible(true);        XmlParserState state = (XmlParserState) stateField.get(configParser);        SqlMapConfiguration sqlMapConfig = state.getConfig();        //反射取设置cache的方法,执行        Method wireUpCacheModels = sqlMapConfig.getClass().getDeclaredMethod(                "wireUpCacheModels");        wireUpCacheModels.setAccessible(true);        wireUpCacheModels.invoke(sqlMapConfig);        //*************************************************************************        return client;    }    /**     * Apply the given iBATIS TransactionConfig to the SqlMapClient.     * <p>     * The default implementation casts to ExtendedSqlMapClient, retrieves the     * maximum number of concurrent transactions from the     * SqlMapExecutorDelegate, and sets an iBATIS TransactionManager with the     * given TransactionConfig.     *      * @param sqlMapClient     *            the SqlMapClient to apply the TransactionConfig to     * @param transactionConfig     *            the iBATIS TransactionConfig to apply     * @see com.ibatis.sqlmap.engine.impl.ExtendedSqlMapClient     * @see com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate#getMaxTransactions     * @see com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate#setTxManager     */    protected void applyTransactionConfig(SqlMapClient sqlMapClient,            TransactionConfig transactionConfig) {        if (!(sqlMapClient instanceof ExtendedSqlMapClient)) {            throw new IllegalArgumentException(                    "Cannot set TransactionConfig with DataSource for SqlMapClient if not of type "                            + "ExtendedSqlMapClient: " + sqlMapClient);        }        ExtendedSqlMapClient extendedClient = (ExtendedSqlMapClient) sqlMapClient;        transactionConfig.setMaximumConcurrentTransactions(extendedClient.getDelegate()                .getMaxTransactions());        extendedClient.getDelegate().setTxManager(                new TransactionManager(transactionConfig));    }    public SqlMapClient getObject() {        return this.sqlMapClient;    }    public Class<? extends SqlMapClient> getObjectType() {        return (this.sqlMapClient != null ? this.sqlMapClient.getClass()                : SqlMapClient.class);    }    public boolean isSingleton() {        return true;    }    /**     * Inner class to avoid hard-coded iBATIS 2.3.2 dependency (XmlParserState     * class).     */    private static class SqlMapParserFactory {        public static SqlMapParser createSqlMapParser(SqlMapConfigParser configParser) {            // Ideally: XmlParserState state = configParser.getState();            // Should raise an enhancement request with iBATIS...            XmlParserState state = null;            try {                Field stateField = SqlMapConfigParser.class.getDeclaredField("state");                stateField.setAccessible(true);                state = (XmlParserState) stateField.get(configParser);            } catch (Exception ex) {                throw new IllegalStateException(                        "iBATIS 2.3.2 'state' field not found in SqlMapConfigParser class - "                                + "please upgrade to IBATIS 2.3.2 or higher in order to use the new 'mappingLocations' feature. "                                + ex);            }            return new SqlMapParser(state);        }    }}

?

修改Spring配置文件

<bean id="sqlMapClient" />  <property name="dataSource" ref="dataSource" />  <property name="mappingLocations" value="classpath*:/**/sqlmap/*SqlMap.xml" /></bean>

?
至此,cache又工作如初了。
编后:
·反射会消耗,但仅仅在初始化时一次性消耗,还可以接受。
·iBatis的cache能力比较弱,但没太多要求的情况下是个省事的方案,聊胜于无。
·Mybatis3.0的cache暂时不用为好,EhCache选项本身还有bug,实在很无语。

?

热点排行