Contents

springboot mybatis aop 读写分离

注解

  • 定义两个注解

     1
     2import java.lang.annotation.ElementType;
     3import java.lang.annotation.Retention;
     4import java.lang.annotation.RetentionPolicy;
     5import java.lang.annotation.Target;
     6
     7/**
     8 * 主库可读写
     9 */
    10@Target(ElementType.METHOD)
    11@Retention(RetentionPolicy.RUNTIME)
    12public @interface Master {
    13}
    
     1import java.lang.annotation.ElementType;
     2import java.lang.annotation.Retention;
     3import java.lang.annotation.RetentionPolicy;
     4import java.lang.annotation.Target;
     5
     6/**
     7 * 从库可读
     8 */
     9@Target(ElementType.METHOD)
    10@Retention(RetentionPolicy.RUNTIME)
    11public @interface Slave {
    12}
    

    数据库配置

     1
     2spring:
     3  datasource:
     4    master:
     5      jdbc-url: jdbc:mysql://192.168.1.22:3307/test
     6      username: root
     7      password: 123456
     8      driver-class-name: com.mysql.cj.jdbc.Driver
     9    slave1:
    10      jdbc-url: jdbc:mysql://192.168.1.22:3307/test
    11      username: root   # 只读账户
    12      password: 123456
    13      driver-class-name: com.mysql.cj.jdbc.Driver
    14    slave2:
    15      jdbc-url: jdbc:mysql://192.168.1.22:3307/test
    16      username: root   # 只读账户
    17      password: 123456
    18      driver-class-name: com.mysql.cj.jdbc.Driver
    19
    
    • 定义 数据源

      1
      2public enum  DBTypeEnum {
      3    MASTER, SLAVE1, SLAVE2;
      4}
      5
      

      mybatis 配置

      数据源配置

       1import org.springframework.beans.factory.annotation.Qualifier;
       2import org.springframework.boot.context.properties.ConfigurationProperties;
       3import org.springframework.boot.jdbc.DataSourceBuilder;
       4import org.springframework.context.annotation.Bean;
       5import org.springframework.context.annotation.Configuration;
       6
       7import javax.sql.DataSource;
       8import java.util.HashMap;
       9import java.util.Map;
      10
      11@Configuration
      12public class DataSourceConfig {
      13    @Bean
      14    @ConfigurationProperties("spring.datasource.master")
      15    public DataSource masterDataSource() {
      16        return DataSourceBuilder.create().build();
      17    }
      18
      19    @Bean
      20    @ConfigurationProperties("spring.datasource.slave1")
      21    public DataSource slave1DataSource() {
      22        return DataSourceBuilder.create().build();
      23    }
      24
      25    @Bean
      26    @ConfigurationProperties("spring.datasource.slave2")
      27    public DataSource slave2DataSource() {
      28        return DataSourceBuilder.create().build();
      29    }
      30
      31    @Bean
      32    public DataSource routingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
      33                                          @Qualifier("slave1DataSource") DataSource slave1DataSource,
      34                                          @Qualifier("slave2DataSource") DataSource slave2DataSource) {
      35        Map<Object, Object> targetDataSources = new HashMap<>();
      36        targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);
      37        targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource);
      38        targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource);
      39        RoutingDataSource routingDataSource = new RoutingDataSource();
      40        routingDataSource.setDefaultTargetDataSource(masterDataSource);
      41        routingDataSource.setTargetDataSources(targetDataSources);
      42        return routingDataSource;
      43    }
      44
      45}
      46
      

      mybatis 配置

       1
       2
       3import org.apache.ibatis.session.SqlSessionFactory;
       4import org.mybatis.spring.SqlSessionFactoryBean;
       5import org.springframework.context.annotation.Bean;
       6import org.springframework.context.annotation.Configuration;
       7import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
       8import org.springframework.jdbc.datasource.DataSourceTransactionManager;
       9import org.springframework.transaction.PlatformTransactionManager;
      10import org.springframework.transaction.annotation.EnableTransactionManagement;
      11
      12import javax.annotation.Resource;
      13import javax.sql.DataSource;
      14
      15@Configuration
      16@EnableTransactionManagement
      17public class MyBatisConfig {
      18
      19    @Resource(name = "routingDataSource")
      20    private DataSource routingDataSource;
      21
      22    @Bean
      23    public SqlSessionFactory sqlSessionFactory() throws Exception {
      24        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
      25        sqlSessionFactoryBean.setDataSource(routingDataSource);
      26//        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
      27        return sqlSessionFactoryBean.getObject();
      28    }
      29
      30    @Bean
      31    public PlatformTransactionManager platformTransactionManager() {
      32        return new DataSourceTransactionManager(routingDataSource);
      33    }
      34}
      35
      

      动态数据源

       1import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
       2import org.springframework.lang.Nullable;
       3
       4public class RoutingDataSource extends AbstractRoutingDataSource {
       5    @Nullable
       6    @Override
       7    protected Object determineCurrentLookupKey() {
       8        return DBContextHolder.get();
       9    }
      10}
      11
      

      数据源切换

       1
       2import java.util.concurrent.atomic.AtomicInteger;
       3
       4public class DBContextHolder {
       5    private static final ThreadLocal<DBTypeEnum> contextHolder = new ThreadLocal<>();
       6
       7    private static final AtomicInteger counter = new AtomicInteger(-1);
       8
       9    public static void set(DBTypeEnum dbType) {
      10        contextHolder.set(dbType);
      11    }
      12
      13    public static DBTypeEnum get() {
      14        return contextHolder.get();
      15    }
      16
      17    public static void remove() {
      18        contextHolder.remove();
      19    }
      20
      21    public static void master() {
      22        set(DBTypeEnum.MASTER);
      23        System.out.println("切换到master");
      24    }
      25
      26    public static void slave() {
      27        //  轮询
      28        int index = counter.getAndIncrement() % 2;
      29        if (counter.get() > 9999) {
      30            counter.set(-1);
      31        }
      32        if (index == 0) {
      33            set(DBTypeEnum.SLAVE1);
      34            System.out.println("切换到slave1");
      35        } else {
      36            set(DBTypeEnum.SLAVE2);
      37            System.out.println("切换到slave2");
      38        }
      39    }
      40
      41}
      42
      

      aop 切面

       1import org.aspectj.lang.annotation.After;
       2import org.aspectj.lang.annotation.Aspect;
       3import org.aspectj.lang.annotation.Before;
       4import org.springframework.stereotype.Component;
       5
       6@Aspect
       7@Component
       8public class DataSourceAop {
       9    @Before("@annotation(com.example.demo.annotation.Master)")
      10    public void master() {
      11        DBContextHolder.master();
      12    }
      13
      14    @Before("@annotation(com.example.demo.annotation.Slave)")
      15    public void slave() {
      16        DBContextHolder.slave();
      17    }
      18
      19    @After("@annotation(com.example.demo.annotation.Slave)||@annotation(com.example.demo.annotation.Master)")
      20    public void afterSwitchDB() {
      21        DBContextHolder.remove();
      22    }
      23}
      

      使用方式

       1
       2
       3@Service
       4public class UserServiceImpl implements UserService {
       5    @Autowired
       6    private UserMapper userMapper;
       7
       8    /**
       9     * 主库写入数据
      10     * @param user
      11     * @return
      12     */
      13    @Override
      14    @Master
      15    public int save(User user) {
      16        return userMapper.save(user);
      17    }
      18
      19    /**
      20     * 从库查询数据
      21     * @return
      22     */
      23    @Override
      24    @Slave
      25    public User get() {
      26        return userMapper.get();
      27    }
      28}
      29
      30
      
       1
       2public interface UserMapper {
       3
       4    @Insert("insert into t_test(name)values(#{name})")
       5    int save(User user);
       6
       7    @Select("select * from t_test where id>=(select floor(rand() * (select max(id) from t_test))) order by id limit 1")
       8    User get();
       9}
      10
      

      test

       1@SpringBootTest
       2class DemoApplicationTests {
       3    @Autowired
       4    private UserService userService;
       5
       6    @Test
       7    void testDb() throws InterruptedException {
       8        for (int i = 0; i < 10; i++) {
       9            User user = userService.get();
      10            System.out.println(user);
      11            TimeUnit.SECONDS.sleep(1);
      12            user.setName(UUID.randomUUID().toString());
      13            userService.save(user);
      14        }
      15    }
      16
      17}
      
       1切换到slave2
       2User{id=8, name='e87f057f-9dc9-4f59-8ff4-3a01682487d0'}
       3切换到master
       4切换到slave1
       5User{id=2, name='d786167b-d29f-49eb-bb1d-dc48f01f761c'}
       6切换到master
       7切换到slave2
       8User{id=6, name='c680a686-3ede-419d-bd66-de82966d5f96'}
       9切换到master
      10切换到slave1
      11User{id=5, name='07d2a8d4-3414-49a0-bc37-065a1688a55f'}
      12切换到master
      13切换到slave2
      14User{id=3, name='bac785d9-743d-48db-9b74-c2d51ba878fa'}
      15切换到master
      16切换到slave1
      17User{id=7, name='0c3866a0-208b-4530-8c8d-9502dea753ff'}
      18切换到master
      19切换到slave2
      20User{id=3, name='bac785d9-743d-48db-9b74-c2d51ba878fa'}
      21切换到master
      22切换到slave1
      23User{id=8, name='e87f057f-9dc9-4f59-8ff4-3a01682487d0'}
      24切换到master