可能是最卷的Spring源码系列(十七):spring-mybatis

本文会讲到mybatis的使用和核心原理分析、spring-mybatis的整合原理,目的是真正搞明白mybatis是如何使用spring的扩展点的

Mybatis部分

mybatis的简单使用

我们通常都是在spring环境下使用mybatis,那么mybatis在非spring的环境下mybatis是如何使用呢?下面将会采用注解的方式来使用mybatis

		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);
		// 数据查询阶段
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 这一步完成了代理
		BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
		Blog blog = mapper.selectBlog();
		log.info("blog {}", blog);

总结一下,mybatis的使用为下面几个步骤:
1、创建数据源和事务工厂
2、创建Environment 对象,并把数据源和事务工厂传入该对象
3、创建SqlSessionFactory, 并把Environment 传入该对象
4、获取SqlSession
5、调用sqlSession.getMapper获取BlogMapper 对象
6、执行BlogMapper 中的相关方法

mybatis核心原理

上述调用过程看似很简单的,其实关键在于第5步,我们可以看到第五步传入了一个接口,返回也是用接口去接的,那么为什么在第6步就可以直接调用这个方法呢,这里直接说结论,第五步是使用代理完成的,获取的是接口的代理对象,这个对象已经对接口中的方法进行了增强,才能使我们能在第六步顺利完成调用。
在这里,我们模拟下这一过程,如果你对jdk代理的使用还不了解,请完成了解后再看下面步骤

public class CustomSqlSession {

	public <T> T getMapper(Class<T> clazz) {
		Object proxy = Proxy.newProxyInstance(CustomSqlSession.class.getClassLoader(), new Class[]{clazz}, new CustomInvocationHandler());
		return (T) proxy;
	}


	class CustomInvocationHandler implements InvocationHandler {
		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			if (method.isAnnotationPresent(Select.class)) {
				Select select = method.getAnnotation(Select.class);
				String sql = select.value()[0];
				log.info("sql:{}", sql);
			}
			if (method.getName().equals("toString")) {
				return proxy.getClass().getName();
			}
			return null;
		}
	}
}
public static void main(String[] args) {

   	CustomSqlSession  customSqlSession = new CustomSqlSession();
   	BlogMapper mapper = customSqlSession.getMapper(BlogMapper.class);
   	Blog blog = mapper.selectBlog();
   }

就这么一个简单的过程,就能完成对接口的代理,使得接口中的方法可以正常执行

mybatis-spring部分

mybatis整合

这个过程相信大家都很熟悉了,为了文章的完整性还是在总结一下

@Configuration
@MapperScan("haoxu.wang.mybatis.common")
public class MybatisConfig {
	@Bean()
	public DataSource dataSource() {
		DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
		driverManagerDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
		driverManagerDataSource.setPassword("123456");
		driverManagerDataSource.setUsername("root");
		driverManagerDataSource.setUrl("jdbc:mysql://39.105.156.80:3306/demo?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC");
		return driverManagerDataSource;
	}

	@Bean
	public SqlSessionFactory sqlSessionFactory() throws Exception {
		SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
		factoryBean.setDataSource(dataSource());
		return factoryBean.getObject();
	}
}
public static void main(String[] args) {
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MybatisConfig.class);
		Blog blog = ac.getBean(BlogMapper.class).selectBlog();
		log.info("blog {}", blog);
	}

可以看的出来,一个@MapperScan注解就完成了把mapper接口注入到spring容器中,这里是如何完成的呢?
根据spring给用户提供的接口以及扩展点,有一下方式可以完成对对象的注入
1.xml中的bean标签

这种方法需要提供一个类,然后交给spring去初始化,奈何mybatis的mapper是mybatis通过代理创建的,所以无法使用这种方式。
其实这里还可以配置工厂方法进行注入,但是为每个mapper都写一个工厂方法显然不大合适

2.@bean注解

为每个mapper都写一个@bean方法,可以实现,但是也不合适

3.@Componse注解扫描

需要交给spring去实例化,无法实现

4.registerBean、registerBeanDefinition、registerSingleton

registerBean、registerBeanDefinition需要交给spring去实例化,无法实现;
registerSingleton可以实现但是spring没这么做,为什么没这么做?我认为是因为registerSingleton这种方式属于给用户提供的api,不是属于扩展点,如果mybatis这么做,其实是需要用户手动去调用这个方法的,如何能通过扩展点注入才是mybatis的出路

这里如果使用BeanFactoryPostProcessor中调用registerSingleton确实可以属于扩展点,也可以优雅的完成mapper的注入,但是mybatis没这么做

5.FactoryBean方法

mybatis就是选择了这种方法,至于为什么选择这种办法,其实这跟spring给用户提供的扩展点有关系,spring给用户提供了很多的扩展点,要不就是使用第4中方式中的办法,但是除了registerSingleton,其余两个都需要把类交给spring,如何能把类交给spring,但是又能自己处理初始化?spring提供了FactoryBean这种方式。简单概括这种方式,就是在FactoryBean中定义类的初始化方式,然后把FactoryBean注入到容器,当容器进行初始化时,会注入两个对象。一个是FactoryBean,一个是FactoryBean定义的对象。

mybatis整合原理

@Retention(RetentionPolicy.RUNTIME)
@Import(CustomImportBeanDefinitionRegistrar.class)
public @interface CustomScan {
	String value() default "com.sahdo";
}
public class CustomFactoryBean  implements FactoryBean{
	
	Class mapperInterface;

	public CustomFactoryBean(){
		System.out.println("xxxx");
	}
	
	public CustomFactoryBean(Class mapperInterface){
		this.mapperInterface=mapperInterface;
	}

	@Override
	public Object getObject() throws Exception {
		CustomSqlSession customSqlSession = new CustomSqlSession();
		Object mapper = customSqlSession.getMapper(mapperInterface);
		return mapper;
	}

	@Override
	public Class<?> getObjectType() {
		return mapperInterface;
	}

	public void setMapperInterface(Class mapperInterface) {
		this.mapperInterface = mapperInterface;
	}
}
public class CustomImportBeanDefinitionRegistrar  implements ImportBeanDefinitionRegistrar {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		//customBeanFactory  -bd
		MergedAnnotations annotations = importingClassMetadata.getAnnotations();
		//spring scan
		List<Class> list = new ArrayList<>();
		list.add(BlogMapper.class);

		for (Class aClass : list) {
			BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(CustomFactoryBean.class);
			AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
			beanDefinition.getPropertyValues().add("mapperInterface",aClass);

			String beanName=(aClass.getSimpleName().substring(0,1).toLowerCase())+aClass.getSimpleName().substring(1);
			registry.registerBeanDefinition(beanName,beanDefinition);
		}

	}
}

就这样完成了mapper的注入,调用非常简单

@Configuration
@CustomScan("haoxu.wang.mybatis.common")
public class CustomMybatisConfig {
}
public class Test {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(CustomMybatisConfig.class);
		BlogMapper bean = ac.getBean(BlogMapper.class);
		bean.selectBlog();
	}
}

上述就模拟了spring-mybatis的整合,其实官方的整合代码中有很多的细节,有时间单独分析官方的整合代码

上一篇:SpringBoot_MyBatis


下一篇:熬夜整理出来的SSM必考60道大厂面试题,真的实用