美文网首页
mybatis-spring

mybatis-spring

作者: 我是许仙 | 来源:发表于2020-07-15 17:21 被阅读0次

    为什么要整和spring

    虽然mybatis已经很好用了,但是每次都需要创建sqlSession自己管理sqlsession的生命周期,硬编码获取mapper,自己管理事物的提交与回滚。mybatis-spring只是对mybatis的一些简单封装,让他可以融入到spring中,接入了spirng之后,不需要我们去管理sqlSessionFactory,由spring去管理对象 简化方法调用。只需要我们注入Mapper就可以了。而且现在项目大多都是面向spring开发,不集成spring开发不方便。MyBatis-Spring为什么是mybatis-spring而不是spring-mybatis呢?因为这个插件是mybatis团队负责开发的,并不是spring给开发的 spring团队开发的插件大多是spring-xx的格式。

    地址

    官网地址: http://mybatis.org/spring/zh/index.html

    使用

    1. 引入依赖

      <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.5</version>
      </dependency>
      
    2. 创建SqlSessionFactory

      <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
      </bean>
      
        @Bean
          public SqlSessionFactory sqlSessionFactory() throws Exception {
              SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
              factoryBean.setDataSource(dataSource);
              factoryBean.setTypeAliasesPackage("com.example.spring.mybatis.demo.domain");
              factoryBean.setMapperLocations(resolveMapperLocations());
              return factoryBean.getObject();
      
          }
      
          private Resource[] resolveMapperLocations() {
              ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
              List<String> mapperLocations = new ArrayList<>();
              mapperLocations.add("classpath*:xml/*Mapper*.xml");
              List<Resource> resources = new ArrayList<>();
              for (String mapperLocation : mapperLocations) {
                  try {
                      Resource[] mappers = resourceResolver.getResources(mapperLocation);
                      resources.addAll(Arrays.asList(mappers));
                  } catch (IOException e) {
                      // ignore
                  }
              }
              return resources.toArray(new Resource[resources.size()]);
          }
      
      
    3. 配置mapper映射器

      @Bean
      public UserMapper userMapper() throws Exception {
        SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory());
        return sqlSessionTemplate.getMapper(UserMapper.class);
      }
      

    就可以直接在代码中获取Mapper接口

        @Autowired
        private ManMapper manMapper;
    
        @Test
        public void Test() {
            System.out.println(manMapper.selectList());
        }
    

    mybatis集成spring之后使用如此简单,怎么做到的呢?先回顾下原生的mybatis怎么使用的

    private SqlSessionFactory sqlSessionFactory;
    
    @PostConstruct
    public void before()  throws Exception{
        //1 加载配置文件
        String resources = "mybatis-config.xml";
        //2 读取配置获取流
        InputStream inputStream = Resources.getResourceAsStream(resources);
        //看到buider 建造者模式  SqlSessionFactory 工厂模式
        //3 创建SqlSession工厂
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    
    }
    
    @Test
    void contextLoads() {
        //4 获取sqlSession SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //5  获取Mapper
        ManMapper manMapper = sqlSession.getMapper(ManMapper.class);
        //6执行sql
        System.out.println(manMapper.selectList());
        sqlSession.close();
    }
    

    原生的mybatis查询sql需要5步,那么spring中到底做了什么封装使得在项目中直接注入mapper接口就可以进行查询了呢

    1. sqlSessionFactory怎么获取的?

    public class SqlSessionFactoryBean
        implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    

    FactoryBean作用: 通过实现FactoryBean.getObject()方法能够让我们自定义Bean的创建过程。

    ApplicationListener<ApplicationEvent> 作用: 能够让SqlSessionFactoryBean有能力监听应用发出的一些事件通知。

    也就是说当我们需要告知spring怎么实例化一个bean的时候需要实现FactoryBean接口。比如说SqlSessionFactoryBean,他的作用是创建一个SqlSessionFactory并交由spring管理。

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setTypeAliasesPackage("com.example.spring.mybatis.demo.domain");
        factoryBean.setMapperLocations(resolveMapperLocations());
        return factoryBean.getObject();
    }
    

    在上面这段代码中,就是配置一个Mybatis的SqlSessionFactory。setxxx其实是设置属性,最重要的是.getObjet()方法。看下源码。

    SqlSessionFactoryBean

    @Override
    public SqlSessionFactory getObject() throws Exception {
      return buildSqlSessionFactory();
    }
    
    protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new sqlSessionFactoryBuilder();
        //mybatis Configuration类,负责mybatis全局配置信息。
        final Configuration targetConfiguration;
    
        targetConfiguration = xmlConfigBuilder.getConfiguration();
      
        ....
          //配置拦截 省略部分代码
          targetConfiguration.addInterceptor(plugin);
          //配置类型处理器 省略部分代码
          targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
        ....
        //这部分不就是原生的mybatis生成SqlSessionFactory的代码吗?
        return this.sqlSessionFactoryBuilder.build(targetConfiguration);
      }
    
    

    其实就是在getObject方法中调用了Mybatis提供的生成SqlSessionFactory的方法,this.sqlSessionFactoryBuilder.build(targetConfiguration)其中的targetConfiguration的数据就是取的 factoryBean.setDataSource(dataSource); factoryBean.setTypeAliasesPackage("com.example.spring.mybatis.demo.domain");

    这些属性而已。其实这里已经执行了原生代码的 1,2,3步。

    2. sqlSession怎么获取

    首先为什么不能用Mybatis提供的DefaultSqlSession mybatis文档中提到

    DefaultSqlSession不是线程安全的每个线程都应该有自己的生命周期,因此不能被共享,他的最佳作用域是请求或方法作用域。绝对不能将sqlSession的实例引用放在静态作用域中也不能将他放在任何类型的托管作用域中。

    /**
     * The default implementation for {@link SqlSession}.
       注意这的类不是线程安全的
     * Note that this class is not Thread-Safe.
     *
     * @author Clinton Begin
     */
    public class DefaultSqlSession implements SqlSession {
    

    因为不能直接吧DefaultSqlSession交由spring管理,所以spring-mybatis做了一层封装。

    @Bean
    public SqlSessionTemplate sqlSession() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory());
    }
    
    public class SqlSessionTemplate implements SqlSession, DisposableBean {
       public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
      }
    

    SqlSessionTemplate ** 继承了 mybatis 的SqlSession接口,构造函数中需要传入SqlSessionFactory。原始的myabtis代码中SqlSession是由第4步SqlSessionFactory.openSession()**方法来获取的,其核心代码是

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        ...
        final Executor executor = configuration.newExecutor(tx, execType);
        //新建一个sqlSession
        return new DefaultSqlSession(configuration, executor, autoCommit);
        ....
    }
    

    每次一个新的请求过来都需要创建一个新的SqlSession(new DefaultSqlSession())用来保证sqlSession是根当前线程绑定的。

    在spring中对象的创建是交由ioc容器管理的,那么如何在spring中如何保证sqlSession是跟线程绑定的呢?换句话说如何保证注入的sqlSession 每次请求都是一个新的对象呢?

    核心代码 SqlSessionTemplate

    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());
    }
    

    在实例化一个SqlSessionTemplate的时候构造函数中初始化了一个this.sqlSessionProxy(private final SqlSession sqlSessionProxy;)属性,而初始化的值确实一个代理对象,代理了SqlSession接口,代理类是SqlSessionInterceptor.

    private class SqlSessionInterceptor implements InvocationHandler {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //获取SqlSession 1
        SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
            SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
        try {
          //执行SqlSession方法 2
          Object result = method.invoke(sqlSession, args);
          if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
            sqlSession.commit(true);
          }
          return result;
        } catch (Throwable t) {
    
    public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
        PersistenceExceptionTranslator exceptionTranslator) {
    
      notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
      notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
    
      SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    
      SqlSession session = sessionHolder(executorType, holder);
      if (session != null) {
        return session;
      }
      //这不就是原生的mybatis获取一个session的方法吗 获取一个新的session
      session = sessionFactory.openSession(executorType);
    
      registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
    
      return session;
    }
    

    整理一下。定义了一个SqlSessionTemplate类,交由ioc容器管理。在实例化的时候SqlSessionTemplate中初始化了

    sqlSessionProxy属性,这个SqlSessionProxy类型是SqlSession接口并且他的实现类是一个代理接口SqlSessionInterceptor ,在执行SqlSessionTemplate的方法的时候,SqlSessionTemplate的方法做了层转发,转发到SqlSessionProxy

    ...
    @Override
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
      //转发
      return this.sqlSessionProxy.selectList(statement, parameter, rowBounds);
    }
    
    @Override
    public void select(String statement, ResultHandler handler) {
      //转发
      this.sqlSessionProxy.select(statement, handler);
    }
    ...
    

    SqlSessionProxy来执行,而我们知道SqlSessionProxy是代理类,那么所有的方法执行都要经过SqlSessionInterceptor.invoke()方法。而SqlSessionInterceptor.invoke()方法内部的逻辑是选获取缓存中的数据,如果缓存中的数据没有,则调用原生的sqlsessionFactoty.openSession方法新new一个新的SqlSession(1),然后调用Object result = method.invoke(sqlSession, args);此方法中的sqlSession就是刚才new的一个sqlSession(1)。然后执行sqlSession(1)所对应的方法,因为SqlSessionInterceptor实现了SqlSession接口,所以SqlSessionInterceptor有的接口sqlSession(1)中也有。从而实现了每次获取sqlSession都能拿到一个新的。

    核心是代理模式的应用。也有点类装饰器模式的意思(TemplateSqlSession 对 SqlSessionProxy装饰)。

    3. Mapper如何获取的?

    //直接注入每一个实体类给你用
    @Bean
    public ManMapper manMapper() throws Exception {
        MapperFactoryBean<ManMapper> factoryBean = new MapperFactoryBean<>();
        factoryBean.setMapperInterface(ManMapper.class);
        factoryBean.setSqlSessionFactory(sqlSessionFactory());
        return factoryBean.getObject();
    }
    

    这是一种很简单的方式,一个mapper注册一个bean。

    public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    

    MapperFactoryBean实现了 FactoryBean接口,可以自定义对象的实例化过程。

    @Override
    public T getObject() throws Exception {
      return getSqlSession().getMapper(this.mapperInterface);
    }
    

    SqlSessionTemplate

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> T getMapper(Class<T> type) {
      return getConfiguration().getMapper(type, this);
    }
    

    Configuration

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
      return mapperRegistry.getMapper(type, sqlSession);
    }
    

    通过层层转发,最后调用Mybatis的底层接口

    MapperProxyFactory

    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {
      return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }
    

    并没有什么太复杂的东西,其实就是把Mybatis的第5步用 MapperFactoryBean封装了起来,生成的对象自交spring管理而已。

    4. 扫描类如果注册Mapper?

    通过注解定义扫描的包

    @Configuration
    @MapperScan(basePackages = "com.example.spring.mybatis.demo.mapper")
    public class Config {
    
    public class MapperScannerConfigurer
        implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
    

    MapperScannerConfigurer类实现了BeanDefinitionRegistryPostProcessor接口重写了 postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法。

    其中BeanDefinitionRegistry 保存了bean的定义信息,以后BeanFactory就是按照BeanDefinitionRegistry里面保存的每一个bean定义信息创建bean实例。

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
      if (this.processPropertyPlaceHolders) {
        processPropertyPlaceHolders();
      }
      //实现了扫描的类
      ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
      scanner.setAddToConfig(this.addToConfig);
      scanner.setAnnotationClass(this.annotationClass);
      scanner.setMarkerInterface(this.markerInterface);
      scanner.setSqlSessionFactory(this.sqlSessionFactory);
      scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
      scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
      scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
      scanner.setResourceLoader(this.applicationContext);
      scanner.setBeanNameGenerator(this.nameGenerator);
      scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
      if (StringUtils.hasText(lazyInitialization)) {
        scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
      }
      scanner.registerFilters();
      //核心方法
      scanner.scan(
          StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
    }
    

    ClassPathBeanDefinitionScanner继承了ClassPathScanningCandidateComponentProvider。构造方法必传一个BeanDefinitionRegistry

    public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
      public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
            this(registry, true);
        }
    }
    

    其中的scan()方法又调用了 doScan()方法

    @Override
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
      Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
     
    processBeanDefinitions(beanDefinitions);
    
      return beanDefinitions;
    }
    

    核心方法,有点类似于代理传入的是Mapper接口,这里对bean属性进行了设置,变成了MapperFactoryBean类。后续就跟第三步一样,没啥区别了。

    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
      GenericBeanDefinition definition;
      for (BeanDefinitionHolder holder : beanDefinitions) {
        definition = (GenericBeanDefinition) holder.getBeanDefinition();
        String beanClassName = definition.getBeanClassName();
       //设置构造参数,传入原始类(mapper 接口)
    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 
        //设置类属性
        definition.setBeanClass(this.mapperFactoryBeanClass);
    
        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)) {
          
          definition.getPropertyValues().add("sqlSessionTemplate",
              new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
          explicitFactoryUsed = true;
        } else if (this.sqlSessionTemplate != null) {
          
          definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
          explicitFactoryUsed = true;
        }
        definition.setLazyInit(lazyInitialization);
      }
    }
    

    相关文章

      网友评论

          本文标题:mybatis-spring

          本文链接:https://www.haomeiwen.com/subject/npsjhktx.html