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));
}
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 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
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 am learning Spring MVC. I am trying to use #Resource to inject DataSource. It is like this:
web.xml of Tomcat:
<resource-ref>
<description>DB Connection</description>
<res-ref-name>jdbc/TestDB</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
context.xml:
Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="10000"
username="sa" password="" driverClassName="org.h2.Driver"
url="jdbc:h2:tcp://localhost/~/test"/>
The controller code (using Spring MVC framework):
#Controller
public class SimpleControllerAnnotation {
//#Resource(name="dataSource")
#Resource(name="jdbc/TestDB")
private DataSource dataSource;
public DataSource getDataSource() {
return dataSource;
}
//#Resource(name="dataSource")
#Resource(name="jdbc/TestDB")
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
#RequestMapping("/testDataSource")
public ModelAndView testDataSource() {
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
String name = null;
String ID = null;
try {
con = dataSource.getConnection();
stmt = con.createStatement();
rs = stmt.executeQuery("select ID, name from STUDENT");
while(rs.next()){
name = rs.getString("name");
ID = rs.getString("ID");
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
try {
if(rs != null) rs.close();
if(stmt != null) stmt.close();
if(con != null) con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
ModelAndView mw = new ModelAndView("TestDataSourceForm");
mw.addObject("DataSourceValue",dataSource);
mw.addObject("Name",name);
mw.addObject("ID",ID);
return mw;
}
In this code, I am using #Resource to inject the DataSource, which I intend to "get" from Tomcat, which I set up in Tomcat (the web.xml and context.xml shared above).
When I run this program, I get the following exception:
SEVERE: Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'simpleControllerAnnotation': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'jdbc/TestDB' is defined
The jdbc/TestDB is the DataSource which I set up in Tomcat.
I have following queries:
1) Is it possible to have DataSource which we create in Tomcat to be injected this way? Or we have to use JNDI lookup. In one of the posts that I read on internet, it was said that JNDI lookup is sort of outdated and these days Dependency injection is preferred way.
2) In general, is it best practice to set-up the DataSources in App server/Web Container or to manage in the application itself. From what I read over the posts, it is preferred let App server/Container to manage this.
Any help to past this error really appreciated.
Apache Tomcat processes #Resource annotations only on classes that it itself loads (such as Filters, Servlets and Listeners).
In your case your controller class is loaded by Spring Framework and Spring is responsible for processing the #Resource annotation. Read the Spring documentation (Reference guide).
According to Spring Reference Guide [1], the value in #Resource annotation is the name of a Spring bean.
It says that the name can be used for JDNI lookup if you configure a SimpleJndiBeanFactory, but recommends against it and advices to configure referenced beans explicitly. -> [2]
[1] http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-resource-annotation
[2] http://docs.spring.io/spring/docs/current/spring-framework-reference/html/xsd-config.html#xsd-config-body-schemas-jee