-
接SpringBoot简单多模块框架搭建(1)---先启动再说,我们搭建数据源,然后实现读写分离
-
大概就是在yml文件中配置durid,自定义注解,自定义数据源(配置durid),最后自定义个sqlSessionFactory(Mybatis)来使用数据源
-
先来一张项目结构图
-
先搞durid数据源,在base模块下的pom.xml中引入依赖(看上篇base模块的pom.xml),然后在resources的application.yml中添加durid数据源,代码如下:
server:
context-path: /boot
port: 8080
session:
timeout: 300
spring:
profiles:
active: dev
---
#使用---分隔,下面的配置与在application-dev.yml配置一样,没区别,只不过一个写在同个文件里,一个写在不同的文件里
spring:
profiles: dev
#数据源配置
datasource:
type: com.alibaba.druid.pool.DruidDataSource
write:
# driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/boot?useUnicode=true&characterEncoding=utf-8
username: root
password: root123
read:
# driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/boot?useUnicode=true&characterEncoding=utf-8
username: root
password: root123
#设置日志级别,后面用包名(package)确定生效的区域,默认是INFO
logging:
level.org.meichao: DEBUG
-
最后我们写一个durid配置类,来设置一些基础属性
package org.meichao.config;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DruidConfig {
/**
* 注册一个StatViewServlet
* @return
*/
@Bean
public ServletRegistrationBean druidStatViewServlet(){
ServletRegistrationBean srb = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
//添加初始化参数
//白名单:没有配置或者为空,则允许访问
srb.addInitParameter("allow","127.0.0.1");
//黑名单:(共同存在时,deny优先allow)提示:Sorry,you are not permitted to view this page.
//srb.addInitParameter("deny","");
srb.addInitParameter("loginUsername","admin");
srb.addInitParameter("loginPassword","admin");
//是否能够重置数据源,禁用HTML页面上的"Reset ALL"功能
srb.addInitParameter("resetEnable","false");
return srb;
}
/**
* 注册一个filterRegistrationBean
*/
@Bean
public FilterRegistrationBean druidStatFilter(){
FilterRegistrationBean frb = new FilterRegistrationBean(new WebStatFilter());
//添加过滤规则
frb.addUrlPatterns("/*");
//添加要忽略的格式信息
frb.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return frb;
}
}
-
配置好了durid数据源后,我们开始搞读写分离,先新键一个DataSourceType枚举来确定数据库的类型(读库还是写库)
package org.meichao.config.vo;
public enum DataSourceType {
read("read","从库"),write("write","主库");
private String type;
private String name;
DataSourceType(String type, String name) {
this.type = type;
this.name = name;
}
public String getType() {
return type;
}
public String getName() {
return name;
}
}
-
然后新建两个注解@Read和@Write
package org.meichao.config.annotation;
import java.lang.annotation.*;
/**
* Target指明了修饰的这个注解的使用范围,即被描述的注解可以用在哪里
* METHOD,TYPE分别表示方法和类
* Retention 指明修饰的注解的生存周期,即会保留到哪个阶段
* RUNTIME运行级别保留,编译后的class文件中存在,在jvm运行时保留,可以被反射调用
* Document 指明修饰的注解,可以被例如javadoc此类的工具文档化,只负责标记,没有成员取值
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Read {
String value() default "";
}
package org.meichao.config.annotation;
import java.lang.annotation.*;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Write {
String value() default "";
}
-
再然后新建一个DataSourceConfig类来定义数据源
package org.meichao.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
/**
* 定义数据源
*/
@Configuration
public class DataSourceConfig {
private Logger logger = LoggerFactory.getLogger(DataSourceConfig.class);
@Value("${spring.datasource.type}")
private Class<? extends DataSource> dataSourceType;
/**
* 配置写库
* DataSourceBuilder里有几个默认的数据源,
* 必须得加@Primary来确定优先执行,不然会报数据源找不到
* @Primary 当几个类都实现继承了同一个类,且都被@Configuration注释,用@Primary来确定优先执行哪个类
* 和@Qualifier作用差不多,只不过@qualifier是通过name属性决定执行哪个类
*/
@Bean(name = "writeDataSource",destroyMethod = "close")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.write")
public DataSource writeDataSource(){
logger.info("-----------writeDataSource init----------");
return DataSourceBuilder.create().type(dataSourceType).build();
}
/**
* 配置读库
*
*/
@Bean(name = "readDataSource",destroyMethod = "close")
@ConfigurationProperties(prefix = "spring.datasource.read")
public DataSource readDataSource(){
logger.info("-----------readDataSource init----------");
return DataSourceBuilder.create().type(dataSourceType).build();
}
}
-
再来新建一个数据源切换的处理类DataSourceContextHolder
package org.meichao.config;
import org.meichao.config.vo.DataSourceType;
public class DataSourceContextHolder {
private static final ThreadLocal<String> local = new ThreadLocal<>();
public static ThreadLocal<String> getLocal(){
return local;
}
/**
* 切换到读库
*/
public static void switch2Read(){
local.set(DataSourceType.read.getType());
}
/**
* 切换到写库
*/
public static void switch2Write(){
local.set(DataSourceType.write.getType());
}
/**
* 返回当前线程的此线程局部变量的副本中的值
*/
public static String getDataSourceType(){
return local.get();
}
}
-
然后再来构建一个数据源代理,满足我们的数据源动态切换
package org.meichao.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 构建数据源代理,使满足数据源的动态切换
*/
public class DataSourceProxy extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
//获取当前线程中设置的变量值,获取key来决定要使用哪个数据源
return DataSourceContextHolder.getLocal();
}
}
-
终于到我们最重要的环节了,怎么才能让数据源自动切换呢,新建一个AOP来拦截本地线程变量
package org.meichao.config.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.meichao.config.DataSourceContextHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* aop拦截设置本地线程变量
*/
@Aspect
@Component
public class DataSourceAop {
private static Logger logger = LoggerFactory.getLogger(DataSourceAop.class);
//本地线程
private static final ThreadLocal<String> local = new ThreadLocal<>();
/**
* 数据源切换为读库
*/
@Before("@annotation(org.meichao.config.annotation.Read)")
public void setReadDataSourceType(){
logger.info("dataSource切换到:Read");
//local.set(DataSourceType.read.getType());
DataSourceContextHolder.switch2Read();
}
/**
* 数据源切换为写库
*/
@Before("@annotation(org.meichao.config.annotation.Write)")
public void setWriteDataSourceType(){
logger.info("dataSource切换到:Write");
//local.set(DataSourceType.write.getType());
DataSourceContextHolder.switch2Write();
}
}
-
然后我们配置一下mybatis,就是自定义一个sqlSessionFactory来使用我们配置的数据源,不多BB,看代码
-
package org.meichao.config; import org.apache.ibatis.session.SqlSessionFactory; import org.meichao.config.vo.DataSourceType; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.annotation.MapperScan; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * mybatis配置 */ @Configuration @AutoConfigureAfter({DataSourceConfig.class}) @MapperScan(basePackages = {"org.meichao.*.dao"}) public class MybatisConfig { private static Logger logger = LoggerFactory.getLogger(MybatisConfig.class); @Bean public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSourceProxy") DataSourceProxy dataSourceProxy) throws Exception { logger.info("-----------sqlSessionFactory init----------"); SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSourceProxy); return sqlSessionFactoryBean.getObject(); } /** * @Qualifier 指定了哪个实现类才是我们需要加载的类,用name来确定 * @param writeDataSource * @param readDataSource * @return */ @Bean public DataSourceProxy dataSourceProxy(@Qualifier("writeDataSource") DataSource writeDataSource, @Qualifier("readDataSource") DataSource readDataSource){ logger.info("-----------dataSourceProxy init----------"); Map<Object,Object> targetDataSource = new HashMap<>(); targetDataSource.put(DataSourceType.write.getType(), writeDataSource); targetDataSource.put(DataSourceType.read.getType(), readDataSource); DataSourceProxy dataSourceProxy = new DataSourceProxy(); //设置默认的数据源 dataSourceProxy.setDefaultTargetDataSource(writeDataSource); //设置数据源 dataSourceProxy.setTargetDataSources(targetDataSource); return dataSourceProxy; } }
-
完事,那么问题来了,这样就行了吗,好吧,我们还需要在方法上加上注解,您看:
-
来一张配置成功后启动项目日志
-
再来一张使用切换数据库日志(里面其他打印日志后面会写文章,如果有的话)
-
完结撒花