(十一)Spring Boot整合Mybatis使用druid实现多数据源自动切换

版权声明:本博客为学习、笔记之用,以笔记形式记录学习的知识与感悟。学习过程中可能参考各种资料,如觉文中表述过分引用,请务必告知,以便迅速处理。如有错漏,不吝赐教。 https://blog.csdn.net/qq_34021712/article/details/77542433
demo源代码下载:点此下载 

为了在开发中以最简单的方法使用,本文基于注解和AOP的方法实现,在spring boot框架的项目中,添加本文实现的代码类后,只需要配置好数据源就可以直接通过注解使用,简单方便。

配置过程

添加aop依赖

<!-- Spring AOP -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

application.properties

jdbc.datasources=ds,ds1
jdbc.ds.driverClassName=com.mysql.jdbc.Driver
jdbc.ds.url=jdbc\:mysql\://localhost\:3306/ryx?characterEncoding\=UTF-8
jdbc.ds.username=root
jdbc.ds.password=123456


jdbc.ds1.driverClassName=com.mysql.jdbc.Driver
jdbc.ds1.url=jdbc\:mysql\://172.20.1.121\:3306/ryx?characterEncoding\=UTF-8
jdbc.ds1.username=root
jdbc.ds1.password=root
#druid监控
druid.username=root
druid.password=Ruyixing2017

SpringMVC.java

@Configuration
public class SpringMVC extends WebMvcConfigurerAdapter {
	
	@Value("${druid.username}")
	private String druidUsername;
	
	@Value("${druid.password}")
	private String druidPassword;
	
	@Bean
    public HttpMessageConverter<String> responseBodyConverter() {
        StringHttpMessageConverter converter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
        return converter;
    }
	
	@Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        super.configureMessageConverters(converters);
        converters.add(responseBodyConverter());
    }
	
	//资源映射
	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
	    registry.addResourceHandler("/mystatic/**")
	            .addResourceLocations("classpath:/mystatic/");
	}
	
	/**
     * druid监控
     * @return
     */
    @Bean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean reg = new ServletRegistrationBean();
        reg.setServlet(new StatViewServlet());
        reg.addUrlMappings("/druid/*");
        reg.addInitParameter("loginUsername", druidUsername);
        reg.addInitParameter("loginPassword", druidPassword);
        return reg;
    }


    /**
     * druid监控过滤
     * @return
     */
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }    
}

DynamicDataSource.java

public class DynamicDataSource extends AbstractRoutingDataSource {
	
	private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();


	@Override
	protected Object determineCurrentLookupKey() {
		return getDataSourceType();
	}
	
	public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }


    public static String getDataSourceType() {
        return contextHolder.get();
    }


    public static void clearDataSourceType() {
        contextHolder.remove();
    }


}

DataSourceAspect.java

@Aspect
@Order(-1)//在@Transactional事务标注的方法内切换数据源需要设置
@Component
public class DataSourceAspect {
	
	private static final Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);


	@Before("@annotation(dataTest)")
	public void setDataSourceKey(JoinPoint point,TargetDataSource dataTest) throws Throwable{
		//根据连接点所属的类实例,动态切换数据源
		logger.info("切换数据源为:{}",dataTest.value());
		DynamicDataSource.setDataSourceType(dataTest.value());
	}
	
	@After("@annotation(dataTest)")
    public void clearDataSourceType(JoinPoint point,TargetDataSource dataTest) {  
		DynamicDataSource.clearDataSourceType();
    } 
	
}

DynamicDataSourceRegister.java

