设为首页 收藏本站
查看: 555|回复: 0

[经验分享] springboot+druid+mybatis+mysql+多数据源

[复制链接]

尚未签到

发表于 2018-10-5 07:14:02 | 显示全部楼层 |阅读模式
一、项目依赖
  pom.xml中添加atomikos的springboot相关依赖:
  
        
  
            org.springframework.boot
  
            spring-boot-starter-jta-atomikos
  
        
  点进去会发现里面整合好了:transactions-jms、transactions-jta、transactions-jdbc、javax.transaction-api
二、把数据源的相关配置项单独提炼到一个application.yml中:
  注意:
  1.这回我们的spring.datasource.type 是com.alibaba.druid.pool.xa.DruidXADataSource;
  2.spring.jta.transaction-manager-id的值在你的电脑中是唯一的,这个详细请阅读官方文档;
  3.要把之前在application-dev.properties中的spring.datasource.*所有相关配置注释掉;
  完整的yml文件如下:
  spring:
  datasource:
  type: com.alibaba.druid.pool.xa.DruidXADataSource
  druid:
  systemDB:
  name: systemDB
  url: jdbc:mysql://localhost:3306/springboot-mybatis?useUnicode=true&characterEncoding=utf-8
  username: root
  password: root
  # 下面为连接池的补充设置,应用到上面所有数据源中
  # 初始化大小,最小,最大
  initialSize: 5
  minIdle: 5
  maxActive: 20
  # 配置获取连接等待超时的时间
  maxWait: 60000
  # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
  timeBetweenEvictionRunsMillis: 60000
  # 配置一个连接在池中最小生存的时间,单位是毫秒
  minEvictableIdleTimeMillis: 30
  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
  businessDB:
  name: businessDB
  url: jdbc:mysql://localhost:3306/springboot-mybatis2?useUnicode=true&characterEncoding=utf-8
  username: root
  password: root
  # 下面为连接池的补充设置,应用到上面所有数据源中
  # 初始化大小,最小,最大
  initialSize: 5
  minIdle: 5
  maxActive: 20
  # 配置获取连接等待超时的时间
  maxWait: 60000
  # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
  timeBetweenEvictionRunsMillis: 60000
  # 配置一个连接在池中最小生存的时间,单位是毫秒
  minEvictableIdleTimeMillis: 30
  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:

  log-dir:>  transaction-manager-id: txManager
三、在DruidConfig.java中实现多个数据源的注册;分布式事务管理器的注册;druid的注册;
  package com.zjt.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.sql.DataSource;
  import javax.transaction.UserTransaction;
  import java.util.Properties;
  /**
  * Druid配置
  *
  * @author zhaojiatao
  */
  @Configuration

  public>  @Bean(name = "systemDataSource")
  @Primary
  @Autowired
  public DataSource systemDataSource(Environment env) {
  AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
  Properties prop = build(env, "spring.datasource.druid.systemDB.");
  ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
  ds.setUniqueResourceName("systemDB");
  ds.setPoolSize(5);
  ds.setXaProperties(prop);
  return ds;
  }
  @Autowired
  @Bean(name = "businessDataSource")
  public AtomikosDataSourceBean businessDataSource(Environment env) {
  AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
  Properties prop = build(env, "spring.datasource.druid.businessDB.");
  ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
  ds.setUniqueResourceName("businessDB");
  ds.setPoolSize(5);
  ds.setXaProperties(prop);
  return ds;
  }
  /**
  * 注入事物管理器
  * @return
  */
  @Bean(name = "xatx")
  public JtaTransactionManager regTransactionManager () {
  UserTransactionManager userTransactionManager = new UserTransactionManager();
  UserTransaction userTransaction = new UserTransactionImp();
  return new JtaTransactionManager(userTransaction, userTransactionManager);
  }
  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;
  }
  @Bean
  public ServletRegistrationBean druidServlet() {
  ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
  //控制台管理用户,加入下面2行 进入druid后台就需要登录
  //servletRegistrationBean.addInitParameter("loginUsername", "admin");
  //servletRegistrationBean.addInitParameter("loginPassword", "admin");
  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();
  statFilter.setLogSlowSql(true); //slowSqlMillis用来配置SQL慢的标准,执行时间超过slowSqlMillis的就是慢。
  statFilter.setMergeSql(true); //SQL合并配置
  statFilter.setSlowSqlMillis(1000);//slowSqlMillis的缺省值为3000,也就是3秒。
  return statFilter;
  }
  @Bean
  public WallFilter wallFilter(){
  WallFilter wallFilter = new WallFilter();
  //允许执行多条SQL
  WallConfig config = new WallConfig();
  config.setMultiStatementAllow(true);
  wallFilter.setConfig(config);
  return wallFilter;
  }
  }
四、分别配置每个数据源对应的sqlSessionFactory,以及MapperScan扫描的包:
  MybatisDatasourceConfig.java
  package com.zjt.config;
  import com.zjt.util.MyMapper;
  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
  * @version 1.0, 2017/11/24
  * @description
  */
  @Configuration
  // 精确到 mapper 目录,以便跟其他数据源隔离
  @MapperScan(basePackages = "com.zjt.mapper", markerInterface = MyMapper.class, sqlSessionFactoryRef = "sqlSessionFactory")

  public>  @Autowired
  @Qualifier("systemDataSource")
  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:mapper/*.xml"));
  return factoryBean.getObject();
  }
  @Bean
  public SqlSessionTemplate sqlSessionTemplate() throws Exception {
  SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory()); // 使用上面配置的Factory
  return template;
  }
  //关于事务管理器,不管是JPA还是JDBC等都实现自接口 PlatformTransactionManager
  // 如果你添加的是 spring-boot-starter-jdbc 依赖,框架会默认注入 DataSourceTransactionManager 实例。
  //在Spring容器中,我们手工注解@Bean 将被优先加载,框架不会重新实例化其他的 PlatformTransactionManager 实现类。
  /*@Bean(name = "transactionManager")
  @Primary
  public DataSourceTransactionManager masterTransactionManager() {
  //MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源
  // 与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
  return new DataSourceTransactionManager(ds);
  }*/
  }
  MybatisDatasource2Config.java
  package com.zjt.config;
  import com.zjt.util.MyMapper;
  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
  * @version 1.0, 2017/11/24
  * @description
  */
  @Configuration
  // 精确到 mapper 目录,以便跟其他数据源隔离
  @MapperScan(basePackages = "com.zjt.mapper2", markerInterface = MyMapper.class, sqlSessionFactoryRef = "sqlSessionFactory2")

  public>  @Autowired
  @Qualifier("businessDataSource")
  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:mapper2/*.xml"));
  return factoryBean.getObject();
  }
  @Bean
  public SqlSessionTemplate sqlSessionTemplate2() throws Exception {
  SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory2()); // 使用上面配置的Factory
  return template;
  }
  //关于事务管理器,不管是JPA还是JDBC等都实现自接口 PlatformTransactionManager
  // 如果你添加的是 spring-boot-starter-jdbc 依赖,框架会默认注入 DataSourceTransactionManager 实例。
  //在Spring容器中,我们手工注解@Bean 将被优先加载,框架不会重新实例化其他的 PlatformTransactionManager 实现类。
  /*@Bean(name = "transactionManager2")
  @Primary
  public DataSourceTransactionManager masterTransactionManager() {
  //MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源
  // 与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
  return new DataSourceTransactionManager(ds);
  }*/
  }
四、由于我们本例中只使用一个事务管理器:xatx,故就不在使用TxAdviceInterceptor.java和TxAdvice2Interceptor.java中配置的事务管理器了;有需求的童鞋可以自己配置其他的事务管理器;
五、新建分布式业务测试接口JtaTestService.java和实现类JtaTestServiceImpl.java
  其实就是一个很简单的test01()方法,在该方法中我们分别先后调用classService.saveOrUpdateTClass(tClass);和teacherService.saveOrUpdateTeacher(teacher);
  实现先后操作两个数据源:然后我们可以自己debug跟踪事务的提交时机,此外,也可以在在两个方法全执行结束之后,手动制造一个运行时异常,来检查分布式事务是否全部回滚;
  注意:
  在实现类的方法中我使用的是:
  @Transactional(transactionManager = "xatx", propagation = Propagation.REQUIRED, rollbackFor = { java.lang.RuntimeException.class })
  从而指定了使用哪个事务管理器,事务隔离级别(一般都用我这个默认的),回滚的条件(一般可以使用Exception),这三个可以自己根据业务实际修改;
  package com.zjt.service3;
  import java.util.Map;
  public interface JtaTestService {
  public Map test01();
  }
  package com.zjt.service3.impl;
  import com.zjt.entity.TClass;
  import com.zjt.entity.Teacher;
  import com.zjt.service.TClassService;
  import com.zjt.service2.TeacherService;
  import com.zjt.service3.JtaTestService;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.beans.factory.annotation.Qualifier;
  import org.springframework.stereotype.Service;
  import org.springframework.transaction.annotation.Propagation;
  import org.springframework.transaction.annotation.Transactional;
  import java.util.LinkedHashMap;
  import java.util.Map;
  @Service("jtaTestServiceImpl")

  public>  @Autowired
  @Qualifier("teacherServiceImpl")
  private TeacherService teacherService;
  @Autowired
  @Qualifier("tclassServiceImpl")
  private TClassService tclassService;
  @Override
  @Transactional(transactionManager = "xatx", propagation = Propagation.REQUIRED, rollbackFor = { java.lang.RuntimeException.class })
  public Map test01() {
  LinkedHashMap resultMap=new LinkedHashMap();
  TClass tClass=new TClass();
  tClass.setName("8888");
  tclassService.saveOrUpdateTClass(tClass);
  Teacher teacher=new Teacher();
  teacher.setName("8888");
  teacherService.saveOrUpdateTeacher(teacher);
  System.out.println(1/0);
  resultMap.put("state","success");
  resultMap.put("message","分布式事务同步成功");
  return resultMap;
  }
  }
六、建立JtaTestContoller.java,接受一个来自前端的http请求,触发JtaTestService 的test01方法:
  package com.zjt.web;
  import com.zjt.service3.JtaTestService;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.beans.factory.annotation.Qualifier;
  import org.springframework.stereotype.Controller;
  import org.springframework.web.bind.annotation.RequestMapping;
  import org.springframework.web.bind.annotation.ResponseBody;
  import java.util.LinkedHashMap;
  import java.util.Map;
  @Controller
  @RequestMapping("/jtaTest")

  public>  @Autowired
  @Qualifier("jtaTestServiceImpl")
  private JtaTestService taTestService;
  @ResponseBody
  @RequestMapping("/test01")
  public Map test01(){
  LinkedHashMap resultMap=new LinkedHashMap();
  try {
  return taTestService.test01();
  }catch (Exception e){
  resultMap.put("state","fail");
  resultMap.put("message","分布式事务同步失败");
  return resultMap;
  }
  }
七、在test.ftl中增加一个按钮来测试;
  //分布式事务测试
  $("#JTATest").click(function(){
  $.ajax({
  type: "POST",
  url: "${basePath!}/jtaTest/test01",
  data: {}    ,
  async: false,
  error: function (request) {
  layer.alert("与服务器连接失败/(ㄒoㄒ)/~~");
  return false;
  },
  success: function (data) {
  if (data.state == 'fail') {
  layer.alert(data.message);
  return false;
  }else if(data.state == 'success'){
  layer.alert(data.message);
  }
  }
  });
  });

  
https://images2017.cnblogs.com/blog/1065783/201802/1065783-20180202220623328-1360076669.png抛出运行时异常,并被spring事务拦截器拦截,并捕获异常:


九、后记:
  本文源代码:https://github.com/zhaojiatao/springboot-zjt-chapter10-springboot-atomikos-mysql-mybatis-druid.git
  代码在tomcat和jetty环境下均可完成事务回滚;
  在事务回滚时可能报一个Transactional not active的警告,我google后,老外也说不出这个具体作用,大部分人认为这只是一个警告,可以忽略;大家谁懂得或者仔细翻阅源代码后懂得的,可以告诉我一下。谢谢
  }



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-612400-1-1.html 上篇帖子: Mysql 忘记root密码的完美解决方法 下篇帖子: MySQL/MariaDB的查询缓存
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表