Spring 集成 MyBaits 有 xml 配置文件和注解两种方式,在实际开发中,注解的方式是较为简便、使用较多的,下面我们就来介绍 Spring 是如何通过注解的方式集成 MyBatis 的?
在介绍 spring 集成 mybaits 之前,我们先来看一看 mybaits 程序独立开发是如何做的?
① mybaits-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>
<!--加载同目录下的jdbc.properties配置文件-->
<properties resource="jdbc.properties"></properties>
<!--配置类型别名-->
<typeAliases>
<package name="org.mybatis.builder"/>
</typeAliases>
<!--数据源配置-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--SQL语句的映射-->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
</configuration>
② jdbc.properties 配置参数
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=UTF-8
jdbc.uid=root
jdbc.password=123456
③ MyBatis 程序核心(SqlSessionFactory对象)
mport java.io.IOException;
import java.io.InputStream;
public class Test {
public static void main(String[] args) throws IOException {
// 1. 加载 mybaits-config.xml 配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 创建 SqlSessionFactory 对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 获取 SqlSession 对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 执行 sqlSession 对象执行查询, 获取指定mapper对象
UserDao userDao = sqlSession.getMapper(UserDao.class);
// 5. 执行查询
User user = userDao.findUserById(1);
// 6. 释放资源
sqlSession.close();
}
}
下面开始进入正题:Spring 通过注解集成 MyBatis(同时,采用双数据源)
① 在 pom.xml 依赖中引入相关坐标
<dependencies>
<!-- mysql连接驱动 -->
<dependencie>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependencie>
<!-- druid数据库连接池 -->
<dependencie>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependencie>
<!-- mybatis坐标 -->
<dependencie>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependencie>
<!-- mybatis整合spring坐标 -->
<dependencie>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependencie>
<!-- mybatis依赖jdbc坐标 -->
<dependencie>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependencie>
</<dependencies>>
② 配置 application.properties 参数
server.port=8090
server.servlet-path=/
#主数据源
spring.datasource.primary.driverClassName = com.mysql.jdbc.Driver
spring.datasource.primary.url = jdbc:mysql://localhost:3306/db1?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.primary.username = root
spring.datasource.primary.password = root
#副数据源
spring.datasource.secondary.driverClassName = com.mysql.jdbc.Driver
spring.datasource.secondary.url = jdbc:mysql://localhost:3306/db2?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.secondary.username = root
spring.datasource.secondary.password = root
#通用属性
spring.datasource.maxActive=20
spring.datasource.minIdle=8
spring.datasource.initialSize=10
spring.datasource.testWhileIdle = true
spring.datasource.validationQuery=SELECT 1
spring.datasource.timeBetweenEvictionRunsMillis = 3600000
③ Spring 注解集成 MyBatis 入口
@Configuration // 该注解类似于spring配置文件
@EnableTransactionManagement // 开启注解式事务驱动
public class MyBatisConfig{
@Value("${spring.datasource.maxActive}")
private int maxActive;
@Value("${spring.datasource.minIdle}")
private int minIdle;
@Value("${spring.datasource.initialSize}")
private int initialSize;
@Value("${spring.datasource.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.datasource.validationQuery}")
private String validationQuery;
@Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
private int timeBetweenEvictionRunsMillis;
/**
* 主数据源配置
* @ConfigurationProperties:支持属性文件和javaBean的映射
* @throws Exception
*/
@Bean
@ConfigurationProperties(prefix="spring.datasource.primary" )
public DataSource primaryDataSource() throws Exception {
System.out.println("创建主数据源");
return createDataSource();
}
/**
* 子数据源配置
* @return
* @throws Exception
*/
@Bean
@ConfigurationProperties(prefix="spring.datasource.secondary" )
public DataSource secondaryDataSource() throws Exception {
System.out.println("创建子数据源");
return createDataSource();
}
/**
* @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@Autowire注解报错(@Autowired,默认是根据类型Type来自动注入的)
* @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)
*/
@Bean
@Primary // 这个@Primary与下面的DatabaseType.primary无关
public DynamicDataSource dataSource(@Qualifier("primaryDataSource") DataSource primaryDataSource,
@Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
// DatabaseType 是自定义的枚举类型的类,下面步骤中会给出
targetDataSources.put(DatabaseType.primary, primaryDataSource);
targetDataSources.put(DatabaseType.secondary, secondaryDataSource);
DynamicDataSource dataSource = new DynamicDataSource();
// 将所有的数据源设置进DynamicDataSource(该类继承了AbstractRoutingDataSource类)
dataSource.setTargetDataSources(targetDataSources);
// 设置默认的数据源为主数据源
dataSource.setDefaultTargetDataSource(primaryDataSource);
return dataSource;
}
/**
* 根据数据源创建SqlSessionFactory
*/
@Bean
public SqlSessionFactory sqlSessionFactory(DynamicDataSource ds) throws Exception {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
fb.setDataSource(ds);// 指定数据源(这个必须有,否则报错)
//fb.setTypeAliasesPackage("com.huan.model");// 为基包下的类设置别名
fb.setMapperLocations(resolver.getResources("classpath:com/spring/*/mapper/impl/*.xml"));// 指定映射文件位置
return fb.getObject();
}
//加载属性并赋值
private DataSource createDataSource() {
org.apache.tomcat.jdbc.pool.DataSource dataSource = (org.apache.tomcat.jdbc.pool.DataSource)DataSourceBuilder.create().build();
dataSource.setMaxActive(maxActive);
dataSource.setMinIdle(minIdle);
dataSource.setInitialSize(initialSize);
dataSource.setTestWhileIdle(testWhileIdle);
dataSource.setValidationQuery(validationQuery);
dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
return dataSource;
}
/**
* 配置事务管理器
*/
@Bean
public DataSourceTransactionManager transactionManager(DynamicDataSource dynamicDataSource) throws Exception {
return new DataSourceTransactionManager(dynamicDataSource);
}
}
至此,我们已经成功地完成了 Spring 对 MyBatis 集成的工作,而上述的 DynamicDataSource 类实现了主数据源和副数据源的切换,下面我们来看一下具体实现:
④ 自定义 @DS 注解,用于标注在 service 层切换数据源
/**
* Class Description 注解类,用于标注在Service层切换数据源
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DS {
String value() default "primary";
}
⑤ 设置枚举类,记录数据源
/**
* 列出所有的数据源key(常用数据库名称来命名)
* 注意:这里数据源与数据库是一对一的,DatabaseType中的变量名称就是数据库的名称
* "primary" 代指主数据源
* "second" 代指副数据源
*/
public enum DatabaseType {
primary,
secondary
}
⑥ 保存一个线程安全的 DatabaseType 容器
/**
* 保存当前线程所对应的 DatabaseType
*/
public class DatabaseContextHolder {
private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<>();
public static DatabaseType getDatabaseType(){
return contextHolder.get();
}
public static void setDatabaseType(DatabaseType type) {
contextHolder.set(type);
}
public static void clearDataSourceType() {
contextHolder.remove();
}
}
⑦ 设置一个拦截器: 拦截 service 包下的所有方法, 根据注解进行数据源的切换
/**
* 拦截 service 包下的所有方法, 根据注解进行数据源的切换
* DatabaseContextHolder.setDatabaseType() 方法: 将数据源的key设置到当前线程中
*/
@Aspect
@Component
public class DynamicDataSourceAspect {
/**
* 使用空方法定义切点表达式
*/
@Pointcut("execution(* com.spring.patent.service.**.*(..))")
public void declareJointPointExpression() {
}
@Before("declareJointPointExpression()")
public void setDataSourceKey(JoinPoint point) throws NoSuchMethodException, SecurityException{
/*logger.info("拦截到了" + point.getSignature().getName() +"方法...");*/
Signature signature = point.getSignature();
MethodSignature methodSignature = (MethodSignature)signature;
Method targetMethod = methodSignature.getMethod();
DS ds = targetMethod.getAnnotation(DS.class);
if(ds != null){
// 1. service方法上有@DS注解, 注解的值是谁就使用谁的数据源(setDatabaseType将数据源设置到当前线程中)
String dsname = ds.value();
if("primary".equals(dsname)){
DatabaseContextHolder.setDatabaseType(DatabaseType.primary);
}else if("secondary".equals(dsname)){
DatabaseContextHolder.setDatabaseType(DatabaseType.secondary);
}
}else{
// 2. service方法上没有@DS注解时, 默认使用主数据源(setDatabaseType将数据源设置到当前线程中)
DatabaseContextHolder.setDatabaseType(DatabaseType.primary);
}
}
@After("declareJointPointExpression()")
public void restoreDataSource(JoinPoint point) throws NoSuchMethodException, SecurityException{
//方法执行完毕之后,销毁当前数据源信息,进行垃圾回收。
DatabaseContextHolder.clearDataSourceType();
}
}
⑧ 获取当前线程持有的数据源的 key,并将其设置进 DynamicDataSource 中
/**
* 动态数据源(需要继承AbstractRoutingDataSource)
* 该类通过DatabaseContextHolder对象在一个线程内获取对应的数据源
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {// 决定当前数据源的key
// 获取当前线程对应的数据源的key, 并设置进DynamicDataSource
return DatabaseContextHolder.getDatabaseType();
}
}