有很多框架需要与Spring进行整合,而整合的核心思路就是把其他框架所产生的对象放到Spring容器中,让其成为一个bean。比如Mybatis,Mybatis框架本身是可以单独使用的,而单独使用Mybtis框架就需要用到Mybatis所提供的一些类构造出对应的对象,然后使用该对象,就能使用到Mybatis框架给我们提供的功能,和Mybatis整合SPring就是为了将这些对象放入Spring中成为Bean,在我们的Spring项目中就能很方便的使用这些对象了,也就能很方便的使用Mybatis框架提供的功能了。
导入jar包:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.3</version>
</dependency>
要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。在 MyBatis-Spring 中,可使用 SqlSessionFactoryBean 来创建 SqlSessionFactory。 要配置这个工厂 Bean,只需要把下面代码放在 Spring 的 XML 配置文件中:
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost/mysql"></property>
<property name="username" value="root"></property>
<property name="password" value="1234"></property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
SqlSessionFactory 需要一个 DataSource(数据源)。 这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。
假设你定义了一个如下的 mapper 接口:
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{userId}")
User getUser(@Param("userId") String userId);
}
那么可以通过 MapperFactoryBean 将接口加入到 Spring 中:
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
需要注意的是:所指定的映射器类必须是一个接口,而不是具体的实现类。在这个示例中,通过注解来指定 SQL 语句,但是也可以使用 MyBatis 映射器的 XML 配置文件。
配置好之后,你就可以像 Spring 中普通的 Bean 注入方法那样,将映射器注入到你的业务或服务对象中。MapperFactoryBean 将会负责 SqlSession 的创建和关闭。如果使用了 Spring 的事务功能,那么当事务完成时,Session 将会被提交或回滚。最终任何异常都会被转换成 Spring 的 DataAccessException 异常。
要调用 MyBatis 的数据方法,只需一行代码:
public class FooServiceImpl implements FooService {
private final UserMapper userMapper;
public FooServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
public User doSomeBusinessStuff(String userId) {
return this.userMapper.getUser(userId);
}
}
public interface UserMapper {
@Select("select 'user'")
String selectById();
}
@Component
public class UserService {
@Autowired
UserMapper userMapper;
public void test(){
System.out.println(userMapper.selectById());
}
}
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(AppConfig.class);
applicationContext.refresh();
UserService aService = (UserService) applicationContext.getBean("userService");
aService.test();
applicationContext.close();
}
允许上面代码我们知道肯定会报错,报错的原因是什么,主要是我们的spring容器中是没有userMapper
这个bean的,所以依赖注入这里会失败。那我们分析,userMapper这里要注入的到底是什么对象?由Mybatis的工作原理我们知道,userMapper
要注入的应该是mybatis为该接口产生的一个代理对象,一旦我们拿到这个Mybatis产生的代理对象,我们就可以真正的做到操作Mybatis框架了,这也是Spring和Mybatis整合的一个关键点,这也是Spring整合其它框架的思路。而创建这个对象的关键就是,FactoryBean。
@Component
public class JackFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
//使用jdk动态代理的技术
Object ProxyInstance= Proxy.newProxyInstance(JackFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
return ProxyInstance;
}
@Override
public Class<?> getObjectType() {
return UserMapper.class;
}
}
在分析Bean的生命周期源码的时候,我们知道FacotryBean底层的beanName为jackFactoryBean
,但它所对应的Bean类型为getObject方法所返回的对象。这里我们以jdk动态代理技术为UserMapper生成了一个代理对象,测试一下上面的测试代码。
现在我们就可以成功注入UserMapper对象到UserService中了
但我们发现一个问题,如果我们每需要一个Mapper我们都去创建一个FactoryBean,这是很不合理的,所以我做一下改进。
@Component
public class JackFactoryBean implements FactoryBean {
private Class MapperInterface;
@Override
public Object getObject() throws Exception {
//使用jdk动态代理的技术
Object ProxyInstance= Proxy.newProxyInstance(JackFactoryBean.class.getClassLoader(), new Class[]{MapperInterface}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
return ProxyInstance;
}
@Override
public Class<?> getObjectType() {
return MapperInterface;
}
}
但新的问题出来了,由于JackFactoryBean
上面加了@Component注解,这是一个单例bean,而我们基于上面的改进代码我们知道,我们是要用这个FactoryBean去创建多个mapper的,所以我们不能这么定义bean,这里我们在测试类中改用以前用过的方式来创建bean。
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(AppConfig.class);
AbstractBeanDefinition beanDefinition=BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(JackFactoryBean.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
applicationContext.registerBeanDefinition("userMapper",beanDefinition);
AbstractBeanDefinition beanDefinition1=BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition1.setBeanClass(JackFactoryBean.class);
beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
applicationContext.registerBeanDefinition("orderMapper",beanDefinition1);
applicationContext.refresh();
UserService userService = (UserService) applicationContext.getBean("userService");
userService.test();
applicationContext.close();
}
使用上面方法就给JackFactoryBean
类型创建了多个bean,注意userMapper
和orderMapper
所对应的类型都是userMapper
和orderMapper
的代理对象。但上面这些代码需要程序员在启动Spring之前自己写,这同样是不可取的。现在我们详细我们的根本目的是往容器中注入BeanDefinition,然后生成我们需要的bean,但仔细回顾我们前面学的知识,还有哪些方式可以注册BeanDefinition,这里我们就想到了前一章学到的BeanDefinitionRegistryPostProcessor
接口。
@Component
public class JackBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
AbstractBeanDefinition beanDefinition= BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(JackFactoryBean.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
registry.registerBeanDefinition("userMapper",beanDefinition);
AbstractBeanDefinition beanDefinition1=BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition1.setBeanClass(JackFactoryBean.class);
beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
registry.registerBeanDefinition("orderMapper",beanDefinition1);
}
}
但上面代码的灵活性还是不高,因为我们还是把OrderMapper和UserMapper给写死了,所以我们能不能提供一种包扫描的方式,自动拿到所有的Mapper,所以思路有了下面就开始实现。首先我们自定义一个注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface JackMapperScan {
String value();
}
这里我们要知道其实是在实现
@MapperScan
注解
然后在配置类上加上该注解,现在我们就通过注解拿到我们的路径值了
@ComponentScan("com.zhouyu")
@EnableScheduling
@PropertySource("classpath:spring.properties")
@EnableTransactionManagement
@JackMapperScan("com.zhouyu.mapper")
public class AppConfig {
}
现在我们采用前面学习过的另一种思路来注入BeanDefinition,即使用ImportBeanDefinitionRegistrar
接口,(使用这个类的原因是,我们可以很方便拿到注解信息)
public class JackImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
AbstractBeanDefinition beanDefinition= BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(JackFactoryBean.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
registry.registerBeanDefinition("userMapper",beanDefinition);
AbstractBeanDefinition beanDefinition1=BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition1.setBeanClass(JackFactoryBean.class);
beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
registry.registerBeanDefinition("orderMapper",beanDefinition1);
}
}
然后在配置类上导入该类,我们看过源码知道这个类加@Component
是没有效果的,所以我们在配置类上导入该类。
@ComponentScan("com.zhouyu")
@EnableScheduling
@PropertySource("classpath:spring.properties")
@EnableTransactionManagement
@JackMapperScan("com.zhouyu.mapper")
@Import(JackImportBeanDefinitionRegistrar.class)
public class AppConfig {
}
现在BeanDefinition是可以注入进容器的。
我们知道,在Spring解析我们的注解,它会解析该注解和注解底层使用的注解,所以现在我们可以把Import注解加入到我们自定义的注解里面。所以现在我们就可以真正的开始解析扫描路径了。
public class JackImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(JackMapperScan.class.getName());
//然后拿到扫描路径
String value = (String) annotationAttributes.get("value");
System.out.println(value);
AbstractBeanDefinition beanDefinition= BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(JackFactoryBean.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
registry.registerBeanDefinition("userMapper",beanDefinition);
AbstractBeanDefinition beanDefinition1=BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition1.setBeanClass(JackFactoryBean.class);
beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
registry.registerBeanDefinition("orderMapper",beanDefinition1);
}
}
我们就成功拿到了扫描路径
下面的关键是如何实现扫描逻辑,前面我们说过Spring是提供了一个扫描器的,它在扫描BeanDefinition的时候起了关键作用,即ClassPathBeanDefinitionScanner
,下面自定义一个Scanner。
public class JackScanner extends ClassPathBeanDefinitionScanner {
public JackScanner(BeanDefinitionRegistry registry) {
super(registry);
}
}
有了扫描器,现在的问题是我们知道Spring底层的扫描器是不会扫接口的,所以我们要重写isCandidateComponent
方法让其支持扫描借口。
public class JackScanner extends ClassPathBeanDefinitionScanner {
public JackScanner(BeanDefinitionRegistry registry) {
super(registry);
}
//我们是不能使用Spring提供的扫描逻辑的
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
//我们只关心接口
return beanDefinition.getMetadata().isInterface();
}
}
另外一点从源码我们知道,源码中会判断includefilters属性,这是Component的一个属性,即加了Component注解会自动包含在includefilters属性中,所以我们也要把自己定义的这些接口全部加入到includefilters属性中。
public class JackImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(JackMapperScan.class.getName());
//然后拿到扫描路径
String value = (String) annotationAttributes.get("value");
JackScanner scanner=new JackScanner(registry);
scanner.addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//这样写扫描路径下的所有类和接口都会反在IncludeFileter属性中
return true;
}
});
scanner.scan(value);
AbstractBeanDefinition beanDefinition= BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(JackFactoryBean.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
registry.registerBeanDefinition("userMapper",beanDefinition);
AbstractBeanDefinition beanDefinition1=BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition1.setBeanClass(JackFactoryBean.class);
beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
registry.registerBeanDefinition("orderMapper",beanDefinition1);
}
}
我们测试一下,现在能否拿到mapper生成的BeanDefinition,首先完善一下代码:
public class JackScanner extends ClassPathBeanDefinitionScanner {
public JackScanner(BeanDefinitionRegistry registry) {
super(registry);
}
//我们是不能使用Spring提供的扫描逻辑的
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
System.out.println(beanDefinitionHolders);
return beanDefinitionHolders;
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
//我们只关心接口
return beanDefinition.getMetadata().isInterface();
}
}
现在我们通过打破Spring原始扫描的逻辑,确实是拿到了自己需要的mapper的bean
但现在的问题是,我们需要的不是接口的实际类型,但是我们实际需要的是这些借口的代理对象,所以我们还需要对BeanDefinition做一些处理。
public class JackScanner extends ClassPathBeanDefinitionScanner {
public JackScanner(BeanDefinitionRegistry registry) {
super(registry);
}
//我们是不能使用Spring提供的扫描逻辑的
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
GenericBeanDefinition beanDefinition= (GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
beanDefinition.setBeanClassName(JackFactoryBean.class.getName());
beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
return beanDefinitionHolders;
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
//我们只关心接口
return beanDefinition.getMetadata().isInterface();
}
}
再对JackImportBeanDefinitionRegistrar作一些修改:
public class JackImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(JackMapperScan.class.getName());
//然后拿到扫描路径
String value = (String) annotationAttributes.get("value");
JackScanner scanner=new JackScanner(registry);
scanner.addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//这样写扫描路径下的所有类和接口都会反在IncludeFileter属性中
return true;
}
});
scanner.scan(value);
}
}
现在我们的代码就可以成功运行了
现在还有一个问题,就是现在mapper的代理对象是我们自己使用jdk代理生成的,但是我们现在需要的使用Mybatis来生成我们的代理对象。所以在代理这一块我们需要重新写一下。在使用Mybatis时,我们使用的使用的SqlSession对象来获取代理对象的。
public class JackFactoryBean implements FactoryBean {
private Class MapperInterface;
private SqlSession sqlSession;
public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
sqlSessionFactory.getConfiguration().addMapper(MapperInterface);
this.sqlSession = sqlSessionFactory.openSession();
}
public JackFactoryBean(Class mapperInterface) {
this.MapperInterface = mapperInterface;
}
@Override
public Object getObject() throws Exception {
return sqlSession.getMapper(MapperInterface);
}
@Override
public Class<?> getObjectType() {
return MapperInterface;
}
}
在注入SqlSession时,我们使用的是Set方法注入,但是我们需要一个SqlSessionFactory
对象在Spring容器中,现在的关键是如何向Spring容器中加入SqlSessionFactory
的对象。
@ComponentScan("com.zhouyu")
@EnableScheduling
@PropertySource("classpath:spring.properties")
@EnableTransactionManagement
@JackMapperScan("com.zhouyu.mapper")
public class AppConfig {
@Bean
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://127.0.0.1:3306/mysql_learn?characterEncoding=utf-8&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
</configuration>
现在就可以成功的输出sql语句的结果了
现在Spring和Mybatis已经整合完了,关键点是如何将Mybatis的代理对象注入到Spring容器中。
上面我们自动动手整合了Spring和Mybatis,其实spring-mybatis包底层原理几乎差不多,只是过程更复杂,现在我们基于spring-mybatis的版本1.3.2
来分析一下源码。
首先我们从@MapperScan
注解开始
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends Annotation> annotationClass() default Annotation.class;
Class<?> markerInterface() default Class.class;
String sqlSessionTemplateRef() default "";
String sqlSessionFactoryRef() default "";
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
String lazyInitialization() default "";
String defaultScope() default AbstractBeanDefinition.SCOPE_DEFAULT;
}
上面就用了@Import(MapperScannerRegistrar.class)
注解,这里就对应我们案例中的@Import(JackImportBeanDefinitionRegistrar.class)
,我们进去看一下这个类。
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
/**
* {@inheritDoc}
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}
/**
* {@inheritDoc}
*/
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
上面这个类同样实现ImportBeanDefinitionRegistrar
接口,重写了registerBeanDefinitions
方法。
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
上面代码和案例中一样是拿@MapperScan
注解的一些信息。
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
上面就是mybatis自定义的扫描器
scanner.doScan(StringUtils.toStringArray(basePackages));
然后这里就开始使用子定义的扫描器开始扫描
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
上面就是Mybatis重写了spring框架的doscan方法:
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
上面代码就是调用Spring的doScan方法拿到BeanDefinition
processBeanDefinitions(beanDefinitions);
上面代码就是去修改BeanDefinition
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
上面代码思路和我们一样同样是遍历并修改BeanDefinition,关键代码就是下面这几行:
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
然后this.mapperFactoryBean就是Mybatis写的一个FactoryBean,我们看一下这个类。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
//要生成代理对象的接口的类型
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
//intentionally empty
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* {@inheritDoc}
*/
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
//设置SqlSession
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
/**
* {@inheritDoc}
*/
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
.......
}
上面代码和我们一样首先定义了要生成代理对象的接口的类型
private Class<T> mapperInterface;
然后获取SqlSession对象
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSession sqlSession;
private boolean externalSqlSession;
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
this.externalSqlSession = true;
}
public SqlSession getSqlSession() {
return this.sqlSession;
}
@Override
protected void checkDaoConfig() {
notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
}
SqlSessionDaoSupport是MapperFactoryBean的父类,主要就是用来处理SqlSession的,包括设置SqlSessionFactory,前面源码在processBeanDefinitions设置了注入形式为Bytype
,所以在注入SqlSession时会自动调用该类的所有set方法,这样SqlSession对象就可以被生成了。
这里有个不同点,我们使用openSession来获取SqlSession对象,但是这里是使用sqlSessionTemplate来创建sqlSession的
这里还是挺重要的,这部分源码需要涉及Mybatis源码分析,这部分在分析Mybatis源码分析。我们现在只需要知道有了sqlSessionTemplate,我们就可以使用sqlSessionTemplate.getMapper()
来获取代理对象。前面我们的案例中是直接使用openSession来获取SqlSession对象然后执行selectone
等底层函数与数据库进行交互。我们看看sqlSessionTemplate在做什么,因为sqlSessionTemplate在上面代码获得后会被直接返回,所以实际在Mybatis中我们操作的是这个对象来与数据库交互的。
ublic class SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
.....
}
如果我们现在用SqlSessionTemplate对象去进行查询,例如调用selectOne
@Override
public <T> T selectOne(String statement) {
return this.sqlSessionProxy.<T> selectOne(statement);
}
我们会发现它会使用内部的一个this.sqlSessionProxy
的属性去执行selectone
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
从构造函数中可以发现this.sqlSessionProxy
使用jdk代理来SqlSession接口。然后我们进入selectOne方法,可以发现进入了DefaultSqlSession
,这就是Mybatis底层sqlSession真正的实现类。Mybatis为上面不直接使用SqlSession而是绕这么大一圈。
我们知道DefaultSqlSession
在容器中就这么一个对象,且从其源码看出它是一个线程不安全的类,如果在高并发场景下,多个线程来使用这个DefaultSqlSession
那么可能就出现一些难以预料的并非问题。而SqlSessionTemplate却是线程安全的,那么它是如何保证线程安全的?
它的关键点就是使用ThreadLocal,让每个线程都有一份自己的DefaultSqlSession
对象。这样就可以保证线程的一个安全性。关键是要理解this.sqlSessionProxy
这个对象
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
}
invoke方法中首先调用下面代码获取了一个真正的SqlSession对象
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
是Spring事务相关的东西,这里我们先不讲Spring事务相关的内容,我们只需要在这句代码中就操作的ThreadLocal对象,然后ThreadLocal中存的对象是SqlSessionHolder,然后就执行下面这段代码:
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
上面代码就是判断当前线程的ThreadLocal中是否有SqlSessionHolder对象,如果有就直接返回当前线程ThreadLocal中存储的SqlSession对象,如果没有就调用sessionFactory.openSession重新创建一个存入到当前的ThreadLocal中( registerSessionHolder)。
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
SqlSessionHolder holder;
//判断是否开启了Spring事务
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Environment environment = sessionFactory.getConfiguration().getEnvironment();
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
}
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();
} else {
if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
}
} else {
throw new TransientDataAccessResourceException(
"SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");
}
}
}
if (TransactionSynchronizationManager.isSynchronizationActive())
就是判断我们有没有开启Spring事务,如果我们没有开启Spring事务,因为每次在一个方法中执行一条Sql语句(每个Sql都会看成一个独立的整体)都会创建一个新的SqlSession,这就会导致Mysql一级缓存失效,因为一级缓存有效的保证是同一个SqlSession执行多个sql语句。所以要让一级缓存不失效,就开启Spring事务即可,这样在一个方法中的所有sql就会看成一个整体,只需要一个SqlSession处理即可。