zoukankan      html  css  js  c++  java
  • 【spring boot】SpringBoot初学(7)– 多数据源及其事务

    前言

      github: https://github.com/vergilyn/SpringBootDemo

      代码位置:

        image

      参考:

        Spring Boot Reference Guide , §77.2 Configure Two DataSources

        springboot + mybatis + 多数据源

        springboot + mybatis + 多数据源 (AOP实现)

    一、准备

      因为配置的是oracle、mysql、JdbcTemplate,所以需要各自的驱动jar和JdbcTemplate所需要的jar。

    <!-- spring jdbc支持 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    
    <!-- mysql驱动支持 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    
    <!-- oracle驱动支持。注:此驱动maven不一定下载得到。-->
    <dependency>
        <groupId>com.oracle</groupId>
        <artifactId>ojdbc6</artifactId>
        <version>11.1.0.7.0</version>
    </dependency>
    # DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
    spring.datasource.continue-on-error=false # Do not stop if an error occurs while initializing the database.
    spring.datasource.data= # Data (DML) script resource references.
    spring.datasource.data-username= # User of the database to execute DML scripts (if different).
    spring.datasource.data-password= # Password of the database to execute DML scripts (if different).
    spring.datasource.dbcp2.*= # Commons DBCP2 specific settings
    spring.datasource.driver-class-name= # Fully qualified name of the JDBC driver. Auto-detected based on the URL by default.
    spring.datasource.generate-unique-name=false # Generate a random datasource name.
    spring.datasource.hikari.*= # Hikari specific settings
    spring.datasource.initialize=true # Populate the database using 'data.sql'.
    spring.datasource.jmx-enabled=false # Enable JMX support (if provided by the underlying pool).
    spring.datasource.jndi-name= # JNDI location of the datasource. Class, url, username & password are ignored when set.
    spring.datasource.name=testdb # Name of the datasource.
    spring.datasource.password= # Login password of the database.
    spring.datasource.platform=all # Platform to use in the schema resource (schema-${platform}.sql).
    spring.datasource.schema= # Schema (DDL) script resource references.
    spring.datasource.schema-username= # User of the database to execute DDL scripts (if different).
    spring.datasource.schema-password= # Password of the database to execute DDL scripts (if different).
    spring.datasource.separator=; # Statement separator in SQL initialization scripts.
    spring.datasource.sql-script-encoding= # SQL scripts encoding.
    spring.datasource.tomcat.*= # Tomcat datasource specific settings
    spring.datasource.type= # Fully qualified name of the connection pool implementation to use. By default, it is auto-detected from the classpath.
    spring.datasource.url= # JDBC url of the database.
    spring.datasource.username=

      oralce、mysql中的表结构是一模一样的。表名:MYSQL_PARENT、ORACLE_PARENT

      字段:主键PARENT_ID,INT类型。非空字段PARENT_NAME,VARCHAR类型。

    二、完整demo

      image

      代码结构说明:

        1. mysql、oracle的包下,放各自DataSource的service、dao。

        2. 包config放置多数据源的配置,其中包括DataSource、DataSourceTransactionManager等。

     

    2.1 多数据源的DataSource、TransactionManager、JdbcTemplate配置
    @Configuration
    @PropertySource("classpath:config/dbMulti/db_multi.properties")
    public class DBmultiConfig {
        /* 此处 @Bean + @Qualifier("oracleDB") 等价于 @Bean("oracleDB").
         * 如果写成@Bean("oracleDB"),在idea中,之后的@Qualifier("oracleDB")会有error提示.但不影响代码的正确性.
         */
        @Bean
        @Qualifier("oracleDB")
        @Primary
        @ConfigurationProperties("oracle.datasource")
        public DataSource oracleDataSource() {
            return DataSourceBuilder.create().build();
        }
    
        @Bean(name = "oracleJT")
        public JdbcTemplate oracleJdbcTemplate(@Qualifier("oracleDB")DataSource dataSource){
            return new JdbcTemplate(dataSource);
        }
        @Bean("oracleTS")
        public DataSourceTransactionManager oracleTransactionManager(@Qualifier("oracleDB")DataSource dataSource){
            return new DataSourceTransactionManager(dataSource);
        }
    //  多数据源mybatis的sqlSession注入
    //  @Bean("oracleSS")
        public SqlSessionFactory oracleSqlSession(@Qualifier("oracleDB")DataSource dataSource) throws Exception {
            SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
            factoryBean.setDataSource(dataSource);
            //bean.setXX(...) 其余mybatis的设置
            /* 例如:以下是mybatis基于*.xml文件配置,如果整个持久层操作不需要使用到xml文件的话(只用注解就可以搞定). 则无需set.
            * factoryBean.setTypeAliasesPackage(env.getProperty("mybatis.typeAliasesPackage")); // 指定基包
             * factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapperLocations")));
            */
            return factoryBean.getObject();
        }
    
    
        @Bean
        @Qualifier("mysqlDB")
        @ConfigurationProperties("mysql.datasource")
        public DataSource mysqlDataSource() {
            return DataSourceBuilder.create().build();
        }
    
        @Bean(name = "mysqlJT")
        public JdbcTemplate mysqlJdbcTemplate(@Qualifier("mysqlDB") DataSource dataSource){
            return new JdbcTemplate(dataSource);
        }
    
        @Bean("mysqlTS")
        public DataSourceTransactionManager mysqlTransactionManager(@Qualifier("mysqlDB")DataSource dataSource){
            return new DataSourceTransactionManager(dataSource);
        }
    }
    #### spring boot配置多数据源及事务
    
    #### mysql数据源配置
    mysql.datasource.maximum-pool-size=30
    mysql.datasource.url=jdbc:mysql://localhost/VERGILYN
    mysql.datasource.username=root
    mysql.datasource.password=409839163
    mysql.datasource.max-total=30
    
    #### oracle数据源配置
    oracle.datasource.maximum-pool-size=30
    oracle.datasource.url=jdbc:oracle:thin:@127.0.0.1:1521:orcl
    oracle.datasource.username=vergilyn
    oracle.datasource.password=409839163
    oracle.datasource.max-total=30
    db_multi.properties

      @Primary:标明此DataSource是一个主数据源。(其实@Primary的作用并不是用来标明主数据源的,参考@Primary用法:在spring中常被忽视的注解 @Primary)

      oracleDataSource()、mysqlDataSource():分别用来配置注入mysql、oracle的DataSource。

      oracleTransactionManager()、mysqlTransactionManager():分别用来配置注入mysql、oracle的DataSource的事务管理,都交由spring统一管理。

      db_multi.properties:和datasource配置是一样的,只要保证最后的(.url .username)正确,前面的mysql.datasource、oracle.datasource可以随意命名的。

      (基于mybatis的SqlSession并没有测试过,但应该思路没错。)

    2.2 oracle、mysql的service&dao的实现
      2.2.1 service
    @Service
    public class OracleService {
        @Autowired
        private MysqlService mysqlService;
        @Autowired
        private OracleDao oracleDao;
    
        @Transactional(transactionManager = "oracleTS",propagation = Propagation.REQUIRED)
        public Parent getById(int parentId){
            return oracleDao.getById(parentId);
        }
    
        @Transactional(transactionManager = "oracleTS",rollbackFor = Exception.class)
        public void insert(Parent p) throws Exception{
            oracleDao.insert(p);
        }
    
        @Transactional(transactionManager = "oracleTS",rollbackFor = Exception.class)
        public void insertDBmulti(Parent parent,boolean isSameTransaction) throws Exception {
            oracleDao.insert(parent);
            if(isSameTransaction){
                mysqlService.insert(parent);
            }else{
                try {
                    mysqlService.insert(parent);
                }catch (Exception e){
                    e.printStackTrace();;
                }
            }
    
        }
        @Transactional(transactionManager = "oracleTS",propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
        public void insertREQUIRES_NEW(Parent parent) throws Exception {
            this.insert(parent);
        }
    }
     */
    @Service
    public class MysqlService {
        @Autowired
        private MysqlDao mysqlDao;
        @Autowired
        private OracleService oracleService;
    
        @Transactional(transactionManager = "mysqlTS",propagation = Propagation.REQUIRED)
        public Parent getById(int parentId){
            return mysqlDao.getById(parentId);
        }
    
        @Transactional(transactionManager = "mysqlTS",rollbackFor = Exception.class)
        public void insert(Parent p) throws Exception{
            mysqlDao.insert(p);
        }
    
        @Transactional(transactionManager = "mysqlTS",propagation = Propagation.REQUIRED)
        public void insertREQUIRES_NEW(Parent parent) throws Exception {
            oracleService.insertREQUIRES_NEW(parent);
            this.insert(parent);
        }
    }
    MysqlService.java
      2.2.2 dao
    @Repository
    public class OracleDao {
        @Resource(name = "oracleJT")
        private JdbcTemplate jdbcTemplate;
    
        public Parent getById(int parentId) {
            String sql = "select * from oracle_parent where id = ?";
    
            return jdbcTemplate.queryForObject(sql,new Object[]{parentId}
                    ,new BeanPropertyRowMapper<Parent>(Parent.class));
        }
    
        public void insert(Parent p) {
            String sql = "insert into oracle_parent(parent_id,parent_name) values(?,?)";
    
            jdbcTemplate.update(sql,new Object[]{p.getParentId(),p.getParentName()});
        }
    }
    
    @Repository("mysqlDao")
    public class MysqlDao {
    
    //  @Resource(name = "mysqlJT") 等价于 @Qualifier("mysqlJT") + @Autowired
    //  Resource是j2ee提供的,而Autowired、Qualifier是由spring提供的.为了降低与spring的耦合度,建议用Resource.
        @Qualifier("mysqlJT")
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        public Parent getById(int parentId) {
            String sql = "select * from mysql_parent where id = ?";
    
            return jdbcTemplate.queryForObject(sql,new Object[]{parentId}
                            ,new BeanPropertyRowMapper<Parent>(Parent.class));
        }
    
        public void insert(Parent p) {
            String sql = "insert into mysql_parent(parent_id,parent_name) values(?,?)";
    
            jdbcTemplate.update(sql,new Object[]{p.getParentId(),p.getParentName()});
        }
    }
    
    MysqlDao.java

    说明:

      1、@Resource与@Qualifier+@Autowired的区别

        @Resource是javax.annotation.Resource,即由j2ee提供。

        而@Qualifier+@Autowired是由spring提供。

        (网上的资料说,为了降低与spring的耦合度,建议使用@Resource。不理解为什么要降低与spring的耦合度...)

      2、以上dao的demo每个类中都要注入各自DataSource的JdbcTemplate(mybatis则要注入SqlSession)

        可借由extends各自继承一个父类的BaseDao,来简化代码。(也可用aop来实现)

      3、service也存在2的问题。要频繁的写@Transactional配置,指定各自的TrancactionManager。

        虽然,也可以借由aop来统一管理。但,个人并不建议。可能个人习惯事务要明确写明。

      2.2.3 SpringBootApplication、Junit测试、其他
    public class Parent implements Serializable{
        private int parentId;
        private String parentName;
        public Parent() {
        }
    
        public Parent(int parentId, String parentName) {
            this.parentId = parentId;
            this.parentName = parentName;
        }
    
        public int getParentId() {
            return parentId;
        }
    
        public void setParentId(int parentId) {
            this.parentId = parentId;
        }
    
        public String getParentName() {
            return parentName;
        }
    
        public void setParentName(String parentName) {
            this.parentName = parentName;
        }
    }
    
    Parent.java
    @SpringBootApplication
    public class DBmultiApplication{
        public static void main(String[] args) {
            SpringApplication app = new SpringApplication(DBmultiApplication.class);
            app.run(args);
        }
    }
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = DBmultiApplication.class)
    public class DBmultiApplicationTest {
        @Autowired
        private MysqlService mysqlService;
        @Autowired
        private OracleService oracleService;
        private final Parent parent = new Parent(100,"Vergilyn");
    
        @Test
        public void mysqlInsert() throws Exception {
            mysqlService.insert(parent);
            System.out.println("mysql insert end.");
        }
    
        @Test
        public void oracleInsert() throws Exception {
            oracleService.insert(parent);
            System.out.println("oracle insert end.");
        }
    
        @Test
        public void sameTransaction() throws Exception {
            oracleService.insertDBmulti(parent, true);
            System.out.println("sameTransaction() end.");
    
        }
    
        @Test
        public void diffTransaction() throws Exception {
            oracleService.insertDBmulti(parent, false);
            System.out.println("diffTransaction() end.");
        }
    
        /**
         * 在mysql中,先调用oracle,此oracle的事务是:REQUIRES_NEW。
          * 所以,即使在mysql方法中最后被回滚,oracle也被正确insert一行数据。
         */
        @Test
        public void insertREQUIRES_NEW() throws Exception {
            mysqlService.insertREQUIRES_NEW(parent);
            System.out.println("insertREQUIRES_NEW() end.");
        }
    }
    

    测试说明:

      1. mysqlInsert()、oracleInsert() 测试各自的DataSource、TransactionManager、JdbcTemplate是否配置正确。

      2. sameTransaction当insert的时候,oracle、mysql要么都成功,要么同时回滚。

        如demo,先在oracle插入一行数据,再在mysql中插入一行。如果,mysql中存在id=100的数据,导致mysql插入失败。那么产生RuntimeException,所以事务回滚。即oracle中不会被插入一行数据,此操作被回滚了。

        而如diffTransaction(),在oracle中把mysql抛出的异常吃掉了。所以,oracle不会被回滚。

      3.  如果insert之间互不影响,可有2种解决方式(自己知道的)

        i. 把异常吃掉,那么就不会回滚了。如oracleService.insertDBmulti(...)

        ii. 调用的其他事务另起一个新事务,如mysqlService.insertREQUIRES_NEW(...),其中oracleService.insertREQUIRES_NEW(...)的事务并声明为propagation = Propagation.REQUIRES_NEW。

  • 相关阅读:

    二分查找法
    LeetCode-Two Sum III
    LeetCode-Add and Search Word
    LeetCode-Longest Substring with At Least K Repeating Characters
    LeetCode-Rearrange String k Distance Apart
    LeetCode-Game of Life
    LeetCode-Walls and Gates
    LeetCode-Water and Jug Problem
    LeetCode-Inorder Successor in BST
  • 原文地址:https://www.cnblogs.com/VergiLyn/p/6624867.html
Copyright © 2011-2022 走看看