SpringBoot整合Mybatis多数据源Druid监控以及atomikos 多数据源事务处理 跨数据源连表查询

在上一篇博客中已经讲解了SpringBoot整合Mybatis单数据源以及Druid监控 ,如未了解请移步:SpringBoot整合Mybatis 以及Druid 数据监控


下面开始正文

一.所需依赖

       <!-- SpringBoot集成mybatis框架 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.spring.boot.starter.version}</version>
        </dependency>

        <!-- pagehelper 分页插件 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>${pagehelper.spring.boot.starter.version}</version>
        </dependency>

        <!--阿里数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <!--注意我这里是5版本须声明 version-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.driver.version}</version>
        </dependency>
        <!--分布式事务-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jta-atomikos</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

二.多数据源下SpringBoot的Yml配置文件

spring:
  datasource:
    type: com.alibaba.druid.pool.xa.DruidXADataSource
    druid:
      #第一个数据源
      one:
        name: oneDataSource
        url: jdbc:mysql://127.0.0.1:3306/one?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: root
        password: root
        # 下面为连接池的补充设置,应用到上面所有数据源中
        # 初始化大小,最小,最大
        initialSize: 5
        minIdle: 5
        maxActive: 20
        # 配置获取连接等待超时的时间
        maxWait: 60000
        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # 配置一个连接在池中最小生存的时间,单位是毫秒
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1
        validationQueryTimeout: 10000
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        # 打开PSCache,并且指定每个连接上PSCache的大小
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        filters: stat,wall
        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
        # 合并多个DruidDataSource的监控数据
        useGlobalDataSourceStat: true

      #第二个数据源
      two:
        name: twoDataSource
        url: jdbc:mysql://127.0.0.1:3306/two?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
        username: root
        password: root
        # 下面为连接池的补充设置,应用到上面所有数据源中
        # 初始化大小,最小,最大
        initialSize: 5
        minIdle: 5
        maxActive: 20
        # 配置获取连接等待超时的时间
        maxWait: 60000
        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # 配置一个连接在池中最小生存的时间,单位是毫秒
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1
        validationQueryTimeout: 10000
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        # 打开PSCache,并且指定每个连接上PSCache的大小
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        filters: stat,wall
        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
        # 合并多个DruidDataSource的监控数据
        useGlobalDataSourceStat: true

  #jta多数据源事务相关参数配置
  jta:
    #日志存放位置  默认在项目根目录下的transaction-logs下
    #log-dir: classpath:tx-logs
    transaction-manager-id: txManager

三.开始事务排除默认加载数据源

springboot 一启动就自会动寻找 数据源并设为单数据源,所以我们需要先将他排除掉

在主启动类中的@SpringBootApplication 添加(exclude = {DataSourceAutoConfiguration.class})

并开启事务管理器

/**
 * @author leilei
 * desc EnableTransactionManagement 开启事务管理器
 * exclude = {DataSourceAutoConfiguration.class 排除springboot 一启动就自动寻找 数据源并设为单数据源
 */
@EnableTransactionManagement
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class SpringbootMybatisAtomikosMoredatasourceApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootMybatisAtomikosMoredatasourceApplication.class, args);
    }

}

四.多数据源下Druid 监控配置

1.需要将单数据源的DataSource换为支持多数据源事务的AtomikosDataSourceBean ,并加载自定义的一二数据源Druid 连接信息

2.配置Druid 访问设置以及并发数 慢查询 过滤,,,,

整体代码如下

package com.leilei.config;

import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.transaction.UserTransaction;
import java.util.Properties;

/**
 * 多数据源和Druid配置
 * @author leilei
 */
@Configuration
public class DruidConfig {

    /**
     * 数据源1配置   使用AtomikosDataSourceBean 支持多数据源事务
     * @param env
     * @return  Primary 指定主库  (必须指定一个主库 否则会报错)
     */
    @Bean(name = "MybatisOneDataSource")
    @Primary
    @Autowired
    public AtomikosDataSourceBean oneDataSource(Environment env) {
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        Properties prop = build(env, "spring.datasource.druid.one.");
        ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        ds.setUniqueResourceName("oneDataSource");
        ds.setPoolSize(5);
        ds.setXaProperties(prop);
        return ds;
    }

    /**
     * 数据源2配置  使用AtomikosDataSourceBean 支持多数据源事务
     * @param env
     * @return
     */
    @Autowired
    @Bean(name = "MybatisTwoDataSource")
    public AtomikosDataSourceBean twoDataSource(Environment env) {
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        Properties prop = build(env, "spring.datasource.druid.two.");
        ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        ds.setUniqueResourceName("twoDataSource");
        ds.setPoolSize(5);
        ds.setXaProperties(prop);
        return ds;
    }

//    /**
//     * 注入事物管理器
//     * @return
//     */
//    @Bean(name = "leijta")
//    public JtaTransactionManager regTransactionManager () {
//        UserTransactionManager userTransactionManager = new UserTransactionManager();
//        UserTransaction userTransaction = new UserTransactionImp();
//        return new JtaTransactionManager(userTransaction, userTransactionManager);
//    }

    /**
     * 从配置文件中加载数据源信息
     * @param env
     * @param prefix
     * @return
     */
    private Properties build(Environment env, String prefix) {
        Properties prop = new Properties();
        prop.put("url", env.getProperty(prefix + "url"));
        prop.put("username", env.getProperty(prefix + "username"));
        prop.put("password", env.getProperty(prefix + "password"));
        prop.put("driverClassName", env.getProperty(prefix + "driverClassName", ""));
        prop.put("initialSize", env.getProperty(prefix + "initialSize", Integer.class));
        prop.put("maxActive", env.getProperty(prefix + "maxActive", Integer.class));
        prop.put("minIdle", env.getProperty(prefix + "minIdle", Integer.class));
        prop.put("maxWait", env.getProperty(prefix + "maxWait", Integer.class));
        prop.put("poolPreparedStatements", env.getProperty(prefix + "poolPreparedStatements", Boolean.class));
        prop.put("maxPoolPreparedStatementPerConnectionSize",
                env.getProperty(prefix + "maxPoolPreparedStatementPerConnectionSize", Integer.class));
        prop.put("maxPoolPreparedStatementPerConnectionSize",
                env.getProperty(prefix + "maxPoolPreparedStatementPerConnectionSize", Integer.class));
        prop.put("validationQuery", env.getProperty(prefix + "validationQuery"));
        prop.put("validationQueryTimeout", env.getProperty(prefix + "validationQueryTimeout", Integer.class));
        prop.put("testOnBorrow", env.getProperty(prefix + "testOnBorrow", Boolean.class));
        prop.put("testOnReturn", env.getProperty(prefix + "testOnReturn", Boolean.class));
        prop.put("testWhileIdle", env.getProperty(prefix + "testWhileIdle", Boolean.class));
        prop.put("timeBetweenEvictionRunsMillis",
                env.getProperty(prefix + "timeBetweenEvictionRunsMillis", Integer.class));
        prop.put("minEvictableIdleTimeMillis", env.getProperty(prefix + "minEvictableIdleTimeMillis", Integer.class));
        prop.put("filters", env.getProperty(prefix + "filters"));
        return prop;
    }

    /**
     * druid访问配置
     * @return
     */
    @Bean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        //控制台管理用户,加入下面2行 进入druid后台就需要登录
        servletRegistrationBean.addInitParameter("loginUsername", "leilei");
        servletRegistrationBean.addInitParameter("loginPassword", "123456");
        return servletRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        filterRegistrationBean.addInitParameter("profileEnable", "true");
        return filterRegistrationBean;
    }

    @Bean
    public StatFilter statFilter(){
        StatFilter statFilter = new StatFilter();
        //slowSqlMillis用来配置SQL慢的标准,执行时间超过slowSqlMillis的就是慢。
        statFilter.setLogSlowSql(true);
        //SQL合并配置
        statFilter.setMergeSql(true);
        //slowSqlMillis的缺省值为3000,也就是3秒。
        statFilter.setSlowSqlMillis(1000);
        return statFilter;
    }

    @Bean
    public WallFilter wallFilter(){
        WallFilter wallFilter = new WallFilter();
        //允许执行多条SQL
        WallConfig config = new WallConfig();
        config.setMultiStatementAllow(true);
        wallFilter.setConfig(config);
        return wallFilter;
    }
}

五.SqlSessionFactory 和 SqlSessionTemplate 配置

SqlSessionFactory是MyBatis的关键对象,它是个单个数据库映射关系经过编译后的内存镜像

SqlSession是MyBatis的关键对象,执行持久化操作的独享,类似于JDBC中的Connection.它是应用程序与持久层之间执行交互操作的一个单线程对象,也是MyBatis执行持久化操作的关键对象.SqlSession对象完全包含以数据库为背景的所有执行SQL操作的方法,它的底层封装了JDBC连接,可以用SqlSession实例来直接执行被映射的SQL语句。

第一个数据源的SqlSessionFactory SqlSessionTemplate 配置

package com.leilei.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import javax.sql.DataSource;

/**
 * @author : leilei
 * @date : 16:30 2020/3/1
 * @desc : Mybatis 多数据源  第一个数据源配置
 */
@Configuration
@MapperScan(basePackages = "com.leilei.mapper.one", sqlSessionFactoryRef = "sqlSessionFactory")
public class MybatisOneDataSource {
    @Autowired
    @Qualifier("MybatisOneDataSource")
    private DataSource ds;

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(ds);
        //指定mapper xml目录
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath:com/leilei/mapper/one/*.xml"));
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setMapUnderscoreToCamelCase(true);

        factoryBean.setConfiguration(configuration);
        return factoryBean.getObject();

    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate() throws Exception {
        // 使用上面配置的Factory
        SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory());
        return template;
    }
}

其实第二个或者以下的N个数据源也都一样,只需要扫描不同数据源对应的mapper层以及实体类映射层 注入不同的数据源即可

@Qualifier 是Spring中的注解 在有相同类型不同名称的多个bean 情况下 根据bean的名称选择注入

package com.leilei.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import javax.sql.DataSource;

/**
 * @author : leilei
 * @date : 16:31 2020/3/1
 * @desc : Mybvatis 第二个数据源配置 精确到 mapper 目录,以便跟其他数据源隔离
 */
@Configuration
@MapperScan(basePackages = "com.leilei.mapper.two", sqlSessionFactoryRef = "sqlSessionFactory2")
public class MybatisTwoDataSource {
    @Autowired
    @Qualifier("MybatisTwoDataSource")
    private DataSource ds;

    @Bean
    public SqlSessionFactory sqlSessionFactory2() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(ds);
        //指定mapper xml目录
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath:com/leilei/mapper/two/*.xml"));
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setMapUnderscoreToCamelCase(true);

        factoryBean.setConfiguration(configuration);
        return factoryBean.getObject();

    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate2() throws Exception {
        // 使用上面配置的Factory
        SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory2());
        return template;
    }
}

接下来将数据导入不同的数据库 我这里real_eseate 导入了数据库一 user 导入了数据库二 ,sql文件会在文章末尾项目源码中提供

导入后,使用Mybatis的代码生成器生成一波,可查看我的上一篇博客,其中讲解了Mybatis代码生成器的使用:SpringBoot整合Mybatis 以及Druid 数据监控

自定义异常响应

/**
 * @author : leilei
 * @date : 18:20 2020/3/1
 * @desc : 自定义异常响应
 */
@RestControllerAdvice
public class ExceptionHadler {

    @ExceptionHandler(value = Exception.class)
    public Map<String, Object> exceptionHandler(HttpServletRequest req, Exception e) {
        HashMap<String, Object> map = new HashMap<>(4);
        map.put("请求状态", "False");
        map.put("请求路径", req.getRequestURI());
        map.put("请求方式", req.getMethod());
        map.put("错误信息", e.getMessage());
        return map;
    }
}

查看Druid监控页面:

在这里插入图片描述
多个数据源的信息会排列展示出来,向下翻可继续查看
在这里插入图片描述
在这里插入图片描述

六.事务测试

使用一个数据源 测试
@PostMapping("insert")
    public Long insert(User user) {
        userService.insert(user);
        return user.getId();
    }

故意制造异常测试 是否插入成功

    @Override
    public int insert(User record) {
        //单个数据源二测试事务
        userMapper.insert(new User("test", "12345"));
        int a = 1 / 0;
        return userMapper.insert(record);
    }

未测试前数据库数据:

在这里插入图片描述
在这里插入图片描述

结果
多次更新数据库后,发现结果未变
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UVj4f1I4-1583125208473)(C:\Users\leilei\AppData\Roaming\Typora\typora-user-images\1583123136720.png)]

发现单个数据源事务是OK的

测试多数据源事务

未开始前数据
数据库一六条数据 数据库二 2条数据
在这里插入图片描述

