在一个完整的JavaWeb项目中,通常包括web层,service层,dao层这三层结构,整个项目的类对应的bean对象,通过Spring的IOC框架来管理。所以为了方便mybatis框架的使用,mybatis提供了对spring框架的接入实现,在项目的pom.xml中通常需要增加以下配置来引入Spring对mybatis框架相关组件的管理:
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>x.x.xversion>
dependency>
以上配置已经在内部引入了mybatis相关的包,故不需要引入以下配置了:如果不基于spring来使用mybatis,则通常使用以下配置:
dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>x.x.xversion>
dependency>
结合Spring来使用mybatis之后,则需要通过Spring的方式来配置mybatis的相关组件对应的bean对象,如SqlSessionFactory等。Spring一般支持通过在Spring的XML配置文件applicationContext.xml中配置,或者使用注解配置。具体使用方法可以参照官方文档:mybatis-spring
Mybatis中主要包含SqlSessionFactory,SQL配置mapper.xml,Mapper接口三大组件,所以Spring整合Mybatis也是基于这三个组件来展开的。
在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>
由以上配置可知,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"/>
<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,以及其他配置信息。
在spring中配置Mapper接口时,可以基于之后的调用方式来配置。
配置SqlSessionTemplate作为spring的bean:SqlSessionTemplate是线程安全的,内部基于动态代理来实现线程安全,即在代理方法invoke中每次创建一个临时的SqlSession对象来调用。
<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);
}
}
在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);
}
}
应用代码不直接依赖SqlSession,只依赖Mapper接口,是基于mybatis-spring提供的MapperFactoryBean实现的。在这种方式中SqlSession没有注册到spring的IOC容器中,而是Mapper接口对应的代理对象MapperProxy注册到了spring的IOC容器中,刚好与第一种方式相反,MapperProxy在内部包含了SqlSession对象的引用。
MapperFactoryBean实现了FactoryBean接口,spring在启动创建bean对象时,调用MapperFactoryBean的getObject方法:
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的执行。
在应用代码中,可以在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);
}
}
通过一个个来配置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 {
...
}