Should you set up database connection properties in server.xml or context.xml
Asked Answered
T

5

83

I am trying to set up the database connection properties using JNDI for a Spring web application.

I am considering two approaches as below:

Approach 1:

In your Spring configuration you may have something like:

<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/facs"/>

Then in your webapp /META-INF/context.xml file you should have something similar too:

<?xml version='1.0' encoding='utf-8'?>

<!-- antiResourceLocking="true" -->
<Context path="/podd-apn"
         reloadable="true"
         cachingAllowed="false"
         antiResourceLocking="true"
         >

  <Resource name="jdbc/facs"              
            type="javax.sql.DataSource" username="${database.username}" password="${database.password}"
            driverClassName="org.postgresql.Driver" 
            url="${database.url}"
            maxActive="8" maxIdle="4"
            global="jdbc/facs" 
            />


</Context>

And in your web.xml you should something like:

<!-- JNDI -->
  <resource-ref>
    <description>FACs Datasource</description>
    <res-ref-name>jdbc/facs</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
  </resource-ref> 


Approach 2:

Setup in the Spring context like this:

<jee:jndi-lookup id="dbDataSource"
   jndi-name="jdbc/DatabaseName"
   expected-type="javax.sql.DataSource" />

You can declare the JNDI resource in Tomcat's server.xml using something like this:

<GlobalNamingResources>
  <Resource name="jdbc/DatabaseName" auth="Container" type="javax.sql.DataSource"
              username="dbUsername" password="dbPasswd"
              url="jdbc:postgresql://localhost/dbname"
              driverClassName="org.postgresql.Driver"
              initialSize="5" maxWait="5000"
              maxActive="120" maxIdle="5"
              validationQuery="select 1"
              poolPreparedStatements="true"/>
</GlobalNamingResources/>

And reference the JNDI resource from Tomcat's web context.xml like this:

<ResourceLink name="jdbc/DatabaseName"
   global="jdbc/DatabaseName"
   type="javax.sql.DataSource"/>


My question is where is the best place to keep database properties? Should they be placed in server.xml or context.xml?

Also, if I have 2 databases, should I use two configs?

Also, is it best practice to directly place them in either server.xml or context.xml? Or do I need to configure through Tomcat Manager GUI console?

Thanks!

Triadelphous answered 25/2, 2013 at 9:55 Comment(0)
P
29

I prefer a third approach that takes the best from Approach 1 and Approach 2 described by user1016403.

Approach 3

  1. Save database properties on the server.xml
  2. reference the server.xml database properties from the web application META-INF/context.xml

Approach 3 benefits

While the first point is useful for security reasons the second point is useful for referencing server properties value from the web application, even if server properties values will change.

Moreover decoupling resource definitions on the server from their use by the web application makes such configuration scalable across organizations with various complexity where different teams work on different tiers/layers: the server administrators team can work without conflicting with developers team if the administrator shares the same JNDI name with the developer for each resource.

Approach 3 implementation

Define the JNDI name jdbc/ApplicationContext_DatabaseName.

Declare the jdbc/ApplicationContext_DatabaseName's various properties and values in Tomcat's server.xml using something like this:

<GlobalNamingResources>
  <Resource name="jdbc/ApplicationContext_DatabaseName" auth="Container" type="javax.sql.DataSource"
              username="dbUsername" password="dbPasswd"
              url="jdbc:postgresql://localhost/dbname"
              driverClassName="org.postgresql.Driver"
              initialSize="5" maxWait="5000"
              maxActive="120" maxIdle="5"
              validationQuery="select 1"
              poolPreparedStatements="true"/>
</GlobalNamingResources/>

Link the jdbc/ApplicationContext_DatabaseName's properties from web application META-INF/context.xml by an application-private JNDI context java:comp/env/ specified in the name attribute:

<Context path="/ApplicationContext" ... >
  <!--
    "global" attribute links to GlobalNamingResources in the ${catalina.base}/conf/server.xml (server administrator team)
    "name" attribute is relative to the application-private JNDI context java:comp/env/ and is looked up from the java web application (application developer team)
  -->
  <ResourceLink global="jdbc/ApplicationContext_DatabaseName" name="jdbc/DatabaseName" type="javax.sql.DataSource"/>
</Context>

Finally, in order to use the JNDI resource, specify the JNDI name jdbc/DatabaseName in web application's deployment descriptor:

<resource-ref>
    <description>DatabaseName's Datasource</description>
    <res-ref-name>jdbc/DatabaseName</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
</resource-ref> 

and in Spring context:

<jee:jndi-lookup id="DatabaseNameDataSource"
   jndi-name="jdbc/DatabaseName"
   expected-type="javax.sql.DataSource" />

Approach 3 drawbacks

If the JNDI name gets changed then both the server.xml and the META-INF/context.xml will have to be edited and a deploy would be necessary; nevertheless this scenario is rare.

Approach 3 variations

