概述
-
在一个完整的JavaWeb项目中,通常包括web层,service层,dao层这三层结构,整个项目的类对应的bean对象,通过Spring的IOC框架来管理。所以为了方便mybatis框架的使用,mybatis提供了对spring框架的接入实现,在项目的pom.xml中通常需要增加以下配置来引入Spring对mybatis框架相关组件的管理:
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>x.x.x</version> </dependency>
以上配置已经在内部引入了mybatis相关的包,故不需要引入以下配置了:如果不基于spring来使用mybatis,则通常使用以下配置:
dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>x.x.x</version> </dependency>
-
结合Spring来使用mybatis之后,则需要通过Spring的方式来配置mybatis的相关组件对应的bean对象,如SqlSessionFactory等。Spring一般支持通过在Spring的XML配置文件applicationContext.xml中配置,或者使用注解配置。具体使用方法可以参照官方文档:mybatis-spring
Spring整合Mybatis的配置方式与实现原理
Mybatis中主要包含SqlSessionFactory,SQL配置mapper.xml,Mapper接口三大组件,所以Spring整合Mybatis也是基于这三个组件来展开的。
一. SqlSessionFactory和mapper.xml的配置
SqlSessionFactory的配置
-
在mybatis-spring中提供了一个SqlSessionFactoryBean,即实现了spring的FactoryBean接口,来生成SqlSessionFactory的对象bean并注入到Spring容器来管理。所以可以在spring的XML配置applicationContext.xml中配置该bean,如果基于Java的配置方式,通常在@Configuration注解的配置类使用@Bean注解一个名为sqlSessionFactory的方法来注入该bean对象,其中类型为org.mybatis.spring.SqlSessionFactoryBean。以下为在applicationContext.xml中的配置:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="mapperLocations" value="classpath:mybatis/mapper/**/*.xml" /> <property name="configLocation" value="classpath:mybatis/mybitas-config.xml" /> </bean>
mapper.xml在SqlSessionFactory中的配置
-
由以上配置可知,sqlSessionFactory对应的bean标签内包含dataSource,mapperLocations,configLocation三个属性,分别为:数据源bean引用,mapper.xml配置文件位置,mybatisConfig.xml配置文件位置。
-
其中mapperLocations属性是可选的:(1)如果Mapper接口和mapper.xml在相同的包内,则不需要指定;(2)如果在mybatis的配置文件mybatisConfig.xml中已经通过mappers节点配置了,则不需要指定,如下为mybatisConfig.xml的mappers节点:
<mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> <!-- 使用相对于类路径的资源引用 --> <mapper url="file:///var/mappers/BlogMapper.xml"/> <!-- 使用完全限定资源定位符(URL) --> <mapper class="org.mybatis.builder.PostMapper"/> <!-- 使用映射器接口实现类的完全限定类名 --> </mappers>
-
所以mapper.xml的加载位置,既可以在Spring的XML配置applicationContext.xml中,作为sqlSessionFactory的bean节点内部的一个mapperLocations属性来指定,也可以在mybatis自身的配置文件mybatisConfig.xml的mappers节点来指定。
-
在spring的IOC容器中管理的是sqlSessionFactory这个bean,与跟不使用spring的mybatis一样,在sqlSessionFactory内部通过配置属性Configuration来维护mapper.xml相关的SQL,以及其他配置信息。
二. Mapper接口的配置:以应用代码的调用方式来区分
在spring中配置Mapper接口时,可以基于之后的调用方式来配置。
1. 依赖SqlSession调用
- 在应用代码中,可以指定Mapper接口,依赖SqlSession来获取该Mapper接口对应的代理对象MapperProxy,从而进行调用。其中MapperProxy实现了JDK的InvocationHandler接口,为Mapper接口在mybatis中对应的动态代理对象,故可以直接通过Mapper接口的引用来调用对应的方法。在spring中提供了一个线程安全的SqlSession接口实现SqlSessionTemplate。
-
配置SqlSessionTemplate作为spring的bean:SqlSessionTemplate是线程安全的,内部基于动态代理来实现线程安全,即在代理方法invoke中每次创建一个临时的SqlSession对象来调用。
<!-- mybatis spring sqlSessionTemplate,使用时直接让spring注入即可 --> <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory"></constructor-arg> </bean>
-
在DAO中注入SqlSessionSession,并指定需要调用的Mapper接口,其中selectOne直接指定Mapper接口类全限定名和方法的字符串,getMapper则指定Mapper接口的类对象。
@Repository public class UserDao{ // 注入SqlSessionTemplate的bean对象 @Autowired private SqlSessionTemplate sqlSessionTemplate; // 指定mapper接口的类名和方法 public User getUser(int id) { return sqlSessionTemplate.selectOne(UserMapper.class.getName() + ".getUser", id); } // 指定mapper接口,获取动态代理对象引用 public User getUserV2(int id) { UserMapper dao = sqlSessionTemplate.getMapper(UserMapper.class); return dao.getUser(id); } }
-
- 在这种方式的实现中,Mapper接口对应的代理对象MapperProxy并没有注册到spring的IOC容器中,而只是sqlSession注册到了spring的IOC容器。由于sqlSession对象包含了sqlSessionFactory的引用,故可以间接从sqlSessionFactory对象内部的Configuration中获取该mapper接口对应的MapperProxy对象。
SqlSessionDaoSupport
-
在DAO中除了可以直接注入sqlSessionTemplate,然后通过指定Mapper接口来获取对应的MapperProsxy代理对象之外,DAO类自身可以继承SqlSessionDaoSupport,然后在DAO的方法中通过调用getSession方法来获取sqlSessionTemplate:
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao { public User getUserById(User user) { return (User) getSqlSession().selectOne(UserMapper.class.getName() + "".getUser", user); } }
2. 不直接依赖SqlSession,只依赖Mapper接口
-
应用代码不直接依赖SqlSession,只依赖Mapper接口,是基于mybatis-spring提供的MapperFactoryBean实现的。在这种方式中SqlSession没有注册到spring的IOC容器中,而是Mapper接口对应的代理对象MapperProxy注册到了spring的IOC容器中,刚好与第一种方式相反,MapperProxy在内部包含了SqlSession对象的引用。
-
MapperFactoryBean实现了FactoryBean接口,spring在启动创建bean对象时,调用MapperFactoryBean的getObject方法:
- 调用getSqlSession方法获取内部依赖SqlSession,具体为线程安全的SqlSessionTemplate,然后调用SqlSession的getMapper方法,创建指定Mapper接口对应的代理对象MapperProxy,然后将该代理对象注册到sqlSessionFactory的Configuration中;
- 将该方法的返回值,即该代理对象注册到spring的IOC容器中,从而可以在应用代码中直接注入使用。
-
MapperFactoryBean的getObject方法实现如下:
// spring容器创建bean对象实例时调用,如在spring容器启动时,会创建单例的bean对象实例, // 该类也是单例,故也会在spring启动时创建bean对象,即会调用这个方法。 @Override public T getObject() throws Exception { // getSqlSession返回sqlSessionTemplate对象,这个对象为线程安全的,被所有mapper共享。 // getMapper方法创建mapper对象,具体为一个代理对象MapperProxy,该getObject方法返回后, // 将该代理对象注册成spring容器中的一个单例bean对象实例。 getSqlSession().getMapper(this.mapperInterface); }
-
故实际注册到spring容器的bean是Mapper接口在mybatis内部对应的代理对象MapperProxy,在应用代码中可以直接注入该代理对象bean,并通过该代理对象bean来调用Mapper接口的方法来触发对应mapper.xml中定义的SQL的执行。
(1)一个个Mapper接口配置
-
在应用代码中,可以在spring的XML配置文件applicationContext.xml中直接配置Mapper接口对应的bean,其中类型为MapperFactoryBean,这种方式需要为每个Mapper都配置一次。
<!--创建数据映射器,数据映射器必须为接口--> <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInterface" value="com.yzxie.demo.dao.UserMapper" /> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean> <bean id="blogMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInterface" value="com.yzxie.demo.dao.BlogMapper" /> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean>
-
在内部通过mapperInterface属性指定mapper接口,通过sqlSessionFactory来指定sqlSessionFactory引用。
-
然后在应用代码中,通常为DAO层中,可以注入该mapper代理对象直接调用:
@Repository public class UserDao{ // 注入userMapper @Autowired private UserMapper userMapper; public User getUser(int id) { return userMapper.getUser(id); } }
(2)自动扫描Mapper接口配置
-
通过一个个来配置mapper略显繁琐,所以在mybatis-spring中提供了MapperScannerConfigurer这个类用于自动扫描给定包下面的mapper接口并在内部,自动为每个mapper接口创建对应的MapperFactoryBean。在spring的XML配置applicationContext.xml的配置方式如下:扫描com.yzxie.demo.dao.mapper包下的所有Mapper接口。
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.yzxie.demo.dao.mapper" /> </bean>
MapperScannerConfigurer的内部实现方法
-
MapperScannerConfigurer是BeanDefinitionRegistryPostProcessor接口的一个实现类,BeanDefinitionRegistryPostProcessor实现了BeanFactoryPostProcessor。即MapperScannerConfigurer是一个BeanFactory后置处理器,在spring的IOC容器的生命周期中,spring容器启动创建好对应的beanDefinitions之后,会调用BeanFactoryPostProcess对beanDefinition进行加工,之后才是创建对应的bean对象实例注册的spring的IOC容器中。
-
BeanDefinitionRegistryPostProcessor这个BeanFactoryPostProcessor的主要作用是往spring容器中注册更多的beanDefintions,具体在postProcessBeanDefinitionRegistry方法定义,如下为MapperScannerConfigurer的postProcessBeanDefinitionRegistry实现:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { // 处理basePackages,beanName上面的placeholder processPropertyPlaceHolders(); } // 类路径扫描器 ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); scanner.setAddToConfig(this.addToConfig); // 根据注解annotationClass,特定接口markerInterface对该类路径进行过滤筛选,默认不过滤 scanner.setAnnotationClass(this.annotationClass); scanner.setMarkerInterface(this.markerInterface); scanner.setSqlSessionFactory(this.sqlSessionFactory); scanner.setSqlSessionTemplate(this.sqlSessionTemplate); scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName); scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); scanner.setResourceLoader(this.applicationContext); scanner.setBeanNameGenerator(this.nameGenerator); // 过滤需要加载哪些类文件到内存,生成Resource对象,在下一步继续进行筛选确定哪些是candidateBeanDefinition scanner.registerFilters(); // 从basePackage指定的包加载接口类并生成BeanDefinition注册到BeanFactory scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
-
postProcessBeanDefinitionRegistry实现为通过ClassPathMapperScanner来扫描该指定包类路径获取相关的Mapper接口,其中通过调用scanner.registerFilters()设置需要加载哪些类作为Mapper接口:默认为加载包下面的所有类文件为Resource。
public void registerFilters() { boolean acceptAllInterfaces = true; // if specified, use the given annotation and / or marker interface if (this.annotationClass != null) { addIncludeFilter(new AnnotationTypeFilter(this.annotationClass)); acceptAllInterfaces = false; } // override AssignableTypeFilter to ignore matches on the actual marker interface if (this.markerInterface != null) { addIncludeFilter(new AssignableTypeFilter(this.markerInterface) { @Override protected boolean matchClassName(String className) { return false; } }); acceptAllInterfaces = false; } if (acceptAllInterfaces) { // 默认为对包下面的所有类都进行加载成Resource, // 然后再使用isCandidateComponent方法进行判断是否需要生成BeanDefinition并注册到Spring容器 // default include filter that accepts all classes addIncludeFilter((metadataReader, metadataReaderFactory) -> true); } // 在上面加载所有类的基础上,排除掉package-info.java这个类 // exclude package-info.java addExcludeFilter((metadataReader, metadataReaderFactory) -> { String className = metadataReader.getClassMetadata().getClassName(); return className.endsWith("package-info"); }); }
-
然后在scan方法中,使用这些类对应的Resource来判断每个类是否为接口,如果是接口且该接口为顶层接口,即没有继承、实现其他接口,则认为是Mapper接口,核心判断方法为ClassPathMapperScanner的isCandidateComponent方法:
// 判断给定的类是否需要生成BeanDefinition注册到Spring容器 @Override protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { // 接口且该接口为顶层接口,即没有继承、实现其他接口,则为候选mapper return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent(); }
注解配置方式
-
以上分析都是在spring的XML配置文件applicationContext.xml进行配置的,mybatis-spring也提供了基于注解的方式来配置sqlSessionFactory和Mapper接口。
-
sqlSessionFactory主要是在@Configuration注解的配置类中使用@Bean注解的名为sqlSessionFactory的方法来配置;
-
Mapper接口主要是通过在@Configuration注解的配置类中结合@MapperScan注解来指定需要扫描获取mapper接口的包。
@Configuration @MapperScan("org.mybatis.spring.sample.mapper") public class AppConfig { @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .addScript("schema.sql") .build(); } @Bean public DataSourceTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource()); return sessionFactory.getObject(); } }
-
在内部实现中@MapperScan注解的解析器为MapperScannerRegistrar,定义如下:MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,spring在处理@Configuration注解的配置类时,会处理@MapperScan注解调用这个接口的registerBeanDefinitions方法,注册更多的beanDefinitions到spring的IOC容器中。
@Import(MapperScannerRegistrar.class) @Repeatable(MapperScans.class) public @interface MapperScan { ... } public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { ... }