問題來源
最近在集成spring和mybatis時遇到了很多問題,從網上查了也解決了,但是就是心里有點別扭,想看看到底怎么回事,所以跟了下源碼,終於發現了其中的奧妙。
問題分析
首先我們來看看基本的配置。
spring的配置:
<!-- 數據庫配置 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${db.driver}"/> <property name="url" value="${db.url}"/> <property name="username" value="${db.userName}"/> <property name="password" value="${db.password}"/> <property name="maxActive" value="${druid.maxActive}"></property> <property name="maxWait" value="${druid.maxWait}"></property> </bean> <bean id="mSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--數據庫連接池 --> <property name="dataSource" ref="dataSource"/> <!-- 加載mybatis mapper文件的配置 --> <property name="mapperLocations" value="classpath*:mapper/*.xml"/> </bean> <!-- sqlSession不是必選項 --> <!-- <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="mSqlSessionFactory"/> </bean> --> <!--動態代理實現 不用寫dao的實現 --> <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.zex.dao" /> <property name="sqlSessionFactoryBeanName" value="mSqlSessionFactory"></property> </bean> <!-- 事務管理 --> <bean id="transactionManagermeta" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 事務注解支持 --> <tx:annotation-driven/>
mapper文件和dao接口
controller層代碼
源碼跟蹤
首先我們分解下spring-mybatis配置信息,數據庫配置不說了,我們來看看sqlSessionFactory的配置
<bean id="mSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--數據庫連接池 --> <property name="dataSource" ref="dataSource"/> <!-- 加載mybatis mapper文件的配置 --> <property name="mapperLocations" value="classpath*:mapper/*.xml"/> </bean>
這個配置,主要是把SqlSessionFactoryBean用spring管理起來了,我們一起來看看這個bean的作用
/**
* {@code FactoryBean} that creates an MyBatis {@code SqlSessionFactory}.
* This is the usual way to set up a shared MyBatis {@code SqlSessionFactory} in a Spring application context;
* the SqlSessionFactory can then be passed to MyBatis-based DAOs via dependency injection.
*
* Either {@code DataSourceTransactionManager} or {@code JtaTransactionManager} can be used for transaction
* demarcation in combination with a {@code SqlSessionFactory}. JTA should be used for transactions
* which span multiple databases or when container managed transactions (CMT) are being used.
*/
這是這個類的注釋:這個類主要用來創建Mybatis需要的SqlSessionFactory,在spring的上下文共享這個類。這里可以看出這個類用來
管理mybatis的配置信息,講mybatis的信息管理載spring中,我們看下基本屬性。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class); private Resource configLocation; private Configuration configuration; private Resource[] mapperLocations; private DataSource dataSource; private TransactionFactory transactionFactory; private Properties configurationProperties; private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); private SqlSessionFactory sqlSessionFactory; //EnvironmentAware requires spring 3.1 private String environment = SqlSessionFactoryBean.class.getSimpleName(); private boolean failFast; private Interceptor[] plugins; private TypeHandler<?>[] typeHandlers; private String typeHandlersPackage; private Class<?>[] typeAliases; private String typeAliasesPackage; private Class<?> typeAliasesSuperType; //issue #19. No default provider. private DatabaseIdProvider databaseIdProvider; private Class<? extends VFS> vfs; private Cache cache; private ObjectFactory objectFactory; private ObjectWrapperFactory objectWrapperFactory;
}
這里我們看到了有個
private Resource configLocation;
這個屬性用來管理mybatis基本配置信息的xml的位置,sqlSessionFactoryBean會根據這個配置加載Configuration,當然我們也可以通過這個類中
其他的參數來配置,例如typeHandler,typeAliasesPackages等等,這些既可以在Configuration的xml中配置,也可以直接配置。所以這個bean主要作用就是生成configuration,
然后通過sqlSessionFactoryBuilder來創建sqlSessionFactory,可以說最重要的就是創建這個sqlSessionFactory。
接下來我們看看SqlSessionTemplate
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="mSqlSessionFactory"/> </bean>
SqlSessionTemplate這個類實現了mybatis的sqlSession,mybatis的sqlSession主要是執行數據庫操作,spring實現了SqlSessionTemplate這個類,主要是講mybatis對數據庫的
操作轉嫁到spring中來,讓spring來進行數據的操作。我們看看這個類的屬性。
public class SqlSessionTemplate implements SqlSession { private final SqlSessionFactory sqlSessionFactory; private final ExecutorType executorType; private final SqlSession sqlSessionProxy; private final PersistenceExceptionTranslator exceptionTranslator; /** * Constructs a Spring managed SqlSession with the {@code SqlSessionFactory} * provided as an argument. * * @param sqlSessionFactory */ public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType()); } /** * Constructs a Spring managed SqlSession with the {@code SqlSessionFactory} * provided as an argument and the given {@code ExecutorType} * {@code ExecutorType} cannot be changed once the {@code SqlSessionTemplate} * is constructed. * * @param sqlSessionFactory * @param executorType */ public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) { this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator( sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true)); } /** * 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; this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); }
這個類包含有四個基本的屬性,其中的SqlSessionFactory就是我們之前通過SqlSessionFactoryBean生成的那個SqlSessionFactory,他的作用是提供Configation,
另一個重要的屬性就是SqlSessionProxy這個類其實是個代理類,代理的Mybatis的sqlSession接口,這樣他就可以擁有MyBatis的sqlSession的所有方法了。這個代理類在執行的時候
其實是走的
SqlSessionInterceptor的invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { final SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() sqlSession.commit(true); } return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } }
這個方法就是獲取真實的sqlSession然后調用數據庫的操作。
ok,以上就是spring和mybatis的整合點,接下來我們看看是如何只通過一個接口就能操作數據庫的,肯定用的是代理模式,只是mybatis用的太好了。
首先我們來看個類,MapperFactoryBean
/** * BeanFactory that enables injection of MyBatis mapper interfaces. It can be set up with a * SqlSessionFactory or a pre-configured SqlSessionTemplate. * <p> * Sample configuration: * * <pre class="code"> * {@code * <bean id="baseMapper" class="org.mybatis.spring.mapper.MapperFactoryBean" abstract="true" lazy-init="true"> * <property name="sqlSessionFactory" ref="sqlSessionFactory" /> * </bean> * * <bean id="oneMapper" parent="baseMapper"> * <property name="mapperInterface" value="my.package.MyMapperInterface" /> * </bean> * * <bean id="anotherMapper" parent="baseMapper"> * <property name="mapperInterface" value="my.package.MyAnotherMapperInterface" /> * </bean> * } * </pre> * <p> * Note that this factory can only inject <em>interfaces</em>, not concrete classes. * * @author Eduardo Macarron * * @see SqlSessionTemplate */ public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { private Class<T> mapperInterface; private boolean addToConfig = true; public MapperFactoryBean() { //intentionally empty }/** * {@inheritDoc} */ @Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); } }
這個類就是可以注入mapper接口的工廠類,可以理解為他可以通過接口生產一個代理類用來調用接口的工作,首先他是個FactoryBean可以通過getObject(),獲取到他管理的bean,
這個類最主要的就是傳入一個sqlSessionFactory。
我們先來看看一般的用法
<bean id="baseMapper" class="org.mybatis.spring.mapper.MapperFactoryBean" abstract="true" lazy-init="true"> * <property name="sqlSessionFactory" ref="sqlSessionFactory" /> * </bean> * * <bean id="oneMapper" parent="baseMapper"> * <property name="mapperInterface" value="my.package.MyMapperInterface" /> * </bean> * * <bean id="anotherMapper" parent="baseMapper"> * <property name="mapperInterface" value="my.package.MyAnotherMapperInterface" /> * </bean>
我們看到將這個類由spring管理,然后注入sqlSessionFactory,然后又用其他的類去繼承它並注入接口,這樣這個接口就被管理起來了,生成了代理類。我們在獲取這個接口的時候得到的其實就是代理類。不過這樣子有點麻煩,我們每次都要進行接口的配置,所以spring提供了org.mybatis.spring.mapper.MapperScannerConfigurer這個類來管理所有的接口了,這個類會所有所有的配置的包中的接口,然后將每個接口的定義設置好生成代理相應的信息。
@Override public Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); } else { for (BeanDefinitionHolder holder : beanDefinitions) { GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition(); if (logger.isDebugEnabled()) { logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface"); } // the mapper interface is the original class of the bean // but, the actual class of the bean is MapperFactoryBean definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName()); definition.setBeanClass(MapperFactoryBean.class); definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false; if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) { definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionFactory != null) { definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory); explicitFactoryUsed = true; } if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { if (explicitFactoryUsed) { logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionTemplate != null) { if (explicitFactoryUsed) { logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); explicitFactoryUsed = true; } if (!explicitFactoryUsed) { if (logger.isDebugEnabled()) { logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); } definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } } } return beanDefinitions; }
我們重點看下
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class); definition.getPropertyValues().add("addToConfig", this.addToConfig);
這三行代碼,指定了mapper接口的類型--MapperFactoryBean,以及相應的接口信息,這樣bean的定義就指定了必要的信息,當spring創建這個mapper接口對應的bean的時候就會生成相應的MapperFactoryBean類,當需要接口實例時就會調用MapperFactoryBean的getObject()方法獲取相應的bean。
@Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); }
這個方法就是獲取mapper接口對應的代理類,這個方法給我們上了一堂如何完美利用jdk代理類的課。建議大家可以研究下。這里不是我們的重點,就不帶大家研讀了。
問題總結
解決這個問題對我們有什么好處呢,首先我們可以在這個過程中更加熟悉spring的bean的創建過程,以及mybatis的代理的生成過程,以及spring-mybatis集成的相關了解,了解這個我們可以更好的寫一些類去設計和mybatis更好地連接。