Many data sources used by one web application

Simply add configurations to Tomcat's server.xml:

<GlobalNamingResources>
  <Resource name="jdbc/ApplicationContext_DatabaseName1" ... />
  <Resource name="jdbc/ApplicationContext_DatabaseName2" ... />
  ...
</GlobalNamingResources/>

Add link web application META-INF/context.xml by an application-private JNDI context java:comp/env/ specified in the name attribute:

<Context path="/ApplicationContext" ... >
  <ResourceLink global="jdbc/ApplicationContext_DatabaseName1" name="jdbc/DatabaseName1" ... />
  <ResourceLink global="jdbc/ApplicationContext_DatabaseName2" name="jdbc/DatabaseName2" ... />
  ...
</Context>

Finally add JNDI resources usage in web application's deployment descriptor:

<resource-ref>
    <description>DatabaseName1's Datasource</description>
    <res-ref-name>jdbc/DatabaseName1</res-ref-name> ... 
</resource-ref> 
<resource-ref>
    <description>DatabaseName2's Datasource</description>
    <res-ref-name>jdbc/DatabaseName2</res-ref-name> ... 
</resource-ref>
...

and in Spring context:

<jee:jndi-lookup id="DatabaseName1DataSource"
   jndi-name="jdbc/DatabaseName1" ... />
<jee:jndi-lookup id="DatabaseName2DataSource"
   jndi-name="jdbc/DatabaseName2" ... />
...


Many data sources used by many web application on the same server

Simply add configuration to Tomcat's server.xml:

<GlobalNamingResources>
  <Resource name="jdbc/ApplicationContextX_DatabaseName1" ... />
  <Resource name="jdbc/ApplicationContextX_DatabaseName2" ... />
  <Resource name="jdbc/ApplicationContextY_DatabaseName1" ... />
  <Resource name="jdbc/ApplicationContextY_DatabaseName2" ... />
  ...
</GlobalNamingResources/>

the others configuration should be deducible from previous variation case.


Many data sources to the same database used by many web application on the same server

In such case a Tomcat's server.xml configurations like:

<GlobalNamingResources>
  <Resource name="jdbc/ApplicationContextX_DatabaseName" ... />
  <Resource name="jdbc/ApplicationContextY_DatabaseName" ... />

ends up in two different web applications META-INF/context.xml like:

<Context path="/ApplicationContextX" ... >
  <ResourceLink global="jdbc/ApplicationContextX_DatabaseName" name="jdbc/DatabaseName" ... />
</Context>

and like:

<Context path="/ApplicationContextY" ... >
  <ResourceLink global="jdbc/ApplicationContextY_DatabaseName" name="jdbc/DatabaseName" ... />
</Context>

so someone might be worried about the fact that the same name="jdbc/DatabaseName" is looked up, and then used, by two different applications deployed on the same server: this is not a problem because the jdbc/DatabaseName is an application-private JNDI context java:comp/env/, so ApplicationContextX by using java:comp/env/ can't (by design) look up the resource linked to global="jdbc/ApplicationContextY_DatabaseName".

Of course if you felt more relaxed without this worry you might use a different naming strategy like:

<Context path="/ApplicationContextX" ... >
  <ResourceLink global="jdbc/ApplicationContextX_DatabaseName" name="jdbc/applicationXprivateDatabaseName" ... />
</Context>

and like:

<Context path="/ApplicationContextY" ... >
  <ResourceLink global="jdbc/ApplicationContextY_DatabaseName" name="jdbc/applicationYprivateDatabaseName" ... />
</Context>
Pavid answered 29/4, 2014 at 15:24 Comment(4)
Question regarding your "Many data sources to the same database used by many web application on the same server" scenario... <Resource name="jdbc/ApplicationContextX_DatabaseName" ... /> <Resource name="jdbc/ApplicationContextY_DatabaseName" ... /> If the resources were connection pools, would this give you two separate pools, one per webapp? Whereas, if I linked from both webapps to one resource, there would be just one connection pool, correct? Any reasons to prefer one to the other? (separate DB connection pools one per webapp vs one connection pool shared by all webapps)? Thanks.Pipe
@Pipe - Q1: If the resources were connection pools, would this give you two separate pools, one per webapp? A1: Yes it would.Pavid
@Pipe - Q2: Whereas, if I linked from both webapps to one resource, there would be just one connection pool, correct? A2: Correct.Pavid
@Pipe - Q3: Any reasons to prefer one to the other? (separate DB connection pools one per webapp vs one connection pool shared by all webapps)? A3: I prefer "separate DB connection pools one per webapp" because I wouldn't that intensive use of webAppX ends up in slowness of webAppY due to exhaustion of webAppX's connection pool; moreover the database monitoring and logging system couldn't distinguish between webAppX and webAppY requests, so understanding and isolating problems will be difficult or worse impossible.Pavid
H
24

YOUR_APP.xml file

