具有多个数据源 Oracle 和 H2 的 Spring Boot
Posted
技术标签:
【中文标题】具有多个数据源 Oracle 和 H2 的 Spring Boot【英文标题】:Spring Boot with multiple datasources Oracle and H2 【发布时间】:2017-04-06 05:57:22 【问题描述】:我为我的 Angular 项目开发了一个 Spring Boot RestController,但遇到了问题。在我的 rest-service 逻辑中,我使用了两个不同的数据库来获取数据。
这里可以看到数据源配置:
[应用程序属性]
#datasource1
spring.datasource.url=[url]
spring.datasource.username=[username]
spring.datasource.password=[password]
spring.datasource.driverClassName=org.h2.Driver
#datasource2
spring.secondDatasource.url=[url]
spring.secondDatasource.username=[username]
spring.secondDatasource.password=[password]
spring.secondDatasource.driverClassName=oracle.jdbc.OracleDriver
[DatasourceConfig.java]
@Bean
@Primary
@ConfigurationProperties(prefix="spring.datasource")
public DataSource h2DataSource()
return DataSourceBuilder.create().build();
@Bean
@ConfigurationProperties(prefix="spring.secondDatasource")
public DataSource oracleDataSource()
return DataSourceBuilder.create().build();
日志输出:(这个问题没有抛出异常)
...
2016-11-22 13:20:25.853 [INFO ] 1 [main] d.b.s.Application : Started Application in 7.757 seconds (JVM running for 12.515)
2016-11-22 13:20:31.731 [INFO ] 62 [http-nio-8080-exec-1] o.s.w.s.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2016-11-22 13:20:31.757 [INFO ] 62 [http-nio-8080-exec-1] o.s.w.s.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 26 ms
2016-11-22 13:20:34.984 [INFO ] 65 [http-nio-8080-exec-5] d.b.s.r.RESTclient : /rest/getMyData
2016-11-22 13:20:34.992 [INFO ] 63 [http-nio-8080-exec-2] d.b.s.r.RESTclient : /rest/getMyData2
2016-11-22 13:20:34.993 [INFO ] 69 [http-nio-8080-exec-8] d.b.s.r.RESTclient : /rest/getMyData3
2016-11-22 13:20:35.004 [DEBUG] 65 [http-nio-8080-exec-5] d.b.s.DataSourceService : getH2Connection()
2016-11-22 13:20:35.022 [DEBUG] 63 [http-nio-8080-exec-2] d.b.s.DataSourceService : getOracleConnection()
2016-11-22 13:20:35.022 [DEBUG] 69 [http-nio-8080-exec-8] d.b.s.DataSourceService : getH2Connection()
问题是,rest-service 的每个使用的工作线程 (http-nio-8080-?, ...) 在我的 DataSourceService 中的 datasource.getConnection() 处挂起。它永远冻结并等待无法获取的连接。
@Service
public class DataSourceService
private final DataSource h2DataSource;
private final DataSource oracleDataSource;
@Autowired
public DataSourceService(DataSource h2DataSource, DataSource oracleDataSource)
this.h2DataSource = h2DataSource;
this.oracleDataSource = oracleDataSource;
public Connection getH2Connection() throws SQLException
LoggerUtil.logDebug(getClass(), "getH2Connection()");
return h2Connection.getConnection();
public Connection getOracleConnection() throws SQLException
LoggerUtil.logDebug(getClass(), "getOracleConnection()");
return oracleConnection.getConnection();
关于它的奇怪之处在于,有时它无需更改任何代码行(5% 的时间)就可以工作,而且大多数情况下(95% 的时间)它都会挂起。
在我的 pom.xml 中,我有以下数据库驱动程序依赖项:
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.4</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.193</version>
<scope>runtime</scope>
</dependency>
如果所有数据源都具有相同的驱动程序,这将永远不会发生,但结合起来我发现了这种奇怪的行为。
我的想法是,只有在预言机之前初始化 h2Database 时才会发生这种情况。此时 spring 类加载器会加载 h2-driver 类并将它们缓存起来。接下来,oracle 被初始化,一些类要关闭相同,以便一些 h2 类用于 oracle。正因为如此,才产生了奇怪的冻结状态。 (反之亦然)。
我现在的问题:为什么我不能在 Spring-boot 中同时使用 H2 和 Oracle 数据库?
编辑:
经过一些调试,我发现了更多信息:
[org.apache.tomcat.jdbc.pool.ClassLoaderUtil]
Row 29: loadClass(...)
...
Row 38: return Class.forName(className, true, cl); //className: "oracle.jdbc.OracleDriver" cl: Launcher$AppClassLoader
在 ClassLoaderUtil 中是按类名查找 Driver-Class。类名“org.h2.Driver”可以正常解析,但“oracle.jdbc.OracleDriver”不行。
之后我做了一个线程转储,您可以在其中看到卡住的点。此时有5个卡住的线程分别卡在不同的位置。
http-nio-8080-exec-2@8307(可运行) (仅使用 oracle 数据库)
"http-nio-8080-exec-2@8307" daemon prio=5 tid=0x40 nid=NA runnable
java.lang.Thread.State: RUNNABLE
blocks http-nio-8080-exec-6@8311
at java.lang.Class.forName0(Class.java:-1)
at java.lang.Class.forName(Class.java:348)
at org.apache.tomcat.jdbc.pool.ClassLoaderUtil.loadClass(ClassLoaderUtil.java:38)
at org.apache.tomcat.jdbc.pool.PooledConnection.connectUsingDriver(PooledConnection.java:271)
at org.apache.tomcat.jdbc.pool.PooledConnection.connect(PooledConnection.java:203)
at org.apache.tomcat.jdbc.pool.ConnectionPool.createConnection(ConnectionPool.java:718)
at org.apache.tomcat.jdbc.pool.ConnectionPool.borrowConnection(ConnectionPool.java:650)
at org.apache.tomcat.jdbc.pool.ConnectionPool.init(ConnectionPool.java:468)
at org.apache.tomcat.jdbc.pool.ConnectionPool.<init>(ConnectionPool.java:143)
at org.apache.tomcat.jdbc.pool.DataSourceProxy.pCreatePool(DataSourceProxy.java:118)
- locked <0x2139> (a org.apache.tomcat.jdbc.pool.DataSource)
at org.apache.tomcat.jdbc.pool.DataSourceProxy.createPool(DataSourceProxy.java:107)
at org.apache.tomcat.jdbc.pool.DataSourceProxy.getConnection(DataSourceProxy.java:131)
at d.b.s.DataSourceService.getConnection(DataSourceService.java:51) //<-- That's my package
...
http-nio-8080-exec-3@8308(可运行) (oracle和h2数据对比)
"http-nio-8080-exec-3@8308" daemon prio=5 tid=0x41 nid=NA runnable
java.lang.Thread.State: RUNNABLE
at oracle.jdbc.driver.OracleDriver.<clinit>(OracleDriver.java:190)
at java.lang.Class.forName0(Class.java:-1)
at java.lang.Class.forName(Class.java:348)
at org.apache.tomcat.jdbc.pool.ClassLoaderUtil.loadClass(ClassLoaderUtil.java:38)
at org.apache.tomcat.jdbc.pool.PooledConnection.connectUsingDriver(PooledConnection.java:271)
at org.apache.tomcat.jdbc.pool.PooledConnection.connect(PooledConnection.java:203)
at org.apache.tomcat.jdbc.pool.ConnectionPool.createConnection(ConnectionPool.java:718)
at org.apache.tomcat.jdbc.pool.ConnectionPool.borrowConnection(ConnectionPool.java:650)
at org.apache.tomcat.jdbc.pool.ConnectionPool.init(ConnectionPool.java:468)
at org.apache.tomcat.jdbc.pool.ConnectionPool.<init>(ConnectionPool.java:143)
at org.apache.tomcat.jdbc.pool.DataSourceProxy.pCreatePool(DataSourceProxy.java:118)
- locked <0x213d> (a org.apache.tomcat.jdbc.pool.DataSource)
at org.apache.tomcat.jdbc.pool.DataSourceProxy.createPool(DataSourceProxy.java:107)
at org.apache.tomcat.jdbc.pool.DataSourceProxy.getConnection(DataSourceProxy.java:131)
at d.b.s.DataSourceService.getConnection(DataSourceService.java:51)
...
http-nio-8080-exec-4@8309(可运行) (oracle和h2数据对比)
(与 exec-2 相同)
http-nio-8080-exec-5@8310(可运行) (仅使用 h2 数据库)
"http-nio-8080-exec-5@8310" daemon prio=5 tid=0x43 nid=NA runnable
java.lang.Thread.State: RUNNABLE
at sun.reflect.GeneratedConstructorAccessor109.newInstance(Unknown Source:-1)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:-1)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at java.lang.Class.newInstance(Class.java:442)
at java.util.ServiceLoader$LazyIterator.nextService(ServiceLoader.java:380)
at java.util.ServiceLoader$LazyIterator.next(ServiceLoader.java:404)
at java.util.ServiceLoader$1.next(ServiceLoader.java:480)
at java.sql.DriverManager$2.run(DriverManager.java:603)
at java.sql.DriverManager$2.run(DriverManager.java:583)
at java.security.AccessController.doPrivileged(AccessController.java:-1)
at java.sql.DriverManager.loadInitialDrivers(DriverManager.java:583)
at java.sql.DriverManager.<clinit>(DriverManager.java:101)
at org.h2.Driver.load(Driver.java:155)
at org.h2.Driver.<clinit>(Driver.java:41)
at java.lang.Class.forName0(Class.java:-1)
- locked <0x211b> (a java.lang.Class)
at java.lang.Class.forName(Class.java:348)
at org.apache.tomcat.jdbc.pool.ClassLoaderUtil.loadClass(ClassLoaderUtil.java:38)
at org.apache.tomcat.jdbc.pool.PooledConnection.connectUsingDriver(PooledConnection.java:271)
at org.apache.tomcat.jdbc.pool.PooledConnection.connect(PooledConnection.java:203)
at org.apache.tomcat.jdbc.pool.ConnectionPool.createConnection(ConnectionPool.java:718)
at org.apache.tomcat.jdbc.pool.ConnectionPool.borrowConnection(ConnectionPool.java:650)
at org.apache.tomcat.jdbc.pool.ConnectionPool.init(ConnectionPool.java:468)
at org.apache.tomcat.jdbc.pool.ConnectionPool.<init>(ConnectionPool.java:143)
at org.apache.tomcat.jdbc.pool.DataSourceProxy.pCreatePool(DataSourceProxy.java:118)
at org.apache.tomcat.jdbc.pool.DataSourceProxy.createPool(DataSourceProxy.java:107)
at org.apache.tomcat.jdbc.pool.DataSourceProxy.getConnection(DataSourceProxy.java:131)
- locked <0x20e9> (a org.apache.tomcat.jdbc.pool.DataSource)
at d.b.s.DataSourceService.getConnection(DataSourceService.java:51)
...
http-nio-8080-exec-6@8311(等待监视器进入) (oracle和h2数据对比)
"http-nio-8080-exec-6@8311" daemon prio=5 tid=0x44 nid=NA waiting for monitor entry
java.lang.Thread.State: BLOCKED
waiting for http-nio-8080-exec-2@8307 to release lock on <0x2139> (a org.apache.tomcat.jdbc.pool.DataSource)
at org.apache.tomcat.jdbc.pool.DataSourceProxy.pCreatePool(DataSourceProxy.java:115)
at org.apache.tomcat.jdbc.pool.DataSourceProxy.createPool(DataSourceProxy.java:107)
at org.apache.tomcat.jdbc.pool.DataSourceProxy.getConnection(DataSourceProxy.java:131)
at d.b.s.DataSourceService.getConnection(DataSourceService.java:51)
...
【问题讨论】:
对我来说似乎不合法,我认为这与 Spring Boot 无关。由于您正在配置自己的DataSource
,因此您完全绕过了 Spring Boot 为您所做的事情。也许池没有足够的连接并且您没有正确关闭连接?同样,与 Spring Boot 无关。
【参考方案1】:
我也遇到了和你一样的问题,但是在h2和mysql中。我没有同时使用h2和oracle,在这个post,也许你能找到一些有用的。
这是我的双数据源配置:
maven 依赖:
<!-- stater mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<!-- jdbc -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
```
datasource:
km:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/km?useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: 123456
max-active: 100
max-idle: 10
max-wait: 10000
test-while-idle: true
esb:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/esb?useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: 123456
max-active: 100
max-idle: 10
max-wait: 10000
test-while-idle: true
```
数据源 km 配置类: ```
import com.package.km.api.commons.config.datasource.annotation.UseDatasourceKM;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.lang.invoke.MethodHandles;
import java.sql.SQLException;
/**
* km datasource config
* Created by BeeNoisy on 16/5/23.
*/
@Configuration
@MapperScan(basePackages = "package.km", annotationClass = UseDatasourceKM.class, sqlSessionFactoryRef = KMDatasourceConfig.SQL_SESSION_FACTORY_NAME)
public class KMDatasourceConfig
public static final String SQL_SESSION_FACTORY_NAME = "sessionFactoryKm";
public static final String TX_MANAGER = "txManagerKm";
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Bean(name = "datasourceKm")
@Primary
@ConfigurationProperties(prefix = "datasource.km")
public DataSource dataSourceKm()
return DataSourceBuilder.create().build();
@Bean(name = TX_MANAGER)
@Primary
public PlatformTransactionManager txManagerKm()
return new DataSourceTransactionManager(dataSourceKm());
@Bean(name = KMDatasourceConfig.SQL_SESSION_FACTORY_NAME)
@Primary
public SqlSessionFactory sqlSessionFactoryBean() throws Exception
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("/mybatis/mybatis-conf.xml"));
sqlSessionFactoryBean.setDataSource(dataSourceKm());
return sqlSessionFactoryBean.getObject();
```
这里是数据源 esb 配置类:
```
import com.package.km.api.commons.condition.EsbEnabledCondition;
import com.package.km.api.commons.config.datasource.annotation.UseDatasourceESB;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.lang.invoke.MethodHandles;
/**
* Created by BeeNoisy on 16/5/23.
*/
@Configuration
@Conditional(EsbEnabledCondition.class)
@MapperScan(basePackages = "package.esb", annotationClass = UseDatasourceESB.class, sqlSessionFactoryRef = EsbDatasourceConfig.SQL_SESSION_FACTORY_NAME)
public class EsbDatasourceConfig
public static final String SQL_SESSION_FACTORY_NAME = "sessionFactoryEsb";
public static final String TX_MANAGER = "txManagerEsb";
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Bean(name = "datasourceEsb")
@Conditional(EsbEnabledCondition.class)
@ConfigurationProperties(prefix = "datasource.esb")
public DataSource dataSourceEsb()
return DataSourceBuilder.create().build();
@Bean(name = TX_MANAGER)
@Conditional(EsbEnabledCondition.class)
public PlatformTransactionManager txManagerEsb()
return new DataSourceTransactionManager(dataSourceEsb());
@Bean(name = EsbDatasourceConfig.SQL_SESSION_FACTORY_NAME)
@Conditional(EsbEnabledCondition.class)
public SqlSessionFactory sqlSessionFactoryBean() throws Exception
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("/mybatis/mybatis-conf.xml"));
sqlSessionFactoryBean.setDataSource(dataSourceEsb());
return sqlSessionFactoryBean.getObject();
```
然后您可以使用两个注释:UseDatasourceESB
或 UseDatasourceKM
来注释您的映射器类,如:
```
@UseDatasourceKM
public interface GroupBaseDAO
public static final String COL_ALL = " id, name, create_time, last_update_time ";
public static final String TABLE = " group_base ";
@Select(" select " + COL_ALL + " from " + TABLE + " where id = #id ")
public GroupBase findById(@Param("id") int id);
@Select(" select " +
COL_ALL +
" from " +
TABLE +
" where id < #lastId " +
" limit #count ")
public List<GroupBase> list(
@Param("lastId") int lastId,
@Param("count") int count
);
...
```
mysql\h2 和 oracle 中有两个数据源。
y 顺便说一句,您可以在调试模型中运行您的代码,并进入getConnection()
方法以查找更多详细信息。
【讨论】:
以上是关于具有多个数据源 Oracle 和 H2 的 Spring Boot的主要内容,如果未能解决你的问题,请参考以下文章