相关框架mybatis
入门
分析--个人见解
如何创建SqlSessionFactory?什么是SqlSession?什么是Mapper? mybatis-spring
入门
实操:构建一个基本的MyBatis配置 总结Mapper创建方式
方式一:SqlSessionFactory/SqlSessionTemplate方式二:MapperFactoryBean方式三:@MapperScans/@MapperScan 一探究竟:@MapperScan刨根问底:MapperScannerConfigurer
Mapper到底是什么?
MapperRegistryMapperProxyFactoryMapperProxy 源码总结 极致体验:面向Spring Boot! mybatis-spring-boot-starter
MybatisAutoConfiguration
相关框架mybatismybatis-springmybatis-spring-boot-startermybatis-plus mybatis
官方文档
入门快速入门
步骤1 引入maven
org.mybatis mybatis x.x.x
步骤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 构建 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的基本使用包含如下几个类
SqlSessionFactorySqlSessionXXXMapper
所以问题来了
如何创建SqlSessionFactory?需要DataSource(数据源)、事务管理器、Mapper配置。
当然还有其他配置信息,但是这里我们只讨论必要的配置信息。
SqlSession由SqlSessionFactory创建生成,SqlSession用来执行sql。Mapper操作需要在SqlSession下执行。
什么是Mapper?实际SQL操作使用的最多的便是Mapper,SqlSessionFactory主要用于配置过程。SqlSession也是可以手动获取,但是通常是隐式获取,而Mapper才是开发使用最多的。
mybatis-spring官方文档
入门快速开始
这里我们通过看xml配置,因为我觉得这样更清晰
使用 SqlSessionFactoryBean来创建 SqlSessionFactory
创建Mapper
- 指定sqlSessionFactory指定mapperInterface
实操:构建一个基本的MyBatis配置
相关源码:https://gitee.com/xu_xiaobao/tobehero/tree/master/study-mybatis
引入maven
这里我们只体现mybatis相关
org.mybatis mybatis 3.5.9 org.mybatis mybatis-spring 2.0.7
准备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的存在,一对多的话也没有任何意义。
@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;
}
}
正如上面所说,SqlSessionTemplate和SqlSessionFactory具有相同的作用,换言之在创建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:负责注入MapperScannerConfigurerMapperScannerConfigurer:最终完成Mapper类的注入
MapperScan.java
MapperScannerRegistrar.java
所以,下面重点是MapperScannerConfigurer都做了些什么?
- org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry
这里出现了一个postProcessBeanDefinitionRegistry方法:
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
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));
}
- scanner:org.mybatis.spring.mapper.ClassPathMapperScanner#scan
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);
}
- org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
扫描得到所有的@Mapper注释的类,然后将其注册的IOC
@Override public SetdoScan(String... basePackages) { //得到所有的@Mapper注解声明的类定义 Set 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; }
- org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions
默认为注册MapperFactoryBean到IOC,可修改
private void processBeanDefinitions(SetbeanDefinitions) { 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来得到的。
Mapper对象登记处
既然我们知道怎么得到的,那么我们首先要知道怎么放进去的?
org.apache.ibatis.binding.MapperRegistry#addMapper,最终由MapperProxyFactory负责MapperProxy的创建工作。
publicvoid addMapper(Class 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 getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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 MapperProxyFactoryMapperProxy{ ... ... @SuppressWarnings("unchecked") protected T newInstance(MapperProxy mapperProxy) { //最终用MapperProxy对象来代理Mapper接口 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { //MapperProxy final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(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);
}
}
源码总结
- 首先MyBatis的核心为SqlSessionFactory,通过SqlSessionFactory得到Mapper类完成SQL处理,同时在使用Mapper的过程中,需要一个SqlSession环境。Spring中增加了SqlSessionTemplate包装SqlSessionFactory,并在使用时屏蔽掉SqlSession的操作,所以SqlSessionTemplate对类似selectOne类似的MyBatis操作也是需要通过jdk代理完成代理工作。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被作为SqlSession被MapperProxy所持有了当我们调用Mapper中的方法,实际会通过MapperProxy#invoke被拦截执行。同时最终因为SqlSession即为SqlSessionTemplate,所以最终而言MapperProxy负责找到映射sql-mapper的SQL语句,最终交由SqlSession进行SQL的执行。
官方文档
翻译后
正如您可能已经知道的,要将MyBatis与Spring结合使用,您至少需要一个SqlSessionFactory和至少一个mapper接口。
MyBatis-Spring-Boot-Starter 将会:自动检测到 一个已存在的DataSource通过使用SqlSessionFactoryBean传入DataSource创建并注册一个SqlSessionFactory实例。通过SqlSessionFactory来创建并注册一个SqlSessionTemplate自动扫描Mappers,将其链接到SqlSessionTemplate,并将其注册到Spring上下文,以便将其注入到bean中
maven
MybatisAutoConfigurationorg.mybatis.spring.boot mybatis-spring-boot-starter 2.2.2



