项目中常常使用mybatis配合spring进行数据库操作,但是我们知道,数据的操作是要求做到线程安全的,而且按照原来的jdbc的使用方式,每次操作完成之后都要将连接关闭,但是实际使用中我们并没有这么干。
更让人疑惑的点是,spring中默认使用单例形式来加载bean,而往往我们也不会改变这种默认,所以,是所有线程共享数据连接?
让我们来看看真相!
自然是要个栗子的:
我们来看下spring中配置mybatis数据库操作bean(使用 druid 连接池):
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}" />
<property name="driverClassName" value="${jdbc.driver}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml" />
</bean>
<!-- scope="prototype" 另说,另讨论,我们先以mapper形式看一下 -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
<!-- 事务 -->
<bean name="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
而在java代码中使用则是使用依赖注入直接使用 @resource sqlSession, 如下:
@Resource
private SqlSessionTemplate sqlSession;
@Override
public User getUser(Map<String, String> cond) {
// 此句执行db查询
User result = sqlSession.selectOne(NAME_SPACE
+ ".getUser", cond);
return result;
}
这个sqlSession就是直接去操作数据库了看起来是这样,是在bean初始化的时候依赖注入的!
所以,难道每次进入该操作的时候,sqlSession 的实例都会变化吗?答案是否定的。
那么,肯定就是往下使用的时候才发生的变化呗!
再往下走,可以看到,调用了一个代理来进行具体的查询!
// org/mybatis/spring/SqlSessionTemplate.selectOne()
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.<T> selectOne(statement, parameter);
}
为啥要用代理呢?自己直接查不就行了吗?其实,用代理是有好处的,那就可以可以进行另外的包装!
代理是怎么生成的呢?其实只要看一下 SqlSessionTemplate 的构造方法就知道了!
/**
* Constructs a Spring managed {@code SqlSession} with the given
* {@code SqlSessionFactory} and {@code ExecutorType}.
* A custom {@code SQLExceptionTranslator} can be provided as an
* argument so any {@code PersistenceException} thrown by MyBatis
* can be custom translated to a {@code RuntimeException}
* The {@code SQLExceptionTranslator} can also be null and thus no
* exception translation will be done and MyBatis exceptions will be
* thrown
*
* @param sqlSessionFactory
* @param executorType
* @param exceptionTranslator
*/
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
// 生成代理 SqlSessionInterceptor 为 InvocationHandler
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSe