问题来源
最近在集成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更好地连接。