【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。

原文地址:https://www.cnblogs.com/VergiLyn/p/6624867.html