MyBatis 与 Spring 是如何结合在一起工作的——mybatis-spring(version:1.2.2)
在MyBatis-Spring的项目中,我们一般会为MyBatis配置两个配置文件 beans-mybatis.xml 和 mybatis-config.xml。
其中 beans-mybatis.xml 中配置的是MyBatis 和 Spring结合使用时委托给 spring 管理的 bean。
mybatis-config.xml 中是MyBatis 自身的配置。
例:beans-mybatis.xml
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" p:dataSource-ref="dataSource" p:configLocation="classpath:mybatis-config.xml"
p:mapperLocations="classpath:mapper/**/*.xml" />
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" p:basePackage="com.cn.kvn.usage.dao" />
mybatis-config.xml:
<configuration>
<settings>
<setting name="lazyLoadingEnabled" value="false" />
<!-- logback日志指定打印sql用 -->
<setting name="logPrefix" value="dao." />
</settings>
<typeAliases>
<!-- 别名定义 -->
</typeAliases>
<!-- 插件 -->
<plugins>
<plugin interceptor="com.cn.kvn.framework.jdbc.mybatis.interceptor.PageInterceptor">
<property name="dialectClassName" value="com.cn.kvn.framework.jdbc.mybatis.interceptor.MySQLDialect" />
</plugin>
</plugins>
</configuration>
mybatis-spring的入口就在bean的定义那里(beans-mybatis.xml ): SqlSessionFactoryBean 、MapperScannerConfigurer
#####1. SqlSessionFactoryBean
SqlSessionFactoryBean实现了 InitializingBean ,在 afterPropertiesSet() 中会对 mybatis的配置 p:configLocation="classpath:mybatis-config.xml" 、 p:mapperLocations="classpath:mapper/**/*.xml" 进行解析。
org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory():
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration; // MyBatis 的配置,存放 mybatis-config.xml 中的配置
XMLConfigBuilder xmlConfigBuilder = null; // mybatis-config.xml 的解析器,解析出来的内容放在 Configuration 中
if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
configuration = new Configuration();
configuration.setVariables(this.configurationProperties);
} if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
} if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
} if (hasLength(this.typeAliasesPackage)) { // typeAlias : 类型别名,用于sqlmap中类型(parameterType、resultType)的配置
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); // 注册 typeAliases
}
} if (!isEmpty(this.typeAliases)) { // typeAlias : 类型别名,用于sqlmap中类型(parameterType、resultType)的配置
for (Class<?> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias); // 注册 typeAliases
}
} if (!isEmpty(this.plugins)) { // plugins : mybatis 的插件,即 MyBatis 的拦截器和扩展点。可以针对Executor 、 StatementHandler 、 ResultSetHandler 和 PameterHandler 进行拦截扩展
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin); // 添加 MyBatis Interceptor
}
} if (hasLength(this.typeHandlersPackage)) { // typeHandler : 类型转换器,对java类型和sqlmap中的类型进行转换
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeHandlersPackageArray) {
configuration.getTypeHandlerRegistry().register(packageToScan); // 注册 typeHandler
}
} if (!isEmpty(this.typeHandlers)) { // typeHandler : 类型转换器,对java类型和sqlmap中的类型进行转换
for (TypeHandler<?> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler); // 注册 typeHandler
}
} if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse(); // xmlConfigBuilder解析 mybatis-spring.xml 配置
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset(); // 重置 ErrorContext
}
} if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory(); // 默认的 transactionFactory
} Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
configuration.setEnvironment(environment); if (this.databaseIdProvider != null) {
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); // DatabaseId 用于对多数据库的支持
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
} if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
} try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse(); // xmlMapperBuilder:解析 mapperLocation 中指定的 sqlmap 文件,即解析 sql 语句
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset(); // 重置 ErrorContext
} }
}
return this.sqlSessionFactoryBuilder.build(configuration); // 将 configuration 配置设置到 org.apache.ibatis.session.defaults.DefaultSqlSessionFactory 中
}
附:ErrorContext: 解析配置文件时,用于存储异常信息(ThreadLocal实现的)
跟配置解析相关的类:
XMLConfigBuilder : 解析 mybatis-config.xml 中 MyBatis 自身的配置。
XmlMapperBuilder : 解析 sqlmap 中的配置,包括 <resultMap> 和 sql 语句
ResultMapResolver : 解析 sqlmap 中的 <resultMap> 节点
XMLStatementBuilder : 解析 sql 语句(<insert>、<delete>、<update>、<select>)
举例:mybatis-config.xml 的解析是通过 XMLConfigBuilder 来完成的
mybatis-config.xml中能够配置的属性:(XMLConfigBuilder#parseConfiguration(XNode))
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties")); //issue #117 read properties first
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
settingsElement(root.evalNode("settings"));
environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
其中<settings>标签可以配置的属性如下:(XMLConfigBuilder#settingsElement(XNode))
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
#####2. MapperScannerConfigurer
MapperScannerConfigurer 的作用是扫描 java 的 Dao 接口,来与 mapper 做映射关系
org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry(BeanDefinitionRegistry)
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
} 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(); // java类型过滤器,排除一些不符合要求的 Dao
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); // 进行 Dao 扫描,扫描 basePackage 包下面的类
}
org.mybatis.spring.mapper.ClassPathMapperScanner#scan(String... basePackages):
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); // 调用 ClassPathBeanDefinitionScanner#doScan()解析出bean
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition(); // the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName()); // mapperInterface 是 Dao 对应的 bean 的原始类型
definition.setBeanClass(MapperFactoryBean.class); // MapperFactoryBean 是 Dao 对应的 bean 的实际类型。也就是所有的 Dao 对应的 bean ,最后都是 MapperFactoryBean,通过 MapperFactoryBean 来做 Dao 的代理,将CRUD操作分发到具体的 sqlmap 中的 sql 去执行。 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)) {
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
} if (!explicitFactoryUsed) {
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); // 让 MapperFactoryBean 可以通过 @Autowired 注解来进行注入。即注入 xxxDao
} return beanDefinitions;
}
#####3. MapperFactoryBean
MapperFactoryBean 继承了SqlSessionDaoSupport,默认使用了 org.mybatis.spring.SqlSessionTemplate 来操作CRUD方法。
org.mybatis.spring.SqlSessionTemplate 的构造方法:
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; // 默认使用 MyBatisExceptionTranslator ,对 SQL 异常进行友好转换
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
SqlSessionTemplate 又是通过 sqlSessionProxy 来操作CRUD方法的。
sqlSessionProxy 是JDK 动态代理,通过 SqlSessionInterceptor 来拦截 org.apache.ibatis.session.SqlSession 接口里面的 CRUD 操作。
org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor:
/**
* Proxy needed to route MyBatis method calls to the proper SqlSession got
* from Spring's Transaction Manager
* It also unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to
* pass a {@code PersistenceException} to the {@code PersistenceExceptionTranslator}.
*/
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator); // 从当前线程中获取一个 SqlSession ,如果没有,就创建一个新的 SqlSession。(org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession(ExecutorType execType))。这样 spring 和 mybatis 就结合在一起了
try {
Object result = method.invoke(sqlSession, args); // 执行 SqlSession 的方法(CRUD操作)。
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); // 对 Method#invoke(Object, Object...) 的异常进行解封装
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); // 对 SQL 异常进行友好转换
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
至此,我们还有一个问题没有解决:我们在调用 xxDao 中的方法 xxMethod 时,mybatis-spring.jar 是如何找到 xxMethod 对应的配置文件 xxMapper.xml 中的 sql 语句的?
它是通过多个 JDK 动态代理,去拦截 mapperInterface(xxDao)中的方法来实现的。具体可以看下图:
通过上图,前面的疑问就迎刃而解了。^_^
附:MapperFactoryBean 中的 sqlSessionFactory 是如何注入的?
在 Spring 容器里面 IJobBeforehandRetryDao.java 对应的 beanName = "IJobBeforehandRetryDao"
相应的 RootBeanDefinition为:
Root bean: class [org.mybatis.spring.mapper.MapperFactoryBean]; scope=singleton; abstract=false;
lazyInit=false; autowireMode=2; dependencyCheck=0; autowireCandidate=true; primary=false;
factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null;
defined in file [E:\gitWorkspace\dal-job\dal-job-core\target\classes\com\kvn\dal\core\dao\IJobBeforehandRetryDao.class]
IJobBeforehandRetryDao 对应的 bean 是一个 FactoryBean。Spring 对 FactoryBean 里面的属性有特殊处理。
AbstractAutowireCapableBeanFactory#autowireByType(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs)
MapperFactoryBean#setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)会被调用。
这样,MapperFactoryBean 中的属性 sqlSession就会被赋值。 this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
MyBatis 与 Spring 是如何结合在一起工作的——mybatis-spring(version:1.2.2)的更多相关文章
- spring和mybatis集成,自动生成model、mapper,增加mybatis分页功能
软件简介 Spring是一个流行的控制反转(IoC)和面向切面(AOP)的容器框架,在java webapp开发中使用广泛.http://projects.spring.io/spring-frame ...
- spring 5.x 系列第6篇 —— 整合 mybatis + druid 连接池 (代码配置方式)
源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all 项目目录结构 1.创建maven工程,除了Spring基本依赖外,还需要导 ...
- spring 5.x 系列第5篇 —— 整合 mybatis + druid 连接池 (xml配置方式)
源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all 项目目录结构 1.创建maven工程,除了Spring基本依赖外,还需要导 ...
- Spring Boot2 系列教程(二十二)整合 MyBatis 多数据源
关于多数据源的配置,前面和大伙介绍过 JdbcTemplate 多数据源配置,那个比较简单,本文来和大伙说说 MyBatis 多数据源的配置. 其实关于多数据源,我的态度还是和之前一样,复杂的就直接上 ...
- Spring Boot教程(三十七)整合MyBatis
Spring中整合MyBatis就不多说了,最近大量使用Spring Boot,因此整理一下Spring Boot中整合MyBatis的步骤.搜了一下Spring Boot整合MyBatis的文章,方 ...
- Spring Boot 2.x基础教程:使用MyBatis的XML配置方式
上一篇我们介绍了如何在Spring Boot中整合我们国人最常用的MyBatis来实现对关系型数据库的访问.但是上一篇中使用了注解方式来实现,而对于很多MyBatis老用户还是习惯于XML的开发方式, ...
- 从零搭建Spring Boot脚手架(3):集成mybatis
1. 前言 今天继续搭建我们的kono Spring Boot脚手架,上一文集成了一些基础的功能,比如统一返回体.统一异常处理.快速类型转换.参数校验等常用必备功能,并编写了一些单元测试进行验证,今天 ...
- spring boot中连接数据库报错500(mybatis)
spring boot中连接数据库报错500(mybatis) pom.xml中的依赖 <!-- 集成mybatis--> <dependency> <groupId&g ...
- 从零搭建Spring Boot脚手架(5):整合 Mybatis Plus
1. 前言 在上一文中我根据Mybatis中Mapper的生命周期手动实现了一个简单的通用Mapper功能,但是遗憾的是它缺乏实际生产的检验.因此我选择更加成熟的一个Mybatis开发增强包.它就是已 ...
随机推荐
- ASP.NET MVC分页 Ajax+JsRender
前段时间整mvc的分页,倒是很顺利,参考了以下几篇博客,启发很大. http://www.cnblogs.com/tangmingjun/archive/2012/05/30/2526301.html ...
- Eclipse启动都会Error when loading the SDK
http://jingyan.baidu.com/article/aa6a2c14fb54190d4c19c480.html
- Linux系统下邮件服务器的搭建(Postfix+Dovecot)
对于网站来说,发送各种例如注册通知的邮件是很基本的一个需求,之前我一直用的是腾讯的企业邮箱,感觉挺方便的,直接可以绑定QQ邮箱接收邮件,网站配置一下SMTP也就可以发出邮件. 但是在前几天由于有重要信 ...
- Java设计模式(16)中介模式(Mediator模式)
Mediator定义:用一个中介对象来封装一系列关于对象交互行为. 为何使用Mediator模式/中介模式 各个对象之间的交互操作非常多,每个对象的行为操作都依赖彼此对方,修改一个对象的行为,同时会涉 ...
- 【转】工作中使用Trepn Power Profiler的应用总结
Trepn™ Profiler 工具的概述 Trepn™工具是高通开发的运行在使用高通骁龙芯片或者硬件开发设备等移动设备上 分析功耗和性能的一个应用. ## 特点 ## 1 2 3 Six fast- ...
- 让QtCreator在调试时显示字符串 Qt调试助手 QtDebuggingHelper qtc-debugging-helper
When starting gdb with application message “Debugging Helper Missing” is displayed [Solved] http://q ...
- Selenium常用操作汇总二——如何操作select下拉框
下面我们来看一下selenium webdriver是如何来处理select下拉框的,以http://passport.51.com/reg2.5p这个页面为例.这个页面中有4个下拉框,下面演示4种选 ...
- lapacke svd实例
参考 intel MTK实例 https://software.intel.com/sites/products/documentation/doclib/mkl_sa/11/mkl_lapack_e ...
- pandas汇总和计算描述统计
pandas 对象拥有一组常用的数学和统计方法. 他们大部分都属于简约和汇总统计, 用于从Series中提取单个值(如sum或mean) 或从DataFrame的行或列中提取一个Series.跟对应的 ...
- Eclipse初次java开发问题总结-4-Maven使用问题汇总
Non-resolvable parent POM [INFO] Scanning for projects... [ERROR] The build could not read 1 project ...