zoukankan      html  css  js  c++  java
  • SpringBoot集成Atomikos使用Oracle数据库mybatis、jta框架

    项目中需要数据库分布式事物的实现,于是采用了atumikos技术。

    因为生产上需要稳定,所以采用了springboot 1.5.9.RELEASE版本。

    本文代码gitlab下载地址: https://gitlab.com/atomikos/springBootMultDB-druidOracle.git

    新建一个springboot项目,然后依次添加本文所有代码。我的项目结构如下:

    1、pom.xml 代码

    <project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>springBootMultDB</groupId>
        <artifactId>springBootMultDB-druidOracle</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
        <name>springBootMultDB-druidOracle</name>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>1.5.9.RELEASE</version>
            <relativePath />
        </parent>
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>3.8.1</version>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <exclusions>
                    <!-- 排除spring boot默认使用的tomcat,使用jetty -->
                    <exclusion>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-tomcat</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jetty</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.2</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.12</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.1.10</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jta-atomikos</artifactId>
            </dependency>
            <dependency>
                <groupId>com.oracle</groupId>
                <artifactId>ojdbc6</artifactId>
                <version>11.2.0.4.0</version>
            </dependency>
        </dependencies>
    </project>

    这里的ojdbc6的jar注意一下,不是maven中央库能够下载到的,所以需要去oracle官方下载驱动jar包:

    https://www.oracle.com/technetwork/database/enterprise-edition/jdbc-112010-090769.html

    下载好驱动jar包之后,cmd命令窗口进入jar包所在目录,执行mvn安装jar到本地repository库,mvn命令:

    mvn install:install-file -Dfile=d:/java-jar/ojdbc6.jar -DgroupId=com.oracle -DartifactId=ojdbc6 -Dversion=11.2.0.4.0 -Dpackaging=jar

    把-Dfile的参数替换成你本地的ojdbc6的目录即可。

    -Dversion版本参数查看方法:打开ojdbc6.jar,进入META-INF,打开MANIFEST.MF,找到 Implementation-Version的值,我这里是: 11.2.0.4.0

    2、application.properties配置

    server.port=8082
    spring.application.name=springBootMultDB-druidOracle
    
    # Oracle 1
    spring.datasource.test1.url=jdbc:oracle:thin:@localhost:1521:orcl
    spring.datasource.test1.username=system
    spring.datasource.test1.password=ZHUwen12
    
    spring.datasource.test1.minPoolSize = 3
    spring.datasource.test1.maxPoolSize = 25
    spring.datasource.test1.maxLifetime = 20000
    spring.datasource.test1.borrowConnectionTimeout = 30
    spring.datasource.test1.loginTimeout = 30
    spring.datasource.test1.maintenanceInterval = 60
    spring.datasource.test1.maxIdleTime = 60
    spring.datasource.test1.testQuery = select 1 from dual
    
    # # Oracle 2
    spring.datasource.test2.url=jdbc:oracle:thin:@localhost:1521:orcl
    spring.datasource.test2.username=zhuwen
    spring.datasource.test2.password=ZHUwen12
    spring.datasource.test2.minPoolSize = 3
    spring.datasource.test2.maxPoolSize = 25
    #连接最大存活时间
    spring.datasource.test2.maxLifetime = 20000
    #获取连接失败重新获等待最大时间
    spring.datasource.test2.borrowConnectionTimeout = 30
    #登入超时
    spring.datasource.test2.loginTimeout = 30
    # 连接回收时间
    spring.datasource.test2.maintenanceInterval = 60
    #最大闲置时间,超过最小连接池连接的连接将将关闭
    spring.datasource.test2.maxIdleTime = 60
    spring.datasource.test2.testQuery = select 1 from dual

    这里的oracle两个库配置,只有username不一样,在oracle里面,两个user可视为两个数据库。

    3、App.java启动类

    package com.zhuguang;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.slf4j.LoggerFactory;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    
    import com.zhuguang.datasource.DBConfig1;
    import com.zhuguang.datasource.DBConfig2;
    
    /**
     * 非常感谢腾讯课堂烛光学院的lisa老师
     * @author zhuwen
     *
     */
    @SpringBootApplication
    @EnableConfigurationProperties(value = { DBConfig1.class, DBConfig2.class })
    @MapperScan(basePackages = { "com.zhuguang.mapper" })
    public class App {
        private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(App.class);
    
        public static void main(String[] args) {
            SpringApplication.run(App.class, args);
        }
    
    }

    4、两个Oracle数据库配置类

    package com.zhuguang.datasource;
    import org.springframework.boot.context.properties.ConfigurationProperties;                  
    @ConfigurationProperties(prefix = "spring.datasource.test1")
    public class DBConfig1 {
    
        private String url;
        private String username;
        private String password;
        private int minPoolSize;
        private int maxPoolSize;
        private int maxLifetime;
        private int borrowConnectionTimeout;
        private int loginTimeout;
        private int maintenanceInterval;
        private int maxIdleTime;
        private String testQuery;
    
        getter and setter...
    }

    第二个数据库配置类与DBConfig1一样,唯一不同的地方在于 prefix = "spring.datasource.test2",类名叫DBConfig2

    5、atomikos分布式数据源配置类
    TestMyBatisConfig1
    package com.zhuguang.datasource;
    import java.sql.SQLException;
    import javax.sql.DataSource;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.mybatis.spring.SqlSessionTemplate;
    import org.mybatis.spring.annotation.MapperScan;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import com.alibaba.druid.pool.xa.DruidXADataSource;
    @Configuration
    // basePackages 最好分开配置 如果放在同一个文件夹可能会报错
    @MapperScan(basePackages = "com.zhuguang.db1", sqlSessionTemplateRef = "testSqlSessionTemplate")
    public class TestMyBatisConfig1 {
        private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(TestMyBatisConfig1.class);
    
        // 配置数据源
        @Primary
        @Bean(name = "dataSource1")
        public DataSource testDataSource(DBConfig1 testConfig) throws SQLException {
            //Atomikos统一管理分布式事务
            AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
    
    //        Properties p = new Properties();
    //        p.setProperty ( "user" , testConfig.getUsername() );
    //        p.setProperty ( "password" , testConfig.getPassword() );
    //        p.setProperty ( "URL" , testConfig.getUrl() );
    //        xaDataSource.setXaProperties ( p );
    
            //用druidXADataSource方式或者上面的Properties方式都可以
            DruidXADataSource druidXADataSource = new DruidXADataSource();
            druidXADataSource.setUrl(testConfig.getUrl());
            druidXADataSource.setUsername(testConfig.getUsername());
            druidXADataSource.setPassword(testConfig.getPassword());
            
            xaDataSource.setUniqueResourceName("oracle1");
            xaDataSource.setXaDataSource(druidXADataSource);
            xaDataSource.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
            xaDataSource.setMaxLifetime(testConfig.getMaxLifetime());
            xaDataSource.setMinPoolSize(testConfig.getMinPoolSize());
            xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize());
            xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout());
            xaDataSource.setLoginTimeout(testConfig.getLoginTimeout());
            xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval());
            xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime());
            xaDataSource.setTestQuery(testConfig.getTestQuery());
            
            LOG.info("分布式事物dataSource1实例化成功");
            return xaDataSource;
        }
    
        @Primary
        @Bean(name = "testSqlSessionFactory")
        public SqlSessionFactory testSqlSessionFactory(@Qualifier("dataSource1") DataSource dataSource)
                throws Exception {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(dataSource);
            return bean.getObject();
        }
    
        @Primary
        @Bean(name = "testSqlSessionTemplate")
        public SqlSessionTemplate testSqlSessionTemplate(
                @Qualifier("testSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    }

     再建一个一模一样的TestMyBatisConfig1类,取名叫TestMyBatisConfig2,不同的地方是注解和bean命名:

    package com.zhuguang.datasource;
    import java.sql.SQLException;
    import java.util.Properties;
    import javax.sql.DataSource;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.mybatis.spring.SqlSessionTemplate;
    import org.mybatis.spring.annotation.MapperScan;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import com.alibaba.druid.pool.xa.DruidXADataSource;
    import com.atomikos.jdbc.AtomikosDataSourceBean;
    import oracle.jdbc.xa.client.OracleXADataSource;
    // basePackages 最好分开配置 如果放在同一个文件夹可能会报错
    @Configuration
    @MapperScan(basePackages = "com.zhuguang.db2", sqlSessionTemplateRef = "test2SqlSessionTemplate")
    public class TestMyBatisConfig2 {
        private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(TestMyBatisConfig2.class);
    
        // 配置数据源
        @Bean(name = "dataSource2")
        public DataSource testDataSource(DBConfig2 testConfig) throws SQLException {
            AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
            
    //        Properties p = new Properties();
    //        p.setProperty ( "user" , testConfig.getUsername() );
    //        p.setProperty ( "password" , testConfig.getPassword() );
    //        p.setProperty ( "URL" , testConfig.getUrl() );
    //        xaDataSource.setXaProperties ( p );
    
            //用druidXADataSource方式或者上面的Properties方式都可以
            DruidXADataSource druidXADataSource = new DruidXADataSource();
            druidXADataSource.setUrl(testConfig.getUrl());
            druidXADataSource.setUsername(testConfig.getUsername());
            druidXADataSource.setPassword(testConfig.getPassword());
            
            xaDataSource.setUniqueResourceName("oracle2");
            xaDataSource.setXaDataSource(druidXADataSource);
            xaDataSource.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
            xaDataSource.setMaxLifetime(testConfig.getMaxLifetime());
            xaDataSource.setMinPoolSize(testConfig.getMinPoolSize());
            xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize());
            xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout());
            xaDataSource.setLoginTimeout(testConfig.getLoginTimeout());
            xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval());
            xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime());
            xaDataSource.setTestQuery(testConfig.getTestQuery());
            
            LOG.info("分布式事物dataSource2实例化成功");
            return xaDataSource;
        }
    
        @Bean(name = "test2SqlSessionFactory")
        public SqlSessionFactory testSqlSessionFactory(@Qualifier("dataSource2") DataSource dataSource)
                throws Exception {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(dataSource);
            return bean.getObject();
        }
    
        @Bean(name = "test2SqlSessionTemplate")
        public SqlSessionTemplate testSqlSessionTemplate(
                @Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    }

    6、为两个数据库分别建立USERS表

    首先在两个Oracle数据库里都建立表:

    CREATE TABLE users  (
      name varchar2(20 BYTE),
      age NUMBER(*,0) 
    );

    并建立entity:

    package com.zhuguang.entity;
    public class Users {
        private String id;
        private String name;
        private Integer age;
        
        getter and setter..
    }

    7、为users建立mapper类

    package com.zhuguang.db1.dao;
    
    import java.util.List;
    import org.apache.ibatis.annotations.Delete;
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Param;
    import org.apache.ibatis.annotations.Select;
    import com.zhuguang.entity.Users;
    
    public interface DB1_UserMapper {
        @Select("SELECT * FROM USERS WHERE NAME = #{name}")
        Users findByName(@Param("name") String name);
    
        @Insert("INSERT INTO USERS(NAME, AGE) VALUES(#{name}, #{age})")
        int insert(@Param("name") String name, @Param("age") Integer age);
    
        @Delete("Delete from USERS")
        void deleteAll();
    
        @Select("select 'oracle1' as id,t.* from USERS t")
        List<Users> queryAll();
    }
    package com.zhuguang.db2.dao;
    
    import java.util.List;
    import org.apache.ibatis.annotations.Delete;
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Param;
    import org.apache.ibatis.annotations.Select;
    import com.zhuguang.entity.Users;
    
    //test1 DB
    public interface DB2_UserMapper {
        @Select("SELECT * FROM USERS WHERE NAME = #{name}")
        Users findByName(@Param("name") String name);
    
        @Insert("INSERT INTO USERS(NAME, AGE) VALUES(#{name}, #{age})")
        int insert(@Param("name") String name, @Param("age") Integer age);
    
        /**
         * 用于演示插入数据库异常的情况
         */
        @Insert("INSERT INTO not_exists_table_USERS(NAME, AGE) VALUES(#{name}, #{age})")
        int insertNotExistsTable(@Param("name") String name, @Param("age") Integer age);
        
        @Delete("Delete from USERS")
        void deleteAll();
    
        @Select("select 'oracle2' as id,t.* from USERS t")
        List<Users> queryAll();
    }

    8、建立Controller

    package com.zhuguang.controller;
    
    import java.util.Date;
    import java.util.List;
    import javax.annotation.Resource;
    import javax.sql.DataSource;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import com.zhuguang.db1.service.DB1_UserService;
    import com.zhuguang.db2.service.DB2_UserService;
    import com.zhuguang.service.IndexService;
    /**
     *  感谢腾讯课堂烛光学院lisa老师
     * @author zhuwen
     *
     */
    @RestController
    public class IndexController {
        private static Logger log = LoggerFactory.getLogger(IndexController.class);
    
        @Autowired
        private DB1_UserService userService1;
        @Autowired
        private DB2_UserService userService2;
        @Autowired
        private IndexService indexService;
        
        //想查看数据源,可以这么注解
        @Resource
        @Qualifier("dataSource1")
        private DataSource dataSource1;
    
        @RequestMapping("/insertDB1")
        public String insertTest001(String name, Integer age) {
            // userMapperTest01.insert(name, age);
            userService1.insertDB1(name, age);
            return "success insertDB1";
        }
    
        @RequestMapping("/insertDB2")
        public String insertTest002(String name, Integer age) {
            userService2.insertDB2(name, age);
            return "success insertDB2";
        }
    
        /**
         * atomikos效果:分布式事物。两个数据库都插入值
         * 
         * @param name
         * @param age
         * @return
         */
        @RequestMapping("/insertTwoDBs")
        public String insertTwoDBs(String name, Integer age) {
            indexService.insertTwoDBs(name, age);
            return "success insertTwoDBs";
        }
    
        /**
         * atomikos效果:分布式事物。 演示发生异常分布式事物回滚
         * 
         * @param name
         * @param age
         * @return
         */
        @RequestMapping("/insertTwoDBsWithError")
        public String insertTwoDBsWithError(String name, Integer age) {
            indexService.insertTwoDBsWithError(name, age);
            return "success insertTwoDBs";
        }
    
        /**
         * atomikos效果:分布式事物。 演示发生异常分布式事物回滚
         *  直接调用mapper方式
         * @param name
         * @param age
         * @return
         */
        @RequestMapping("/insertTwoDBsUseMapperWithError")
        public String insertTwoDBsUseMapperWithError(String name, Integer age) {
            indexService.insertTwoDBsUseMapperWithError(name, age);
            return "success insertTwoDBsUseMapperWithError";
        }
    
        /**
         * 获取两个数据库的所有数据
         * @return
         */
        @RequestMapping("/queryAll")
        public List queryAll() {
            List list = indexService.queryAll();
            list.add(new Date().toLocaleString()); //加上时间戳,方便postman观察结果
            return list;
        }
    
        /**
         *  删除两个数据库的所有数据
         * @return
         */
        @RequestMapping("/deleteAll")
        public String deleteAll() {
            indexService.deleteAll();
            return "success delete all";
        }
    
    }

    9、建立service

    package com.zhuguang.service;
    
    import java.util.ArrayList;
    import java.util.List;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.util.CollectionUtils;
    import com.zhuguang.db1.dao.DB1_UserMapper;
    import com.zhuguang.db1.service.DB1_UserService;
    import com.zhuguang.db2.dao.DB2_UserMapper;
    import com.zhuguang.db2.service.DB2_UserService;
    import com.zhuguang.entity.Users;
    
    @Service
    public class IndexService {
        @Autowired
        private DB1_UserMapper db1UserMapper;
        @Autowired
        private DB2_UserMapper db2UserMapper;
        @Autowired
        private DB1_UserService db1UserService;
        @Autowired
        private DB2_UserService db2UserService;
        /**
         *  atomikos效果:分布式事物。两个数据库都插入值
         * @return
         */
        @Transactional
        public void insertTwoDBs(String name, Integer age) {
            db1UserMapper.insert(name, age);
            db2UserMapper.insert(name, age);
        }
    
        @Transactional
        public void deleteAll() {
            db1UserMapper.deleteAll();
            //不同数据库。test1,test2
            //userService2.insertDB2(name, age);
            db2UserMapper.deleteAll();//test2   
    //        int i = 1 / 0;//
        }
    
        /**
         * atomikos效果:分布式事物。
          *  演示发生异常分布式事物回滚
          *  这里无论error 1、2、3,任何一处发生异常,分布式事物都会回滚
         */
        @Transactional //(rollbackFor = { Exception.class })
        public void insertTwoDBsWithError(String name, Integer age) {
            db1UserService.insert2DB1(name, age);
            db2UserService.insert2DB2(name, age);
            //int i = 1 / 0; // error 1
        }    
        
        /**
         * atomikos效果:分布式事物。
          *  演示发生异常分布式事物回滚
          *  这里无论error 1、2、3,任何一处发生异常,分布式事物都会回滚
          *  此方法效果等同于insertTwoDBsWithError
         */
        @Transactional
        public void insertTwoDBsUseMapperWithError(String name, Integer age) {
            db1UserMapper.insert(name, age);
            db2UserMapper.insert(name, age);
            db2UserMapper.insertNotExistsTable(name, age);
        }
    
        public List queryAll() {
            List all = new ArrayList();
            
            List<Users> list1 = db1UserService.queryAll();
            if(CollectionUtils.isEmpty(list1)) {
                all.add("db1 没有任何数据!");
            }else {
                all.addAll(list1);
            }
            
            List<Users> list2 = db2UserService.queryAll();
            if(CollectionUtils.isEmpty(list2)) {
                all.add("db2 没有任何数据!");
            }else {
                all.addAll(list2);
            }
            
            return all;
        }
        
    }

    10、使用postman验证

    主要验证:/insertTwoDBsUseMapperWithError 这个效果,是否任何一处DB产生错误,都会使分布式事物回滚。

     11、druid监控页面

    启动App.java之后,只要在浏览器里输入 http://localhost:8082/druid/index.html 就可以进入druid监控页面:

    但是由于这里使用的是atomikos分布式事物DataSource,不是原本的druidDataSource,所以这里几乎监控不到任何有价值的东西,基本作废。

    end.

  • 相关阅读:
    C语言 · 选择排序
    C语言 · 生物芯片
    C语言 · 猜灯谜
    C语言 · x的x次幂结果为10
    C语言 · LOG大侠
    C语言 · 成绩查询系统
    C语言 · C++中map的用法详解
    C语言 · 方程的解
    斯坦福大学公开课:监督学习应用,梯度下降
    斯坦福大学公开课:机器学习的动机与应用
  • 原文地址:https://www.cnblogs.com/zhuwenjoyce/p/10295360.html
Copyright © 2011-2022 走看看