【spring boot】SpringBoot初学(7)– 多数据源及其事务
前言
github: https://github.com/vergilyn/SpringBootDemo
代码位置:
参考:
Spring Boot Reference Guide , §77.2 Configure Two DataSources
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
代码结构说明:
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
@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); } }
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()}); } }
说明:
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; } }
@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。
Github: https://github.com/vergilyn
出处: http://www.cnblogs.com/VergiLyn/
备注: 一只凄惨的中华田园犬.