Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说SpringBoot多数据源(主从数据源)配置[通俗易懂],希望能够帮助你!!!。
学习springboot配置多数据源,先回顾一下springboot配置单数据源的方式
SpringBoot配置mybatis-mysql数据源
本次记录多数据源配置主要是通过druid + mybatis plus + aop的形式实现的,mybatis plus是一个很方便的数据库操作框架,自己也有实现多数据源的jar包,这里没有使用她封装的方法,主要是学习所以是自行实现了一遍简单的多数据源配置和动态切换数据源。
<!-- mybatis-plus多数据源配置jar -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
</dependency>
使用到的依赖
<dependencies>
<!-- druid数据连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!-- mybatis plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- spring-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- springboot配置依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
这里可以看到数据源配置属性路径并非spring.datasource, 这里主要是想通过学习spring-boot配置文件自动装配, 来获取配置并初始化数据源。
utmost:
# 主从数据源配置
datasource:
dynamic:
master:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.92.10:3306/utmost_01?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
userName: root
password: root
slave:
enabled: true # 是否启用从数据源
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.92.10:3306/utmost_02?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
userName: root
password: root
@Getter
@Setter
@ConfigurationProperties(prefix = UtmostDataSourceProperties.PREFIX)
public class UtmostDataSourceProperties {
/** * 配置前缀 */
public static final String PREFIX = "utmost.datasource.dynamic";
/** * master数据源配置前缀 */
public static final String MASTER_PREFIX = "utmost.datasource.dynamic.master";
/** * slave数据源配置前缀 */
public static final String SLAVE_PREFIX = "utmost.datasource.dynamic.slave";
/** * 设置默认数据库, 默认master */
public String primary = "master";
/** * 设置启用数据源, 默认true */
public boolean enabled = true;
/** * 主数据源 */
public SingleDataSourceProperty master;
/** * 从数据源 */
public SingleDataSourceProperty slave;
}
@Data
@Accessors(chain = true)
public class SingleDataSourceProperty {
/** * JDBC driver */
private String driverClassName;
/** * JDBC 数据库地址 */
private String url;
/** * JDBC 用户名 */
private String userName;
/** * JDBC 用户密码 */
private String password;
}
怎么通过UtmostDataSourceProperties类来获取属性, 主要通过@ConfigurationProperties注解实现, 前面依赖中引入了spring-boot-configuration-processor依赖, 在使用yml配置数据源时就会出现一定的提示作用。这是因为在打包编译的时候会生成一个spring-configuration-metadata.json文件,这里就不赘述了,先了解到这是springboot帮助我们生成的作用于提示的文件就可以了。
# mybatis-plus 配置
mybatis-plus:
configuration:
# 开启驼峰
map-underscore-to-camel-case: true
# 关闭一级缓存
local-cache-scope: statement
# 关闭二级缓存
cache-enabled: false
# sql xml文件映射路径
mapper-locations: classpath*:/mapper/*.xml
# MyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名
type-aliases-package: com.zy.utmost.entity
@Slf4j
@Configuration
@AllArgsConstructor
@EnableConfigurationProperties(UtmostDataSourceProperties.class)
@ConditionalOnProperty(prefix = UtmostDataSourceProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
public class UtmostDataSourceAutoConfiguration {
private final UtmostDataSourceProperties utmostDataSourceProperties;
/** * 主数据源(这里配置写的繁琐一点, 可根据个人喜好进行简化.) * * @return DataSource */
@Bean(name = "masterDataSource")
@ConfigurationProperties(UtmostDataSourceProperties.MASTER_PREFIX)
public DataSource masterDataSource() {
SingleDataSourceProperty master = utmostDataSourceProperties.master;
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setName(DataSourceConstants.MASTER);
druidDataSource.setDriverClassName(master.getDriverClassName());
druidDataSource.setUrl(master.getUrl());
druidDataSource.setUsername(master.getUserName());
druidDataSource.setPassword(master.getPassword());
return druidDataSource;
}
/** * 从 数据源(这里配置写的繁琐一点, 可根据个人喜好进行简化.) * * @return DataSource */
@Bean(name = "slaveDataSource")
@ConfigurationProperties(UtmostDataSourceProperties.SLAVE_PREFIX)
@ConditionalOnProperty(prefix = UtmostDataSourceProperties.SLAVE_PREFIX, name = "enabled", havingValue = "true")
public DataSource slaveDataSource() {
SingleDataSourceProperty slave = utmostDataSourceProperties.slave;
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setName(DataSourceConstants.SLAVE);
druidDataSource.setDriverClassName(slave.getDriverClassName());
druidDataSource.setUrl(slave.getUrl());
druidDataSource.setUsername(slave.getUserName());
druidDataSource.setPassword(slave.getPassword());
return druidDataSource;
}
/** * 动态数据源 * * @return DynamicDataSource */
@Bean
@Primary
public DynamicDataSource dynamicDataSource() {
Map<Object, Object> targetMap = new HashMap<>(2);
targetMap.put(DataSourceConstants.MASTER, masterDataSource());
targetMap.put(DataSourceConstants.SLAVE, slaveDataSource());
DynamicDataSource dynamicDataSource = new DynamicDataSource(masterDataSource(), targetMap);
log.info("动态数据源装配完成...");
return dynamicDataSource;
}
}
通过继承重写AbstractRoutingDataSource 数据源路由来实现数据源动态切换的功能.
public class DynamicDataSource extends AbstractRoutingDataSource {
/** * 使用线程切换数据源 */
private static ThreadLocal<String> contextHandler = new ThreadLocal<>();
/** * 数据源key集合 */
private static List<Object> dataSourceKeys = new ArrayList<>();
/** * 配置数据源 * * @param defaultDataSource 主数据源 * @param targetDataSourceMap 其他数据源集合 */
public DynamicDataSource(DataSource defaultDataSource, Map<Object, Object> targetDataSourceMap) {
super.setDefaultTargetDataSource(defaultDataSource);
super.setTargetDataSources(targetDataSourceMap);
super.afterPropertiesSet();
// 初始化所有数据源的key
addAllDataSourceKeys(targetDataSourceMap.keySet());
}
public static void setDataSourceKeys(String key) {
contextHandler.set(key);
}
public static ThreadLocal<String> getDataSourceKeys() {
return contextHandler;
}
public static void removeDataSourceKeys() {
contextHandler.remove();
}
public static boolean containsDataSourceKeys(String key) {
return dataSourceKeys.contains(key);
}
public static boolean addAllDataSourceKeys(Collection<? extends Object> keys) {
return dataSourceKeys.addAll(keys);
}
@Override
protected Object determineCurrentLookupKey() {
return contextHandler.get();
}
}
/** * 注解命名主要是为了好记所以直接使用了DataSource, 在使用时会发现有很 * 多类都是以DataSource命名, 使用时需要注意. * @author yanzy */
@Target({
ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String value() default "";
}
/** * 实现注解织入切换数据源 * * @author yanzy * @date 2021/6/10 23:38 * @since v1.0 */
@Aspect
@Order(-10)
@Component
public class DataSourceAspect {
private Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);
/** * - @within扫描类注解, @annotation扫描方法上注解 * 定义切面 */
@Pointcut("@annotation(com.zy.utmost.annotation.DataSource) || @within(com.zy.utmost.annotation.DataSource)")
public void annotationPointCut() {
}
/** * 前置事件 (方法前切换数据源) * @param point 织入点 * @param dataSource 注解实例 */
@Before("annotationPointCut() && @annotation(dataSource))")
public void beforeMethod(JoinPoint point, DataSource dataSource) {
switchDataSource(point, dataSource);
}
/** * 前置事件 (类前切换数据源) * @param point 织入点 * @param dataSource 注解实例 */
@Before("annotationPointCut() && @within(dataSource))")
public void beforeClass(JoinPoint point, DataSource dataSource) {
switchDataSource(point, dataSource);
}
private void switchDataSource(JoinPoint point,DataSource dataSource) {
String key = dataSource.value();
if (!DynamicDataSource.containsDataSourceKeys(key)) {
logger.debug("数据源切换失败: [{}] - 数据源不存在, 自动使用默认数据源.", key);
} else {
DynamicDataSource.setDataSourceKeys(key);
logger.debug("数据源切换成功: [{}] - 已切换至 - [{}] - 数据源.", point.getSignature().getName(), key);
}
}
/** * 后置增强 (方法/类执行完毕后,将数据源切回默认) * * @param point 织入点 */
@After("annotationPointCut()")
public void after(JoinPoint point) {
if (null != DynamicDataSource.getDataSourceKeys()) {
DynamicDataSource.removeDataSourceKeys();
logger.debug("数据源切换成功: 切换为主数据源.");
}
}
}
测试使用的crud类就不附上代码了, 直接使用spring-boot-test来给数据库插入数据, 验证一下数据源是否可以正常切换.
首先这里使用了两个数据库, 都创建了utmost这个数据库实例并创建有sys_user表.
@Service
public class SysUserServiceImpl implements SysUserService {
@Resource
private SysUserMapper sysUserMapper;
// 主库插入
@Override
public Integer insertUserMaster(SysUser sysUser) {
return sysUserMapper.insert(sysUser);
}
// 从库插入
@Override
@DataSource(value = DataSourceConstants.SLAVE)
public Integer insertUserSlave(SysUser sysUser) {
return sysUserMapper.insert(sysUser);
}
}
@SpringBootTest
public class SysUserTableTest {
@Autowired
private SysUserService sysUserService;
@Test
public void insertByMaster() {
SysUser sysUser = new SysUser();
sysUser.setUserName("admin");
sysUser.setLoginName("admin");
sysUser.setPassword("admin");
sysUserService.insertUserMaster(sysUser);
}
@Test
public void insertBySlave() {
SysUser sysUser = new SysUser();
sysUser.setUserName("admin");
sysUser.setLoginName("admin");
sysUser.setPassword("admin");
sysUserService.insertUserSlave(sysUser);
}
}
测试主库插入, 不标注注解时, 默认使用主库.
测试从库插入, 调用使用了切换数据源注解得到方法
@Service
@DataSource(value = DataSourceConstants.SLAVE)
public class SysUserServiceImpl implements SysUserService {
@Resource
private SysUserMapper sysUserMapper;
@Override
public Integer insertUserMaster(SysUser sysUser) {
return sysUserMapper.insert(sysUser);
}
@Override
public Integer insertUserSlave(SysUser sysUser) {
return sysUserMapper.insert(sysUser);
}
}
调用insertUserMaster, 验证数据源是否切换成功.
好啦~ 以上主要记录一下自己在学习过程中是如何配置多数据源的(严格来说代码中的写法应该是主从数据源 嘿嘿)。
一般主从数据源主要是为了做读写分离的,后面学习总结完读写分离操作后在进行分享记录啦~~
今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
上一篇
已是最后文章
下一篇
已是最新文章