mysql讀寫分離—spring boot +mybatis

配置好mysql的主從複製後,讀寫分離需要在代碼層面實現,本實例採用spring boot集成mybatis的方式是實現,數據源連接池使用druid

1、加入maven依賴,配置application.yml文件

pom.xml依賴jar

  <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.0</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-spring-boot-starter</artifactId>
            <version>1.1.13</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

application.xml配置數源

spring:
  freemarker:
    suffix: .html
    request-context-attribute: request
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    druid:
      master:
        url: jdbc:mysql://192.168.2.131:3306/app_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: root
        # 從庫數據源
      slave:
          # 從數據源開關/默認關閉
        enabled: true
        url: jdbc:mysql://192.168.2.132:3306/app_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: root
      initialSize: 5
      # 最小連接池數量
      minIdle: 10
      # 最大連接池數量
      maxActive: 20
      # 配置獲取連接等待超時的時間
      maxWait: 60000
      # 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一個連接在池中最小生存的時間,單位是毫秒
      minEvictableIdleTimeMillis: 300000
      # 配置一個連接在池中最大生存的時間,單位是毫秒
      maxEvictableIdleTimeMillis: 900000
      # 配置檢測連接是否有效
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      webStatFilter:
        enabled: true
      statViewServlet:
        enabled: true
        # 設置白名單,不填則允許所有訪問
        allow:
        url-pattern: /monitor/druid/*
      filter:
        stat:
          enabled: true
          # 慢SQL記錄
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: true
        wall:
          config:
            multi-statement-allow: true
spring:
  freemarker:
    suffix: .html
    request-context-attribute: request
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    druid:
      master:
        url: jdbc:mysql://192.168.2.131:3306/app_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: root
        # 從庫數據源
      slave:
          # 從數據源開關/默認關閉
        enabled: true
        url: jdbc:mysql://192.168.2.132:3306/app_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: root
      initialSize: 5
      # 最小連接池數量
      minIdle: 10
      # 最大連接池數量
      maxActive: 20
      # 配置獲取連接等待超時的時間
      maxWait: 60000
      # 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一個連接在池中最小生存的時間,單位是毫秒
      minEvictableIdleTimeMillis: 300000
      # 配置一個連接在池中最大生存的時間,單位是毫秒
      maxEvictableIdleTimeMillis: 900000
      # 配置檢測連接是否有效
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      webStatFilter:
        enabled: true
      statViewServlet:
        enabled: true
        # 設置白名單,不填則允許所有訪問
        allow:
        url-pattern: /monitor/druid/*
      filter:
        stat:
          enabled: true
          # 慢SQL記錄
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: true
        wall:
          config:
            multi-statement-allow: true

2、java代碼實現

 (1)定義主庫從庫常量

public interface Constant {
    String MASTER="MASTER";
    String SLAVE="SLAVE";
}

(2)數據源切換處理,主庫從庫切換,主庫處理寫數據,從庫處理讀數據

public class DynamicDataSourceContextHolder
{
    public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);

    /**
     * 使用ThreadLocal維護變量,ThreadLocal爲每個使用該變量的線程提供獨立的變量副本,
     *  所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    /**
     * 設置數據源的變量
     */
    public static void setDataSourceType(String dsType)
    {
        log.info("切換到{}數據源", dsType);
        CONTEXT_HOLDER.set(dsType);
    }

    /**
     * 獲得數據源的變量
     */
    public static String getDataSourceType()
    {
        return CONTEXT_HOLDER.get();
    }

    /**
     * 清空數據源變量
     */
    public static void clearDataSourceType()
    {
        CONTEXT_HOLDER.remove();
    }
}

(3)動態數據源路由

