Mysql主从+springboot+mybatis实践篇
注意: 代码连接在文章末尾。
本次主要是按照上一个主从配置过后的数据库进行和springboot的连接实践操作。
环境配置
环境配置过程分为三个步骤
- 相关依赖引入
<dependencies> <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>2.1.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.9</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.6</version> </dependency> <!-- google java lib --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>17.0</version> </dependency> </dependencies>
- application.yml配置文件配置。
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 你的主数据库密码 type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://主数据库IP地址:端口/test?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8 db: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 你的从数据库密码 type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://从数据库ip地址:端口/test?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8 mybatis: mapper-locations: classpath:mappers/*.xml logging: level: com: example: debug
数据库相关配置文件编写
配置数据源
@Configuration public class DruidConfig { /** * 主据源 * * @return 返回数据源对象 */ @Primary @Bean(name = "writeDataSource") @ConfigurationProperties(prefix = "spring.datasource") public DataSource dataSource() { return DataSourceBuilder.create().type(com.alibaba.druid.pool.DruidDataSource.class).build(); } /** * 从数据源 * * @return 返回数据源对象 */ @Bean(name = "readDataSource") @ConfigurationProperties(prefix = "spring.db") public DataSource readDataSource0() { return DataSourceBuilder.create().type(com.alibaba.druid.pool.DruidDataSource.class).build(); } }
配置数据源路由和事务等
@Configuration @EnableTransactionManagement(order = 2) @MapperScan(basePackages = {"com.example.demoms.mapper"}) public class MybatisConfig implements TransactionManagementConfigurer, ApplicationContextAware { private static ApplicationContext context; /** * 数据源路由代理 * 将数据源以map的形式放入数据源路由中,key分别为write和read, * 切面拦截指定方法的调用,判断是读还是写,将read或write放入ThreadLocale变量中 * RoutingDataSource重写的determineCurrentLookupKey决定要使用哪个数据源, * 然后AbstractRoutingDataSource中的determineTargetDataSource的方法在map变量中将数据源取出, * * @return */ @Bean public AbstractRoutingDataSource routingDataSourceProxy() { RoutingDataSource proxy = new RoutingDataSource(); Map<Object, Object> targetDataSources = Maps.newHashMap(); targetDataSources.put("write", context.getBean("writeDataSource", DataSource.class)); targetDataSources.put("read", context.getBean("readDataSource", DataSource.class)); proxy.setDefaultTargetDataSource(context.getBean("writeDataSource", DataSource.class)); proxy.setTargetDataSources(targetDataSources); return proxy; } @Bean @ConditionalOnMissingBean public SqlSessionFactoryBean sqlSessionFactory() throws IOException { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(routingDataSourceProxy()); bean.setVfs(SpringBootVFS.class); bean.setTypeAliasesPackage("com.example.demoms.entity"); // Resource configResource = new ClassPathResource("mybatis/mybatis.cfg.xml"); // bean.setConfigLocation(configResource); ResourcePatternResolver mapperResource = new PathMatchingResourcePatternResolver(); Resource[] resources = mapperResource.getResources("classpath:mappers/*.xml"); bean.setMapperLocations(resources); return bean; } @Override public PlatformTransactionManager annotationDrivenTransactionManager() { return new DataSourceTransactionManager(routingDataSourceProxy()); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if (context == null) { context = applicationContext; } } }
数据源路由类
public class RoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { String typeKey = DataSourceContextHolder.getJdbcType(); if (typeKey == null) { return "write"; }else { if(typeKey.equals("read")) return "read"; return "write"; } } }
定义切换数据源的注解
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DynamicDataSource { /** * 数据源key值 * * @return */ String value() default "write"; }
定义切面,拦截定义的注解,获取ThreadLocal中的值
@Aspect @Component @Order(1) public class DynamicDataSourceAspect { @Pointcut("@annotation(DynamicDataSource)") public void annotationPointcut() { } /** * 切换数据源 * * @param point 节点 */ @Before("annotationPointcut()") public void setDataSourceType(JoinPoint point) { MethodSignature methodSignature = (MethodSignature) point.getSignature(); Method method = methodSignature.getMethod(); DynamicDataSource annotation = method.getAnnotation(DynamicDataSource.class); String value = annotation.value(); System.out.println("切面中::"+value); if ("write".equals(value)){ DataSourceContextHolder.write(); }else { DataSourceContextHolder.read(); } } @After("annotationPointcut()") public void clear() { System.out.println("切面中::清除类型"); DataSourceContextHolder.clearDbType(); } }
DataSourceContextHolder类
public class DataSourceContextHolder { private static Logger logger = LoggerFactory.getLogger(DataSourceContextHolder.class); private final static ThreadLocal<String> local = new ThreadLocal<>(); public static ThreadLocal<String> getLocal() { return local; } public static void read() { logger.debug("切换至[读]数据源"); System.out.println("切换至[读]数据源"); local.set("read"); } public static void write() { logger.debug("切换至[写]数据源"); System.out.println("切换至[写]数据源"); local.set("write"); } public static String getJdbcType() { return local.get(); } /** * 清理链接类型 */ public static void clearDbType() { local.remove(); } }
注解的使用
在service层的方法上加入@DynamicDataSource("read")
,根据RoutingDataSource中的定义,如果不加注解,使用的是主数据源。
注意:以上方式在加入@Transactional注解或@LcnTransaction注解后并不能实现切换数据源,
测试
先配置好对应的mapper controller service 等。 在service的实现类中定义方法
@Override @DynamicDataSource("write") //这个代表的是从写数据源里边进行数据查询 public String getInfo() { return testMapper.getInfo(); }
结果如下:
网络异常,图片无法展示
|
整体项目姐构图:
网络异常,图片无法展示
|
代码链接:https://englishcode.lanzoul.com/iphJ0019fq7e