Mybatis学习路线

相关框架

  • mybatis
  • mybatis-spring
  • mybatis-spring-boot-starter
  • mybatis-plus

mybatis

官方文档

入门

快速入门
步骤1 引入maven

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>

步骤2 从 XML 中构建 SqlSessionFactory

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

或者 不使用 XML 构建 SqlSessionFactory

DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

步骤3 从 SqlSessionFactory 中获取 SqlSession

try (SqlSession session = sqlSessionFactory.openSession()) {
  Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
}

或者

try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
}

分析–个人见解

以上部分是我们参考官方做的记录–>所以下面才是重点内容,不然直接看官方文档就OK了!!!以下是个人理解,不喜勿喷!!!
通过上面可以看出来,对于mybatis的基本使用包含如下几个类

  • SqlSessionFactory
  • SqlSession
  • XXXMapper

所以问题来了

如何创建SqlSessionFactory?

需要DataSource(数据源)、事务管理器、Mapper配置。
当然还有其他配置信息,但是这里我们只讨论必要的配置信息。

什么是SqlSession?

SqlSession由SqlSessionFactory创建生成,SqlSession用来执行sql。Mapper操作需要在SqlSession下执行。

什么是Mapper?

实际SQL操作使用的最多的便是Mapper,SqlSessionFactory主要用于配置过程。SqlSession也是可以手动获取,但是通常是隐式获取,而Mapper才是开发使用最多的。

mybatis-spring

官方文档

入门

快速开始
这里我们通过看xml配置,因为我觉得这样更清晰
使用 SqlSessionFactoryBean来创建 SqlSessionFactory

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>

创建Mapper

  1. 指定sqlSessionFactory
  2. 指定mapperInterface
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

实操:构建一个基本的MyBatis配置

相关源码:https://gitee.com/xu_xiaobao/tobehero/tree/master/study-mybatis
引入maven
这里我们只体现mybatis相关

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.9</version>
</dependency>

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.7</version>
</dependency>

准备Mapper接口类

public interface HeroMapper {
    @Select("select * from hero where username = #{username}")
    Hero getUser(@Param("username") String username);
}

配置javaBean

@Configuration
public class SimpleMyBatisConfig {
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        //使用下划线"_"转驼峰
        configuration.setMapUnderscoreToCamelCase(true);
        factoryBean.setConfiguration(configuration);
        return factoryBean.getObject();
    }

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

上面的Mapper处理方式,似乎不是很优雅,我们修改一下写法,在创建SqlSessionFactory的时候,通过指定configuration#addMappers完成Mapper注入。
修改后如下

@Configuration
public class SimpleMyBatisConfig {
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        //使用下划线"_"转驼峰
        configuration.setMapUnderscoreToCamelCase(true);
        //添加mapper包路径
        configuration.addMappers("top.tobehero.study.mybatis.mapper");
        factoryBean.setConfiguration(configuration);
        return factoryBean.getObject();
    }

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

总结Mapper创建方式

方式一:SqlSessionFactory/SqlSessionTemplate

正如上面所说:
实操:构建一个基本的MyBatis配置
补充:(个人理解,有误莫怪)
SqlSessionTemplate:属于Spring概念,实际即为包装SqlSessionFactory,完成一些模板话操作,如打开Session/关闭之类的公共模板操作。
SqlSessionFactory:核心类,完成真正的业务操作,即框架核心功能关注点。
一般情况下,一个SqlSessionFactory的存在,对应这个一个SqlSessionTemplate的存在,一对多的话也没有任何意义。

方式二:MapperFactoryBean

@Configuration
public class SimpleMyBatisConfig {
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        //使用下划线"_"转驼峰
        configuration.setMapUnderscoreToCamelCase(true);
        //添加mapper包路径
        configuration.addMappers("top.tobehero.study.mybatis.mapper");
        factoryBean.setConfiguration(configuration);
        return factoryBean.getObject();
    }
    @Bean
    public MapperFactoryBean userMapper(@Autowired  SqlSessionFactory sqlSessionFactory) throws Exception {
        MapperFactoryBean mfb = new MapperFactoryBean();
        mfb.setMapperInterface(HeroMapper.class);
        mfb.setSqlSessionFactory(sqlSessionFactory);
        return mfb;
    }
}

