MyBatis本是Apache的一个开源项目iBatis,2010年这个项目由Apache Software Foundation迁移到了Google Code,并且改名为MyBatis(MyBatis源码下载地址为:http://code.google.com/p/mybatis/)
MyBatis是支持普通的SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有JDBC代码和参数的手工设置以及结果集的检索。MyBatis使用简单的XML或注解用于配置和原始映射,将接口和Java的POJOs(Plain Old Java Objects,普通的java对象)映射成数据库中的记录。
Spring整合MyBatis到底做了什么来简化程序猿的业务开发呢?我们一起分析一下
(1)Spring配置文件
配置文件是Spring的核心,Spring的所有操作也都是从配置文件开始的,所以我们首先从配置文件看下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
<util:properties id="db" location="classpath:db.properties">
</util:properties>
<bean id="dbcp" class="org.apache.commons.dbcp.BasicDataSource">
<property name="username" value="#{db.user}"></property>
<property name="password" value="#{db.pwd}"></property>
<property name="driverClassName" value="#{db.driver}"></property>
<property name="url" value="#{db.url}"></property>
</bean>
<bean id="sqlsessionfactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dbcp"></property>
<property name="mapperLocations"
value="classpath:org/tarena/note/sql/*.xml">
</property>
</bean>
<bean id="liuzhe" class="org.tarena.note.entity.User"></bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.tarena.note.dao">
</property>
<!-- 默认引入sqlSessionFcatory -->
</bean>
<!-- 扫描service controller -->
<context:component-scan base-package="org.tarena.note"/>
<!-- handlermapping @RequestMapping,@ResponseBody,@ExceptionHandler-->
<mvc:annotation-driven/>
(2)映射文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
<mapper namespace="org.tarena.note.dao.UserMapperDao">
<select id="findByName" parameterType="String"
resultType="org.tarena.note.entity.User">
select*from cn_user where cn_user_name=#{name}
</select>
<update id="updateToken" parameterType="map">
update cn_user set cn_user_token=#{userToken} where cn_user_id=#{userId}
</update>
</mapper>
Spring中使用MyBatis非常方便,我们甚至不知道自己在使用MyBatis.
源码分析
通过Spring整合MyBatis的示例,我们感受到了Spring为用户更加快捷地进行开发所做的努力,开发人员效率由此而得到提升。当时,相对于使用来说,我们更想知道其背后隐藏的秘密,Spring整合MyBatis如何实现的呢?我们在Spring的配置文件中,可以看到起关键作用的一个类:org.mybatis.spring.SqlSessionFactoryBean它将其他相关的bean组装到了一起,我们分析就从此类开始:
1)sqlSessionFactory创建
通过配置文件我们分析,对于配置文件的解析,Spring通过org.mybatis.Spring.SqlSessionFactoryBean封装了MyBatis实现。这个类中有两个接口:
- InitializingBean:实现此接口的bean会在初始化时调用其afterPropertiesSet方法来进行bean的逻辑初始化。
- FactoryBean:一旦某个bean实现此接口,那么通过getBean方法获取bean时其实是获取此类的getObject返回的实例。
我们先看看InitializingBean的aferPropertiesSet()方法
SqlSessionFactoryBean的初始化
public void afterPropertiesSet() throws Exception{
notNull(dataSource,"Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder,"Property 'sqlSessionFactoryBuilder' " +
"is required");
this.sqlSessionFactory = buildSqlSessionFactory();
}
/**
* 此函数的目的就是对sqlSessionFactory的初始化,通过之前展示的独立使用MyBatis的示例我们了解到SqlSessionFactory
* 是所有MyBatis功能的基础
*/
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configLocation != null){
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(),null,this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
configuration = new Configuration();
configuration.setVariables(this.configurationPreperties);
}
if (this.objectFactory != null){
configuration.setObjectFactory(this.objectWrapperFactory);
}
if (this.objectWrapperFactory != null){
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if(haslength(this.typeAliasesPackage)){
String[] typeAliasPackageArray =
tokenizeToStringArray(this.typeAliasesPackage,ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for(String packageToScan:typeAliasPackageArray){
configuration.getTypeAliasRegistry().registerAliases(packageToScan,typeAliasesSuperType==null?Object.class:typeAliasesSuperType);
if(this.logger.isDebugEnabled()){
this.logger.debug("Scanned package:'"+packageToScan+"' for aliases");
}
}
}
if(!isEmpty(this.typeAliases)){
for(Class<?> typeAlias:this.typeAliases){
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
if(this.logger.isDebugEnabled()){
this.logger.debug("Scanned pacakge:'" + packageToScan+"' for aliases");
}
}
}
if(!isEmpty(this.plugins)){
for(Interceptor plugin:this.plugins){
configuration.addInterceptor(plugin);
if(this.logger.isDebugEnabled()){
this.logger.debug("Registered plugin:'"+plugin+"'");
}
}
}
if(hasLength(this.typeHandlersPackage)){
String[] typeHandlerPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for(String packageToScan:typeHandlersPackageArray){
configuration.getTypeHandlerRegistry().register(packageToScan);
if(this.logger.isDebugEnabled()){
this.logger.debug("Scanned package:'"+packageToScan+"'for type handlers");
}
}
}
if(!isEmpty(this.typeHandlers)){
for(TypeHandler<?> typeHandler:this.typeHandlers){
configuration.getTypeHandlerRegistry().register(typeHandler);
if(this.logger.isDebugEnabled()){
this.logger.debug("Registered type handler:'"+typeHandler+"'");
}
}
}
if(xmlConfigBuilder != null){
try{
xmlConfigBuilder.parse();
if(this.logger.isDebugEnabled()){
this.logger.debug("Parseed configuration file:'"+this.configLocation+"'");
}
}catch(Exception ex){
throw new NestedIOException("Failed to parse config resource:"+this.configLocation,ex);
}finally{
ErrowContext.instance().reset();
}
}
if(this.transactionFactory == null){
this.transactionFactory = new SpringManagedTransactionFactory();
}
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));
}catch(SQLException e){
throw new NestedIOException("Failed getting a databaseId",e);
}
}
if(!isEmpty(this.mapperLocation)){
for(Resource mapperLocation: this.mapperLocation){
if(mapperLocation == null){
continue;
}
try{
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configguration,mapperLocation.toString(),configuration.getSqlFragments());
xmlMapperBuilder.parse();
}catch(Excpetion e){
throw new NestedIOException("Failed to parse mapping resource:'"+mapperLocation+"'",e);
}finally{
ErrorContext.instance.reset();
}
if(this.logger.isDebugEnabled()){
this.logger.debug("Parse mapper file:'"+mapperLocation+"'");
}
}
}else{
if(this.logger.isDebugEnabled()){
this.logger.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
从以上函数可以看出,配置文件还可以支持多种属性的配置,如configLocation,objectFactory,ojectWrapperFactory,typeAliasesPackage,typeAliases,typeHandlersPackage,plugins,typeHandlers,transactionFactory,databaseIdProvider,mapperLocations.
获取sqlSessionFactoryBean实例
由于SqlSessionFactoryBean实现了FactoryBean接口,所以当通过getBean方法获取对应实例时,其实是过去该类的getObject()函数返回的实例,也就是获取初始化后的sqlSessionFactory属性。
public SqlSessionFactory getObject() throws Exception{
if(this.sqlSessionFactory == null){
afterPropertiesSet();
}
return this.sqlSessionFactory;
}