spring如何管理mybatis(一) ----- 动态代理接口

本文内容纲要:

- 问题来源

- 问题分析

- 源码跟踪

- 问题总结

问题来源

  最近在集成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接口

  Image

controller层代码

Image

源码跟踪

     首先我们分解下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更好地连接。

本文内容总结:问题来源,问题分析,源码跟踪,问题总结,

原文链接:https://www.cnblogs.com/zcmzex/p/8877697.html

以上是 spring如何管理mybatis(一) ----- 动态代理接口 的全部内容, 来源链接: utcz.com/z/296274.html

回到顶部