1.引入与作用
(1)引入
Spring对第三方开元orm的支持一贯做法是使用模板模式封装一个Template类,如iBatis的SqlMapClient封装为SQLMapClientTemplate,hibernate的HibernateTemplate。
(2)作用
我们在创建Dao的时候会继承SqlMapClientDaoSupport,通过SqlMapClientDaoSupport的SqlMapClient属性来操作数据库。
SqlMapClientTemplate是帮我们做一些通用操作的,如:dataSource的初始化、释放数据库连接,开启/关闭Session等
其中最重要的SqlMapClientTemplate.execute方法,其他操作,如:queryForObject、update等都是通过传递SqlMapClientCallback给execute,execute做好上述初始化操作后,再调用SqlMapClientCallback的doInSqlMapClient方法操作数据库返回结果,然后再关闭数据库连接和Session
2.SqlMapClientFactoryBean
SqlMapClientFactoryBean实际上是用来管理iBatis的Ioc容器
public class SqlMapClientFactoryBean implements FactoryBean<SqlMapClient>, InitializingBean
{
//当前线程绑定iBatis中blob/clob等大字段数据处理器资源
private static final ThreadLocal<LobHandler> configTimeLobHandlerHolder = new ThreadLocal<LobHandler>();
public static LobHandler getConfigTimeLobHandler()
{
return configTimeLobHandlerHolder.get();
}
//iBatis配置资源路径
private Resource[] configLocations;
//iBatis映射文件路径
private Resource[] mappingLocations;
//iBatis的SqlMapClient属性
private Properties sqlMapClientProperties;
//数据源
private DataSource dataSource;
//是否使用spring的事务包装数据源
private boolean useTransactionAwareDataSource = true;
//事务配置类
private Class transactionConfigClass = ExternalTransactionConfig.class;
//事务配置属性
private Properties transactionConfigProperties;
//blob/clob等lob类型处理器
private LobHandler lobHandler;
//iBatis的SqlMapClient
private SqlMapClient sqlMapClient;
//初始化事务配置文件,设置不允许事务自动提交
public SqlMapClientFactoryBean()
{
this.transactionConfigProperties = new Properties();
this.transactionConfigProperties.setProperty("SetAutoCommitAllowed", "false");
}
//指定sqlMapClient资源文件路径
public void setConfigLocation(Resource configLocation)
{
this.configLocations = (configLocation != null ? new Resource[] {configLocation} : null);
}
//指定多个sqlMapClient资源文件路径,在运行时会合并
public void setConfigLocations(Resource[] configLocations)
{
this.configLocations = configLocations;
}
//设置ibatis映射文件路径
public void setMappingLocations(Resource[] mappingLocations)
{
this.mappingLocations = mappingLocations;
}
//通过属性文件配置SqlMapClient中的属性
public void setSqlMapClientProperties(Properties sqlMapClientProperties)
{
this.sqlMapClientProperties = sqlMapClientProperties;
}
//设置ibatis使用的数据源
public void setDataSource(DataSource dataSource)
{
this.dataSource = dataSource;
}
//设置是否使用事务包装
public void setUseTransactionAwareDataSource(boolean useTransactionAwareDataSource)
{
this.useTransactionAwareDataSource = useTransactionAwareDataSource;
}
//设置iBatis使用的事务配置类
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;
}
//设置事务配置属性
public void setTransactionConfigProperties(Properties transactionConfigProperties)
{
this.transactionConfigProperties = transactionConfigProperties;
}
//设置大字段处理器
public void setLobHandler(LobHandler lobHandler)
{
this.lobHandler = lobHandler;
}
//ioc容器初始化完成之后的回调方法,
public void afterPropertiesSet() throws Exception
{
//配置lob处理器
if (this.lobHandler != null)
{
configTimeLobHandlerHolder.set(this.lobHandler);
}
//创建iBatis的SqlMapClient
try
{
this.sqlMapClient = buildSqlMapClient(this.configLocations, this.mappingLocations, this.sqlMapClientProperties);
//为创建的SqlMapClient设置数据源
if (this.dataSource != null)
{
//创建事务配置实例
TransactionConfig transactionConfig = (TransactionConfig) this.transactionConfigClass.newInstance();
//获取数据源
DataSource dataSourceToUse = this.dataSource;
//如果iBatis使用事务包装的数据源,并且当前获取到的数据源不是代理类型
if (this.useTransactionAwareDataSource && !(this.dataSource instanceof TransactionAwareDataSourceProxy))
{
//为指定数据源创建爱你事务包装代理
dataSourceToUse = new TransactionAwareDataSourceProxy(this.dataSource);
}
//为事务配置对象设置数据源
transactionConfig.setDataSource(dataSourceToUse);
//初始化实物配置对象
transactionConfig.initialize(this.transactionConfigProperties);
applyTransactionConfig(this.sqlMapClient, transactionConfig);
}
}
//创建SqlMapClient成功后,清楚当前县城绑定的lob处理器
finally
{
if (this.lobHandler != null)
{
// Reset LobHandler holder.
configTimeLobHandlerHolder.remove();
}
}
}
//具体创建SqlMapClient的方法
//根据给定的iBatis配置文件,映射文件和配置中的属性文件创建SqlMapClient
protected SqlMapClient buildSqlMapClient(
Resource[] configLocations, Resource[] mappingLocations, Properties properties)
throws IOException
{
//如果给定iBatis配置文件路径为空
if (ObjectUtils.isEmpty(configLocations))
{
throw new IllegalArgumentException("At least 1 'configLocation' entry is required");
}
SqlMapClient client = null;
//创建ibatis配置文件解析器
SqlMapConfigParser configParser = new SqlMapConfigParser();
//遍历所有iBatis配置文件
for (Resource configLocation : configLocations)
{
InputStream is = configLocation.getInputStream();
try
{
//根据配置创建SqlMapClient
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;
}
//将ibatis配置中指定的事务配置应用到SqlMapClient上
protected void applyTransactionConfig(SqlMapClient sqlMapClient, TransactionConfig transactionConfig)
{
//如果SqlMapClient不是ExtendedSqlMapClient类型,则无法将配置应用到SqlMapClient对象上
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());
//为SqlMapClient设置事务处理器
extendedClient.getDelegate().setTxManager(new TransactionManager(transactionConfig));
}
//spring ioc容器中对应用提供的一个获取被管理对象的方法
public SqlMapClient getObject()
{
return this.sqlMapClient;
}
public Class<? extends SqlMapClient> getObjectType()
{
return (this.sqlMapClient != null ? this.sqlMapClient.getClass() : SqlMapClient.class);
}
//默认spring ioc中管理的对象是单例的
public boolean isSingleton()
{
return true;
}
//ibatis映射解析工厂类
private static class SqlMapParserFactory
{
//创建映射解析器
public static SqlMapParser createSqlMapParser(SqlMapConfigParser configParser)
{
XmlParserState state = null;
try
{
//通过反射获得SqlMapConfigParser类中的state字段的值
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);
}
}
}
SqlMapClientFactoryBean实现了spring的FactoryBean接口,是spring管理iBatis的ioc容器。在ioc容器初始化过程中主要完成定位iBatis配置文件和映射文件等工作
同时SqlMapClientFactoryBean实现了InitializingBean接口,实现了afterPropertiesSet方法,该方法时在ioc容器初始化完成之后由ioc容器进行回调的,在该方法中主要是根据定义的IBatis配置文件和映射文件创建SqlMapClient对象的过程
3.SqlMapClientTemplate
spring通过SqlMapClientTemplate对ibatis的一些通用操作做了统一的封装处理,同时也对ibatis的api做了一些封装,方便开发者使用。
(1)execute方法
和JdbcTemplate和HibernateTemplate一样,SqlMapClientTemplate也是通过execute方法封装iBatis增删改查前的通用操作,同时在execute方法中调用相应的回调对象的回调犯法来真正完成ibatis的处理操作
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");
//通过SqlMapClient对象打开一个IbatisSqlMapSession
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);
try
{
//获取连接
ibatisCon = session.getCurrentConnection();
//如果当前的SqlMapSession还没有创建过连接
if (ibatisCon == null)
{
//如果ibatis数据源已经在spring的事务管理之下,则直接使用数据源创建连接
//否则,使用DataSourceUtils创建连接,并且将其置于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);
}
//调用具体增删改查操作回调对象的方法
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);
}
}
}
//关闭iBatis的SqlMapSession
finally
{
if (ibatisCon == null)
{
session.close();
}
}
}
(2)spring封装的iBatis api方法
以spring的queryForObject方法为例:
//查询对象
public Object queryForObject(final String statementName, final Object parameterObject)
throws DataAccessException
{
//调用execute方法,参数是实现了SqlMapClientCallback接口的匿名内部类
//execute方法中回调该对象的doInSqlMapClient方法
return execute(new SqlMapClientCallback<Object>()
{
//真正调用ibatis api执行具体操作的方法
public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException
{
//调用SqlMapSession对象的queryForObject方法
return executor.queryForObject(statementName, parameterObject);
}
});
}
4.使用
(1)SqlMapClientFactoryBean的装配
SqlMapClientFactoryBean是SqlMapClientTemplate使用的基础,如果在spring中没有装配SqlMapClientFactoryBean,那么SqlMapClientTemp将不可用,将报空指针异常
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<!-- iBatis sqlmap config 文件位置 -->
<property name="configLocation" value="/WEB-INF/sqlmap-config.xml"/>
<!-- 在SpringFramework配置文件中使用的数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 如果需要读写Lob字段,需要注入在SpringFramework配置文件中配置好的Handler,这里是Oracle的数据库 -->
<property name="lobHandler" ref="oracleLobHandler"/>
</bean>
(2)Dao实现类继承SqlMapClientDaoSupport类
import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;
......
public class ReportDAOImpl extends SqlMapClientDaoSupport implement ReportDAO
{
......
}
(3)在spring中为daoImpl类入住SqlMapClient实例
<bean id="reportDao" class="com.test.dao.ReportDAOImpl">
<!-- 装配SqlMapClientFactoryBean -->
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
这里有一个问题:SqlMapClientFactoryBean和SqlMapClient没有任何继承或者实现关系,可以这样注入吗?这里不知道怎么解释
以上说白了就是:Dao实现类继承SqlMapClientDaoSupport,SqlMapClientDaoSupport中有一个SqlMapClientTemplate属性,这个属性在spring配置文件中注入就可以了,注入的是SqlMapClientFactoryBean实例
(4)使用SqlMapClientTemplate查询
1)没有参数的查询
List result = getSqlMapClientTemplate().queryForList("TestSpace.qryTest");
2)按照主键查询
Long id = new Long("2");
Object resultObj = getSqlMapClientTemplate().queryForObject("TestSpace.getTest", id);
3)使用pojo构成的参数查询
ObjectA objA = new ObjectA();
objA.setParam1("test1");
objA.setParam2("test2");
......
List result = getSqlMapClientTemplate().queryForList("TestSpace.qryTestByParam", objA);
4)分页查询
List result = getSqlMapClientTemplate().queryForList("TestSpace.qryTestByParam", objA, 4, 40);
(5)使用SqlMapClientTemplate添加数据
ObjectA objA = new ObjectA();
objA.setParam1("test1");
objA.setParam2("test2");
......
getSqlMapClientTemplate().insert("TestSpace.insertTest", objA);
(6)使用SqlMapClientTemplate更新数据
ObjectA objA = new ObjectA();
objA.setParam1("test1");
objA.setParam2("test2");
......
getSqlMapClientTemplate().update("TestSpace.updateTest", objA);
(7)使用SqlMapClientTemplate删除数据
Long id = new Long("2");
getSqlMapClientTemplate().delete("TestSpace.deleteTest", id);