把其他框架所产生的对象放到Spring容器中,让其成为Bean。
比如MyBatis,MyBatis框架可以单独使用,而单独使用MyBatis框架就需要用到MyBatis所提供的一些类构造出对应的对象,然后使用该对象,就能使用到MyBatis框架给我们提供的功能,MyBatis整合Spring就是为了将这些对象放入Spring容器中成为Bean,只要成为了Bean,在我们的Spring项目中就能很方便的使用这些对象了,也就能很方便的使用MyBatis框架所提供的功能了。
既然我们知道了Spring整合MyBatis的核心思想就是将MyBatis的相关对象注入到Spring容器成为Bean,那么我们就可以自己动手试着整合下MyBatis,实现一个简易版的mybatis-spring组件。实际开发中,我们就是通过MyBatis提供的mybatis-spring组件来实现与Spring整合的。
构建一个SqlSessionFactory,并且需要指定Mapper的路径,该路径下的所有接口Mapper都要被放到Spring容器中成为Bean。
mybatis.xml中配置的是数据库连接。
@ComponentScan("com.firechou") // spring扫描bean路径
@FireMapperScan("com.firechou.mapper") // mybatis扫描Mapper路径
public class AppConfig {
/**
* 定义一个SqlSessionFactory对象
* @return
* @throws IOException
*/
@Bean
public SqlSessionFactory sqlSessionFactory() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory;
}
}
<?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">
<!-- 使用jdbc事务管理 -->
<transactionManager type="JDBC"/>
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://rm-cn-zsk3ar7jm0010m0o.rwlb.rds.aliyuncs.com:3306/db_test?characterEncoding=utf-8&useSSL=false"/>
<property name="username" value="firechou"/>
<property name="password" value="xxx"/>
</dataSource>
</environment>
</environments>
</configuration>
@Retention(RetentionPolicy.RUNTIME) // 运行时生效
@Target(ElementType.TYPE) // 类注解
@Import(FireImportBeanDefinitionRegistrar.class)
public @interface FireMapperScan {
String value();
}
通过@Import注解配置FireImportBeanDefinitionRegistrar,Spring在启动时会执行FireImportBeanDefinitionRegistrar#registerBeanDefinitions方法,该方法里面可以实现扫描路径下的Mapper成为Bean。
/**
* 扫描指定路径下的mapper成为spring容器bean
*/
public class FireImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 扫描路径
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(FireMapperScan.class.getName());
String path = (String) annotationAttributes.get("value");
// 自定义扫描器
FireBeanDefinitionScanner scanner = new FireBeanDefinitionScanner(registry);
// 扫描指定路径下所有文件
scanner.addIncludeFilter(new TypeFilter() {
@Override
public boolean match(@NotNull MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
scanner.scan(path);
}
}
自定义扫描器FireBeanDefinitionScanner来实现Mapper接口扫描逻辑。
/**
* 自定义扫描器
*/
public class FireBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
public FireBeanDefinitionScanner(BeanDefinitionRegistry registry) {
super(registry);
}
@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());
// 指定生成代理类的FactoryBean
beanDefinition.setBeanClassName(FireFactoryBean.class.getName());
}
return beanDefinitionHolders;
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface();
}
}
定义生成Mapper接口代理类的FactoryBean为FireFactoryBean。
/**
* 生成mapper代理对象
*/
public class FireFactoryBean implements FactoryBean {
/**
* 表实体对应的mapper接口
*/
private Class mapperInterface;
private SqlSession sqlSession;
/**
* 构造方法注入mapper接口,扫描器指定该构造器注入接口
* @param mapperInterface
*/
public FireFactoryBean(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* 注入AppConfig中定义的SqlSessionFactory对象
* @param sqlSessionFactory
*/
@Autowired
public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
sqlSessionFactory.getConfiguration().addMapper(mapperInterface);
this.sqlSession = sqlSessionFactory.openSession();
}
/**
* 生成代理对象
* @return
* @throws Exception
*/
@Override
public Object getObject() throws Exception {
// mybatis提供的生成代理对象的方法,底层通过jdk动态代理实现
return sqlSession.getMapper(mapperInterface);
}
@Override
public Class<?> getObjectType() {
return mapperInterface;
}
}
注意:
不要将FactoryBean和BeanFactory混淆了。
FactoryBean:是一个Java Bean,但是它是一个能生产出当前对象的工厂Bean,它的实现和工厂模式及修饰器模式很像,比如上面通过实现FactoryBean来自定义生成具体对象的逻辑。
BeanFactory:是一个工厂接口,是整个Spring IOC容器的核心内容,生产并存储很多的bean,常用的就是getBean()方法。比如AnnotationConfigApplicationContext及时一个BeanFactory。
定义mapper:
public interface UserMapper {
@Select("select 'user'")
String selectById();
}
定义service:
@Component
public class UserService {
@Autowired
private UserMapper userMapper;
public void test(){
System.out.println(userMapper.selectById());
}
}
测试类:
public class FireTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
userService.test();
}
}
运行结果:
UserMapper中定义的sql被执行了,验证成功。
带来的好处是,可以不使用@MapperScan注解,而可以直接定义一个Bean,比如:
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.firechou.mapper");
return mapperScannerConfigurer;
}
Mybatis中的一级缓存是基于SqlSession来实现的,所以在执行同一个sql时,如果使用的是同一个SqlSession对象,那么就能利用到一级缓存,提高sql的执行效率。
但是在Spring整合Mybatis后,如果在执行某个方法时,该方法上没有加@Transactional注解,也就是没有开启Spring事务,那么后面在执行具体sql时,每执行一个sql时都会新生成一个SqlSession对象来执行该sql,这就是我们说的一级缓存失效(也就是没有使用同一个SqlSession对象),而如果开启了Spring事务,那么该Spring事务中的多个sql,在执行时会使用同一个SqlSession对象,从而一级缓存生效。
Spring整合MyBatis后一级缓存失效,并不是一个问题,反而是一个正常现象。因为没有加事务时,一个方法中的多个sql执行本来就应该属于不同的SqlSession,当加了事务时,才应该将多个sql放在同一个SqlSession来执行,从而与数据库底层事务对应起来。