Requirement : Create a multi-tenant Application which should insert each tenants data into their respective PDBs based on the tenant id in the request. In other words each tenant or customer will have there own PDB in a CDB, all PDBs will have the same schema and based on then tenant Id in the request a datasource will be selected and the data would be inserted into that PDB.
Stack - spring boot 2.3.0.RELEASE, Oracle 18c, Connection pool - Oracle shared universal connection pool
UCP connection :
<ucp-properties>
<connection-pool
connection-pool-name="pool1"
connection-factory-class-name="oracle.jdbc.pool.OracleDataSource"
url="jdbc:oracle:thin:#(DESCRIPTION=(ADDRESS=(HOST=localhost)(PORT=1521)(PROTOCOL=tcp))(CONNECT_DATA=(SERVICE_NAME=orcl.accounts.intern)))"
user="C##commonuser"
password="commonuser"
initial-pool-size="10"
min-pool-size="5"
max-pool-size="20"
connection-repurpose-threshold="13"
sql-for-validate-connection="select 1 from dual"
shared="true"
>
<connection-property name="oracle.jdbc.ReadTimeout" value="2000"/>
<connection-property name="oracle.net.OUTBOUND_CONNECT_TIMEOUT" value="2000"/>
<data-source data-source-name="pdbcust1" service="pdbcust1.accounts.intern" user="cust1" password="password"/>
<data-source data-source-name="pdbcust2" service="pdbcust2.accounts.intern" user="cust2" password="password"/>
</connection-pool>
</ucp-properties>
Spring datasource config class :
#Bean
public DataSource dataSource() throws SQLException {
System.setProperty("oracle.ucp.jdbc.xmlConfigFile", "file:/" + dbConfigProperties.getUcpConfigFile());
final AbstractRoutingDataSource dataSource = new MultitenantRoutingDataSource();
targetDataSources = new ConcurrentHashMap<>();
final PoolDataSource tenantDataSource1 = getTenantDataSource("pdbcust1", "cust1", "password");
final PoolDataSource tenantDataSource2 = getTenantDataSource("pdbcust2", "cust2", "password");
targetDataSources.put("pdbcust1", tenantDataSource1 );
targetDataSources.put("pdbcust2", tenantDataSource2 );
dataSource.setDefaultTargetDataSource(lTenantDataSource2);
lDataSource.setTargetDataSources(lTargetDataSources);
lDataSource.afterPropertiesSet();
return lDataSource;
}
private static PoolDataSource getTenantDataSource(final String tenantId, String username, String password) {
try {
PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(tenantId);
Properties prop = new Properties();
// prop.setProperty("user", username);
// prop.setProperty("password", password);
//pds.reconfigureDataSource(prop);
return pds;
} catch (final Exception e) {
e.printStackTrace();
}
return null;
}
Above configuration does not work and throws the following error when I fire a request with a tenant id :
java.sql.SQLSyntaxErrorException: ORA-00942: table or view does not exist
at oracle.jdbc.driver.T4CTTIoer11.processError(T4CTTIoer11.java:509) ~[ojdbc8-19.3.0.0.jar:19.3.0.0.0]
at oracle.jdbc.driver.T4CTTIoer11.processError(T4CTTIoer11.java:461) ~[ojdbc8-19.3.0.0.jar:19.3.0.0.0]
at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:1104) ~[ojdbc8-19.3.0.0.jar:19.3.0.0.0]
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:550) ~[ojdbc8-19.3.0.0.jar:19.3.0.
However if I uncomment the following lines in the above class and remove the username and password from the UCP file, it works:
prop.setProperty("user", username);
prop.setProperty("password", password);
pds.reconfigureDataSource(prop);
So my questions are :
Why does this happen?
The UCP config xmls xsd has a user and password field, how do we use it?
This page describes share pooling https://docs.oracle.com/middleware/12213/wls/JDBCA/shared_pooling_ds.htm#JDBCA-GUID-4B7DA858-327E-4CEA-A68C-376792D4A466
This has line : "This common user must exist in all PDBs that are connected to the sharing data sources"
What does it mean?
In order to use ucp shared pool feature, the database user must be same for all the datasources sharing a common pool of connection. So you should not use user and password under datasource element.
<data-source data-source-name="pdbcust1" service="pdbcust1.accounts.intern" />
<data-source data-source-name="pdbcust2" service="pdbcust2.accounts.intern"/>
If you have a requirement to use a different user for each pdb then shared pool is not an option. In that case you should define two different pools in the XML, one for each PDB, that means you should not set the shared=true in connection-pool element. Also, for non shared pools there is no need of using a common user at pool level, you can directly use pdb user, password and service name under pool element.
<ucp-properties>
<connection-pool
connection-pool-name="pool1"
connection-factory-class-name="oracle.jdbc.pool.OracleDataSource"
url="jdbc:oracle:thin:#(DESCRIPTION=(ADDRESS=(HOST=localhost)(PORT=1521)(PROTOCOL=tcp))(CONNECT_DATA=(SERVICE_NAME=cust1.accounts.intern)))"
user="cust1"
password="cust1password"
initial-pool-size="10"
min-pool-size="5"
max-pool-size="20"
sql-for-validate-connection="select 1 from dual"
>
<connection-property name="oracle.jdbc.ReadTimeout" value="2000"/>
<connection-property name="oracle.net.OUTBOUND_CONNECT_TIMEOUT" value="2000"/>
<data-source data-source-name="pdbcust1" />
</connection-pool>
<connection-pool
connection-pool-name="pool2"
connection-factory-class-name="oracle.jdbc.pool.OracleDataSource"
url="jdbc:oracle:thin:#(DESCRIPTION=(ADDRESS=(HOST=localhost)(PORT=1521)(PROTOCOL=tcp))(CONNECT_DATA=(SERVICE_NAME=pdbcust2.accounts.intern)))"
user="cust2"
password="cust2password"
initial-pool-size="10"
min-pool-size="5"
max-pool-size="20"
sql-for-validate-connection="select 1 from dual"
>
<connection-property name="oracle.jdbc.ReadTimeout" value="2000"/>
<connection-property name="oracle.net.OUTBOUND_CONNECT_TIMEOUT" value="2000"/>
<data-source data-source-name="pdbcust2" />
</connection-pool>
</ucp-properties>
Related
I am experimenting with the JSR-352 implementation in the Liberty Profile and have been stumped by an 'ORA-01002: fetch out of sequence' error after my reader processes the first 10 items of my JDBC ResultSet. My chunk size is set to 100.
Here is my reader:
#Dependent
#Named("myItemReader")
public class MyItemReader extends AbstractItemReader {
#Resource(name="jdbc/somedb",shareable=false)
private DataSource lavDb;
private PreparedStatement stmt;
private ResultSet rs;
#Override
public void open(Serializable checkpoint) throws Exception {
Connection con = lavDb.getConnection();
con.setAutoCommit(false);
stmt = con.prepareStatement("select id from sometable",
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
rs = stmt.executeQuery();
}
#Override
public MyInputRecord readItem() throws Exception{
if(rs.next()){
return new MyInputRecord(rs.getInt(1));
}
return null;
}
#Override
public void close(){
try{
rs.close();
stmt.close();
}
catch(Exception e){
e.printStackTrace();
}
}
}
If the ResultSet processing occurs in the open() method then I do not encounter any errors.
Here is my batchPersistence configuration from my server.xml:
<dataSource id="oracle-wasadmin" jdbcDriverRef="wasoracledriver" jndiName="jdbc/wasoracledb" type="javax.sql.XADataSource">
<properties.oracle URL="${wasadmin.jdbcurl}" password="xxxxxx" user="yyyyyy"/>
<conionManager agedTimeout="1m" maxIdleTime="15m" maxPoolSize="25" minPoolSize="0"/>
</dataSource>
<jdbcDriver id="wasoracledriver" javax.sql.XADataSource="oracle.jdbc.xa.client.OracleXADataSource">
<library>
<fileset dir="${shared.resource.dir}/oracle" includes="*.jar"/>
</library>
</jdbcDriver>
Here is the error in the joblog:
com.ibm.jbatch.container.exception.BatchContainerRuntimeException: Failure in Read-Process-Write Loop
at com.ibm.jbatch.container.controller.impl.ChunkStepControllerImpl.invokeChunk(ChunkStepControllerImpl.java:702)
at com.ibm.jbatch.container.controller.impl.ChunkStepControllerImpl.invokeCoreStep(ChunkStepControllerImpl.java:792)
at com.ibm.jbatch.container.controller.impl.BaseStepControllerImpl.execute(BaseStepControllerImpl.java:292)
at com.ibm.jbatch.container.controller.impl.ExecutionTransitioner.doExecutionLoop(ExecutionTransitioner.java:118)
at com.ibm.jbatch.container.controller.impl.WorkUnitThreadControllerImpl.executeCoreTransitionLoop(WorkUnitThreadControllerImpl.java:94)
at com.ibm.jbatch.container.controller.impl.WorkUnitThreadControllerImpl.executeWorkUnit(WorkUnitThreadControllerImpl.java:155)
at com.ibm.jbatch.container.controller.impl.WorkUnitThreadControllerImpl$AbstractControllerHelper.runExecutionOnThread(WorkUnitThreadControllerImpl.java:480)
at com.ibm.jbatch.container.controller.impl.WorkUnitThreadControllerImpl.runExecutionOnThread(WorkUnitThreadControllerImpl.java:90)
at com.ibm.jbatch.container.util.BatchWorkUnit.run(BatchWorkUnit.java:117)
at com.ibm.ws.context.service.serializable.ContextualRunnable.run(ContextualRunnable.java:80)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
Caused by: com.ibm.jbatch.container.exception.BatchContainerRuntimeException: java.sql.SQLException: ORA-01002: fetch out of sequence
at com.ibm.jbatch.container.controller.impl.ChunkStepControllerImpl.readItem(ChunkStepControllerImpl.java:354)
at com.ibm.jbatch.container.controller.impl.ChunkStepControllerImpl.readAndProcess(ChunkStepControllerImpl.java:245)
at com.ibm.jbatch.container.controller.impl.ChunkStepControllerImpl.invokeChunk(ChunkStepControllerImpl.java:626)
... 14 more
Caused by: java.sql.SQLException: ORA-01002: fetch out of sequence
I have additional logs, etc, if they would be helpful. Thanks in advance.
Don't cache JDBC Statement or ResultSet instances across threads. The JDBC programming model does not support multi-threaded access, which is likely why you are running into this error. And also why it works fine from within the open method, which runs on a single thread. When using JDBC, it is okay to cache DataSource, but not Connection or anything beneath Connection. Let the application server manage pooling of Connections and Statements, which it does in a thread safe way.
For a Liferay 6.2 custom portlet accessing a non liferay Oracle database we are running into an issue where the data source returned is null.
We have configured the tomcat/conf/context.xml
<!-- Adding custom New non liferay datasource -->
<Resource name="jdbc/NewPool"
auth="Container"
type="javax.sql.DataSource"
driverClassName="oracle.jdbc.OracleDriver"
url="jdbc:oracle:thin:#(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = dbservernameorip)(PORT = 9999))
(CONNECT_DATA = (SERVER = DEDICATED)
(SERVICE_NAME = dbSIDorservicename)))"
username="user"
password="pwd"
maxActive="35"
maxIdle="10"
maxWait="20000"
removeAbandoned="true"
logAbandoned="true"
minEvictableIdleTimeMillis="3600000"
timeBetweenEvictionRunsMillis="1800000"
testOnBorrow="true"
testOnReturn="false"
/>
The portlet web.xml contains:
<resource-ref>
<description>Oracle Datasource example</description>
<res-ref-name>jdbc/NewPool</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
The code for lookup is:
String JNDI = "jdbc/NewPool"
_log.debug("JNDI Name is: " + JNDI);
_log.debug("dataSource in dbConnect is :" + dataSource);
Context context = new InitialContext();
Context envContext = (Context)context.lookup("java:/comp/env");
_log.debug("envContext in dbConnect is :" + envContext);
try {
DataSource ds = (DataSource)envContext.lookup(JNDI);
Liferay can use the context.xml resource with a similar data source for the Liferay Oracle database successfully.
Is some other wiring needed for Liferay portlet to establish a connection to another database?
The same portlet code works on weblogic without the web.xml change. Similar JNDI data source lookup code and configuration works on vanilla tomcat (without liferay) and a plain war (non liferay portlet) file.
Update:
I have checked db connections on the server with netstat -an|grep dbport. this does not show an established connection.
I have also tried setting the portal.security.manager.strategy=none in portal-ext.properties. This did not work either.
Any insight is much appreciated as we are kind of stuck here.
Thanks!
I just stumbled over this thread in the Liferay Forum which basically says. oput this in your tomcat/conf/server.xml
<GlobalNamingResources>
<!-- Editable user database that can also be used by
UserDatabaseRealm to authenticate users
-->
<Resource auth="Container" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" name="UserDatabase" pathname="conf/tomcat-users.xml" type="org.apache.catalina.UserDatabase"/>
<Resource name="jdbc/XXX" auth="Container" type="javax.sql.DataSource"
maxActive="20" maxIdle="10" maxWait="5000"
removeAbandoned="true" removeAbandonedTimeout="250" validationQuery="SELECT 1"
username="user2" password="pwd2"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost/myOtherDb"/>
and this in your context.xml:
<ResourceLink name="jdbc/XXX" global="jdbc/XXX" type="javax.sql.DataSource">
It should do the trick. If you really asking WHY Liferay can find the jndi resource, but not your portlet:
I have no clue...
We also had some problems using the resource and pools. Since we have very few requests to handle and performance were not a concern for our scenario we used a JDBC connection directly without a pool and it's working fine (we are connecting to a MS Sql server)
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
String url = "jdbc:jtds:sqlserver://host/dbname";
String driver = "net.sourceforge.jtds.jdbc.Driver";
String db_userName = PropsUtil.get("jdbc.default.username");
String db_password = PropsUtil.get("jdbc.default.password");
try {
Class.forName(driver);
conn = DriverManager.getConnection(url, db_userName, db_password);
String sql = "SELECT * FROM Users";
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
while(rs.next()){
// DO WHAT YOU WANT
return true;
}
rs.close();
}catch(SQLException se){
//Handle errors for JDBC
se.printStackTrace();
}catch(Exception e){
//Handle errors for Class.forName
e.printStackTrace();
}finally{
//finally block used to close resources
try{
if(stmt!=null)
conn.close();
}catch(SQLException se){
}// do nothing
try{
if(conn!=null)
conn.close();
}catch(SQLException se){
se.printStackTrace();
}//end finally try
}//end try
And it's working fine. Username and password are configured in portal-ext.properties (We are using the same account used for liferay own db)
Hope this helps
I believe we found our issue. It seems like it was a typo.
All references to dataSource need to be changed to ds. There was a code changed. It turned out the the variable name was not changed to ds in code after the ds variable was declared during troubleshooting the issue.
String JNDI = "jdbc/NewPool"
_log.debug("JNDI Name is: " + JNDI);
_log.debug("dataSource in dbConnect is :" + ds);
Context context = new InitialContext();
Context envContext = (Context)context.lookup("java:/comp/env");
_log.debug("envContext in dbConnect is :" + envContext);
try {
DataSource ds = (DataSource)envContext.lookup(JNDI);
_log.debug("dataSource in dbConnect is :" + ds)
We need to test this out. I will post the results after the final test.
I need to build a small reporting application that is producing a report from a database. For now there are just 2 reports that are cronned daily and weekly. OK folks this is what I'm trying to do to make it easily extendable in the future.
1) Scan reports directory for .properties files whose content is like this:
report.name=Weekly Management report
report.datasource=myDataSource
report.bootstrap.sql=SELECT getdate()
report.cron.expression= 0/2 * * * MON-FRI
report.service.activator.class=reporting.qvalent.JDBCReportExtractor
report.recipient.email=konstantin#localhost
2) For each file, create a spring integration route that will do the following:
a) poll database with a bootstrap query according to cron expression
b) invoke activator class that will actually gather all required data from the datasource and maybe enrich Thymeleaf context
c) merge thymeleaf context with the template and email it
What I do now is this:
public class ReportDefinitionLoader implements BeanDefinitionRegistryPostProcessor {
private ConfigurableListableBeanFactory beanFactory;
private void prepareBeansForReport(Resource resource) throws IOException {
try {
Properties props = PropertiesLoaderUtils.loadProperties(resource);
String reportName = props.getProperty(REPORT_NAME_PROPERTY);
String reportDatasource = props.getProperty(REPORT_DATASOURCE_PROPERTY);
String reportCronExpression = props.getProperty(REPORT_CRON_EXPRESSION_PROPERTY);
String reportBootstrapQuery = props.getProperty(REPORT_BOOTSTRAP_QUERY_PROPERTY);
CronTrigger cronTrigger = new CronTrigger(reportCronExpression);
beanFactory.registerSingleton(reportName + CRON_TRIGGER_BEAN_NAME, cronTrigger);
DataSource dataSource = (DataSource) beanFactory.getBean(reportDatasource);
beanFactory.getBean()
beanFactory.autowireBean(dataSource);
QueueChannel channel1 = new QueueChannel();
JdbcPollingChannelAdapter jdbcPollingChannelAdapter = new JdbcPollingChannelAdapter(dataSource, reportBootstrapQuery);
SourcePollingChannelAdapter adapter = new SourcePollingChannelAdapter();
TimerManagerTaskScheduler taskScheduler = new TimerManagerTaskScheduler();
taskScheduler.schedule(new Runnable() {
#Override
public void run() {
}
}, cronTrigger);
adapter.setOutputChannel(channel1);
adapter.setSource(jdbcPollingChannelAdapter);
adapter.setBeanFactory(beanFactory);
adapter.setTaskScheduler(taskScheduler);
adapter.start();
} catch (IOException e) {
LOG.error("Could not load properties from resource: " + resource.getFile().getName(), e);
}
}
}
But the dataSource bean is having unresolved properties as defined in the XML
<bean id="myDataSource" class = "com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driver}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.user}" />
<property name="password" value="${jdbc.password}" />
Can you please advise how do I get the bean from Spring context for further using it in another class that would also be placed under spring context? At the moment, I'm getting these when it starts up:
2014-09-04 11:15:12,545 [WARN] - Could not load driverClass ${jdbc.driver}
java.lang.ClassNotFoundException: ${jdbc.driver}
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1720)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1571)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:190)
at com.mchange.v2.c3p0.DriverManagerDataSource.ensureDriverLoaded(DriverManagerDataSource.java:100)
at com.mchange.v2.c3p0.DriverManagerDataSource.getConnection(DriverManagerDataSource.java:132)
When I'm using XML configuration, data source properties are resolved OK, so this problem is not because of misconfigured PropertyPlaceholderConfigurer.
I implemented wrong interface - InitializingBean seems to be the right one.
Now I'm creating bean definitions inside afterPropertiesSet() callback. Rubber duck debugging does work! Thanks everyone.
Been pulling my hair trying to set up a data source in in Tomcat for an application. The steps I've taken are
Create a META-INF/context.xml with the following content
<Context>
<Resource name="jdbc/mydb"
auth="Container"
type="javax.sql.DataSource"
driverClassName="oracle.jdbc.OracleDriver"
url="jdbc:oracle:thin:#gpqa6.myserver.com:1526:gpqa6"
username="foo"
password="foobar"
maxActive="20"
maxIdle="30"
maxWait="-1" />
</Context>
Enter the following text in WEB-INF/web.xml
<resource-ref>
<description>Datasource</description>
<res-ref-name>jdbc/mydb</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
Using it in the code
public class MyServlet extends HttpServlet {
private DataSource ds;
public void init() {
try {
Context ctx = new InitialContext();
ds = (DataSource) ctx.lookup("java:comp/env/jdbc/mydb");
} catch (NamingException e) {
e.printStackTrace();
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//this is where the exception is thrown
Connection conn = ds.getConnection();
//...do stuff with the connection
}
But keep on getting the following error
"Encounter exception during mapping. Error: Cannot create PoolableConnectionFactory (ORA-01017: invalid username/password; logon denied
)
org.apache.tomcat.dbcp.dbcp.SQLNestedException: Cannot create PoolableConnectionFactory (ORA-01017: invalid username/password; logon denied"
I know that the username and password are correct. Because the following code works
Class.forName("oracle.jdbc.OracleDriver").newInstance();
conn = DriverManager.getConnection(
"jdbc:oracle:thin:#gpqa6.myserver.com:1526:gpqa6", "foo", "foobar");
I've even tried to enter an invalid url but still get the same error. What's going on???
Also, does Tomcat have some way of testing the data source similar to WebLogic or Glassfish?
Now it's working. Seem like after I establish a connection with the following code
Class.forName("oracle.jdbc.OracleDriver").newInstance();
conn = DriverManager.getConnection(
"jdbc:oracle:thin:#gpqa6.myserver.com:1526:gpqa6", "foo", "foobar");
then switching back to using the data source, it's fine. Maybe it's a caching issue?
--Update--
It is a caching issue. Ijust have to delete the \conf\Catalina folder.
This link really helped.
http://pwu-developer.blogspot.com/2010/02/why-isnt-my-datasource-configuration.html
This page on Spring JDBC says
The DataSourceUtils class … provides static methods to obtain connections from JNDI
However the API doc for DataSourceUtils does not include the said static methods, as far as I can see.
What am I missing?
As I understand, what would be really useful to you is JndiObjectFactoryBean. This Spring factory bean returns object published in JNDI.
You would configure it like this, and then you would get the Connection using DataSourceUtils on the injected DataSource:
<bean name="myDataSourceInJndi" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>java:comp/env/jdbc/MyDataSource</value>
</property>
</bean>
<bean name="myBean" class="MyClass">
...
<property name="dataSource" ref="myDataSourceInJndi">
...
</bean>
Hmm... Somehow, the Javadoc of DataSourceUtils is more "accurate" (I mean, the doc is not wrong but technically, you obtain a connection from a DataSource - that can be obtained via JNDI):
Helper class that provides static methods for obtaining JDBC Connections from a DataSource.
And the following methods should be what you're looking for:
DataSourceUtils.getConnection(DataSource)
DataSourceUtils.getGetConnection(DataSource)
Basic example (from the MySQL documentation):
// Create a new application context. this processes the Spring config
ApplicationContext ctx = new ClassPathXmlApplicationContext("ex1appContext.xml");
// Retrieve the data source from the application context
DataSource ds = (DataSource) ctx.getBean("dataSource");
// Open a database connection using Spring's DataSourceUtils
Connection c = DataSourceUtils.getConnection(ds);
try {
// retrieve a list of three random cities
PreparedStatement ps = c.prepareStatement(
"select City.Name as 'City', Country.Name as 'Country' " +
"from City inner join Country on City.CountryCode = Country.Code " +
"order by rand() limit 3");
ResultSet rs = ps.executeQuery();
while(rs.next()) {
String city = rs.getString("City");
String country = rs.getString("Country");
System.out.printf("The city %s is in %s%n", city, country);
}
} catch (SQLException ex) {
// something has failed and we print a stack trace to analyse the error
ex.printStackTrace();
// ignore failure closing connection
try { c.close(); } catch (SQLException e) { }
} finally {
// properly release our connection
DataSourceUtils.releaseConnection(c, ds);
}
For further future references: the jndi is managed on the application server i.e. : standalone.xml (Wildfly) with Postgresql driver:
<datasource jta="true" jndi-name="java:comp/env/jdbc/MyDataSource" pool-name="myDS" enabled="true">
<connection-url>jdbc:postgresql:[<//host>[:<5432>/]]<database></connection-url>
<driver>postgresql</driver>
<security>
<user-name>$USER</user-name>
<password>$PWD</password>
</security>
</datasource>