Spring注解集成MyBatis(双数据源配置)

        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();
    }

}

猜你喜欢

转载自blog.csdn.net/weixin_52850476/article/details/124831849
今日推荐