MyBatis 学习-与 Spring 集成篇
根据官方的说法,在 ibatis3,也就是 Mybatis3 问世之前,Spring3 的开发工作就已经完成了,所以 Spring3 中还是没有对 Mybatis3 的支持。因此由 Mybatis 社区自己开发了一个 Mybatis-Spring 用来满足 Mybatis 用户整合 Spring 的需求。下面就将通过 Mybatis-Spring 来整合 Mybatis 跟 Spring 的用法做一个简单的介绍。
一、SqlSessionFactoryBean
Mybatis 的所有操作都是基于一个 SqlSession 的,而 SqlSession 是由 SqlSessionFactory 来产生的,SqlSessionFactory 又是由 SqlSessionFactoryBuilder 来生成的。而在 Mybatis-Spring 中则使用 SqlSessionFactoryBean 来替代。在这个 Bean 中通过 SqlSessionFactoryBuilder 来建立对应的 SqlSessionFactory,进而获取到 SqlSession。
<!-- 定义MyBatis的SqlSessionFactoryBean --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 指定MyBatis配置文件 --> <property name="configLocation" value="classpath:mybatis-config.xml" /> <!-- 给Bean指定别名 --> <property name="typeAliasesPackage" value="com.enh.bean"/> </bean>
在定义 SqlSessionFactoryBean 的时候。dataSource 属性必须配置,它表示用于连接数据库的数据源,多数据源时会有多个 dataSource,同时也需要配置多个 SqlSessionFactory 来对应。当然,我们也可以指定一些其他的属性,下面简单列举几个:
- mapperLocations:它表示我们的 Mapper 文件存放的位置,当我们的 Mapper 文件跟对应的 Mapper 接口处于同一位置的时候可以不用指定该属性的值。
- configLocation:用于指定 Mybatis 的配置文件位置。如果指定了该属性,那么会以该配置文件的内容作为配置信息构建对应的 SqlSessionFactoryBuilder,但是后续属性指定的内容会覆盖该配置文件里面指定的对应内容。
- typeAliasesPackage:它一般对应我们的实体类所在的包,这个时候会自动取对应包中不包括包名的简单类名作为包括包名的别名。多个 package 之间可以用逗号或者分号等来进行分隔。
- typeAliases:数组类型,用来指定别名的。指定了这个属性后,Mybatis会把这个类型的短名称作为这个类型的别名,前提是该类上没有标注@Alias注解,否则将使用该注解对应的值作为此种类型的别名。
<property name="typeAliases"> <array> <value>com.tiantian.mybatis.model.Blog</value> <value>com.tiantian.mybatis.model.Comment</value> </array> </property>
- plugins:数组类型,用来指定 Mybatis 的 Interceptor。
- typeHandlersPackage:用来指定 TypeHandler 所在的包,如果指定了该属性,SqlSessionFactoryBean 会自动把该包下面的类注册为对应的 TypeHandler。多个 package 之间可以用逗号或者分号等来进行分隔。
- typeHandlers:数组类型,表示 TypeHandler。
二、MapperFactoryBean
接下来就是在 Spring 的 applicationContext 文件中定义我们想要的 Mapper 对象对应的 MapperFactoryBean 了。MapperFactoryBean 实现了 Spring 的 FactoryBean 接口,所以 MapperFactoryBean 是通过 FactoryBean 接口中定义的 getObject 方法来获取对应的 Mapper 对象的。
在定义一个 MapperFactoryBean 的时候有两个属性需要我们注入:
- sqlSessionFactory:Mybatis-Spring 用来生成实现了 SqlSession 接口的 SqlSessionTemplate 对象。
- mapperInterface:我们所要返回的 Mapper 接口全类名。
定义好相应 Mapper 接口对应的 MapperFactoryBean 之后,我们就可以把我们对应的 Mapper 接口注入到由 Spring 管理的 bean 对象中了,比如 Service层。
这时候我们的配置文件是这样的:
<!-- 引用外部配置文件 供数据源使用 --> <context:property-placeholder location="classpath:application.properties"/> <!-- 数据源配置,使用应用内的DBCP数据库连接池 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" > <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!-- 连接池最大空闲 --> <property name="maxIdle" value="${dbcp.maxIdle}" /> <!-- 连接池最大数量 --> <property name="maxActive" value="${dbcp.maxActive}" /> <property name="defaultAutoCommit" value="false" /> </bean> <!-- 定义MyBatis的SqlSessionFactoryBean --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 指定MyBatis配置文件 --> <property name="configLocation" value="classpath:mybatis-config.xml" /> <!-- 给Bean指定别名 --> <property name="typeAliasesPackage" value="com.enh.bean"/> </bean> <!-- 创建MemberMapper对象 --> <bean id="memberMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInterface" value="com.enh.mapper.MemberMapper.java" /> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean>
三、MapperScannerConfigurer
利用上面的方法进行整合的时候,我们有一个 Mapper 就需要定义一个对应的 MapperFactoryBean,当我们的 Mapper 比较少的时候,这样做也还可以,但是当我们的 Mapper 相当多时我们再这样定义一个个 Mapper 对应的 MapperFactoryBean 就显得速度比较慢了。为此 Mybatis-Spring 为我们提供了一个叫做 MapperScannerConfigurer 的类,通过这个类 Mybatis-Spring 会自动为我们注册 Mapper 对应的 MapperFactoryBean 对象。
如果我们需要使用 MapperScannerConfigurer 来帮我们自动扫描和注册 Mapper 接口的话我们需要在 Spring 的 applicationContext 配置文件中定义一个 MapperScannerConfigurer 对应的 bean。对于 MapperScannerConfigurer 而言有一个属性是我们必须指定的,那就是 basePackage。basePackage 是用来指定 Mapper 接口文件所在的基包的,在这个基包或其所有子包下面的 Mapper 接口都将被搜索到。多个基包之间可以使用逗号或者分号进行分隔。最简单的 MapperScannerConfigurer 定义就是只指定一个 basePackage 属性,如:
<!-- 创建所有mapperFactoryBean --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.enh.mapper" /> </bean>
这样 MapperScannerConfigurer 就会扫描指定基包下面的所有接口,并把它们注册为一个个 MapperFactoryBean 对象。
有时候我们指定的基包下面的并不全是我们定义的 Mapper 接口,为此 MapperScannerConfigurer 还为我们提供了另外两个可以缩小搜索和注册范围的属性。一个是 annotationClass,另一个是 markerInterface。
- annotationClass:当指定了 annotationClass 的时候,MapperScannerConfigurer 将只注册使用了 annotationClass 注解标记的接口。
- markerInterface:markerInterface 是用于指定一个接口的,当指定了 markerInterface 之后,MapperScannerConfigurer 将只注册继承自 markerInterface 的接口。
如果上述两个属性都指定了的话,那么 MapperScannerConfigurer 将取它们的并集,而不是交集。即使用了 annotationClass 进行标记或者继承自 markerInterface 的接口都将被注册为一个 MapperFactoryBean。
现在假设我们的 Mapper 接口都继承了一个 SuperMapper 接口,那么我们就可以这样来定义我们的 MapperScannerConfigurer。
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.enh.mapper" /> <property name="markerInterface" value="com.enh.mapper.SuperMapper"/> </bean>
除了用于缩小注册 Mapper 接口范围的属性之外,我们还可以指定一些其他属性,如:
- sqlSessionFactory:这个属性已经废弃。当我们使用了多个数据源的时候我们就需要通过 sqlSessionFactory 来指定在注册 MapperFactoryBean 的时候需要使用的 SqlSessionFactory,因为在没有指定 sqlSessionFactory 的时候,会以 Autowired 的方式自动注入一个。换言之当我们只使用一个数据源的时候,即只定义了一个 SqlSessionFactory 的时候我们就可以不给 MapperScannerConfigurer 指定 SqlSessionFactory。
- sqlSessionFactoryBeanName:它的功能跟 sqlSessionFactory 是一样的,只是它指定的是定义好的 SqlSessionFactory 对应的 bean 名称。
- sqlSessionTemplate:这个属性已经废弃。它的功能也是相当于 sqlSessionFactory 的,因为就像前面说的那样,MapperFactoryBean 最终还是使用的 SqlSession 的 getMapper 方法取的对应的 Mapper 对象。当定义有多个 SqlSessionTemplate 的时候才需要指定它。对于一个 MapperFactoryBean 来说 SqlSessionFactory 和 SqlSessionTemplate 只需要其中一个就可以了,当两者都指定了的时候,SqlSessionFactory 会被忽略。
- sqlSessionTemplateBeanName:指定需要使用的 sqlSessionTemplate 对应的 bean 名称。
注意:由于使用 sqlSessionFactory 和 sqlSessionTemplate 属性时会使一些内容在 PropertyPlaceholderConfigurer 之前加载,导致在配置文件中使用到的外部属性信息无法被及时替换而出错,因此官方现在新的 Mybatis-Spring 中已经把 sqlSessionFactory 和 sqlSessionTemplate 属性废弃了,推荐大家使用 sqlSessionFactoryBeanName 属性和 sqlSessionTemplateBeanName 属性。
四、SqlSessionTemplate
除了上述整合之后直接使用 Mapper 接口之外,Mybatis-Spring 还为我们提供了一种直接使用 SqlSession 的方式。Mybatis-Spring 为我们提供了一个实现了 SqlSession 接口的 SqlSessionTemplate 类,它是线程安全的,可以被多个 Dao 同时使用。同时它还跟 Spring 的事务进行了关联,确保当前被使用的 SqlSession 是一个已经和 Spring 的事务进行绑定了的。而且它还可以自己管理 Session 的提交和关闭。当使用了 Spring 的事务管理机制后,SqlSession 还可以跟着 Spring 的事务一起提交和回滚。
使用 SqlSessionTemplate 时我们可以在 Spring 的 applicationContext 配置文件中如下定义:
<!-- 定义MyBatis的SqlSessionBean --> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean>
这样我们就可以通过 Spring 的依赖注入在 Dao 中直接使用 SqlSessionTemplate 来编程了,这个时候我们的 Dao 可能是这个样子:
package com.erim.dao; @Repository("userDetailDao") @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class UserDetailDao extends BaseDao { public List<UserDetailBean> selectPageTown(BaseBean baseBean, ModelMap model) { return getSqlSession().selectList("userdetail.selectPageTown", baseBean, new RowBounds(baseBean.getPageLinkBean().getStart(), baseBean.getPageLinkBean().getLimit())); } }
五、最终的配置文件
<!-- 引用外部配置文件 供数据源使用 --> <context:property-placeholder location="classpath:application.properties"/> <!-- 数据源配置,使用应用内的DBCP数据库连接池 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" > <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!-- 连接池最大空闲 --> <property name="maxIdle" value="${dbcp.maxIdle}" /> <!-- 连接池最大数量 --> <property name="maxActive" value="${dbcp.maxActive}" /> <property name="defaultAutoCommit" value="false" /> </bean> <!-- 定义MyBatis的SqlSessionFactoryBean --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 指定MyBatis配置文件 --> <property name="configLocation" value="classpath:mybatis-config.xml" /> <!-- 给Bean指定别名 --> <property name="typeAliasesPackage" value="com.enh.bean"/> </bean> <!-- 创建所有mapperFactoryBean --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.enh.mapper" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> <!-- 定义MyBatis的SqlSessionBean --> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean>