springboot + mybatis配置多数据源示例

转:http://www.jb51.net/article/107223.htm

 

在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源。

代码结构:

简要原理:

1)DatabaseType列出所有的数据源的key---key

2)DatabaseContextHolder是一个线程安全的DatabaseType容器,并提供了向其中设置和获取DatabaseType的方法

3)DynamicDataSource继承AbstractRoutingDataSource并重写其中的方法determineCurrentLookupKey(),在该方法中使用DatabaseContextHolder获取当前线程的DatabaseType

4)MyBatisConfig中生成2个数据源DataSource的bean---value

5)MyBatisConfig中将1)和4)组成的key-value对写入到DynamicDataSource动态数据源的targetDataSources属性(当然,同时也会设置2个数据源其中的一个为DynamicDataSource的defaultTargetDataSource属性中)

6)将DynamicDataSource作为primary数据源注入到SqlSessionFactory的dataSource属性中去,并且该dataSource作为transactionManager的入参来构造DataSourceTransactionManager

7)使用的时候,在dao层或service层先使用DatabaseContextHolder设置将要使用的数据源key,然后再调用mapper层进行相应的操作,建议放在dao层去做(当然也可以使用spring aop+自定注解去做)

注意:在mapper层进行操作的时候,会先调用determineCurrentLookupKey()方法获取一个数据源(获取数据源:先根据设置去targetDataSources中去找,若没有,则选择defaultTargetDataSource),之后在进行数据库操作。

 1、假设有两个数据库,配置如下

application.properties

?
1
2
3
4
5
6
7
8
9
10
11
#the first datasource
jdbc.driverClassName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql: //xxx:3306/mytestdb?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
jdbc.username = root
jdbc.password = 123
 
#the second datasource
jdbc2.driverClassName = com.mysql.jdbc.Driver
jdbc2.url = jdbc:mysql: //xxx:3306/mytestdb2?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
jdbc2.username = root
jdbc2.password = 123

说明:在之前的配置的基础上,只增加了上述的第二个数据源。 

2、DatabaseType

?
1
2
3
4
5
6
7
8
9
10
11
package com.xxx.firstboot.common.datasource;
 
/**
  * 列出所有的数据源key(常用数据库名称来命名)
  * 注意:
  * 1)这里数据源与数据库是一对一的
  * 2)DatabaseType中的变量名称就是数据库的名称
  */
public enum DatabaseType {
   mytestdb,mytestdb2
}

作用:列举数据源的key。

3、DatabaseContextHolder

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.xxx.firstboot.common.datasource;
 
/**
  * 作用:
  * 1、保存一个线程安全的DatabaseType容器
  */
public class DatabaseContextHolder {
   private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<>();
   
   public static void setDatabaseType(DatabaseType type){
     contextHolder.set(type);
   }
   
   public static DatabaseType getDatabaseType(){
     return contextHolder.get();
   }
}

作用:构建一个DatabaseType容器,并提供了向其中设置和获取DatabaseType的方法

4、DynamicDataSource

?
1
2
3
4
5
6
7
8
9
10
11
12
package com.xxx.firstboot.common.datasource;
 
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 
/**
  * 动态数据源(需要继承AbstractRoutingDataSource)
  */
public class DynamicDataSource extends AbstractRoutingDataSource {
   protected Object determineCurrentLookupKey() {
     return DatabaseContextHolder.getDatabaseType();
   }
}

作用:使用DatabaseContextHolder获取当前线程的DatabaseType 

5、MyBatisConfig

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package com.xxx.firstboot.common;
 
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
 
import javax.sql.DataSource;
 
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
 
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.xxx.firstboot.common.datasource.DatabaseType;
import com.xxx.firstboot.common.datasource.DynamicDataSource;
 
/**
  * springboot集成mybatis的基本入口 1)创建数据源(如果采用的是默认的tomcat-jdbc数据源,则不需要)
  * 2)创建SqlSessionFactory 3)配置事务管理器,除非需要使用事务,否则不用配置
  */
@Configuration // 该注解类似于spring配置文件
@MapperScan (basePackages = "com.xxx.firstboot.mapper" )
public class MyBatisConfig {
 
   @Autowired
   private Environment env;
 
   /**
    * 创建数据源(数据源的名称:方法名可以取为XXXDataSource(),XXX为数据库名称,该名称也就是数据源的名称)
    */
   @Bean
   public DataSource myTestDbDataSource() throws Exception {
     Properties props = new Properties();
     props.put( "driverClassName" , env.getProperty( "jdbc.driverClassName" ));
     props.put( "url" , env.getProperty( "jdbc.url" ));
     props.put( "username" , env.getProperty( "jdbc.username" ));
     props.put( "password" , env.getProperty( "jdbc.password" ));
     return DruidDataSourceFactory.createDataSource(props);
   }
 
