Spring annotation integration MyBatis (dual data source configuration)

        There are two ways to integrate MyBaits with Spring: xml configuration files and annotations. In actual development, the annotation method is relatively simple and used more. Let’s introduce how Spring integrates MyBatis through annotations.

Before introducing spring integration mybaits, let's take a look at how the independent development of mybaits program is done?

① mybaits-config configuration file

<?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 configuration parameters

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=UTF-8
jdbc.uid=root
jdbc.password=123456

③ MyBatis program core (SqlSessionFactory object)

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

}

Let's get to the point: Spring integrates MyBatis through annotations (at the same time, using dual data sources )

① Introduce relevant coordinates in the pom.xml dependency

<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>>

② Configure application.properties parameters

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 annotation integrated MyBatis entry

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


}

        So far, we have successfully completed the work of Spring's integration of MyBatis, and the above-mentioned DynamicDataSource class realizes the switching between the main data source and the secondary data source. Let's take a look at the specific implementation:

④ Custom @DS annotation, used to mark switching data sources at the service layer

/**
 * Class Description 注解类,用于标注在Service层切换数据源
 * 
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DS {
	String value() default "primary";
}

⑤ Set the enumeration class and record the data source

/**
 * 列出所有的数据源key(常用数据库名称来命名)
 * 注意:这里数据源与数据库是一对一的,DatabaseType中的变量名称就是数据库的名称
 * "primary" 代指主数据源
 * "second" 代指副数据源
 */
public enum DatabaseType {
    primary,
    secondary
}

⑥ Save a thread-safe DatabaseType container

/**
 * 保存当前线程所对应的 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();

    }
}

⑦ Set up an interceptor: intercept all methods under the service package, and switch data sources according to annotations

/**
 * 拦截 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();

    }
}

⑧ Get the key of the data source held by the current thread and set it into DynamicDataSource

/**
 * 动态数据源(需要继承AbstractRoutingDataSource)
 * 该类通过DatabaseContextHolder对象在一个线程内获取对应的数据源
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {// 决定当前数据源的key
        // 获取当前线程对应的数据源的key, 并设置进DynamicDataSource
        return DatabaseContextHolder.getDatabaseType();
    }

}

 

Guess you like

Origin blog.csdn.net/weixin_52850476/article/details/124831849
Recommended