你的位置:首页 > Java教程

[Java教程]第八章 springboot + mybatis + 多数据源


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

代码结构:

简要原理:

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()方法获取一个数据源,之后在进行数据库操作。(这个是猜的,没看源码)

 

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

application.properties

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

View Code

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

 

2、DatabaseType

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

View Code

作用:列举数据源的key。

 

3、DatabaseContextHolder

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

View Code

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

 

4、DynamicDataSource

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

View Code

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

 

5、MyBatisConfig

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

View Code

作用:

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

 

6、使用

ShopMapper:

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

View Code

ShopDao:

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

View Code

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

ShopService:

 

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

View Code

 

ShopController:

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

View Code