I prefer Approach 2 (put everything (not only some attribute in the config), but instead of placing them in the global server.xml or global context.xml you should place it in the application-specific context.xml.default YOUR_APP.xml file in your Tomcat.

The YOUR_APP.xml file is located in $catalinaHome/conf/<engine>/<host> (for example conf/Catalina/localhost/YOUR_APP.xml).

The configuration in application specific YOUR_APP.xml is only available for the specific application.

See the guide published by MuleSoft. And see the official documentation, Tomcat Configuration Reference, page for The Context Container

To quote that documentation:

Individual Context elements may be explicitly defined:

• …

• In individual files (with a ".xml" extension) in the $CATALINA_BASE/conf/[enginename]/[hostname]/ directory. The context path and version will be derived from the base name of the file (the file name less the .xml extension).

• …

Hamman answered 25/2, 2013 at 11:21 Comment(7)
Thanks for ur answer. if i place all the properties in our applications META-INF/context.xml? is it best place to keep?Triadelphous
I do not think that placing some properties value INSIDE (for exampl in META-INF/context.xml) the application is a good approach, because then you have to recompile and deploy the application if the properties changes. - So this would be nearly the same like using no properties at all and placing the values directly in the spring config.xmlHamman
Then which is the recommended place to keep them?Triadelphous
am using Tomcat6..i dont see any such file over there.....whatever context file i have in META_INF folder, that gets copied right?Triadelphous
I would just add, instead of defining these for all apps using context.xml.default, you can use application-specific context configuration files, like yourapp.xml in the same folder, which allows you to point two deployments of the same war to different databases.Richardo
Not a helpful answer. It just states a preference and provides no justification.Typhoeus
@BasilBourque: tomcat.apache.org/tomcat-8.0-doc/config/context.html section "Defining a context" , (not so official: mulesoft.com/tcat/tomcat-context)Hamman
C
10

Approach 4

Instead of using JNDI I work with .properties files and build complex object during program initialization instead on configuration time.

You already use Spring and it is easy construct DataSource by:

<context:property-placeholder location="classpath:app.properties"/>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="oracle.jdbc.OracleDriver"/>
    <property name="url" value="jdbc:oracle:thin:@${db.host}:${db.port}:${db.user}"/>
    <property name="username" value="${db.user}"/>
    <property name="password" value="${db.pass}"/>
</bean>

I completely agree with Ralph with using deployment descriptor in $CATALINA_BASE/conf/[enginename]/[hostname]/$APP.xmlbut instead JNDI I like plain key-value file!

With Spring injecting above properties into bean fields are easy:

@Value("${db.user}") String defaultSchema;

instead of JNDI:

@Inject ApplicationContext context;
Enviroment env = context.getEnvironment();
String defaultSchema = env.getProperty("db.user");

Note also that EL allow this (default values and deep recursive substitution):

@Value('${db.user:testdb}') private String dbUserName;

<property name='username' value='${db.user.${env}}'/>

To externalize .properties file I use modern Tomcat 7 that has org.apache.catalina.loader.VirtualWebappLoader:

<Loader className="org.apache.catalina.loader.VirtualWebappLoader"
        virtualClasspath="/srv/web/app/"/>

So your devops fill virtualClasspath with local external full paths which is separate per application and put local app.propertiesto that dir.

See also:

Cymbal answered 1/10, 2014 at 16:48 Comment(0)
F
1

step 1: context.xml

    <Context path="/projectname">
  <Resource auth="Container" 
            driverClassName="com.mysql.jdbc.Driver"
            logAbandoned="true" 
            maxActive="100" ``
            maxIdle="30" 
            maxWait="10000" 
            name="refname" 
            removeAbandoned="true" 
            removeAbandonedTimeout="60" 
            type="javax.sql.DataSource" 
            url="jdbc:mysql://localhost:8080/dbname" 
            username="root"
            password="root"/>
</Context>

Step 2 : web.xml

<resource-ref>
        <description>DB Connection</description>
        <res-ref-name>refname</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>

Step 3 : create a class to get connection

Connection connection = null;        
            Context context = (Context) new InitialContext().lookup("java:comp/env");
            DataSource ds = (DataSource) context.lookup("refname");
            connection = ds.getConnection();

Everything is set

Figurative answered 27/11, 2017 at 12:53 Comment(0)
A
0

You also can use JNDI URL support for different application configuration for test, integration test, production.

<Context>
...
<Resource auth="Container" factory="com.benasmussen.jndi.url.URLFactory" 
name="url/MyUrl" type="java.net.URL" url="file:///your/path/to/file"/>
...
</Context>

<jee:jndi-lookup id="myUrl" jndi-name="java:comp/env/url/MyUrl" expected-type="java.net.URL" />

Check out the GitHub project Tomcat JNDI URL Support to enable JNDI URL support for Tomcat servers.

Aedile answered 28/12, 2013 at 19:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.