栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

springBoot-mybatis+druid多数据源

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

springBoot-mybatis+druid多数据源

springBoot多数据源

AbstractRoutingDataSource引入依赖配置文件动态数据源配置实体类mapper层servicecontrollersql文件

配置多数据源有多种方式,这里使用的是AOP动态代理的方式进行动态切换的。所谓的动态数据源就是,多个数据源,可以进行切换,比如读写分离,读是一个数据源,写又是一个数据源。不过使用这种方式配置的动态数据源不适合动态扩展,当需要新增数据源的时候,只能重启服务进行重新配置,当然也可以使用nacos配置中新做动态刷新,我只是想到可以怎么做,但是具体没有做过。

AbstractRoutingDataSource

动态数据源最核心的就是AbstractRoutingDataSource 这个抽象类,我们只需要继承这个抽象类,然后重写 determineCurrentLookupKey()方法给他返回一个数据源名称就可以了,到时候他会根据你返回的数据源名称去resolvedDataSources这个map集合中找,resolvedDataSources这个map集合的key就是数据源名称,value对应的就是具体的数据库连接。

    @Nullable
    protected abstract Object determineCurrentLookupKey();

这个是父类的方法,我们在子类进行重写。到时候父类会调用determineTargetDataSource()方法确定目标数据源,然后就调用到子类的实现,这也是人家留给我们的一个扩展,让我能动态的确定数据源。

    protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = this.determineCurrentLookupKey();
        DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
        }

        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        } else {
            return dataSource;
        }
    }
引入依赖


		
			org.springframework.boot
			spring-boot-starter-web
		

		
			org.springframework.boot
			spring-boot-starter-test
			test
		

        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            1.3.1
        

		
			org.springframework.boot
			spring-boot-starter-aop
		

		
			mysql
			mysql-connector-java
            8.0.18
		
        
        
            com.alibaba
            druid-spring-boot-starter
            1.2.8
        
        
            com.github.pagehelper
            pagehelper
            5.3.0
        

    
配置文件
spring:
  datasource:
      master:
            driver-class-name: com.mysql.cj.jdbc.Driver
            type: com.alibaba.druid.pool.DruidDataSource
            url: jdbc:mysql://127.0.0.1:3306/db1?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&serverTimezone=UTC
            username: root
            password: root
      slave:
            driver-class-name: com.mysql.cj.jdbc.Driver
            type: com.alibaba.druid.pool.DruidDataSource
            url: jdbc:mysql://122.132.222.110:3306/db2?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&serverTimezone=GMT%2B8
            username: root
            password: root

mybatis:
    type-aliases-package: com.compass.mapper
    mapper-locations: classpath:mapper
    @Bean("master")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource master() {
         return new DruidDataSource();
    }

    
    @Bean("slave")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slave() {
      return new DruidDataSource();
    }

    
    @Bean("dynamicDataSource")
    public DataSource dynamicDataSource() {
        DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
        Map dataSourceMap = new HashMap<>(2);
        dataSourceMap.put("master", master());
        dataSourceMap.put("slave", slave());
        // 将 master 数据源作为默认指定的数据源
        dynamicRoutingDataSource.setDefaultTargetDataSource(master());
        // 将 master 和 slave 数据源作为指定的数据源
        dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);

        // 将数据源的 key 放到数据源上下文的 key 集合中,用于切换时判断数据源是否有效
        DynamicDataSourceContextHolder.dataSourceKeys.addAll(dataSourceMap.keySet());
        return dynamicRoutingDataSource;
    }

    

    
//    @Bean("sqlSessionFactoryBean")
    public SqlSessionFactoryBean sqlSessionFactoryBean() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        // 配置数据源,此处配置为关键配置,如果没有将 dynamicDataSource 作为数据源则不能实现切换
        sqlSessionFactoryBean.setDataSource(dynamicDataSource());

        // 配置分页插件
        setPagePlugins(sqlSessionFactoryBean);

        // 配置mapper映射路径
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper
    @Bean("sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

        sqlSessionFactoryBean.setDataSource(dynamicDataSource());

        // 配置分页插件
        setPagePlugins(sqlSessionFactoryBean);

        // 配置mapper映射路径
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setMapUnderscoreToCamelCase(true);
        sqlSessionFactoryBean.setConfiguration(configuration);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper
    public void  setPagePlugins(SqlSessionFactoryBean sqlSessionFactoryBean){
        Properties properties = new Properties();
        properties.setProperty("reasonable", "true");
        properties.setProperty("supportMethodsArguments", "true");
        properties.setProperty("pageSizeZero", "true");

        PageInterceptor interceptor = new PageInterceptor();
        interceptor.setProperties(properties);

        sqlSessionFactoryBean.setPlugins(new Interceptor[]{interceptor});
    }


    
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }
}

3.控制动态数据源的AOP切面

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;


@Aspect
// 该切面应当先于 @Transactional 执行
@Order(-1)
@Component
public class DynamicDataSourceAspect {
    
    // @annotation:用来拦截所有被某个注解修饰的方法
    @Before("@annotation(targetDataSource))")
    public void switchDataSource(JoinPoint point, TargetDataSource targetDataSource) {
        if (!DynamicDataSourceContextHolder.containDataSourceKey(targetDataSource.value())) {
            System.out.println("DataSource [{}] doesn't exist, use default DataSource [{}] " + targetDataSource.value());
        } else {
            // 切换数据源
            DynamicDataSourceContextHolder.setDataSourceKey(targetDataSource.value());
            System.out.println("Switch DataSource to [{}] in Method [{}] " +
                    DynamicDataSourceContextHolder.getDataSourceKey() + point.getSignature());
        }
    }

    
    @After("@annotation(targetDataSource))")
    public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource) {
        // 将数据源置为默认数据源
        DynamicDataSourceContextHolder.clearDataSourceKey();
        System.out.println("Restore DataSource to [{}] in Method [{}] " +
                DynamicDataSourceContextHolder.getDataSourceKey() + point.getSignature());
    }
}

4.动态数据源的封装

import java.util.ArrayList;
import java.util.List;


public class DynamicDataSourceContextHolder {

    
    private static final ThreadLocal contextHolder = new ThreadLocal() {

        
        @Override
        protected String initialValue() {
            return "master";
        }
    };


    
    public static List dataSourceKeys = new ArrayList<>();

    
    public static void setDataSourceKey(String key) {
        contextHolder.set(key);
    }

    
    public static String getDataSourceKey() {
        return contextHolder.get();
    }

    
    public static void clearDataSourceKey() {
        contextHolder.remove();
    }

    
    public static boolean containDataSourceKey(String key) {
        return dataSourceKeys.contains(key);
    }
}
 

5.继承AbstractRoutingDataSource返回确定的数据源

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }
}

基本上配置就完毕了。

大致思路,不是很详细,大家可以自行debug

    在 this.finishBeanFactoryInitialization(beanFactory);刷新工厂初始化单实例bean的时候就会初始化SqlSessionFactoryBean,在初始化SqlSessionFactoryBean的途中去初始化DataSource,给AbstractRoutingDataSource的targetDataSources属性赋值,将我们配置的数据源全部都给他。给AbstractRoutingDataSource的defaultTargetDataSource属性也赋值,指定我们默认的数据源。在AbstractRoutingDataSource的afterPropertiesSet()方法中将targetDataSources中的数据源赋值给resolvedDataSources,最后将defaultTargetDataSource的值赋值给resolvedDefaultDataSourceSqlSessionFactoryBean初始化完毕后将SqlSessionFactory传递进去初始化SqlSessionTemplate当我们执行service层中业务方法的时候,就会进入到我们的AOP切面的前置通知,将注解上标注的数据源名称赋值给contextHolder然后StatementHandler对象执行query()方法,在执行query()方法的过程中会去获取数据库连接,然后就来到了我们重写的 determineCurrentLookupKey()方法,我们之前早就在contextHolder中给设置好,所以我们直接从里面取就好了。在AbstractRoutingDataSource的determineTargetDataSource()方法中得到我们返回的数据源名称,然后去resolvedDataSources中去,resolvedDataSources是一个map集合,key就是我们的数据源名称,value就是真实的数据源最终我们的StatementHandler获取到数据源连接,就可以去执行具体的SQL了。

以上准备工作做好,我们就可以进行测试业务代码了。

实体类
public class User implements Serializable {

    private int id;
    private String name;
    private int age;
    private String email;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
    public User() {

    }
    public User(int id, String name, int age, String email) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + ''' +
                ", age=" + age +
                ", email='" + email + ''' +
                '}';
    }
}

mapper层

mapper接口

@Repository
@Mapper
public interface UserMapper {
    User findById(@Param("id") String id);
    Integer addUser(User user);
}

mapper接口的实现(xml)




    
        insert into t_user(name, age, email) values (#{name},#{age},#{email});
    

    
        select * from t_user where id=#{id};
    

service

service接口

public interface UserService {
    User findById(@Param("id") String id);
    Integer addUser(User user);
}

service实现

import com.compass.dds.TargetDataSource;
import com.compass.mapper.UserMapper;
import com.compass.pojo.User;

import com.compass.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserMapper userMapper;


    @TargetDataSource(value = "master") 
    @Override
    public User findById(String id) {

        return userMapper.findById(id);
    }

    @TargetDataSource(value = "slave") 
    @Override
    public Integer addUser(User user) {
        return userMapper.addUser(user);
    }
}

controller
import com.compass.pojo.User;

import com.compass.service.UserService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;


@RestController
public class HelloController {

    @Resource
    UserService userService;


    @GetMapping("/getUserById/{id}")
    public String getUserById(@PathVariable String id){
        return userService.findById(id).toString();
    }

    @GetMapping("/addUser")
    public String addUser(){
        User user = new User(0,"李四", 25, "lisi@qq.com");
        Integer insertCount = userService.addUser(user);
        return "插入的记录数:"+insertCount;
    }



}

我为了方便,没有使用post提交参数,直接用2个get请求也是一样的,两个请求都是不同的数据源,一个数据源是查询,一个数据源是读取。

主启动类就跟正常的启动类一样,这里我就不贴出来了

sql文件
CREATE TABLE `t_user` (
                          `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
                          `name` varchar(255) DEFAULT NULL,
                          `age` int(11) DEFAULT NULL,
                          `email` varchar(255) DEFAULT NULL,
                          PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
# 可以放在不同的数据库中,一个读库,一个写库

到这儿springBoot的多数据源配置句完毕了,感谢欣赏,可以配合MySQL主从复制也可以配合别的一些数据同步工具,来达到读写分离的目的,在下一篇文章中,我将给大家讲解,如果使用MySQL主从复制+多数据源完成读写分离。

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号