Core JSTL[c] Mastering the JSP Standard Tag Library
This section tells you how to create a data source. "How JSTL Locates Data Sources" on page 363 spells out the algorithm that JSTL uses to locate your data source. Fundamentally, there are three ways, listed in Table 9.4, to create a data source. You can specify data source characteristics with a context initialization parameter in your deployment descriptor and let JSTL create a new data source or access an existing one that fits those characteristics. You can specify those characteristics as a JNDI resource or as JDBC parameters. This option is attractive because you don ' t have to write any code. If you care about advanced database features such as connection pooling or distributed transactions, don't use this approach with JDBC parameters, because JSTL will most likely create a data source that's a simplistic wrapper around a JDBC connection. Table 9.4. Creating a Data Source with JSTL [a]
[a] All of the approaches listed in this table can create JDBC or JNDI data sources. [b] A business component can be a servlet, servlet filter, life-cycle listener, Java bean, or custom action. The <sql:setDataSource> action lets you specify a data source in a JSP page. You can specify that data source as an instance of javax.sql.DataSource or as a string that represents a JNDI resource or JDBC parameters. Because you can specify the scope of a data source with <sql:setDataSource>, this option is attractive because you can temporarily set a data source for a particular scope. You can also create a data source in a business component, such as a servlet, servlet filter, life-cycle listener, custom action, or JavaBeans component. This option lets you separate business and presentation logic and gives you total control over the data source that you create. Those characteristics make it the best choice for more complex Web applications developed jointly by page authors and software developers. The following sections discuss the options in Table 9.4:
Specify Your Data Source in the Deployment Descriptor
The easiest way to create a data source is to specify it in your deployment descriptor because you don't have to write any code; for example, Listing 9.1 shows an excerpt from a deployment descriptor that specifies a data source with a JDBC URL and driver. Listing 9.1 WEB-INF/web.xml (Excerpt ”Specifying a Data Source)
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/j2ee/dtds/web-app_2.3.dtd"> <web-app> <context-param> <param-name> javax.servlet.jsp.jstl.sql.dataSource </param-name> <param-value> jdbc:mysql://localhost/core-jstl,org.gjt.mm.mysql.Driver </param-value> </context-param> ... </web-app> The preceding deployment descriptor specifies a data source for the SQL_DATA_SOURCE configuration setting with a context initialization parameter. The name of that context initialization parameter is javax.servlet.jsp.jstl.sql.dataSource , which is the name of the SQL_DATA_SOURCE configuration setting ”see Table 9.2 on page 360. Whenever you specify a configuration setting in the deployment descriptor, you must use the configuration setting's name (in this case, javax.servlet.jsp.jstl.sql.dataSource ) instead of the Config constant by which the configuration setting is known (in this case, SQL_DATA_SOURCE ). See "Configuration Settings" on page 230 for more information about configuration settings and how you can specify them in deployment descriptors and business components .
Note If your JDBC URL contains a comma, you can escape the comma with a backslash, like this: \, . You can also escape the escape character, if necessary, like this: \\ .
When you specify a JDBC data source with a context initialization parameter, that parameter's value must be a string that specifies a JDBC URL. Optionally , you can also specify a JDBC driver, user name, and password. All of those characteristics are specified in one string, separated by commas. The preceding code fragment specifies a JDBC URL ( jdbc:mysql://localhost/core-jstl ) and a JDBC driver ( org.gjt.mm.mysql.Driver) . That code fragment could have also specified a user name and password, like this: <web-app> <context-param> <param-name> javax.servlet.jsp.jstl.sql.dataSource </param-name> <param-value> jdbc:mysql://localhost/core-jstl,org.gjt.mm.mysql.Driver,royBoy,batonRouge </param-value> </context-param> ... </web-app> In the preceding code fragment, royBoy is the user name and batonRouge is the password. If you specify JDBC parameters in your deployment descriptor, like the code fragment listed above, JSTL will create a data source wrapped around a JDBC connection. Typically, that data source won ' t have advanced features like connection pooling or distributed transactions. If you want those advanced features, you can specify a data source of your choosing as a JNDI resource instead of JDBC parameters, like this: <web-app> <context-param> <param-name> javax.servlet.jsp.jstl.sql.dataSource </param-name> <param-value> jdbc/MyDataSource </param-value> </context-param> ... </web-app> In the preceding code fragment, jdbc/MyDataSource specifies a relative path to a JNDI resource. JSTL prepends the context used for Web application resources ”which is standardized by the J2EE specification ”to that relative path; for the preceding code fragment, JSTL will search for a JNDI resource, using the absolute path java:comp/env/jdbc/MyDataSource . Realize that using JNDI does not guarantee that your data source will have advanced features such as connection pooling or distributed transactions; however, using JNDI lets you use a data source that you (or your system administrator) specify. That data source may or may not have advanced features. It's the data source ”not JNDI, which is a directory service ”that provides advanced database features. In contrast, if you specify JDBC parameters in the deployment descriptor or with <sql:setDataSource>, JSTL will create a data source for you, so you have no control over the actual data source. In all likelihood , that data source will not provide advanced features such as connection pooling or distributed transactions. If you specify a data source in the deployment descriptor, that data source is stored in the SQL_DATA_SOURCE configuration setting that ' s accessible to all the JSP pages in your application, so you don't have to explicitly specify a data source for <sql:query>, <sql:update>, and <sql:transaction> actions; for example, you can execute a query like this: <sql:query var='customers'> SELECT * FROM CUSTOMERS </sql:query> You can only specify strings for context initialization parameters, so you can only specify a string for the SQL_DATA_SOURCE configuration setting in the deployment descriptor. If you need to specify an object for your data source, you can do so in a business component or you can use the <sql:setDataSource> action, which is discussed next . Specify Your Data Source with <sql:setDataSource>
The <sql:setDataSource> action lets you specify a data source in a JSP page with this syntax: [6] [6] Items in brackets are optional. See "<sql:setDataSource>" on page 531 for a more complete description of <sql:setDataSource> syntax. <sql:setDataSource [var] [scope] {dataSource url [driver] [user] [password]}/> The <sql:setDataSource> action creates (or accesses an existing) data source [7] and stores it in a scoped variable under the name that you specify with the var attribute. If you don't specify the var attribute, <sql:setDataSource> stores the data source in the SQL_DATA_SOURCE configuration setting. [7] The existing data source must have the exact same characteristics as the specified data source. You can specify a data source with the dataSource attribute of the <sql:setDataSource> action. The value that you specify for the dataSource attribute can be a comma-separated string or a scoped variable that references a string or an instance of javax.sql.DataSource . If that value is a string, it must specify a JNDI resource or JDBC parameters, just like the string that you specify for a data source in the deployment descriptor; for example: <%-- Set values for JDBC url, driver, user name, and password --%> <c:set var='url' value='jdbc:mysql://localhost/core-jstl'/> <c:set var='driver' value='org.gjt.mm.mysql.Driver'/> <c:set var='user' value='royBoy'/> <c:set var='pwd' value='batonRouge'/> <%-- Set the data source for this JSP page --%> <sql:setDataSource dataSource='${url},${driver},${user},${pwd}'/> The preceding code fragment specifies a data source with a JDBC URL, a JDBC driver, and a user name and password. All of those characteristics are specified in a single string, separated by commas. Remember, the driver, user name, and password are all optional. Instead of using the dataSource attribute to specify JDBC parameters with a comma-separated string, as in the preceding code fragment, you can use the url attribute and, optionally, the driver , user , and password attributes, to achieve the same effect, for example: <%-- Set the data source for this JSP page --%> <sql:setDataSource url='jdbc:mysql://localhost/core-jstl' driver='org.gjt.mm.mysql.Driver' user='royBoy' password='batonRouge'/> The preceding code fragment is functionally identical to the code fragment that precedes it. Like the code fragment in "Specify Your Data Source in the Deployment Descriptor" on page 366 that specified a data source in the deployment descriptor, the <sql:setDataSource> action in the preceding code fragment stores the resulting data source in the SQL_DATA_SOURCE configuration setting. Because the scope attribute was not specified for the <sql:setDataSource> action in that code fragment, that data source only applies to the current JSP page. You can specify a different scope, like this: <sql:setDataSource dataSource='${url},${driver},${user},${pwd}' scope='session' /> In the preceding code fragment, the data source specified with JDBC parameters is also stored in the SQL_DATA_SOURCE configuration setting, but that data source applies to session scope, meaning that all <sql:query>, <sql:update>, and <sql:transaction> actions in the current session (that don't specify a dataSource attribute of their own) will implicitly use that data source. You can also specify a data source stored in a JNDI resource with <sql:setDataSource>, just like you can in the deployment descriptor, like this: <sql:setDataSource dataSource='jdbc/MyDataSource'/> In the preceding code fragment, jdbc/MyDataSource specifies a relative path to a JNDI resource. JSTL prepends the context used for Web application resources ”which is standardized by the J2EE specification ”to that relative path; for the preceding code fragment, JSTL will search for a JNDI resource, using the absolute path java:comp/env/jdbc/MyDataSource . In all of the code fragments so far in this section, the <sql:setDataSource> action stores a data source in the SQL_DATA_SOURCE configuration setting, but you can also use <sql:setDataSource> to store a data source in a scoped variable, like this: <sql:setDataSource dataSource='${url},${driver},${user},${pwd}' var='myDataSource' scope='request' /> In the preceding code fragment, the <sql:setDataSource> action stores a data source in a scoped variable named myDataSource . That scoped variable persists only for the current request, but you can change that by specifying a different value for the scope attribute, such as session or application . If you don't specify the scope variable, the scope will default to page and that data source will only be available to SQL actions in the current JSP page. If you use <sql:setDataSource> to store a data source in a scoped variable, as in the preceding code fragment, you must explicitly access that data source in <sql:query>, <sql:update>, and <sql:transaction> actions, like this: <sql:query var='customers' dataSource='${myDataSource}' > SELECT * FROM CUSTOMERS </sql:query> In the preceding code fragment, the <sql:query> action uses the data source specified above with the <sql:setDataSource> action. For the combination of the two preceding code fragments, that <sql:query> action must reside in the same request as the <sql:setDataSource> action. Notice that, unlike specifying a data source in the deployment descriptor, you can specify a scope for a data source with the <sql:setDataSource> action. That feature lets you temporarily override a previously specified data source. For example, if you specify a data source in your deployment descriptor, that data source will apply to all JSP pages in your application. If you subsequently use <sql:setDataSource> (without specifying the var attribute) in a JSP page, the data source you specify with <sql:setDataSource> will temporarily override the data source that you specified in your deployment descriptor. Finally, if you specify JDBC parameters for the <sql:setDataSource> action's dataSource attribute, the data source specified by those parameters will be created by the JSTL implementation. In all likelihood, that data source will not provide advanced features such as connection pooling or distributed transactions. [8] If you need those advanced features, you can specify a data source of your choosing stored in a JNDI resource or you can create your data source in a business component; the latter is discussed in the next section. [8] That restriction is the same for data sources specified with JDBC parameters in the deployment descriptor. See "Specify Your Data Source in the Deployment Descriptor" on page 366 for more information. Create Your Data Source in a Business Component
Besides specifying data sources in deployment descriptors or with the <sql:setDataSource> action, you can also create a data source in a business component, such as a servlet or a life-cycle listener. Of those three approaches, creating a data source in a business component is usually the best option for two reasons. First, you need not specify a data source in your JSP pages, and so you gain a degree of separation between business and presentation logic. Second, you have total control over the data source that you create, which is not the case if JSTL creates a data source for you. The drawback to creating a data source in a business component is that you must write and compile Java code. Because of that requirement, creating a data source in a business component is most appropriate for more complex Web applications developed jointly by page authors and Java developers. This section shows you how to create a data source with an initialization servlet. A Simple Data Source
Before we can create a data source, we need a data source implementation. For illustration only, Listing 9.2 lists a simple data source that acts as a wrapper around a JDBC connection. The data source listed in the preceding code is unremarkable; its only purpose is to provide a data source that we can create in a business component. In a production environment, data sources that you use will most likely be implemented by your database vendor and will probably offer amenities such as connection pooling or distributed transactions. Listing 9.2 WEB-INF/classes/beans/SimpleDataSource.java
package beans; import java.io.PrintWriter; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import javax.sql.DataSource; public class SimpleDataSource implements DataSource { private String driver, url; private boolean driverLoaded = false; public SimpleDataSource(String driver, String url) { this.driver = driver; this.url = url; } public synchronized Connection getConnection() throws SQLException { return getConnection(null, null); } public synchronized Connection getConnection( String user, String pwd) throws SQLException { Connection connection = null; if(!driverLoaded) { try { Class.forName(driver).newInstance(); driverLoaded = true; } catch(ClassNotFoundException ex) { throw new SQLException("Can't find driver " + driver); } } if(user == null pwd == null) connection = DriverManager.getConnection(url); else connection = DriverManager.getConnection(url, user, pwd); return connection; } public PrintWriter getLogWriter() throws SQLException { return null; } public void setLogWriter(PrintWriter logWriter) throws SQLException { throw new SQLException("Logging not supported"); } public int getLoginTimeout() throws SQLException { return 0; } public void setLoginTimeout(int loginTimeout) throws SQLException { throw new SQLException("Login timeout not supported"); } } Creating a Data Source in an Initialization Servlet
Implementing a servlet that creates a data source and makes it available to the JSTL SQL actions is a simple matter, as evidenced by the servlet listed in Listing 9.3. Listing 9.3 WEB-INF/classes/InitializationServlet.java (Initialization Servlet That Creates a Data Source)
import javax.servlet.*; import javax.servlet.http.*; import beans.SimpleDataSource; import javax.servlet.jsp.jstl.core.Config; public class InitializationServlet extends HttpServlet { private ServletContext app = null; // the application public void init() throws ServletException { // Create the data source SimpleDataSource dataSource = new SimpleDataSource ( "org.gjt.mm.mysql.Driver", // db driver "jdbc:mysql://localhost/core-jstl"); // db URL // Store the data source in the SQL_DATA_SOURCE // configuration setting Config.set(getServletContext(), Config.SQL_DATA_SOURCE, dataSource); } public void destroy() { // Remove the SQL_DATA_SOURCE configuration setting Config.remove(getServletContext(), Config.SQL_DATA_SOURCE); } } The servlet listed above creates an instance of SimpleDataSource , specifying a JDBC URL and driver. The SimpleDataSource class is discussed in "A Simple Data Source" on page 372. The servlet stores a data source in the SQL_DATA_SOURCE configuration setting by using the Config.set method. That static method stores an object in a configuration setting; see "The Config Class" on page 239 for more information about the Config class. Because the Config.set method is passed a reference to the servlet context (the application), the data source will be available to all JSP pages in the application. When the servlet is destroyed , it removes the data source from the SQL_DATA_SOURCE configuration setting with the Config.remove method. [9] [9] See "Configuration Settings" on page 230 for more information about configuration settings and the Config class. To ensure that the data source created by the preceding servlet is available to all of your JSP pages, you should load that servlet at startup, which you can do by specifying the <load-on-startup> element in your deployment descriptor. Listing 9.4 lists an excerpt from the deployment descriptor that shows you how to specify that element. Listing 9.4 WEB-INF/web.xml (Excerpt ”Specifying the <load-on-startup> Element)
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/j2ee/dtds/web-app_2.3.dtd"> <web-app> ... <servlet> <servlet-name>initServlet</servlet-name> <servlet-class>InitializationServlet</servlet-class> <load-on-startup/> </servlet> ... </web-app> Because the servlet listed in Listing 9.3 stores a data source in the SQL_DATA_SOURCE configuration setting, you do not need to explicitly specify that data source in your JSP pages, as discussed in "How JSTL Locates Data Sources" on page 363. If you prefer, you can store your data source in a scoped variable instead of the SQL_DATA_SOURCE configuration setting, like this: public class InitializationServlet extends HttpServlet { private String dsName = "myDataSource"; // the data source name public void init() throws ServletException { ... // Store the data source in application scope getServletContext().setAttribute(dsName, dataSource); } public void destroy() { // Remove the data source from application scope getServletContext().removeAttribute(dsName); } } If you store your data source in a scoped variable, as in the preceding code fragment, you must explicitly access that data source with <sql:query>, <sql:update>, and <sql:transaction> actions, as discussed in "How JSTL Locates Data Sources" on page 363. Because the servlet listed in Listing 9.3 hardcodes the JDBC URL and driver, you will have to modify and recompile that servlet if you change your database management system. You can eliminate that dependency by specifying the names of your JDBC URL and driver in your deployment descriptor; for example, Listing 9.5 lists an excerpt from the deployment descriptor that does just that. Listing 9.5 WEB-INF/web.xml (Excerpt ”Specifying JDBC URL and Driver)
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/j2ee/dtds/web-app_2.3.dtd"> <web-app> ... <context-param> <param-name> application.sql.url </param-name> <param-value> jdbc:mysql://localhost/core-jstl </param-value> </context-param> <context-param> <param-name> application.sql.driver </param-name> <param-value> org.gjt.mm.mysql.Driver </param-value> </context-param> <servlet> <servlet-name>initServlet</servlet-name> <servlet-class>InitializationServlet</servlet-class> <load-on-startup/> </servlet> ... </web-app> The preceding deployment descriptor specifies context initialization parameters for a JDBC URL and driver. Those context initialization parameters are used by the servlet listed in Listing 9.6. Listing 9.6 WEB-INF/classes/InitializationServlet.java (Using Context Initialization Parameters for JDBC URL and Driver)
import javax.servlet.*; import javax.servlet.http.*; import beans.SimpleDataSource; import javax.servlet.jsp.jstl.core.Config; public class InitializationServlet extends HttpServlet { private String dsName = "myDataSource"; // the data source name public void init() throws ServletException{ ServletContext app = getServletContext(); // Retrieve the data source name, driver, and url from // initialization parameters String driver = app.getInitParameter("application.sql.driver"), url = app.getInitParameter("application.sql.url"); // Create the data source SimpleDataSource dataSource = new SimpleDataSource ( driver, url); // Store the data source in the SQL_DATA_SOURCE // configuration setting Config.set(app, Config.SQL_DATA_SOURCE, dataSource); } public void destroy() { // Remove the SQL_DATA_SOURCE configuration setting Config.remove(getServletContext(), Config.SQL_DATA_SOURCE); } } The preceding servlet uses the ServletContext.getInitParameter method to retrieve the values for the context initialization parameters specified in the deployment descriptor listed in Listing 9.5. Those values are subsequently used to create a data source, which the servlet stores in the SQL_DATA_SOURCE configuration setting.
|