制造异常,测试

 @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = { java.lang.RuntimeException.class })
    public Map<String, Object> insertToTwoDatebaseService(BodyVo bodyVo) {
        userMapper.insert(bodyVo.getUser());
        //故意制造异常
//        int a = 1 / 0;
        bodyVo.getRealEseate().setBuildTime(new Date());
        realEseateMapper.insert(bodyVo.getRealEseate());
        HashMap<String, Object> map = new HashMap<>();
        map.put("user", bodyVo.getUser());
        map.put("realReseate", bodyVo.getRealEseate());
        return map;
    }

在这里插入图片描述

如图,在数据源二保存后,在保存数据源一之前 发生了异常,那么我们此时查看数据库,看数据源二和一中的数据
在这里插入图片描述
多次刷新后,发现数据库并未改变,这说明 当数据源二保存后 数据源一保存时发生了异常 数据源二保存的数据已经做了回滚 !!!

多数据源事务问题 解决

此时发现,我们项目中多了一个文件夹
在这里插入图片描述
这是多数据源事务日志默认存放的位置 可在SpringBoot Yml配置文件中更改

  #jta多数据源事务相关参数配置
  jta:
    #日志存放位置  默认在项目根目录下的transaction-logs下
    #log-dir: classpath:tx-logs
    transaction-manager-id: txManager

查看日志
在这里插入图片描述

七.跨数据源连表查询

本文中,由于使用了两个数据库 且他们中间有的表存在着关联关系,那么为了查询出详细连表信息呢,就需要跨库连表查询

跨库连表查询其实也和 普通的连表查询一样,只是要指定具体数据库的某个表

不跨库连表查询:

select real.* ,u.* from real_reas real left join user u on real.user_id=u.id

跨库连表查询: one 库中的real_reas two库中的user

select real.* ,u.* from one.real_reas real left join two.user u on real.user_id=u.id

在连表查询的时候记得向本身对象中添加关联类型的字段 以及在映射结果图中添加关联对象属性

一对多 多对一 多对多 等,在本文的上一篇博客中已经很详细的写了

项目中跨库连表查询以及Pagehelper的使用:

/**
     * 跨数据源连表查询  controller
     * @param page
     * @param size
     * @return
     */
    @PostMapping("findall")
    public List<RealEseate> findall(Integer page, Integer size) {
        return atomikosService.findall(page, size);
    }
 
 
     /**
     * 多数据源连表查询   service
     */
    List<RealEseate> findall(Integer page, Integer size);
    
    
    @Override  //serviceimpl
    public List<RealEseate> findall(Integer page, Integer size) {
        PageHelper.startPage(page, size);
        PageHelper.orderBy("build_time desc");
        return realEseateMapper.findMoreDatasource();
    }
    
    
    /**
     * 跨数据源连表查询   mapper
     */
    List<RealEseate> findMoreDatasource();
    
    
    /**
     * mapper.xml
     */
  <select id="findMoreDatasource" resultMap="BaseResultMap">
        SELECT onere.id, onere.project_name, onere.address, 
        onere.house_type, onere.area, onere.build_time, onere.user_id ,
        twouser.id ,twouser.username ,twouser.card_id
        FROM one.real_eseate onere
        LEFT JOIN two.`user` twouser
        ON onere.user_id=twouser.id
    </select>
    
    /**映射结果图*/
    <resultMap id="BaseResultMap" type="com.leilei.entity.one.RealEseate">
        <id column="id" jdbcType="BIGINT" property="id"/>
        <result column="project_name" jdbcType="VARCHAR" property="projectName"/>
        <result column="address" jdbcType="VARCHAR" property="address"/>
        <result column="house_type" jdbcType="VARCHAR" property="houseType"/>
        <result column="area" jdbcType="INTEGER" property="area"/>
        <result column="build_time" jdbcType="TIMESTAMP" property="buildTime"/>
        <result column="user_id" jdbcType="BIGINT" property="userId"/>
        <!--关联对象结果映射-->
        <result column="id" jdbcType="BIGINT" property="user.id"/>
        <result column="username" jdbcType="VARCHAR" property="user.username"/>
        <result column="card_id" jdbcType="VARCHAR" property="user.cardId"/>
    </resultMap>

在这里插入图片描述

八.结语

本文到这里就结束了!!!!

项目源码:Springboot整合Mybatis 使用atomikos管理多数据源事务Druid 多数据监控

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值