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接口

  

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更好地连接。

原文地址:https://www.cnblogs.com/zcmzex/p/8877697.html

时间: 2024-08-25 21:24:56

spring如何管理mybatis(一) ----- 动态代理接口的相关文章

Java EE开发平台随手记5——Mybatis动态代理接口方式的原生用法

为了说明后续的Mybatis扩展,插播一篇广告,先来简要说明一下Mybatis的一种原生用法,不过先声明:下面说的只是Mybatis的其中一种用法,如需要更深入了解Mybatis,请参考官方文档,或者研读源码. 我们知道,使用Mybatis的方式有很多种,从是否集成上分,可以单独使用,也可以和Spring集成使用:从使用方式上分,可以编写静态工具类,在静态工具中调用SqlSession,也可以直接注入SqlSession/ SqlSessionTemplate,还可以编写Dao接口,让mybat

Spring之AOP原理_动态代理

面向方面编程(Aspect Oriented Programming,简称AOP)是一种声明式编程(Declarative Programming).声明式编程是和命令式编程(Imperative Programming)相对的概念.我们平时使用的编程语言,比如C++.Java.Ruby.Python等,都属命令式编程.命令式编程的意思是,程序员需要一步步写清楚程序需要如何做什么(How to do What).声明式编程的意思是,程序员不需要一步步告诉程序如何做,只需要告诉程序在哪些地方做什么

Java语言中反射动态代理接口的解释与演示

Java语言中反射动态代理接口的解释与演示 Java在JDK1.3的时候引入了动态代理机制.可以运用在框架编程与平台编程时候捕获事件.审核数据.日志等功能实现,首先看一下设计模式的UML图解: 当你调用一个接口API时候,实际实现类继承该接口,调用时候经过proxy实现. 在Java中动态代理实现的两个关键接口类与class类分别如下: java.lang.reflect.Proxy java.lang.reflect.InvocationHandler 我们下面就通过InvocationHan

Mybatis的动态代理模式

mybatis的动态代理需要遵循4个规则: 1.xml文件中的namespace的值为接口类的全限命名 2.statement的id要跟接口的方法名相同. 3.statement的parameterType要跟接口的方法的参数的类型相同. 4.statement的resultType要跟接口方法的返回值类型相同. 接口: public Student selectStuById(int id); xml文件: <select id="selectStuById" resultTy

(十二)mybatis之动态代理

mybatis之动态代理的应用 在前文(https://www.cnblogs.com/NYfor2018/p/9093472.html)我们知道了,Mybatis的使用需要用到Mapper映射文件,一个是映射接口,另一个是映射XML文件(此处不详谈映射文件XML),在应用中我们可以感觉到,映射接口似乎对接着XML文件中的实现命令,可是我们在运行程序是时候调用的往往是Mapper接口,而不是一个包含逻辑的实现类.很显然Mapper产生了代理类. 首先,什么是代理模式?代理模式的定义:为其他对象提

细说Spring——AOP详解(动态代理实现AOP)

前言 嗯,我应该是有一段实现没有写过博客了,在写完了细说Spring——AOP详解(AOP概览)之后,我发现我不知道该怎么写AOP这一部分,所以就把写博客这件事给放下了,但是这件事情又不想就这么放弃,所以今天我仔细思考了一下,决定还是要克服困难,我仔细的想了一下怎么讲解AOP实现这一部分,然后我决定由浅入深的讲解动态代理,然后用动态代理实现一个简单的AOP,感觉这样能够让人对AOP的原理有一个比较深刻的认识,希望能帮到大家.而且最近学习又组建了ACM比赛的队伍,虽然已经要大三了,按理来说应该一心

由浅入深分析mybatis通过动态代理实现拦截器(插件)的原理

最近在用mybatis做项目,需要用到mybatis的拦截器功能,就顺便把mybatis的拦截器源码大致的看了一遍,为了温故而知新,在此就按照自己的理解由浅入深的理解一下它的设计. 和大家分享一下,不足和谬误之处欢迎交流.直接入正题. 首先,先不管mybatis的源码是怎么设计的,先假设一下自己要做一个拦截器应该怎么做.拦截器的实现都是基于代理的设计模式设计的,简单的说就是要创造一个目标类的代理类,在代理类中执行目标类的方法并拦截执行拦截器代码. 那么我们就用JDK的动态代理设计一个简单的拦截器

菜鸟学习Spring——60s让你学会动态代理

一.为什么要使用动态代理 当一个对象或多个对象实现了N中方法的时候,由于业务需求需要把这个对象和多个对象的N个方法加入一个共同的方法,比如把所有对象的所有方法加入事务这个时候有三种方法: 方法一:一个一个对象一个一个方法去加,很显然这个方法是一个比较笨的方法. 方法二:加一个静态代理对象将这个静态代理对象实现要加事务对象的接口.然后在静态代理对象里面每个方法里面加上事务. 方法三:使用动态代理对象,进行动态的加载事务. 使用动态代理是为了让对象实现了开闭原则,对扩展开放,而对修改关闭.Sprin

Mybatis Mapper动态代理方式

目录结构及配置文件与原始dao方法相比更简便 只需一个UserMapper的接口,放在一起的配置文件,配置文件中namespace的地址确定jdk动态代理的对象 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybati