SpirngBoot集成mybatis
Spring整合Mybatis
1、核心概念
Spring去整合其他框架的时候,无非是获取得到其他框架中的一些核心的对象让其成为bean,在使用的时候来进行注入。
在mybatis中是可以单独的来进行使用的,但是在整合spring时,就需要把一些核心对象封装成bean,放入到spring容器中来;
那么在mybatis中,我们经常使用的就是注入对应的mapper对应的代理对象。所以这里的mapper是最核心的关键所在。
而mybatis中的核心对象mapper对应的代理对象应该成为bean,这个是核心思路。
mybatis中的mapper的代理对象---->spring容器的bean
2、手写实现
想要让mapper代理对象成为bean:
首先应该成为一个bean定义,但是因为是接口,无法来进行实例化。因为mapper代理对象是mybatis中生成的
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
// 但是这里是一个接口,无法进行实例化
beanDefinition.setBeanClass(UserMapper.class);
beanDefinition.setBeanClassName("userMapper");
使用FactoryBean
@Component
public class MybatisFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(MybatisFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
return userMapper;
}
// 对应的类型
@Override
public Class<?> getObjectType() {
return UserMapper.class;
}
}
这里的工厂bean的名称是mybatisFactoryBean,而实际上的bean是factorybean中调用getObject产生的对象。
那么这里就已经实现了将UserMapper对应的代理类成为了容器中的bean。【只不过执行方法的时候这里打印的是null而已】
问题1、
现在只是针对一个mapper接口来进行实现,不可能说针对每个mapper接口都来写一个FactoryBean来进行实现。那么希望的是针对每个mapper都能够产生,那么利用构造方法来进行实现:
@Component
public class MybatisFactoryBean implements FactoryBean {
private Class interfaceClass;
public MybatisFactoryBean(Class interfaceClass) {
this.interfaceClass = interfaceClass;
}
@Override
public Object getObject() throws Exception {
Object tMapperProxy = (UserMapper) Proxy.newProxyInstance(MybatisFactoryBean.class.getClassLoader(), new Class[]{interfaceClass.getClass()}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName());
return null;
}
});
return tMapperProxy;
}
@Override
public Class<?> getObjectType() {
return interfaceClass.getClass();
}
}
然后在构造bean定义赋值的时候来进行赋值:
beanDefinition.setBeanClass(MybatisFactoryBean.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
beanDefinition.setBeanClassName("userMapper");
AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
// 但是这里是一个接口,无法进行实例化
beanDefinition1.setBeanClass(MybatisFactoryBean.class);
beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OderMapper.class);
beanDefinition1.setBeanClassName("orderMapper");
构造bean的时候对应的类型都是MybatisFactoryBean类型,每个bean名字对应的类型是UserMapper、OderMapper类型的。
问题2
但是不可能我们在将spring和mybatis来进行整合的时候来写上面的类似的代码。所以会变得麻烦。所以利用注册bean定义的接口,看看spring中提供的接口BeanDefinitionRegistryPostProcessor:
public class MybatisBeandefinitionRegistriy implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(MybatisFactoryBean.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
beanDefinitionRegistry.registerBeanDefinition("userMapper",beanDefinition);
AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition1.setBeanClass(MybatisFactoryBean.class);
beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OderMapper.class);
beanDefinitionRegistry.registerBeanDefinition("orderMapper",beanDefinition1);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
// .....
}
}
问题3
但是上面的具体的mapper对于spring来说是不知道的,所以针对各种各样的类型,这种是不可取的。那么这样子应该来针对指定包下面的接口来进行扫描将接口添加进来。那么新的问题又来了:如何获取得到对应的路径??肯定是想通过自定义注解来进行实现:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SelfScan {
String value();
}
那么在哪里可以获取得到这个指定的路径?那么上面注册bean定义的接口肯定是行不通的,那么只能够通过另外一种注册bean定义的接口ImportBeanDefinitionRegistrar来进行实现:
public class MybatisImportBeandifinitaionRegistry implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
这里的importingClassMetadata就是对应的注解源信息,那么就可以通过这个来进行获取得到对应的接口中的包信息。
那么应该将对应的扫描路径赋值给importingClassMetadata,那么这一步需要引入一下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MybatisImportBeandifinitaionRegistry.class)
public @interface SelfScan {
String value();
}
然后在MybatisImportBeandifinitaionRegistry类中来进行实现:
public class MybatisImportBeandifinitaionRegistry implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
MultiValueMap<String, Object> allAnnotationAttributes = importingClassMetadata.getAllAnnotationAttributes(SelfScan.class.getName());
// 获取得到对应的路径信息
String mapperPath = (String) allAnnotationAttributes.get("value").get(0);
// 拿到之后应该来进行扫描,但是spring中已经提供了对应的扫描器,那么我们应该利用这个扫描器来进行操作
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(MybatisFactoryBean.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
registry.registerBeanDefinition("userMapper",beanDefinition);
AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition1.setBeanClass(MybatisFactoryBean.class);
beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OderMapper.class);
registry.registerBeanDefinition("orderMapper",beanDefinition1);
}
}
所以自定义一个扫描器:
public class MybatiScan extends ClassPathBeanDefinitionScanner {
public MybatiScan(BeanDefinitionRegistry registry) {
super(registry);
}
}
然后添加到MybatisImportBeandifinitaionRegistry中来:
public class MybatisImportBeandifinitaionRegistry implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
MultiValueMap<String, Object> allAnnotationAttributes = importingClassMetadata.getAllAnnotationAttributes(SelfScan.class.getName());
String mapperPath = (String) allAnnotationAttributes.get("value").get(0);
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(MybatisFactoryBean.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
registry.registerBeanDefinition("userMapper",beanDefinition);
MybatiScan mybatiScan = new MybatiScan(registry);
// 但是扫描方式存在着问题
mybatiScan.scan(mapperPath);
AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition1.setBeanClass(MybatisFactoryBean.class);
beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OderMapper.class);
registry.registerBeanDefinition("orderMapper",beanDefinition1);
}
}
那么查看下继承类的扫描方式:
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
// 看下这里的扫描方式
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
对应的查找候选的component:
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
.......
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// 第一个判断
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
// 第二个判断
if (isCandidateComponent(sbd)) {
.......
}
但是成为组件的详细判断是:
第一个判断中:过滤条件中:类上是否有@Component注解:
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
第二个判断条件:
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}
那么这两个判断在mybatis扫描的阶段中都应该来进行修改掉:
public class MybatiScan extends ClassPathBeanDefinitionScanner {
public MybatiScan(BeanDefinitionRegistry registry) {
super(registry);
}
// 重写这里的扫描逻辑查看一下是否有对应的bean定义
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
return beanDefinitionHolders;
}
// 判断是否是接口。对应的是上面的第二个判断条件
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface();
}
}
但是这里无法满足第一个判断条件:类上有@Componenet注解,那么这种方式很明显是不可取的。但是根据第一个的判断中,我们可以来加上mybatis自己的扫描逻辑:
public class MybatisImportBeandifinitaionRegistry implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
MultiValueMap<String, Object> allAnnotationAttributes = importingClassMetadata.getAllAnnotationAttributes(SelfScan.class.getName());
String mapperPath = (String) allAnnotationAttributes.get("value").get(0);
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(MybatisFactoryBean.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
registry.registerBeanDefinition("userMapper",beanDefinition);
MybatiScan mybatiScan = new MybatiScan(registry);
// 添加过滤条件
mybatiScan.addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
mybatiScan.scan(mapperPath);
AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition1.setBeanClass(MybatisFactoryBean.class);
beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OderMapper.class);
registry.registerBeanDefinition("orderMapper",beanDefinition1);
}
}
但是这个时候,可以看到bean的类型是对应的mapper类型的,但是我们是通过factorybean中的getObject方法来进行获取得到的,所以这里还需要来做一些修改操作:
public class MybatiScan extends ClassPathBeanDefinitionScanner {
public MybatiScan(BeanDefinitionRegistry registry) {
super(registry);
}
// 重写这里的扫描逻辑查看一下是否有对应的bean定义
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
beanDefinition.setBeanClassName(MybatisFactoryBean.class.getName());
}
return beanDefinitionHolders;
}
// 判断是否是接口
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface();
}
}
那么这种使用方式使用起来,即使是再次添加一个mapper都是可以使用的。
问题4
上面的代理对象是来解决了,但是真实的mapper代理对象应该是由mybatis来产生的,而这里的是我们自己通过JDK的动态代理来生成的,所以不符合要求。
而代理对象是mybatis中通过:
SqlSession session = sqlSessionFactory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
是通过sqlSessionFactory获取得到SqlSession,然后通过SqlSession来进行产生的对象。那么也就意味着我们需要将动态代理给替换掉:
@Component
public class MybatisFactoryBean implements FactoryBean {
private Class interfaceClass;
private SqlSession sqlSession;
public MybatisFactoryBean(Class interfaceClass) {
this.interfaceClass = interfaceClass;
}
@Override
public Object getObject() throws Exception {
return sqlSession.getMapper(interfaceClass);
}
@Override
public Class<?> getObjectType() {
return interfaceClass.getClass();
}
}
那么这个时候需要使用到sqlSessionFactory,那么怎么样才能够获取得到sqlSessionFactory?这是是新的问题:
@Component
public class MybatisFactoryBean implements FactoryBean {
private Class interfaceClass;
private SqlSession sqlSession;
private SqlSessionFactory sqlSessionFactory;
public MybatisFactoryBean(Class interfaceClass) {
this.interfaceClass = interfaceClass;
}
@Autowired
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public Object getObject() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession.getMapper(interfaceClass);
}
@Override
public Class<?> getObjectType() {
return interfaceClass.getClass();
}
}
然后从容器中来进行获取得到对应的值。所以关键性的就是如何来获取得到sqlSessionFactory的地方了。
而这个时候就不是spring和mybatis能够做到的事情了,而是由spring-mybatis中来操作。所以在spring-mybatis中需要来配置对应的胶水包了集成spring和mybatis
总结
spring和mybatis整合的时候最重要的两件事情:1、对应的扫描规则;2、factorybean产生mybatis对应的代理成为容器中的bean;
可以使用mapperscan来进行扫描,也可以使用MapperScannerConfigurer来进行配置【高版本的设计】
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.guang.pojo"/>
<!--<property name="configLocation" value="classpath:sqlMapConfig.xml"/>-->
</bean>
<!--配置映射器扫描-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.guang.dao"/>
</bean>
这里可以有两种方式来进行扫描:1、添加@MapperScan;2、配置MapperScannerConfigurer来进行扫描。
因为MapperScannerConfigurer也实现了BeanDefinitionRegistryPostProcessor的注册。
Spring整合Mybatis
最重要的是去实现扫描和注册对应的bean。
SpringBoot集成mybatis
@Configuration
@Import({MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class})
@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
public MapperScannerRegistrarNotFoundConfiguration() {
}
public void afterPropertiesSet() {
MybatisAutoConfiguration.logger.debug("Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
}
}
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, EnvironmentAware, ImportBeanDefinitionRegistrar {
private BeanFactory beanFactory;
private Environment environment;
public AutoConfiguredMapperScannerRegistrar() {
}
看下这两行代码即可