public class DynamicDataSource extends AbstractRoutingDataSource
{
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
    {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey()
    {
        //如果有多個從庫,可以使用負載均衡算法,實現具體從庫的路由
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

(4)druid 配置屬性,實例化數據源

@Configuration
public class DruidProperties
{
    @Value("${spring.datasource.druid.initialSize}")
    private int initialSize;

    @Value("${spring.datasource.druid.minIdle}")
    private int minIdle;

    @Value("${spring.datasource.druid.maxActive}")
    private int maxActive;

    @Value("${spring.datasource.druid.maxWait}")
    private int maxWait;

    @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
    private int timeBetweenEvictionRunsMillis;

    @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
    private int minEvictableIdleTimeMillis;

    @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
    private int maxEvictableIdleTimeMillis;

    @Value("${spring.datasource.druid.validationQuery}")
    private String validationQuery;

    @Value("${spring.datasource.druid.testWhileIdle}")
    private boolean testWhileIdle;

    @Value("${spring.datasource.druid.testOnBorrow}")
    private boolean testOnBorrow;

    @Value("${spring.datasource.druid.testOnReturn}")
    private boolean testOnReturn;

    public DruidDataSource dataSource(DruidDataSource datasource)
    {
        /** 配置初始化大小、最小、最大 */
        datasource.setInitialSize(initialSize);
        datasource.setMaxActive(maxActive);
        datasource.setMinIdle(minIdle);
        /** 配置獲取連接等待超時的時間 */
        datasource.setMaxWait(maxWait);
        /** 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒 */
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        /** 配置一個連接在池中最小、最大生存的時間,單位是毫秒 */
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
        /**
         * 用來檢測連接是否有效的sql,要求是一個查詢語句,常用select 'x'。如果validationQuery爲null,testOnBorrow、testOnReturn、testWhileIdle都不會起作用。
         */
        datasource.setValidationQuery(validationQuery);
        /** 建議配置爲true,不影響性能,並且保證安全性。申請連接的時候檢測,如果空閒時間大於timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效。 */
        datasource.setTestWhileIdle(testWhileIdle);
        /** 申請連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能。 */
        datasource.setTestOnBorrow(testOnBorrow);
        /** 歸還連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能。 */
        datasource.setTestOnReturn(testOnReturn);
        return datasource;
    }
}
@Configuration
public class DruidConfig {

    @Autowired
    private DruidProperties druidProperties;

    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource  masterDataSource(){
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(dataSource);
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DataSource  slaveDataSource(){
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(dataSource);
    }

    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource()
    {
        Map<Object, Object> targetDataSources = new HashMap<>(2);
        targetDataSources.put(Constant.MASTER, masterDataSource());
        targetDataSources.put(Constant.SLAVE,slaveDataSource());
        return new DynamicDataSource(masterDataSource(), targetDataSources);
    }

    @Bean
    public SqlSessionFactory  sqlSessionFactory() throws  Exception{
        SqlSessionFactoryBean  sqlSessionFactoryBean=new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource());
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
        sqlSessionFactoryBean.setTypeAliasesPackage("cn.com.fcw.mysqlproxy");
        return sqlSessionFactoryBean.getObject();
    }

    /**
     * 開啓事務管理
     * @return
     */
    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager(){
        return  new DataSourceTransactionManager(dataSource());
    }
}

(5)利用AOP技術操作具體的讀寫分離業務

/**
 * 自定義多數據源切換註解
 * 
 * @author inquiry
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RwDataSource
{
    /**
     * 切換數據源名稱
     */
    String value() default Constant.MASTER;
}
@Aspect
@Order(1)
@Component
public class DataSourceAspect
{
    protected Logger logger = LoggerFactory.getLogger(getClass());

    @Pointcut("@annotation(cn.com.fcw.mysqlproxy.config.RwDataSource)")
    public void dsPointCut() { }

    @Around("dsPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable
    {
        MethodSignature signature = (MethodSignature) point.getSignature();

        Method method = signature.getMethod();

        RwDataSource dataSource = method.getAnnotation(RwDataSource.class);

        if (dataSource!=null)
        {
            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value());
        }

        try
        {
            return point.proceed();
        }
        finally
        {
            // 銷燬數據源 在執行方法之後
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }
}

(6)讀寫分離具體實現,將註解使用在service的具體方法上

@Service
public class UserServiceImpl {

    @Autowired
    private UserMapper userMapper;

    @RwDataSource
    public BaseBean insert(UserEntity userEntity){
        Integer res = userMapper.insert(userEntity);
        return BaseBean.getBaseBean(res,res);
    }
    @RwDataSource
    public BaseBean update(UserEntity userEntity){
        Integer res = userMapper.update(userEntity);
        return BaseBean.getBaseBean(res,res);
    }

    @RwDataSource
    public BaseBean delete(Integer id){
        Integer res = userMapper.delete(id);
        return BaseBean.getBaseBean(res,res);
    }

    @RwDataSource(Constant.SLAVE)
    public BaseBean list(){
        List<UserEntity> userEntities = userMapper.selectUserList();
        return BaseBean.getBaseBean(userEntities,0);
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章