1.引言
代码已上传到Github,有兴趣的同学可以下载来看看:https://github.com/ylw-github/SpringBoot-MyCat-Demo
在前面几个章节,我们了解到了MySQL数据库的主从复制、使用MyCat实现读写分离:
回到代码的层面,本文主要开始讲解SpringBoot如何实现读写分离。
SpringBoot项目整合动态数据源(读写分离)思路:
- 配置多个数据源,根据业务需求访问不同的数据,指定对应的策略:增加,删除,修改操作访问对应数据,查询访问对应数据,不同数据库做好的数据一致性的处理。由于此方法相对易懂,简单,不做过多介绍。
- 动态切换数据源,根据配置的文件,业务动态切换访问的数据库:此方案通过Spring的AOP,AspactJ来实现动态织入,通过编程继承实现Spring中的AbstractRoutingDataSource,来实现数据库访问的动态切换,不仅可以方便扩展,不影响现有程序,而且对于此功能的增删也比较容易。
- 通过mycat来实现读写分离:使用mycat提供的读写分离功能,mycat连接多个数据库,数据源只需要连接mycat,对于开发人员而言他还是连接了一个数据库(实际是mysql的mycat中间件),而且也不需要根据不同业务来选择不同的库,这样就不会有多余的代码产生。
2.SpringBoot整合MySQL读写分离
在Spring 2.0.1中引入了AbstractRoutingDataSource
, 该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。
- 项目中需要集成多个数据源分别为读和写的数据源,绑定不同的key。
- 采用AOP技术进行拦截业务逻辑层方法,判断方法的前缀是否需要写或者读的操作。
- 如果方法的前缀是写的操作的时候,直接切换为写的数据源,反之切换为读的数据源。
- 也可以自己定义注解进行封装
2.1 集成步骤
1.新建maven项目SpringBoot-MyCat-Demo
:
2.添加maven依赖:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.23</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
3.application.yml
spring: datasource: ###可读数据源 select: jdbc-url: jdbc:mysql://192.168.162.131:8066/mycat_testdb driver-class-name: com.mysql.jdbc.Driver username: user password: user ####可写数据源 update: jdbc-url: jdbc:mysql://192.168.162.131:8066/mycat_testdb driver-class-name: com.mysql.jdbc.Driver username: root password: 123456 type: com.alibaba.druid.pool.DruidDataSource
4.DataSourceContextHolder
@Component @Lazy(false) public class DataSourceContextHolder { // 采用ThreadLocal 保存本地多数据源 private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); // 设置数据源类型 public static void setDbType(String dbType) { contextHolder.set(dbType); } public static String getDbType() { return contextHolder.get(); } public static void clearDbType() { contextHolder.remove(); } }
5.DataSourceConfig
@Configuration public class DataSourceConfig { // 创建可读数据源 @Bean(name = "selectDataSource") @ConfigurationProperties(prefix = "spring.datasource.select") // application.properteis中对应属性的前缀 public DataSource dataSource1() { return DataSourceBuilder.create().build(); } // 创建可写数据源 @Bean(name = "updateDataSource") @ConfigurationProperties(prefix = "spring.datasource.update") // application.properteis中对应属性的前缀 public DataSource dataSource2() { return DataSourceBuilder.create().build(); } }
6.DynamicDataSource(在Spring 2.0.1中引入了AbstractRoutingDataSource, 该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。)
@Component @Primary public class DynamicDataSource extends AbstractRoutingDataSource { @Autowired @Qualifier("selectDataSource") private DataSource selectDataSource; @Autowired @Qualifier("updateDataSource") private DataSource updateDataSource; /** * 这个是主要的方法,返回的是生效的数据源名称 */ @Override protected Object determineCurrentLookupKey() { System.out.println("DataSourceContextHolder:::" + DataSourceContextHolder.getDbType()); return DataSourceContextHolder.getDbType(); } /** * 配置数据源信息 */ @Override public void afterPropertiesSet() { Map<Object, Object> map = new HashMap<>(); map.put("selectDataSource", selectDataSource); map.put("updateDataSource", updateDataSource); setTargetDataSources(map); setDefaultTargetDataSource(updateDataSource); super.afterPropertiesSet(); } }
7.SwitchDataSourceAOP(通过AOP来选择数据源)
@Aspect @Component @Lazy(false) @Order(0) // Order设定AOP执行顺序 使之在数据库事务上先执行 public class SwitchDataSourceAOP { // 这里切到你的方法目录 @Before("execution(* com.ylw.springboot.mycat.service.*.*(..))") public void process(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); if (methodName.startsWith("get") || methodName.startsWith("count") || methodName.startsWith("find") || methodName.startsWith("list") || methodName.startsWith("select") || methodName.startsWith("check")) { // 读 DataSourceContextHolder.setDbType("selectDataSource"); } else { // 切换dataSource DataSourceContextHolder.setDbType("updateDataSource"); } } }
8.Controller层代码:
@RestController public class UserController { @Autowired private UserService userService; @RequestMapping("/findUser") public List<UserEntity> findUser() { return userService.findUser(); } @RequestMapping("/insertUser") public List<UserEntity> insertUser(String name, int age, String sex) { return userService.insertUser(UUID.randomUUID().toString(), name, age, sex); } }
9.Service层代码:
@Service public class UserService { @Autowired private UserMapper userMapper; public List<UserEntity> findUser() { return userMapper.findUser(); } public List<UserEntity> insertUser(String uuid,String name,int age,String sex) { return userMapper.insertUser(uuid,name,age,sex); } }
10.dao层代码:
public interface UserMapper { @Select("SELECT * FROM t_user ") public List<UserEntity> findUser(); @Select("insert into t_user values (#{uuid},#{name},#{age},#{sex}); ") public List<UserEntity> insertUser(@Param("uuid") String uuid,@Param("name") String name,@Param("age") int age,@Param("sex") String sex); }
3.启动项目测试
1.启动项项目
2.查询用户,浏览器输入:http://localhost:8080/findUser,可以看出把所有的用户都查询出来了。
3.插入用户,浏览器输入:http://localhost:8080/insertUser?name=xiaohong&age=18&sex=1,再次查询,http://localhost:8080/findUser,可以看到新增的xiaohong用户添加成功。
本文完!