1、为什么整合: 管理对象,将对象交给容器管理,便于容器之间依赖的解耦;对象重用,性能友好;
2、如何整合: 通过一个Template封装方法;
1、 如何实现集成spring的关键问题?mybatis-spring.jar 包
1、SqlSessionFactory会话工厂是怎么创建的在什么时候创建?
在ApplicationContext启动的时候根据ApplicationContext.xml中的sqlSessionFactory的Bean配置创建;指定全局配置路径,Mapper路径,DataSource配置;
实现类:sqlSessionFactoryBean,实现了FactoryBean,InitializingBean;
getObject中解析配置,调用Mybatis的全局配置方法-XmlConfigurationBuilder、解析Mapper的配置-XmlMapperBuilder; 返回DefaultSqlSessionFactory;
2、SqlSession怎么获取?为什么不用它来getMapper?
【思考1】能用DefaultSqlSession吗? 不能的话,为什么不能?替代品是什么?怎么在Dao层的实现类获取SqlSeaaion
不能使用,因为DefaultSqlSession不是线程安全的不能在spring中作为单例使用;
而SqlSessionTemplate是线程安全的,用其来替代DefaultSqlSession;为何是线程安全的呢?动态代理创建的sqlSessionProxy(实质是DefaultSqlSession)进行调用,其代理类是SqlSessionInterceptor,查看其invoke方法{getSqlSession(), closeSqlSession() 方法 两个方法中去实现线程安全 在进来的时候通过ThreadLocal保存创建的SqlSessionHolder在对象TransactionSynchronicationManager中的,在执行完之后colse() 来确保线程安全的 }; 一个事务一个SqlSession;
SqlSessionTemplate如何获取?通过继承DaoSupport就可以通过getSqlSession() 获取;
【问题1】:每次都要对DaoSupport的封装方法进行实现,解决办法用BaseDao继承DaoSupport实现,然后后面的继承BaseDao就可以直接用了;
【问题2】:不想创建实现类,用注入的方式
【思考2】接口扫描注册:不想创建实现类怎么办?用@Autowired如何实现?
如何扫描的?不想创建,那么一定是交给了容器在管理;配置
注册的是什么?然后进行注册,这里注册的不是接口号,而是MapperFactoryBean;替换原因是为了继承DaoSupport,去获取SqlSessionTemplate;
3、@Autowired实现注入一个Mapper接口,直接使用;
【思考】:在IOC容器里面我们注册的是什么?注册的时候发生了什么事情? --- 将Mapper注册到容器,在调用的时候调用其方法通过反射实现调用
1,加入依赖包:
org.mybatis
mybatis-spring
1.2.2
spring启动的时候需要使用一个bean.xml配置文件,
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
bean.xml文件中就涉及到整合的配置信息,整合使用到以下的配置:
1、MapperScannerConfigurer类创建ClassPathMapperScanner扫描器:
核心方法:postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法,看看具体内容:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (this.processPropertyPlaceHolders) {
this.processPropertyPlaceHolders();
}
//创建扫描器(1、扫描basePackage包下所有的class类;
// 2、将所有的class类封装成ScannedGenericBeanDefinition对象)
过滤sbd对象,只接受接口类;
属性设置:sqlSessionFactory 、BeanClass
3、将ScannedGenericBeanDefinition 通过注册器BeanDefinitionRegistry 注册到 DefaultListableBeanFactory
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
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);
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
第三过滤sbd对象,只接受接口类,从下面的代码中可以看出。
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
第四完成sbd对象属性的设置,比如设置sqlSessionFactory、BeanClass等,这个sqlSessionFactory是本文接下来要解析的
SqlSessionFactoryBean
sbd.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
sbd.setBeanClass(MapperFactoryBean.class);
sbd.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
第五将过滤出来的sbd对象通过这个BeanDefinitionRegistry registry注册器注册到DefaultListableBeanFactory中,这个registry就是方法postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)中的参数。
总结时序图为:
mapper接口注册之后,在什么地方实例化和使用呢?后面在分析。
2、创建SqlSessionFactoryBean
接着看看spring和mybatis整合的另外一个标签。
这个sqlSessionFactory的实现类中做了什么事情。首先看看这个类的声明:
public class SqlSessionFactoryBean implements FactoryBean,
InitializingBean, ApplicationListener {
//省略代码
}
从配置来看,这个Bean至少提供了两个类属性,一个是和dataSource有关,一个和mapperLocations有关,看实现什么都没有做,只是做了两次简单的赋值工作;
public void setDataSource(DataSource dataSource) {
if (dataSource instanceof TransactionAwareDataSourceProxy) {
this.dataSource = ((TransactionAwareDataSourceProxy)dataSource).getTargetDataSource();
} else {
this.dataSource = dataSource;
}
}
public void setMapperLocations(Resource[] mapperLocations) {
this.mapperLocations = mapperLocations;
}
核心方法1:执行afterPropertiesSet方法,这个方法是接口InitializingBean中的方法,实现创建DefaultSqlSessionFactory。
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.dataSource, "Property 'dataSource' is required");
Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
//buildSqlSessionFactory()方法中,为了简化分析,我们只关心必须要做的事情。
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
XMLConfigBuilder xmlConfigBuilder = null;
//初始化mybatis配置对象 configuration
Configuration configuration;
configuration = new Configuration();
configuration.setVariables(this.configurationProperties);
//省略代码
//创建事务工厂
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
//包装器模式?
Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
configuration.setEnvironment(environment);
if (!ObjectUtils.isEmpty(this.mapperLocations)) {
Resource[] arr$ = this.mapperLocations;
len$ = arr$.length;
for(i$ = 0; i$ < len$; ++i$) {
Resource mapperLocation = arr$[i$];
if (mapperLocation != null) {
try {
//创建XMLMapperBuilder对象;
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
//解析mapperLocations 所代表的 Xml配置文件
xmlMapperBuilder.parse();
} catch (Exception var20) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var20);
} finally {
ErrorContext.instance().reset();
}
}
}
}
//直接创建了DefaultSqlSessionFactory,其中还持有Configuration对象的引用
return this.sqlSessionFactoryBuilder.build(configuration);
}
mapperLocation-->AuthUserMapper.xml,AuthUserMapper.xml的部分代码如下:
//此处代码有省略
update t_auth_user
set user_account = #{userAccount,jdbcType=VARCHAR},
password = #{password,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=TIMESTAMP},
update_time = #{updateTime,jdbcType=TIMESTAMP},
is_delete = #{isDelete,jdbcType=TINYINT}
where id = #{id,jdbcType=BIGINT}
afterPropertiesSet()方法总结起来,就是创建了几个对象,依次是mybatis的核心类Configuration、spring和mybatis集成的事物工厂类SpringManagedTransactionFactory、mybatis的Environment类、mybatis的DefaultSqlSessionFactory类,同时还完成了对mybatis的xml文件解析,并将解析结果封装在Configuration类中。以下为时序图。
核心方法2:执行onApplicationEvent方法 这个方法的调用是在spring容器初始化完成之后,该方法是接口ApplicationListener
public void onApplicationEvent(ApplicationEvent event) {
if (this.failFast && event instanceof ContextRefreshedEvent) {
this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
}
}
调试发现this.failFast这个变量的值是false,所以这个方法不会执行,在此就不做重点分析了。
执行getObject方法
第三个被调用的就是getObject方法,该方法是接口FactoryBean
看看方法内容,就知道是直接返回创建好的sqlSessionFactory,其创建时在afterPropertiesSet()方法中创建的;其对象就是DefaultSqlSessionFactory- mybatis的SqlSessionFactory模板实现。
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
this.afterPropertiesSet();
}
return this.sqlSessionFactory;
}
上面两个标签的解析过程分析完成,现在有两个问题没有答案,第一个是basePackage基础包下面扫描出来的mapper接口怎么实例化的?第二个是这两个标签创建出来的对象怎么配合使用的?
3、实例化过程
整合之后实例化过程; 在本系列的例子中,spring容器在启动的过程中,需要去实例化AuthUserServiceImpl服务类,
@Service
public class AuthUserServiceImpl implements IAuthUserService {
@Resource
private AuthUserMapper authUserMapper;
public AuthUserServiceImpl(){
logger.info("创建 com.test.bean.AuthUserServiceImpl");
}
}
这个类依赖了AuthUserMapper接口,在实例化AuthUserServiceImpl的过程中需要,首先去实例化这个AuthUserMapper接口,但是接口是不能被实例化的,接下来分析这个接口实现类的创建过程。
简单的说明:spring在初始化的过程中,会去创建AuthUserServiceImpl类,创建完成之后,会进行属性赋值,AuthUserMapper这个mybatis接口就是AuthUserServiceImpl的一个属性,
首先根据这个mapper的名字从spring的BeanFactory中获取它的BeanDefinition,再从BeanDefinition中获取BeanClass,AuthUserMapper对应的BeanClass就是MapperFactoryBean,这是为什么呢?在上面分析的内容中提到过,也就是在创建MapperScannerConfigurer对象的时候设置的。
接着就是创建MapperFactoryBean对象了,创建完成之后,就需要对属性进行赋值,他有哪些属性需要赋值呢?这是在创建AuthUserMapper所对应BeanDefinition对象的时候决定的,回顾上面创建MapperScannerConfigurer对象的那部分内容就知道。其中有一个属性就是SqlSessionFactoryBean,要实例化这个对象,这个就需要用到下面这个标签了
上面的内容也已经分析过这个SqlSessionFactoryBean的创建过程。
MapperFactoryBean对象的属性设置完成之后,就调用它的getObject()方法,来获取authUserMapper对应的实现类,从上面图中可以看出来,最后返回的就是一个代理类,这个代理类使用jdk的动态代理创建出来的。
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(),
new Class[]{this.mapperInterface}, mapperProxy);
这个MapperProxy类就是InvocationHandler的实现类:
public class MapperProxy implements InvocationHandler, Serializable {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
}
}
程序在调用authUserMapper对象的某个方法的时候,就会调用到MapperProxy对象的invoke()方法,去完成对数据库的操作。
最后附上一张类图,spring和mybatis整合过程中创建的类: