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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM