How to configure Oracle DataSource programmatically in Spring Boot with a default schema?
#Bean
public DataSource getDataSource() throws SQLException {
OracleDataSource d = new OracleDataSource();
d.setURL(Secrets.get("DB_URL"));
d.setUser(Secrets.get("DB_USER"));
d.setPassword(Secrets.get("DB_PASS"));
// d.setSchema(System.getenv("DB_SCHEMA")); ???
return d;
}
You can't change the schema in the OracleDataSource or using connection URL, you need to execute
ALTER SESSION SET CURRENT_SCHEMA=targetschema;
statement as explained in this answer. According to Connection Properties Recognized by Oracle JDBC Drivers there is no driver property for initial schema.
Full example:
#Bean
public DataSource getDataSource() throws SQLException {
OracleDataSource oracleDs = new OracleDataSource();
oracleDs.setURL(Secrets.get("DB_URL"));
oracleDs.setUser(Secrets.get("DB_USER"));
oracleDs.setPassword(Secrets.get("DB_PASS"));
// other Oracle related settings...
HikariDataSource hikariDs = new HikariDataSource();
hikariDs.setDataSource(oracleDs);
hikariDs.setConnectionInitSql("ALTER SESSION SET CURRENT_SCHEMA = MY_SCHEMA");
return hikariDs;
}
Try to add sql execution into datasources creation method
#Bean
public DataSource getDataSource() throws SQLException {
OracleDataSource d = new OracleDataSource();
d.setURL(Secrets.get("DB_URL"));
d.setUser(Secrets.get("DB_USER"));
d.setPassword(Secrets.get("DB_PASS"));
Resource initSchema = new ClassPathResource("scripts/schema-alter.sql");
DatabasePopulator databasePopulator = new ResourceDatabasePopulator(initSchema);
DatabasePopulatorUtils.execute(databasePopulator, dataSource);
return d;
}
In scripts/schema-alter.sql will be this code
ALTER SESSION SET CURRENT_SCHEMA=targetschema;
In Spring Boot 2 the wanted schema can be set in application.properties file with the following property:
spring.datasource.hikari.connection-init-sql=ALTER SESSION SET CURRENT_SCHEMA = MY_SCHEMA
HikariCP is the default connection pool in Spring Boot 2. To see all HikariCP settings (including "connectionInitSql") in you log file add also the following in application.properties:
logging.level.com.zaxxer.hikari=DEBUG
Related
Context
I am trying to start my spring app without a database(so when no database is available at initialization the app won't be stopped), i managed to do this with the following commands in app.prop:
#DB should not kill the app
spring.sql.init.continue-on-error=true //app should continue if a sql init error arrises
spring.liquibase.enabled=false // liquibase bean shouldn't be initialized at start up, without this command the app crashes anyway
spring.jpa.hibernate.ddl-auto=none
Now the only thing that i need to do is figure a way so when the app does make a successful connection with the db the liquibase migration files will get executed. For this task I understood I need to customize the liquibase bean, the following code shows my progress so far:
#Configuration
public class Config {
#Value("${postgres.host}")
private String host;
#Value("${postgres.port}")
private Integer port;
#Value("${postgres.database}")
private String database;
#Value("${postgres.user}")
private String user;
#Value("${postgres.password}")
private String password;
#Value("${spring.liquibase.change-log}")
private String changelog;
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl(String.format("jdbc:postgresql://%s:%d/%s", host, port, database));
dataSource.setUsername(user);
dataSource.setPassword(password);
return dataSource;
}
#Bean
public SpringLiquibase liquibase() {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setDataSource(dataSource());
liquibase.setChangeLog(changelog);
return liquibase;
}
}
Preferably if the database is down the bean should not be created and if the database is running/ the server established connection with the db at some point the bean will be brought in the context and execute the migration files, i don't know if that is possible as I am a newbie,but let me know if you have any suggestions.
I have the following code in a simple spring boot app...
#Bean
public DataSource getDatasource(){
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUsername(username);
ds.setUrl(url);
ds.setPassword(password);
return ds;
}
This worked great but I wanted connection pooling so I changed to...
#Bean
public DataSource getDatasource(){
try{
OracleConnectionPoolDataSource ds = new OracleConnectionPoolDataSource();
ds.setDriverType("thin");
ds.setUser(username);
ds.setNetworkProtocol("tcp");
ds.setPassword(password);
ds.setDatabaseName(dbName);
ds.setServerName(serverName);
return ds;
} catch (SQLException throwables) {
logger.error(throwables);
System.exit(-1);
}
return null;
}
But from the documentation it looks like getConnection just returns a native connection and I need to configure Spring to call getPooledConnection instead.
Is there another bean I can create or some other way I can do this?
It looks like if you are doing this with Oracle you are supposed to use a library called UCP or Universal Connection Pooling...
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ucp</artifactId>
<version>21.1.0.0</version>
</dependency>
Then I use that to create the datasource like...
PoolDataSource ds = PoolDataSourceFactory.getPoolDataSource();
ds.setConnectionFactoryClassName(OracleDataSource.class.getName());
ds.setURL(url);
ds.setUser(username);
ds.setPassword(password);
ds.setInitialPoolSize(1);
ds.setMinPoolSize(1);
ds.setMaxPoolSize(10);
return ds;
I am finishing my testing to make sure it is working as expected.
For using Universal Connection Pool (UCP), you do need ucp.jar in the classpath. Refer to UCPSample.java for an example.
How to use JMX MBean for HikariCP in Spring boot application? I have a code like this:
#SpringBootApplication
public class App() { ... }
And other class:
#Configuration
public class DatabaseCfg() {
#Bean
#ManagedOperation
public DataSource ds (#Value("${hikari.proprerties}") String config) {
HikariConfig hikariConfig = new HikariConfig(config);
return new HikariDataSource(hikariConfig);
}
In Java Mission Control (or JMX Console) a saw only Datasource managed bean, not JMX MBean for HikariCP (link). Is it possible to add it too?
In Spring Boot 2.0+ you can set the register-mbeans property in your application.properties file
spring.datasource.hikari.register-mbeans = true
If you are using an earlier version of Spring Boot you will also have to set the datasource
spring.datasource.type = com.zaxxer.hikari.HikariDataSource
I believe on your hikariConfig you need to set a few additional settings. You need to register the MBeans and set a pool name on the configuration.
HikariConfig hiakriConfig = new HikariConfig(config);
hikariConfig.setRegisterMbeans(true);
kikariConfig.setPoolName("my-pool-1");
Yes you obviously could drive these through the properties as well. I'm not sure if you are including these in your properties file as they are not listed. Also please note you are spelling properties wrong (#Value("${ds.proprerties}") should probably should be (#Value("${ds.properties}") but I'm not sure how you actually have named variables and property files. You may want to double check if that is where you want to set all of the properties.
Try this. Exclude your Hiakri DataSource Bean from being registered by Spring.
#Resource
private ObjectProvider<MBeanExporter> mBeanExporter;
#Bean("dataSource")
public DataSource createDataSource() {
String url = hikariDataSourceConfig.getUrl();
String username = hikariDataSourceConfig.getUsername();
String password = hikariDataSourceConfig.getPassword();
long idleTimeoutInMilliSeconds =
hikariDataSourceConfig.getIdleTimeOutInMilliseconds();
long maxLifetimeInMilliseconds =
hikariDataSourceConfig.getMaxLifetimeInMilliseconds();
int maximumPoolSize = hikariDataSourceConfig.getMaximumPoolSize();
int minimumIdle = hikariDataSourceConfig.getMinimumIdle();
String poolName = "HikariDataSource";
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setRegisterMbeans(true);
hikariConfig.setJdbcUrl(url);
hikariConfig.setUsername(username);
hikariConfig.setPassword(password);
hikariConfig.setIdleTimeout(idleTimeoutInMilliSeconds);
hikariConfig.setMaxLifetime(maxLifetimeInMilliseconds);
hikariConfig.setMaximumPoolSize(maximumPoolSize);
hikariConfig.setMinimumIdle(minimumIdle);
hikariConfig.setPoolName(poolName);
HikariDataSource dataSource = new HikariDataSource(hikariConfig);
mBeanExporter
.ifUnique((exporter) -> exporter.addExcludedBean("dataSource"));
return dataSource;
}
I have installed Websphere Network deployment server 7.0.0.0
I have configured a cluster on it.
I have configured a data source on it say ORA_DS this data source using "JAAS - J2C authentication data"
When i test the ORA_DS by clicking on "Test connection" button, the test connection is success.
The issue comes when i try to access this data source using my java code.
Here is my code to access data source and create a connection:
public class DSTester
{
/**
* Return the data source.
* #return the data source
*/
private DataSource getDataSource()
{
DataSource dataSource = null;
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.ibm.websphere.naming.WsnInitialContextFactory");
env.put(Context.PROVIDER_URL, "iiop://localhost:9811");
// Retrieve datasource name
String dataSourceName = "EPLA1";
if (dataSource == null)
{
try
{
Context initialContext = new InitialContext(env);
dataSource = (DataSource) initialContext.lookup(dataSourceName);
}
catch (NamingException e1)
{
e1.printStackTrace();
return null;
}
}
return dataSource;
}
public static void main(String[] args)
throws Exception
{
DSTester dsTester = new DSTester();
DataSource ds = dsTester.getDataSource();
System.out.println(ds);
System.out.println(ds.getConnection());
}
}
Here is the output:
com.ibm.ws.rsadapter.jdbc.WSJdbcDataSource#17e40be6
Exception in thread "P=792041:O=0:CT" java.sql.SQLException: ORA-01017: invalid username/password; logon denied
DSRA0010E: SQL State = 72000, Error Code = 1,017
at oracle.jdbc.driver.SQLStateMapping.newSQLException(SQLStateMapping.java:70)
at oracle.jdbc.driver.DatabaseError.newSQLException(DatabaseError.java:133)
at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:206)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:455)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:406)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:399)
at oracle.jdbc.driver.T4CTTIoauthenticate.receiveOauth(T4CTTIoauthenticate.java:799)
at oracle.jdbc.driver.T4CConnection.logon(T4CConnection.java:368)
at oracle.jdbc.driver.PhysicalConnection.<init>(PhysicalConnection.java:508)
at oracle.jdbc.driver.T4CConnection.<init>(T4CConnection.java:203)
at oracle.jdbc.driver.T4CDriverExtension.getConnection(T4CDriverExtension.java:33)
at oracle.jdbc.driver.OracleDriver.connect(OracleDriver.java:510)
at oracle.jdbc.pool.OracleDataSource.getPhysicalConnection(OracleDataSource.java:275)
at oracle.jdbc.pool.OracleDataSource.getConnection(OracleDataSource.java:206)
at oracle.jdbc.pool.OracleConnectionPoolDataSource.getPhysicalConnection(OracleConnectionPoolDataSource.java:139)
at oracle.jdbc.pool.OracleConnectionPoolDataSource.getPooledConnection(OracleConnectionPoolDataSource.java:88)
at oracle.jdbc.pool.OracleConnectionPoolDataSource.getPooledConnection(OracleConnectionPoolDataSource.java:70)
at com.ibm.ws.rsadapter.spi.InternalGenericDataStoreHelper$1.run(InternalGenericDataStoreHelper.java:1175)
at com.ibm.ws.security.util.AccessController.doPrivileged(AccessController.java:118)
at com.ibm.ws.rsadapter.spi.InternalGenericDataStoreHelper.getPooledConnection(InternalGenericDataStoreHelper.java:1212)
at com.ibm.ws.rsadapter.spi.WSRdbDataSource.getPooledConnection(WSRdbDataSource.java:2019)
at com.ibm.ws.rsadapter.spi.WSManagedConnectionFactoryImpl.createManagedConnection(WSManagedConnectionFactoryImpl.java:1422)
at com.ibm.ws.rsadapter.spi.WSDefaultConnectionManagerImpl.allocateConnection(WSDefaultConnectionManagerImpl.java:81)
at com.ibm.ws.rsadapter.jdbc.WSJdbcDataSource.getConnection(WSJdbcDataSource.java:646)
at com.ibm.ws.rsadapter.jdbc.WSJdbcDataSource.getConnection(WSJdbcDataSource.java:613)
at com.test.DSTester.main(DSTester.java:70)
The code works fine if i replace
ds.getConnection()
with
ds.getConnection("ora_user", "ora_password")
My issue is i need to get the connection without specifying login details for Oracle.
Please help me on this issue.
Any clue will be appreciated.
Thanks
I'd guess it would work if you retrieved the datasource from an application running on the WAS.
Try creating a servlet.
Context initialContext = new InitialContext();
DataSource dataSource = (DataSource) initialContext.lookup("EPLA1");
Connection con = dataSource.getConnection();
As within a servlet it is running within WAS it should be fine, if the "Test Connection" works. Running it outside is probably a different context.
I think you need to check all your configuration:
1) Is it application deplyed on cluster or into only one of cluster member?
2) JAAS - J2C authentication data - what is the scope?
Sometimes you need restar all your WAS environment. It depends on resource configuration scope
I'd recomend to you add resource refences for better configuration options.
SeeIBM Tech note
We use Spring's JdbcTemplate which is configured through Spring config as illustrated below. Is there a way to do this without injecting the data source? I'd like to just create the JdbcTemplate instance programmatically and "initalize" the datasource using TheOracleDS.
Our current config:
Java class
private JdbcTemplate jdbcTemplate;
#Resource(name = "myDataSource")
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
Spring config
<jee:jndi-lookup id="myDataSource" jndi-name="java:/TheOracleDS"/>
Oracle datasource config
<xa-datasource>
<jndi-name>TheOracleDS</jndi-name>
...
</xa-datasource>
Update: Reason I'm asking this is I'm not a total believer in dependency injection / having Spring manage beans..
Here's some sample code from a project I've written:
SimpleJdbcTemplate db;
DataSource dataSource = new SingleConnectionDataSource(System.getProperty(
"lingcog.db.connectstring"),
System.getProperty("lingcog.db.username"),
System.getProperty("lingcog.db.password"), false);
db = new SimpleJdbcTemplate(dataSource);
Maybe my code would be simpler if I used injection, but this is a good example of how to do this without using injection.
You can use an org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup object to find the data source you want by JDNI name.
DataSource dataSource = new JndiDataSourceLookup().getDataSource("java:/TheOracleDS")
SimpleJdbcTemplate db=new SimpleJdbcTemplate(dataSource);
Not sure why you want to do that but... you could lookup the JDNI datasource with Spring's JndiDataSourceLookup:
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
lookup.setResourceRef(true); // if the lookup occurs in a J2EE container
DataSource ds = lookup.getDataSource(jndiName);
Or just perform a "manual" lookup using Sun's classes:
Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("jdbc/AcmeDB");
Then, just pass the datasource reference to the JdbcTemplate constructor or call setDataSource(ds).
But, as I said, I have no idea why you don't want to use injection.
Just use a raw JNDI lookup:
public void setDataSourceName(String name) {
InitialContext ctx = new InitialContext();
jdbcTemplate = new JdbcTemplate((DataSource) ctx.lookup(name));
}