史上最详细mybatis与spring整合教程
点击上方"田守枝的技术博客",关注我mybatis本身使用比较灵活,和spring整合也有多种方式。本文一网打尽mybatis与spring整合所有方式,让你彻底掌握my...
点击上方"田守枝的技术博客",关注我
mybatis本身使用比较灵活,和spring整合也有多种方式。本文一网打尽mybatis与spring整合所有方式,让你彻底掌握mybatis与spring整合原理,堪称史上最全面的mybatis与spring整合教程。内容较多,可以先收藏,耐心看完,必定有所收获。
本文总共分为以下10个部分:
整合一:基础回顾
整合二:SqlSessionFactoryBean
整合三:SqlSessionTemplate
整合四:SqlSessionDaoSupport
整合五:MapperFactoryBean
整合六:MapperScannerConfigurer
整合七:@MapperScan
整合八:事务
整合九:springboot
整合十:多数据源
1 基础回顾
mybatis可以单独使用,也可以与spring进行整合。在整合之后,各组件的之间的依赖关系如下图所示:
通常,我们需要在pom.xml文件中引入上述所有依赖。其中:
mysql-connector-java:mysql数据库驱动,用于与mysql建立真实连接。
datasource:数据库连接池。将建立的mysql连接维护到一个连接池中,进行链接复用。典型的连接池如druid、c3p0、tomcat-jdbc、dbcp2、hicaricp等。
mybatis:半自动的orm框架,无需多说。
mybatis-spring:mybatis本身可以单独使用,如果需要与spring进行整合,则需要额外引入mybatis-spring。
spring:mybatis与spring整合后,可以直接在业务层通过@Autowired注解注入Mapper,也会利用spring提供的事务管理机制。
在单独使用mybatis的情况下,我们通常是通过SqlSessionFactory创建SqlSession,然后通过SqlSession来进行增删查操作,如:
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream in = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(1);
} finally {
session.close();
}
这里不做过多介绍,只是为接下来的内容做铺垫。
2 SqlSessionFactoryBean
SqlSessionFactory是mybatis的核心,当与spring进行整合时,我们使用mybatis-spring提供的SqlSessionFactoryBean 来创建其实例,SqlSessionFactoryBean实现了FactoryBean 接口。SqlSessionFactoryBean的配置有2种风格:
保留mybatis的核心配置文件
不保留mybatis的核心配置文件
1、 保留mybatis的核心配置文件
mybatis的配置依然保留在mybatis的核心配置文件mybatis-config文件中,以下是一个示例:
<?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>
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>
<typeAliases>
<package name="com.tianshouzhi.mybatis.entity"/>
</typeAliases>
<mappers>
<mapper resource="mybatis/mappers/UserMapper.xml"/>
</mappers>
</configuration>
细心的读者注意到,在mybatis-config.xml文件中并没有配置<environment>、<dataSource>、<transactionManager>等元素。即使配置了,也会被SqlSessionFactoryBean忽略。我们需要显式的为SqlSessionFactoryBean的dataSource属性引用一个数据源配置,如果不指定,在其初始化时就会抛出异常。通过configLocation属性,指定mybatis核心配置文件的路径。
此时SqlSessionFactoryBean配置方式如下:
<!—-SqlSessionFactoryBean-—>
<bean id="sqlSessionFactory" class=“org.mybatis.spring.SqlSessionFactoryBean">
<!--数据源配置-->
<property name="dataSource" ref=“dataSource"/>
<!--通过configLocation属性指定mybatis核心配置文件mybatis-config.xml路径-->
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="root"/>
<property name="password" value="your password"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<!--其他配置-->
</bean>
2、不保留mybatis的核心配置文件
从mybatis-spring 1.3.0之后,我们可以移除mybatis-config.xml文件,将所有关于myabtis的配置都通过SqlSessionFactoryBean来指定。
以下配置案例演示了与上述等价的配置:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.tianshouzhi.zebracost.entity”/>
<!--从类路径下加载在mybatis/mappers包和它的子包中所有的 MyBatis 映射器 XML 文件-->
<property name="mapperLocations" value="classpath*:mybatis/mappers/**/*.xml"></property>
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="mapUnderscoreToCamelCase" value="true"/>
<property name="cacheEnabled" value="true"/>
<property name="defaultExecutorType" value="SIMPLE"/>
</bean>
</property>
</bean>
在mybatis与spring整合后, 通常我们不会再直接使用SqlSessionFactory,这种方式用起来比较麻烦。
mybatis-spring提供了易于使用的类,如SqlSessionTemplate、SqlSessionDaoSupport等,当然也有大多数读者都已经非常熟悉的MapperScannerConfigurer、MapperFactoryBean等。
3 SqlSessionTemplate
SqlSessionTemplate 是 mybatis-spring 的核心,其实现了SqlSession接口,且线程安全。使用了SqlSessionTemplate之后,我们不再需要通过SqlSessionFactory.openSession()方法来创建SqlSession实例;使用完成之后,也不要调用SqlSession.close()方法进行关闭。另外,对于事务,SqlSessionTemplate 将会保证使用的 SqlSession 是和当前 Spring 的事务相关的。
SqlSessionTemplate依赖于SqlSessionFactory,其配置方式如下所示:
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<bean id="userDao" class="com.tianshouzhi.mybatis.dao.UserDao"/>
之后我们可以在UserD类中直接进行注入。SqlSessionTemplate 是线程安全的, 可以被多个 DAO 所共享使用,以下是一个示例:
public class UserDao {
private static String NAMESPACE = "com.tianshouzhi.zebracost.UserMapper";
@Autowired
SqlSessionTemplate sqlSession;
public User selectById(int id) {
User user = sqlSession.selectOne(NAMESPACE + ".selectById",id);
return user;
}
}
4 SqlSessionDaoSupport
除了直接注入SqlSessionTemplate,也可以编写一个Dao类继承SqlSessionDaoSupport,调用其getSqlSession()方法来返回 SqlSessionTemplate。
SqlSessionDaoSupport 需要一个 sqlSessionFactory 或 sqlSessionTemplate 属性来设置 。如果两者都被设置了 , 那么SqlSessionFactory是被忽略的。
事实上,如果你提供的是一个SqlSessionFactory,SqlSessionDaoSupport内部也会使用其来构造一个SqlSessionTemplate实例。
<bean id="userDao" class="com.tianshouzhi.zebracost.dao.UserDao">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
或:
<bean id="userDao" class="com.tianshouzhi.zebracost.dao.UserDao">
<property name="sqlSessionTemplate" ref="sqlSessionTemplate"/>
</bean>
由于我们的UserDao类继承了SqlSessionDaoSupport,所以你可以在UserDao类中这样使用:
public class UserDao extends SqlSessionDaoSupport{
private static String NAMESPACE = "com.tianshouzhi.zebracost.UserMapper";
public User selectById(int id) {
User user = getSqlSession().selectOne(NAMESPACE + ".selectById",id);
return user;
}
}
5 MapperFactoryBean
无论是使用SqlSessionTemplate,还是继承SqlSessionDaoSupport,我们都需要手工编写DAO类的代码。熟悉mybatis同学知道,SqlSession有一个getMapper()方法,可以让我们通过映射器接口的方式来使用mybatis。如:
SqlSession session = sqlSessionFactory.openSession();
try {
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(1);
} finally {
session.close();
}
但是在与spring进行整合时,是否有更加简单的使用方法呢?能否通过@Autowired注解直接注入Mapper呢?我们期望的使用方式是这样:
public class UserService {
@Autowired
private UserMapper userMapper;
public void insert(User user){
userMapper.insert(user);
}
}
在没有进行任何配置的情况下,直接这样操作显然是会报错的,因为UserMapper是一个接口,且不是spring管理的bean,因此无法直接注入。
这个时候,MapperFactoryBean则可以登场了,通过如下配置,MapperFactoryBean会针对UserMapper接口创建一个代理,并将其变成spring的一个bean。
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.tianshouzhi.mybatis.mapper.UserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
MapperFactoryBean继承了SqlSessionDaoSupport类,这也是为什么我们先介绍SqlSessionDaoSupport的原因。
通过上述配置,我们就可以在一个业务bean中直接注入UserMapper接口了:
public class UserService{
@Autowired
private UserMapper userMapper;
...
}
你可能想知道MapperFactoryBean为什么具有这样的魔力,通过配置就可以使用@Autowired注解了。事实上,其底层是通过对UserMapper接口进行JDK动态代理,内部使用SqlSessionTemplate完成CRUD操作。但是这不是本文的重点,本文讲解的是mybatis是如何spring进行整合的。笔者将其会在其他文章分析MapperFactoryBean的源码。
6 MapperScannerConfigurer
通过MapperFactoryBean配置,已经是mybatis与spring进行时理想的方式了,我们可以简单的通过@Autowired注解进行注入。
但是如果有许多的Mapper接口要配置,针对每个接口都配置一个MapperFactoryBean,会使得我们的配置文件很臃肿。关于这一点,mybatis-spring包中提供了MapperScannerConfigurer来帮助你解决这个问题。
MapperScannerConfigurer可以指定扫描某个包,为这个包下的所有Mapper接口,在Spring上下文中都MapperFactoryBean。如:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.tianshouzhi.mybatis.user.mappers" />
</bean>
basePackage 属性是用于指定Mapper接口的包路径。如果的Mapper接口位于不同的包下,可以使用分号”;”或者逗号”,”进行分割。如:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="aa.bb.mapper;aa.dd.mapper" />
</bean>
注意,如果某个路径还包含子包,子包中的Mapper接口递归地被搜索到。因此对于上述配置,我们可以通过公共的包名进行简化。如:
<property name="basePackage" value="aa" />
你可能想到了,如果指定的公共的包名下面还包含了一些其他的接口,这些接口是你作为其他用途使用到的,并不能作为mybatis的Mapper接口来使用。此时,你可以通过markerInterface属性或者annotationClass属性来进行过滤。
markerInterface属性,顾名思义,通过一个标记接口(接口中不需要定义任何方法),来对Mapper映射器接口进行过滤,如:
public interface MapperInterface{}
接着,你需要将你的映射器接口继承MapperInterface,如:
public interface UserMapper implements MapperInterface{
public void insert(User user);
}
此时你可以为MapperScannerConfigurer指定只有继承了MapperInterface接口的子接口,才为其自动注册MapperFactoryBean,如:
<property name="markerInterface" value=“com.tianshouzhi.mybatis.MybatisMapperInterface"/>
annotationClass属性的作用是类似的,只不过其是根据注解进行过滤。你不需要自定义注解,mybatis已经提供了一个@Mapper注解直接使用即可。配置如下:
<property name="annotationClass" value="org.apache.ibatis.annotations.Mapper"/>
如果同时指定了markerInterface和annotationClass属性,那么只有同时满足这两个条件的接口才会被注册为MapperFactoryBean。
细心的读者可能意识到了,前面配置MapperFactoryBean的时候,我们需要为其指定SqlSessionFactory,或者SqlSessionTemplate。
现在通过MapperScannerConfigurer来自动注册MapperFactoryBean,我们可以为MapperScannerConfigurer指定sqlSessionFactory或sqlSessionTemplate,然后由MapperScannerConfigurer在内部设置到MapperFactoryBean中,如下:
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<!--<property name="sqlSessionTemplate" ref="sqlSessionTemplate"/>-->
sqlSessionFactory和sqlSessionTemplate属性已经不建议使用了。原因在于,这两个属性不支持你使用spring提供的PropertyPlaceholderConfigurer的属性替换。
例如:你配置了SqlSessionFactoryBean来创建SqlSessionFactory实例,前面已经看到必须为其指定一个dataSource属性。很多用户习惯将数据源的配置放到一个独立的配置文件,如jdbc.properties文件中,之后在spring配置中,通过占位符来替换,如:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
</bean>
对于这样的配置,spring在初始化时会报错,因为MapperScannerConfigurer会在PropertyPlaceholderConfigurer初始化之前,就加载dataSource的配置,而此时PropertyPlaceholderConfigurer还未准备好替换的占位符的内容,所以抛出异常。
我们可以使用另外2个属性来替代:
sqlSessionFactoryBeanName
sqlSessionTemplateBeanName
如下:
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--<property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate"/>-->
此时,你依然可以放心大胆的在你的数据源配置中,使用占位符了。
7 @MapperScan
如果读者习惯使用注解,而不是xml文件的方式进行配置,mybatis-spring提供了@MapperScan注解,其可以取代MapperScannerConfigurer。一些读者可能在springboot中也使用过@MapperScan注解,需要注意的是,@MapperScan注解是mybatis-spring中提供的,并不是mybatis-springboot-starter中提供的。
以下演示了如何通过@MapperScan注解的方式来配置mybatis与spring整合,注解中的属性可以与MapperScannerConfigurer相对应。
@Configuration
@MapperScan(
//等价于MapperScannerConfigurer的basePackage属性
basePackages = “com.tianshouzhi.security.mapper”,
//等价于MapperScannerConfigurer的markerInterface属性
markerInterface = MybatisMapperInterface.class,
//等价于MapperScannerConfigurer的annotationClass属性
annotationClass = MybatisMapper.class,
//等价于MapperScannerConfigurer的sqlSessionFactoryBeanName属性
sqlSessionFactoryRef = "sqlSessionFactory")
public class DatabaseConfig {
//定义数据源
@Bean
public DataSource dataSource() {
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
dataSource.setUsername(“your username");
dataSource.setPassword(“you password");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis?characterEncoding=UTF-8");
dataSource.setDriverClass(com.mysql.jdbc.Driver.class);
return dataSource;
}
//定义SqlSessionFactoryBean
@Autowired
@Bean("sqlSessionFactory")
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setDataSource(dataSource);
return ssfb;
}
}
8 事务
上述所有的配置,还没有涉及到mybatis与spring进行整合另一个核心要点,即事务。整合后,我们需要将事务委派给spring来管理。spring提供了声明式事务管理的功能,可以让我们的事务代码变得非常简单。
以下是事务管理器的一个配置案例:
<!--spring 事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--默认事务超时时间-->
<property name="defaultTimeout" value="30000"/>
<!--数据源-->
<property name="dataSource" ref="dataSource" />
<!--提交失败的话,也进行回滚-->
<property name="rollbackOnCommitFailure" value="true"/>
</bean>
<!--开启声明式事务-->
<tx:annotation-driven transaction-manager="transactionManager"/>
之后,在业务bean的方法上添加@Transactional注解,此时这个方法就自动具备了事务的功能,如果出现异常,会自动回滚,没有出现异常则自动交。
@Transactional
public void transfer(final String out,final String in,final Double money) {
accountMapper.outMoney(out, money);
int i=1/0;
accountMapper.inMoney(in, money);
}
基于注解的形式的声明式事务管理器,是最为简单的,也是建议使用的方式。
然而,这里好像并没有看到mybatis与spring事务整合的相关代码,事实上,这里对开发者屏蔽了。mybatis自身提供了一个TransactionFactory接口,当通过mybatis-spring与spring进行整合后,引入了另外一个TransactionFactory接口实现SpringManagedTransactionFactory,如下图:
SpringManagedTransactionFactory,顾名思义,作用就是讲事务委托给spring进行管理。前面提到的SqlSessionFacoryBean有一个transactionFactory属性,如果没有指定的情况下,默认就是使用SpringManagedTransactionFactory。如下:
9 springboot
mybatis开发团队为Spring Boot 提供了 mybatis-spring-boot-starter。你需要引入如下依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
使用了该starter之后,最简单的情况下,你只需要在application.yml中进行数据源的相关配置即可,不需要对mybatis进行任何配置。
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis
username: root
password: your password
会自动利用该DataSource自动完成以下工作:
创建SqlSessionFactoryBean
创建SqlSessionTemplate
扫描Mapper接口,为其注册MapperFactoryBean
这实际上是一种"契约优先"的思想,通过默认的行为为简化复杂的配置。
作为有追求的程序员,我们想知道,mybatis-spring-boot-starter是如何帮我们完成这些默认行为。这里需要简单分析一下源码,源码很少,总共就几个类。
MybatisAutoConfiguration类中包含了自动注册的主要逻辑,这里不进行详细分析,只通过源码片段介绍其作用,注意1、2、3
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration implements InitializingBean {
//1、如果用户没有提供SqlSessionFactory,就自动创建一个SqlSessionFactory
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
...
}
//2、如果用户没有提供SqlSessionTemplate,就自动创建利用SqlSessionFactory自动创建一个SqlSessionTemplate
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
...
}
//3、如果用户没有提供MapperFactoryBean,就通过AutoConfiguredMapperScannerRegistrar来自动注册MapperFactoryBean
//注意,如果使用了@MapperScan注解,也不会生效
//另外,需要注意的是,默认情况下,只会对添加了@Mapper注解的映射器接口进行注册。
@org.springframework.context.annotation.Configuration
@Import({ AutoConfiguredMapperScannerRegistrar.class })
@ConditionalOnMissingBean(MapperFactoryBean.class)
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
...
}
}
敏锐的读者立即发现了,mybatis-spring-boot-starter看似具有魔力,实际上只是在我们前面介绍的SqlSessionFactory、SqlSessionTemplate、MapperFactoryBean等基础知识之上,利用了springboot的扩展点,进行了一些默认的配置行为而已。
另外一点需要注意的是,需要注意的是,一旦你自己提供了MapperScannerConfigurer,或者配置了MapperFactoryBean,那么mybatis-spring-boot-starter的自动配置功能将会失效。细心的读者可能发现了,这些自动配置代码上都有一个@ConditionalOnMissingBean注解,也就是我们不提供的情况下, 才会帮我们自动配置。例如,你自己通过@Bean注解,配置了MapperScannerConfigurer。
@Configuration
public class MyBatisMapperScannerConfig {
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
mapperScannerConfigurer.setBasePackage(“com.tianshouzhi.mybatis.mapper");
return mapperScannerConfigurer;
}
}
当然,spring-boot-starter也支持在application.yml中对mybatis进行配置,如
mybatis:
# mapper映射xml文件的所在路径
mapper-locations: classpath:mapping/*.xml
# 应实体类的路径
type-aliases-package: com.winter.model
10 多数据源
关于多数据源,配置麻烦一点。笔者在之前的文章剖析Spring多数据源,提供了一个多数据源实现,发布到了maven中央仓库,你可以在pom.xml中直接引用。
这个组件可以简化多数据源的开发,减少出错的可能。不过目前并未支持与springboot整合,欢迎有兴趣的读者加入开发一下这个功能。
识别二维码关注我
扫码进群
近期发表:
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)