正如上面所说,SqlSessionTemplateSqlSessionFactory具有相同的作用,换言之在创建MapperFactoryBean的时候,也可以使用SqlSessionTemplate替代SqlSessionFactory,具体API:MapperFactoryBean#setSqlSessionTemplate,请自行实践,不再演示。

方式三:@MapperScans/@MapperScan

上面两种方式均存在使用繁琐的问题,那么框架必然是为了简化开发,所以@MapperScan就是为了简化Mappers的注入过程。
使用如下,只需要注解指定扫描包路径即可完成Mappers的注入。

@Configuration
@MapperScan(basePackageClasses = {HeroMapper.class})
public class SimpleMyBatisConfig {
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        //使用下划线"_"转驼峰
        configuration.setMapUnderscoreToCamelCase(true);
        factoryBean.setConfiguration(configuration);
        return factoryBean.getObject();
    }
    @Bean
    public SqlSessionTemplate sqlSessionTemplate(@Autowired  SqlSessionFactory sqlSessionFactory){
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

一探究竟:@MapperScan

  • @MapperScan:主要完成扫描包路径的设置,以及其他的一些设置
  • MapperScannerRegistrar:负责注入MapperScannerConfigurer
  • MapperScannerConfigurer:最终完成Mapper类的注入

MapperScan.java在这里插入图片描述
MapperScannerRegistrar.java
在这里插入图片描述
所以,下面重点是MapperScannerConfigurer都做了些什么?

刨根问底:MapperScannerConfigurer

  1. org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry
    这里出现了一个postProcessBeanDefinitionRegistry方法:
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {

	/**
	 * Modify the application context's internal bean definition registry after its
	 * standard initialization. All regular bean definitions will have been loaded,
	 * but no beans will have been instantiated yet. This allows for adding further
	 * bean definitions before the next post-processing phase kicks in.
	 * @param registry the bean definition registry used by the application context
	 * @throws org.springframework.beans.BeansException in case of errors
	 */
	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

}

大致意思:
在所有的Bean都完成了定义的注册,但是还未进行Bean的实例化前,允许再进行一些Bean的定义注册。(相信在这里不需要多做解释,这里就是可以程序化注册Mapper定义的切入点了)
这里我们可以看到有一个扫描动作。

@Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
   ...
   //扫描器,注意还把registry传了进去(说明:这个scanner可能会注入一个Bean)
   ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
   ...
   //扫描basePackage
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
  1. scanner:org.mybatis.spring.mapper.ClassPathMapperScanner#scan
/**
* Perform a scan within the specified base packages.
 * @param basePackages the packages to check for annotated classes
 * @return number of beans registered
 */
public int scan(String... basePackages) {
	int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

	//完成扫描
	doScan(basePackages);

	// Register annotation config processors, if necessary.
	if (this.includeAnnotationConfig) {
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}

	return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
  1. org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
    扫描得到所有的@Mapper注释的类,然后将其注册的IOC
  /**
   * Calls the parent search that will search and register all the candidates. Then the registered objects are post
   * processed to set them as MapperFactoryBeans
   */
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
	//得到所有的@Mapper注解声明的类定义
    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 {
      //处理BeanDefinitions
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }
  1. org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions
    默认为注册MapperFactoryBean到IOC,可修改
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  for (BeanDefinitionHolder holder : beanDefinitions) {
    definition = (GenericBeanDefinition) holder.getBeanDefinition();
    String beanClassName = definition.getBeanClassName();
    LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
        + "' mapperInterface");

    // the mapper interface is the original class of the bean
    // but, the actual class of the bean is MapperFactoryBean
    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
    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)) {
      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) {
      LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
    definition.setLazyInit(lazyInitialization);
  }
}

至此,Mapper的注入完成。
值得注意的是:如果默认情况下Mapper类并不需要用@Mapper修饰,即可被@MapperScan扫描到,只需要当前类是接口类且没有默认实现方法就可以被代理。当然我们可以通过在使用@MapperScan时指定属性annotationClass来进行Mapper类的过滤,以避免错误的Mapper被注入并代理。

@MapperScan(basePackageClasses = {HeroMapper.class},annotationClass = Mapper.class)

Mapper到底是什么?

通过上面的讲解,Mapper是通过MapperFactoryBean创建出来的,而且我们只是编写了Mapper的接口类。那么首先肯定是通过代理产生,那么mapper到底是什么?
最终我们发现是通过org.apache.ibatis.binding.MapperRegistry#getMapper来得到的。

MapperRegistry

Mapper对象登记处
既然我们知道怎么得到的,那么我们首先要知道怎么放进去的?
org.apache.ibatis.binding.MapperRegistry#addMapper,最终由MapperProxyFactory负责MapperProxy的创建工作。

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
      	//Mapper代理工厂-->创建MapperProxy
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

再来看一下getMapper
传入一个SqlSession,返回Mapper对象
mapperProxyFactory.newInstance
值得注意:SqlSession也可以是一个代理,so不存在单个Template有并发问题,very nice!!!

@SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      //创建MapperProxy	
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
MapperProxyFactory

最终用MapperProxy对象来代理Mapper接口.
注意用的是JDK代理

public class MapperProxyFactory<T> {
  ... ...
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    //最终用MapperProxy对象来代理Mapper接口
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
  	//MapperProxy
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}
MapperProxy

既然是JDK代理,那么必然实现了InvocationHandler

 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   try {
     if (Object.class.equals(method.getDeclaringClass())) {
       return method.invoke(this, args);
     } else {
       //缓存Invoker->执行调用
       return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
     }
   } catch (Throwable t) {
     throw ExceptionUtil.unwrapThrowable(t);
   }
 }

Invoker#invoke
这里以PlainMethodInvoker为例,最终完成SQL的执行

private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;

    public PlainMethodInvoker(MapperMethod mapperMethod) {
      super();
      this.mapperMethod = mapperMethod;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return mapperMethod.execute(sqlSession, args);
    }
  }

源码总结

  1. 首先MyBatis的核心为SqlSessionFactory,通过SqlSessionFactory得到Mapper类完成SQL处理,同时在使用Mapper的过程中,需要一个SqlSession环境。
  2. Spring中增加了SqlSessionTemplate包装SqlSessionFactory,并在使用时屏蔽掉SqlSession的操作,所以SqlSessionTemplate对类似selectOne类似的MyBatis操作也是需要通过jdk代理完成代理工作。
  3. Spring通过@MapperScan完成对Mappers类的扫描与注册,需要使用MapperScannerConfigurer来完成注入,其中
    3.1 MapperScannerConfigurer主要完成将符合条件的Mapper通过MapperFactoryBean注入IOC中。
    3.2 MapperFactoryBean是一个工厂,是Spring生产Bean的一种方式,我们需要关注的是它的getObject方法,它是通过SqlSessionTemplate#getMapper来得到的注入IOC中的Mapper对象。此时可以看到是由mapperRegistry.getMapper得到的mapper。最终再由MapperProxyFactory生产出一个被MapperProxy代理的Mapper对象。
    3.3 所以最终被注入到IOC的mapper对象是一个被MapperProxy代理的Mapper对象。值得注意的是此时的SqlSessionTemplate被作为SqlSessionMapperProxy所持有了
  4. 当我们调用Mapper中的方法,实际会通过MapperProxy#invoke被拦截执行。同时最终因为SqlSession即为SqlSessionTemplate,所以最终而言MapperProxy负责找到映射sql-mapper的SQL语句,最终交由SqlSession进行SQL的执行。

极致体验:面向Spring Boot!

mybatis-spring-boot-starter

官方文档
在这里插入图片描述
翻译后

正如您可能已经知道的,要将MyBatis与Spring结合使用,您至少需要一个SqlSessionFactory和至少一个mapper接口。
MyBatis-Spring-Boot-Starter 将会:

  • 自动检测到 一个已存在的DataSource
  • 通过使用SqlSessionFactoryBean传入DataSource创建并注册一个SqlSessionFactory实例。
  • 通过SqlSessionFactory来创建并注册一个SqlSessionTemplate
  • 自动扫描Mappers,将其链接到SqlSessionTemplate,并将其注册到Spring上下文,以便将其注入到bean中

maven

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>

MybatisAutoConfiguration

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值