開發環境:JDK1.8+SpringBoot2.1.4.RELEASE+Oracle
這裡我們假設要使用兩個數據源分別為:master和slave。
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.github.noraui</groupId> <artifactId>ojdbc7</artifactId> <version>12.1.0.2</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency> </dependencies>
server: port: 50000 ---spring: jpa: hibernate: ddlAuto: update openInView: true showSql: false databasePlatform: org.hibernate.dialect.Oracle10gDialect---# 第一個數據源 master: datasource: driverClassName: oracle.jdbc.driver.OracleDriver url: jdbc:oracle:thin:@localhost:1521/orcl username: t0 password: t0 type: com.zaxxer.hikari.HikariDataSource hikari: minimumIdle: 10 maximumPoolSize: 200 autoCommit: true idleTimeout: 30000 poolName: MasterDatabookHikariCP maxLifetime: 1800000 connectionTimeout: 30000 connectionTestQuery: SELECT 1 FROM DUAL# 第二個數據源 slave: datasource: driverClassName: oracle.jdbc.driver.OracleDriver url: jdbc:oracle:thin:@localhost:1521/orcl username: t1 password: t1 type: com.zaxxer.hikari.HikariDataSource hikari: minimumIdle: 10 maximumPoolSize: 200 autoCommit: true idleTimeout: 30000 poolName: SlaveDatabookHikariCP maxLifetime: 1800000 connectionTimeout: 30000 connectionTestQuery: SELECT 1 FROM DUAL ---# mybatis 配置,分表對應到不同的包中 master: mybatis: config-location: classpath:/MyBatis-conf.xml type-aliases-package: com.pack.domain #master數據源對應的包 mapper-locations: - classpath:/com/pack/mapper/oracle/*.xml #master數據源對應mapper文件slave: mybatis: config-location: classpath:/MyBatis-conf.xml type-aliases-package: com.pack.slave.domain #slave數據源對應的包 mapper-locations: - classpath:/com/pack/slave/mapper/oracle/*.xml #slave數據源對應mapper文件---# jpa相關的配置master: jpa: repos: com.pack.base.repository #master數據源對應的包配置 domain: com.pack.domain #master對應的實體包slave: jpa: repos: com.pack.slave.repository #salve數據源對應的包配置 domain: com.pack.slave.domain #slave對應的實體包
以上就是兩個數據源對應相關的配置了,大家注意看裡面的注釋。接下來我們看mabatis和jpa對應的類相關的配置了,都是固定的配置。
BaseProperties類
public class BaseDataSourceProperties implements BeanClassLoaderAware, InitializingBean { private ClassLoader classLoader; /** * Name of the datasource. Default to "testdb" when using an embedded database. */ private String name; /** * Whether to generate a random datasource name. */ private boolean generateUniqueName; /** * Fully qualified name of the connection pool implementation to use. By default, it * is auto-detected from the classpath. */ private Class<? extends DataSource> type; /** * Fully qualified name of the JDBC driver. Auto-detected based on the URL by default. */ private String driverClassName; /** * JDBC URL of the database. */ private String url; /** * Login username of the database. */ private String username; /** * Login password of the database. */ private String password; /** * JNDI location of the datasource. Class, url, username & password are ignored when * set. */ private String jndiName; /** * Initialize the datasource with available DDL and DML scripts. */ private DataSourceInitializationMode initializationMode = DataSourceInitializationMode.EMBEDDED; /** * Platform to use in the DDL or DML scripts (such as schema-${platform}.sql or * data-${platform}.sql). */ private String platform = "all"; /** * Schema (DDL) script resource references. */ private List<String> schema; /** * Username of the database to execute DDL scripts (if different). */ private String schemaUsername; /** * Password of the database to execute DDL scripts (if different). */ private String schemaPassword; /** * Data (DML) script resource references. */ private List<String> data; /** * Username of the database to execute DML scripts (if different). */ private String dataUsername; /** * Password of the database to execute DML scripts (if different). */ private String dataPassword; /** * Whether to stop if an error occurs while initializing the database. */ private boolean continueOnError = false; /** * Statement separator in SQL initialization scripts. */ private String separator = ";"; /** * SQL scripts encoding. */ private Charset sqlScriptEncoding; private EmbeddedDatabaseConnection embeddedDatabaseConnection = EmbeddedDatabaseConnection.NONE; private Xa xa = new Xa(); private String uniqueName; @Override public void setBeanClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } @Override public void afterPropertiesSet() throws Exception { this.embeddedDatabaseConnection = EmbeddedDatabaseConnection .get(this.classLoader); } /** * Initialize a {@link DataSourceBuilder} with the state of this instance. * @return a {@link DataSourceBuilder} initialized with the customizations defined on * this instance */ public DataSourceBuilder<?> initializeDataSourceBuilder() { return DataSourceBuilder.create(getClassLoader()).type(getType()) .driverClassName(determineDriverClassName()).url(determineUrl()) .username(determineUsername()).password(determinePassword()); } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public boolean isGenerateUniqueName() { return this.generateUniqueName; } public void setGenerateUniqueName(boolean generateUniqueName) { this.generateUniqueName = generateUniqueName; } public Class<? extends DataSource> getType() { return this.type; } public void setType(Class<? extends DataSource> type) { this.type = type; } /** * Return the configured driver or {@code null} if none was configured. * @return the configured driver * @see #determineDriverClassName() */ public String getDriverClassName() { return this.driverClassName; } public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; } /** * Determine the driver to use based on this configuration and the environment. * @return the driver to use * @since 1.4.0 */ public String determineDriverClassName() { if (StringUtils.hasText(this.driverClassName)) { Assert.state(driverClassIsLoadable(), () -> "Cannot load driver class: " + this.driverClassName); return this.driverClassName; } String driverClassName = null; if (StringUtils.hasText(this.url)) { driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName(); } if (!StringUtils.hasText(driverClassName)) { driverClassName = this.embeddedDatabaseConnection.getDriverClassName(); } if (!StringUtils.hasText(driverClassName)) { throw new DataSourceBeanCreationException( "Failed to determine a suitable driver class", this, this.embeddedDatabaseConnection); } return driverClassName; } private boolean driverClassIsLoadable() { try { ClassUtils.forName(this.driverClassName, null); return true; } catch (UnsupportedClassVersionError ex) { // Driver library has been compiled with a later JDK, propagate error throw ex; } catch (Throwable ex) { return false; } } /** * Return the configured url or {@code null} if none was configured. * @return the configured url * @see #determineUrl() */ public String getUrl() { return this.url; } public void setUrl(String url) { this.url = url; } /** * Determine the url to use based on this configuration and the environment. * @return the url to use * @since 1.4.0 */ public String determineUrl() { if (StringUtils.hasText(this.url)) { return this.url; } String databaseName = determineDatabaseName(); String url = (databaseName != null) ? this.embeddedDatabaseConnection.getUrl(databaseName) : null; if (!StringUtils.hasText(url)) { throw new DataSourceBeanCreationException( "Failed to determine suitable jdbc url", this, this.embeddedDatabaseConnection); } return url; } /** * Determine the name to used based on this configuration. * @return the database name to use or {@code null} * @since 2.0.0 */ public String determineDatabaseName() { if (this.generateUniqueName) { if (this.uniqueName == null) { this.uniqueName = UUID.randomUUID().toString(); } return this.uniqueName; } if (StringUtils.hasLength(this.name)) { return this.name; } if (this.embeddedDatabaseConnection != EmbeddedDatabaseConnection.NONE) { return "testdb"; } return null; } /** * Return the configured username or {@code null} if none was configured. * @return the configured username * @see #determineUsername() */ public String getUsername() { return this.username; } public void setUsername(String username) { this.username = username; } /** * Determine the username to use based on this configuration and the environment. * @return the username to use * @since 1.4.0 */ public String determineUsername() { if (StringUtils.hasText(this.username)) { return this.username; } if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName())) { return "sa"; } return null; } /** * Return the configured password or {@code null} if none was configured. * @return the configured password * @see #determinePassword() */ public String getPassword() { return this.password; } public void setPassword(String password) { this.password = password; } /** * Determine the password to use based on this configuration and the environment. * @return the password to use * @since 1.4.0 */ public String determinePassword() { if (StringUtils.hasText(this.password)) { return this.password; } if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName())) { return ""; } return null; } public String getJndiName() { return this.jndiName; } /** * Allows the DataSource to be managed by the container and obtained via JNDI. The * {@code URL}, {@code driverClassName}, {@code username} and {@code password} fields * will be ignored when using JNDI lookups. * @param jndiName the JNDI name */ public void setJndiName(String jndiName) { this.jndiName = jndiName; } public DataSourceInitializationMode getInitializationMode() { return this.initializationMode; } public void setInitializationMode(DataSourceInitializationMode initializationMode) { this.initializationMode = initializationMode; } public String getPlatform() { return this.platform; } public void setPlatform(String platform) { this.platform = platform; } public List<String> getSchema() { return this.schema; } public void setSchema(List<String> schema) { this.schema = schema; } public String getSchemaUsername() { return this.schemaUsername; } public void setSchemaUsername(String schemaUsername) { this.schemaUsername = schemaUsername; } public String getSchemaPassword() { return this.schemaPassword; } public void setSchemaPassword(String schemaPassword) { this.schemaPassword = schemaPassword; } public List<String> getData() { return this.data; } public void setData(List<String> data) { this.data = data; } public String getDataUsername() { return this.dataUsername; } public void setDataUsername(String dataUsername) { this.dataUsername = dataUsername; } public String getDataPassword() { return this.dataPassword; } public void setDataPassword(String dataPassword) { this.dataPassword = dataPassword; } public boolean isContinueOnError() { return this.continueOnError; } public void setContinueOnError(boolean continueOnError) { this.continueOnError = continueOnError; } public String getSeparator() { return this.separator; } public void setSeparator(String separator) { this.separator = separator; } public Charset getSqlScriptEncoding() { return this.sqlScriptEncoding; } public void setSqlScriptEncoding(Charset sqlScriptEncoding) { this.sqlScriptEncoding = sqlScriptEncoding; } public ClassLoader getClassLoader() { return this.classLoader; } public Xa getXa() { return this.xa; } public void setXa(Xa xa) { this.xa = xa; } /** * XA Specific datasource settings. */ public static class Xa { /** * XA datasource fully qualified name. */ private String dataSourceClassName; /** * Properties to pass to the XA data source. */ private Map<String, String> properties = new LinkedHashMap<>(); public String getDataSourceClassName() { return this.dataSourceClassName; } public void setDataSourceClassName(String dataSourceClassName) { this.dataSourceClassName = dataSourceClassName; } public Map<String, String> getProperties() { return this.properties; } public void setProperties(Map<String, String> properties) { this.properties = properties; } } static class DataSourceBeanCreationException extends BeanCreationException { /** * */ private static final long serialVersionUID = 1L; private final BaseDataSourceProperties properties; private final EmbeddedDatabaseConnection connection; DataSourceBeanCreationException(String message, BaseDataSourceProperties properties, EmbeddedDatabaseConnection connection) { super(message); this.properties = properties; this.connection = connection; } public BaseDataSourceProperties getProperties() { return this.properties; } public EmbeddedDatabaseConnection getConnection() { return this.connection; } }}
mybatis對應的base屬性文件
public class BaseMybatisProperties { /** * Config file path. */ private String configLocation; /** * Location of mybatis mapper files. */ private String[] mapperLocations; /** * Package to scan domain objects. */ private String typeAliasesPackage; /** * Package to scan handlers. */ private String typeHandlersPackage; /** * Check the config file exists. */ private boolean checkConfigLocation = false; /** * Execution mode for {@link org.mybatis.spring.SqlSessionTemplate}. */ private ExecutorType executorType; /** * A Configuration object for customize default settings. If * {@link #configLocation} is specified, this property is not used. */ private Configuration configuration; /** * @since 1.1.0 * @return */ public String getConfigLocation() { return this.configLocation; } /** * @since 1.1.0 * @return */ public void setConfigLocation(String configLocation) { this.configLocation = configLocation; } @Deprecated public String getConfig() { return this.configLocation; } @Deprecated public void setConfig(String config) { this.configLocation = config; } public String[] getMapperLocations() { return this.mapperLocations; } public void setMapperLocations(String[] mapperLocations) { this.mapperLocations = mapperLocations; } public String getTypeHandlersPackage() { return this.typeHandlersPackage; } public void setTypeHandlersPackage(String typeHandlersPackage) { this.typeHandlersPackage = typeHandlersPackage; } public String getTypeAliasesPackage() { return this.typeAliasesPackage; } public void setTypeAliasesPackage(String typeAliasesPackage) { this.typeAliasesPackage = typeAliasesPackage; } public boolean isCheckConfigLocation() { return this.checkConfigLocation; } public void setCheckConfigLocation(boolean checkConfigLocation) { this.checkConfigLocation = checkConfigLocation; } public ExecutorType getExecutorType() { return this.executorType; } public void setExecutorType(ExecutorType executorType) { this.executorType = executorType; } public Configuration getConfiguration() { return configuration; } public void setConfiguration(Configuration configuration) { this.configuration = configuration; } public Resource[] resolveMapperLocations() { List<Resource> resources = new ArrayList<Resource>(); if (this.mapperLocations != null) { for (String mapperLocation : this.mapperLocations) { Resource[] mappers; try { mappers = new PathMatchingResourcePatternResolver().getResources(mapperLocation); resources.addAll(Arrays.asList(mappers)); } catch (IOException e) { } } } Resource[] mapperLocations = new Resource[resources.size()]; mapperLocations = resources.toArray(mapperLocations); return mapperLocations; }}
因為我們使用的是Hikari數據源,所以這裡我是直接copy默認系統Hikari的屬性文件。
也就是這個文件:org.springframework.boot.autoconfigure.jdbc.DataSourceProperties 為啥我不直接繼承這個類而是在自己的項目中新建這麼一個類,是因為我發現這個類有這個註解
@ConfigurationProperties(prefix = "spring.datasource")
怕的是它的這個註解會覆蓋我接下來兩個類的註解(我主要是懶得測試,所以直接copy一份無所謂了)。
接下來看看具體master和slave兩個數據源的屬性文件:
@Component@ConfigurationProperties(prefix = "master.datasource")public class MasterDataSourceProperties extends BaseDataSourceProperties {}
@Component@ConfigurationProperties(prefix = "slave.datasource")public class SlaveDataSourceProperties extends BaseDataSourceProperties {}
@Component@ConfigurationProperties(prefix = "master.mybatis")public class MasterMybatisProperties extends BaseMybatisProperties {}
@Component@ConfigurationProperties(prefix = "slave.mybatis")public class SlaveMybatisProperties extends BaseMybatisProperties {}
接下來是數據源的配置了。
@Configurationpublic class HikariDataSourceConfig { @Bean @Primary public HikariDataSource masterDataSource(MasterDataSourceProperties properties) { HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class); if (StringUtils.hasText(properties.getName())) { dataSource.setPoolName(properties.getName()); } return dataSource; } @Bean public HikariDataSource slaveDataSource(SlaveDataSourceProperties properties) { HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class); if (StringUtils.hasText(properties.getName())) { dataSource.setPoolName(properties.getName()); } return dataSource; } @SuppressWarnings("unchecked") protected static <T> T createDataSource(BaseDataSourceProperties properties, Class<? extends DataSource> type) { return (T) properties.initializeDataSourceBuilder().type(type).build(); } }
因為我們配置的是多個數據源所有其中一個數據源必須加入這個註解@Primary
接下來是jpa的EntityManagerFactory工廠的配置了
public class EntityManagerFactoryConfig { @Configuration @EnableJpaRepositories(basePackages = { "${master.jpa.repos}" }, entityManagerFactoryRef = "masterEntityManagerFactory", transactionManagerRef = "masterJPATransactionManager") static class MasterEntityManagerFactory { @Resource(name = "masterDataSource") private DataSource masterDataSource; @Value("${master.jpa.domain}") private String masterDomainPkg; @Bean @Primary public LocalContainerEntityManagerFactoryBean masterEntityManagerFactory(EntityManagerFactoryBuilder builder) { Map<String, Object> properties = new HashMap<>(); properties.put("hibernate.hbm2ddl.auto", "update"); properties.put("hibernate.id.new_generator_mappings", true); properties.put("hibernate.physical_naming_strategy", SpringPhysicalNamingStrategy.class.getName()) ; return builder.dataSource(masterDataSource).packages(masterDomainPkg).persistenceUnit("master") .properties(properties).build(); } @Bean @Primary public PlatformTransactionManager masterJPATransactionManager(EntityManagerFactoryBuilder builder) { JpaTransactionManager tm = new JpaTransactionManager(masterEntityManagerFactory(builder).getObject()); return tm; } } @Configuration @EnableJpaRepositories(basePackages = { "${slave.jpa.repos}" }, entityManagerFactoryRef = "slaveEntityManagerFactory", transactionManagerRef = "slaveJPATransactionManager") @ConditionalOnProperty(name = "multiple.ds.enabled", havingValue = "true") static class SlaveEntityManagerFactory { @Resource(name = "slaveDataSource") private DataSource slaveDataSource; @Value("${slave.jpa.domain}") private String slaveDomainPkg; @Bean public LocalContainerEntityManagerFactoryBean slaveEntityManagerFactory(EntityManagerFactoryBuilder builder) { Map<String, Object> properties = new HashMap<>(); properties.put("hibernate.hbm2ddl.auto", "update"); properties.put("hibernate.id.new_generator_mappings", true); properties.put("hibernate.physical_naming_strategy", SpringPhysicalNamingStrategy.class.getName()) ; return builder.dataSource(slaveDataSource).packages(slaveDomainPkg).persistenceUnit("slave") .properties(properties).build(); } @Bean public PlatformTransactionManager slaveJPATransactionManager(EntityManagerFactoryBuilder builder) { JpaTransactionManager tm = new JpaTransactionManager(slaveEntityManagerFactory(builder).getObject()); return tm; } }}
public class SqlSessionFactoryConfig { @Configuration static class MasterSqlSessionFactory { @Resource private MasterMybatisProperties properties; @Autowired(required = false) private Interceptor[] interceptors; @Autowired private ResourceLoader resourceLoader = new DefaultResourceLoader(); @Autowired(required = false) private DatabaseIdProvider databaseIdProvider; @Bean public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setVfs(SpringBootVFS.class); if (StringUtils.hasText(this.properties.getConfigLocation())) { factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); } factory.setConfiguration(properties.getConfiguration()); if (!ObjectUtils.isEmpty(this.interceptors)) { factory.setPlugins(this.interceptors); } if (this.databaseIdProvider != null) { factory.setDatabaseIdProvider(this.databaseIdProvider); } if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) { factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); } if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); } if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) { factory.setMapperLocations(this.properties.resolveMapperLocations()); } return factory.getObject(); } @Bean public DataSourceTransactionManager masterTransactionManager(@Qualifier("masterDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory")SqlSessionFactory sqlSessionFactory) { ExecutorType executorType = this.properties.getExecutorType(); if (executorType != null) { return new SqlSessionTemplate(sqlSessionFactory, executorType); } else { return new SqlSessionTemplate(sqlSessionFactory); } } } @Configuration @ConditionalOnProperty(name = "multiple.ds.enabled", havingValue = "true") static class SlaveSqlSessionFactory { @Resource private SlaveMybatisProperties properties; @Autowired(required = false) private Interceptor[] interceptors; @Autowired private ResourceLoader resourceLoader = new DefaultResourceLoader(); @Autowired(required = false) private DatabaseIdProvider databaseIdProvider; @Bean public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setVfs(SpringBootVFS.class); if (StringUtils.hasText(this.properties.getConfigLocation())) { factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); } factory.setConfiguration(properties.getConfiguration()); if (!ObjectUtils.isEmpty(this.interceptors)) { factory.setPlugins(this.interceptors); } if (this.databaseIdProvider != null) { factory.setDatabaseIdProvider(this.databaseIdProvider); } if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) { factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); } if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); } if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) { factory.setMapperLocations(this.properties.resolveMapperLocations()); } return factory.getObject(); } @Bean public DataSourceTransactionManager slaveTransactionManager(@Qualifier("slaveDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean public SqlSessionTemplate slaveSqlSessionTemplate(@Qualifier("slaveSqlSessionFactory")SqlSessionFactory sqlSessionFactory) { ExecutorType executorType = this.properties.getExecutorType(); if (executorType != null) { return new SqlSessionTemplate(sqlSessionFactory, executorType); } else { return new SqlSessionTemplate(sqlSessionFactory); } } } }
接下來還需要配置mapper相關的配置
public class MapperScanConfig { @Configuration @MapperScan(basePackages = {"com.pack.base.mapper"}, sqlSessionTemplateRef = "masterSqlSessionTemplate") static class MasterMapper { } @Configuration @MapperScan(basePackages = {"com.pack.slave.mapper"}, sqlSessionTemplateRef = "slaveSqlSessionTemplate") @ConditionalOnProperty(name = "multiple.ds.enabled", havingValue = "true") static class SlaveMapper { } }
到這裡我們所有的相關配置就完成了,接下來我們只需建立對應的包即可。
MyBatis-conf.xml配置文件內容如下:
測試:
建立com.pack.domain包,然後新建類Users.java
@Entity@Table(name = "T_USERS")@Datapublic class Users { @Id private Long id; private String username ; private String password ; private String realName ; private String phone ; private String idNo ; @Column(length=4000) private String authority ; @Column(columnDefinition="int default 0") private Integer status = 0 ;}
建立包com.pack.slave.domain,然後新建類
@Entity@Table(name = "T_PERSON")@Datapublic class Person{ @Id private Long id; private String name ; private String email ;}
dao我就不寫了。我們啟動伺服器分別在不同的用戶下查看表是否建立,如果都建立了就表示成功。
完畢!!!
給個關注,給個讚唄,謝謝!!!