/**
 * 动态数据源注册
 * @author xiyan
 */
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {


	private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);


	// 数据源配置信息
	private PropertyValues dataSourcePropertyValues;
	// 默认数据源
	private DataSource defaultDataSource;
	// 动态数据源
	private Map<String, DataSource> dynamicDataSources = new HashMap<>();


	/**
	 * 加载多数据源配置
	 * Environment 是用来读取application.properties中的内容
	 */
	@Override
	public void setEnvironment(Environment env) {
		//读取application.properties中以jdbc.开头的属性
		RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "jdbc.");
		String dsPrefixs = propertyResolver.getProperty("datasources");
		for (String dsPrefix : dsPrefixs.split(",")) {// 多个数据源
			Map<String, Object> map = propertyResolver.getSubProperties(dsPrefix + ".");
			DataSource ds = initDataSource(map);
			// 设置默认数据源
			if ("ds".equals(dsPrefix)) {
				defaultDataSource = ds;
			} else {
				dynamicDataSources.put(dsPrefix, ds);
			}
			dataBinder(ds, env);
		}
	}


	/**
	 * 初始化数据源
	 * @param map
	 * @return
	 */
	public DataSource initDataSource(Map<String, Object> map) {
		String driverClassName = map.get("driverClassName").toString();
		String url = map.get("url").toString();
		String username = map.get("username").toString();
		String password = map.get("password").toString();
		DruidDataSource dataSource = new DruidDataSource();
		dataSource.setDriverClassName(driverClassName);
		dataSource.setUrl(url);
		dataSource.setUsername(username);
        dataSource.setPassword(password);
        //配置最大连接
        dataSource.setMaxActive(300);
        //配置初始连接
        dataSource.setInitialSize(20);
        //配置最小连接
        dataSource.setMinIdle(10);
        //连接等待超时时间
        dataSource.setMaxWait(60000);
        //间隔多久进行检测,关闭空闲连接
        dataSource.setTimeBetweenEvictionRunsMillis(60000);
        //一个连接最小生存时间
        dataSource.setMinEvictableIdleTimeMillis(300000);
        //连接等待超时时间 单位为毫秒 缺省启用公平锁,
        //并发效率会有所下降, 如果需要可以通过配置useUnfairLock属性为true使用非公平锁
        dataSource.setUseUnfairLock(true);
        //用来检测是否有效的sql
        dataSource.setValidationQuery("select 'x'");
        dataSource.setTestWhileIdle(true);
        //申请连接时执行validationQuery检测连接是否有效,配置为true会降低性能
        dataSource.setTestOnBorrow(false);
        //归还连接时执行validationQuery检测连接是否有效,配置为true会降低性能
        dataSource.setTestOnReturn(false);
        //打开PSCache,并指定每个连接的PSCache大小启用poolPreparedStatements后,
        //PreparedStatements 和CallableStatements 都会被缓存起来复用,
        //即相同逻辑的SQL可以复用一个游标,这样可以减少创建游标的数量。
        dataSource.setPoolPreparedStatements(true);
        dataSource.setMaxOpenPreparedStatements(20);
        try {
        	 //配置sql监控的filter
            dataSource.setFilters("stat,wall,log4j");
            dataSource.init();
        } catch (SQLException e) {
            throw new RuntimeException("druid datasource init fail");
        }
		/*String dsType = map.get("dsType").toString();
		Class<DataSource> dataSourceType;
		DataSource dataSource = null;
		try {
			dataSourceType = (Class<DataSource>) Class.forName(dsType);


			dataSource = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
					.username(username).password(password).type(dataSourceType).build();;
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}*/
		return dataSource;
	}


	/**
	 * 加载数据源配置信息
	 * @param dataSource
	 * @param env
	 */
	private void dataBinder(DataSource dataSource, Environment env) {
		RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
		dataBinder.setIgnoreNestedProperties(false);// false
		dataBinder.setIgnoreInvalidFields(false);// false
		dataBinder.setIgnoreUnknownFields(true);// true
		if (dataSourcePropertyValues == null) {
			Map<String, Object> values = new RelaxedPropertyResolver(env, "datasource").getSubProperties(".");
			dataSourcePropertyValues = new MutablePropertyValues(values);
		}
		dataBinder.bind(dataSourcePropertyValues);
	}


	/**
	 * 注册数据源been
	 */
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
		// 将主数据源添加到更多数据源中
		targetDataSources.put("dataSource", defaultDataSource);
		// 添加更多数据源
		targetDataSources.putAll(dynamicDataSources);


		// 创建DynamicDataSource
		GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
		beanDefinition.setBeanClass(DynamicDataSource.class);
		beanDefinition.setSynthetic(true);
		MutablePropertyValues mpv = beanDefinition.getPropertyValues();
		mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
		mpv.addPropertyValue("targetDataSources", targetDataSources);
		registry.registerBeanDefinition("dataSource", beanDefinition);


		logger.info("多数据源注册成功");
	}


}

TargetDataSource.java

/**
 * 在方法上使用,用于指定使用哪个数据源
 * @author xiyan
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    String value();
}

HelloApplication.java

@SpringBootApplication
@PropertySource(value={"classpath:config/path.properties"},ignoreResourceNotFound=true,encoding="utf-8")
@Import(DynamicDataSourceRegister.class)
public class HelloApplication {
	
	public static void main(String[] args) {
		SpringApplication.run(HelloApplication.class, args);
	}
	
}

StudentServiceImpl.java

//切换为第二个数据库
@TargetDataSource("ds1")
public Student getStudentById(Integer id) {
	return studentMapper.getStudentById(id);
}
注意:
         1.启动类注册动态数据源
         2.需要切换数据源的方法上添加注解
         3.在一个事务中切换数据源是无效的
我的测试方法:测试两个数据源,在数据源1中 执行插入操作, 然后操作数据源2再次进行插入操作, 操作数据源中的表结构 和sql 都是相同的,然后数据源1 执行了2次插入操作
后续再看一下,多数据源的事务问题。


猜你喜欢

转载自blog.csdn.net/qq_34021712/article/details/77542433