   @Bean
   public DataSource myTestDb2DataSource() throws Exception {
     Properties props = new Properties();
     props.put( "driverClassName" , env.getProperty( "jdbc2.driverClassName" ));
     props.put( "url" , env.getProperty( "jdbc2.url" ));
     props.put( "username" , env.getProperty( "jdbc2.username" ));
     props.put( "password" , env.getProperty( "jdbc2.password" ));
     return DruidDataSourceFactory.createDataSource(props);
   }
 
   /**
    * @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错
    * @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)
    */
   @Bean
   @Primary
   public DynamicDataSource dataSource( @Qualifier ( "myTestDbDataSource" ) DataSource myTestDbDataSource,
       @Qualifier ( "myTestDb2DataSource" ) DataSource myTestDb2DataSource) {
     Map<Object, Object> targetDataSources = new HashMap<>();
     targetDataSources.put(DatabaseType.mytestdb, myTestDbDataSource);
     targetDataSources.put(DatabaseType.mytestdb2, myTestDb2DataSource);
 
     DynamicDataSource dataSource = new DynamicDataSource();
     dataSource.setTargetDataSources(targetDataSources); // 该方法是AbstractRoutingDataSource的方法
     dataSource.setDefaultTargetDataSource(myTestDbDataSource); // 默认的datasource设置为myTestDbDataSource
 
     return dataSource;
   }
 
   /**
    * 根据数据源创建SqlSessionFactory
    */
   @Bean
   public SqlSessionFactory sqlSessionFactory(DynamicDataSource ds) throws Exception {
     SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
     fb.setDataSource(ds); // 指定数据源(这个必须有,否则报错)
     // 下边两句仅仅用于*.xml文件,如果整个持久层操作不需要使用到xml文件的话(只用注解就可以搞定),则不加
     fb.setTypeAliasesPackage(env.getProperty( "mybatis.typeAliasesPackage" )); // 指定基包
     fb.setMapperLocations(
         new PathMatchingResourcePatternResolver().getResources(env.getProperty( "mybatis.mapperLocations" ))); //
 
     return fb.getObject();
   }
 
   /**
    * 配置事务管理器
    */
   @Bean
   public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception {
     return new DataSourceTransactionManager(dataSource);
   }
 
}

作用:

  • 通过读取application.properties文件生成两个数据源(myTestDbDataSource、myTestDb2DataSource)
  • 使用以上生成的两个数据源构造动态数据源dataSource
    • @Primary:指定在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@Autowire注解报错(一般用于多数据源的情况下)
    • @Qualifier:指定名称的注入,当一个接口有多个实现类的时候使用(在本例中,有两个DataSource类型的实例,需要指定名称注入)
    • @Bean:生成的bean实例的名称是方法名(例如上边的@Qualifier注解中使用的名称是前边两个数据源的方法名,而这两个数据源也是使用@Bean注解进行注入的)
  • 通过动态数据源构造SqlSessionFactory和事务管理器(如果不需要事务,后者可以去掉)

 6、使用

ShopMapper:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.xxx.firstboot.mapper;
 
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
 
import com.xxx.firstboot.domain.Shop;
 
public interface ShopMapper {
 
   @Select ( "SELECT * FROM t_shop WHERE id = #{id}" )
   @Results (value = { @Result (id = true , column = "id" , property = "id" ),
             @Result (column = "shop_name" , property = "shopName" ) })
   public Shop getShop( @Param ( "id" ) int id);
 
}

ShopDao:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.xxx.firstboot.dao;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
 
import com.xxx.firstboot.common.datasource.DatabaseContextHolder;
import com.xxx.firstboot.common.datasource.DatabaseType;
import com.xxx.firstboot.domain.Shop;
import com.xxx.firstboot.mapper.ShopMapper;
 
@Repository
public class ShopDao {
   @Autowired
   private ShopMapper mapper;
 
   /**
    * 获取shop
    */
   public Shop getShop( int id) {
     DatabaseContextHolder.setDatabaseType(DatabaseType.mytestdb2);
     return mapper.getShop(id);
   }
}

注意:首先设置了数据源的key,然后调用mapper(在mapper中会首先根据该key从动态数据源中查询出相应的数据源,之后取出连接进行数据库操作)

ShopService:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.xxx.firstboot.service;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import com.xxx.firstboot.dao.ShopDao;
import com.xxx.firstboot.domain.Shop;
 
@Service
public class ShopService {
 
   @Autowired
   private ShopDao dao;
 
   public Shop getShop( int id) {
     return dao.getShop(id);
   }
}

ShopController:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.xxx.firstboot.web;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
 
import com.xxx.firstboot.domain.Shop;
import com.xxx.firstboot.service.ShopService;
 
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
 
@RestController
@RequestMapping ( "/shop" )
@Api ( "shopController相关api" )
public class ShopController {
 
   @Autowired
   private ShopService service;
 
   @ApiOperation ( "获取shop信息,测试多数据源" )
   @RequestMapping (value = "/getShop" , method = RequestMethod.GET)
   public Shop getShop( @RequestParam ( "id" ) int id) {
     return service.getShop(id);
   }
 
}

补:其实DatabaseContextHolder和DynamicDataSource完全可以合为一个类

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值