Examples of Using Java JDBC Driver
Hockey Database
The product installation comes with some sample data (North American ice hockey statistics from 1917-2011). Don’t worry, you don’t need to know anything about ice hockey to use this data!
If you don’t have a database yet, follow the appropriate Quick Start Guide.
If you already have a database, you can load the Hockey schema by running the following:
cd NUODB_HOME/samples/quickstart/sql
nuosql <db-name> --user <username> --password <pwd> --schema Hockey --file create-db.sql
nuosql <db-name> --user <username> --password <pwd> --schema Hockey --file Players.sql
nuosql <db-name> --user <username> --password <pwd> --schema Hockey --file Scoring.sql
nuosql <db-name> --user <username> --password <pwd> --schema Hockey --file Teams.sql
Using Pure JDBC
This example assumes you have created a data source using one of the examples in Database Operations Using Java JDBC Driver.
Then, to find the first player in the Players table, with id aaltoan01
:
/* See 'Database Operations Using Java JDBC Driver' page for how to initialize the DataSource. */
DataSource dataSource = ...;
try (java.sql.Connection dbConnection = dataSource.getConnection()) {
/* Find player with ID = 10 */
String id = "aaltoan01";
try (PreparedStatement stmt = //
dbConnection.prepareStatement("SELECT firstname, lastname FROM Players WHERE playerid=?")) {
stmt.setString(1, id);
ResultSet rs = stmt.executeQuery();
if (rs.next())
System.out.println("Found: " + rs.getString(1) + ' ' + rs.getString(2));
} // End block automatically runs stmt.close() which in turn closes rs
} // End block automatically runs dbConnection.close();
Note that creating the Connection
and PreparedStatement
is done inside a try block so they are automatically closed when they go out of scope.
Using Spring’s JdbcTemplate
Spring supports the DRY (Don’t Repeat Yourself) principle: Don’t write repetitive code over and over.
Spring’s JdbcTemplate
embodies the DRY concept by hiding (encapsulating) much of the tedious complexity of using JDBC.
JdbcTemplate
is also a class that can be used standalone without the rest of Spring if desired.
For example, to find the first player in the Players table, with id aaltoan01
:
/* See 'Database Operations Using Java JDBC Driver' page for how to initialize the DataSource. */
DataSource dataSource = ...;
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
/* queryForMap is just one of the many query methods available. */
Map<String, Object> result = jdbcTemplate
.queryForMap("SELECT firstname, lastname FROM Players WHERE playerid=?", "aaltoan01");
/* The map is an instance of org.springframework.util.LinkedCaseInsensitiveMap holding */
/* column-name/value pairs from the retrieved `ResultSet` (which must contain a single row). */
/* The keys (column-names) are case insensitive. */
System.out.println("Found: " + result.get("FIRSTNAME") + ' ' + result.get("lastname"));
There is no JDBC in this code and the query is reduced to a single API call (queryForMap
).
In general the only JDBC you need to use with JdbcTemplate
is to process a ResultSet
, since Spring cannot know what data you will fetch.
All the methods on JdbcTemplate that access the database create a connection, use it and close it immediately after use to avoid resource leakage.
The template never keeps an unused connection open.
However this aggressive opening and closing of connections requires the use of a connection pool to make it efficient - 'opening' a connection borrows it from the pool and 'closing' it returns it back to the pool again.
|
Using Spring Configuration
Going one step further, here is an example using Spring Bean configuration.
/**
* Configuration classes are used to create Spring Beans. All methods
* annotated with @Bean are invoked automatically. In this case it creates
* our DataSource bean.
*/
@Configuration
class DbSetup {}
@Bean
public DataSource dataSource() {
/* See 'Database Operations Using Java JDBC Driver' page for */
/* how to initialize your NuoDB DataSource. */
...
return dataSource;
}
}
/**
* Services contain transactional methods. Spring automatically creates an
* instance of any class annotated with @Service and automatically wraps
* a transaction around any method annotated with @Transactional.
*/
@Service
class PlayerService {
private PlayerRepository playerRepository;
@Autowired
public PlayerService(PlayerRepository playerRepository) {
this.playerRepository = playerRepository;
}
@Transactional(readOnly=true)
public void displayPlayerWithId(String Id) {
/* Map of column-value pairs from the Player's row in database */
Map<String, Object> playerInfo = playerRepository.findById(id);
System.out.println("Found: " + playerInfo.get("FIRSTNAME")
+ ' ' + playerInfo.get("lastname"));
}
}
/**
* Repositories or DAOs encapsulate data-access. Here we use a JDBC
* Template to fetch the data and return it as a map of column-value
* pairs. Spring automatically creates an instance of any class
* annotated with @Repository.
*/
@Repository
class PlayerRepository {
private JdbcTemplate jdbcTemplate;
/* Spring will automatically provide the data source during creation. */
@Autowired
public PlayerRepository(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
public Map<String, Object> findPlayerById(String Id) {
/* Find player with given id */
String id = "aaltoan01";
Map<String, Object> result = jdbcTemplate.queryForMap
("SELECT firstname, lastname FROM Players WHERE playerid=?", id);
return result;
}
}
Spring supports a concept known as Separation of Concerns, separating application setup and configuration from the business logic that is actually important.
Configuration is isolated in dedicated classes (or XML definitions) known as Spring Beans .
We have already seen the bean class DbSetup
used to create the DataSource
bean.
Class path scanning is also used to find and create Spring Beans.
An instance of any class annotated with @Component
, @Service
, @Repository
or @Configuration
is automatically created at start up.
In the example above, an instance of DBSetup
, PlayerService
and PlayerRepository
would each be created as a Spring Bean.
Notice the @Transactional
annotation on the displayPlayerWithId()
method.
Spring automatically wraps such methods inside a transaction, and commits when the methods returns.
If the method throws a RuntimeException
(or a subclass of RuntimeException
), Spring performs a rollback instead.
It does not perform a rollback if any other type of exception is thrown.
The JdbcTemplate
is integrated with Spring’s transactional support.
If a transaction is already in place, it automatically joins that transaction, using the transaction’s underlying connection.
Only if there is no transaction running in the current thread does it create and manage a connection of its own.
Using Spring Boot
Spring Boot goes a step further and, given some configuration properties, sets up default, obvious Spring configurations for you. Spring Boot thus applies the DRY principle to Spring itself.
Inside Spring Boot there is a default JDBC setup, typical of how most applications would use JDBC.
All you have to do is fill in the blanks to make it work (via properties in Spring Boot’s application.properties
configuration file).
Spring Boot is fundamentally a Programmable Spring Bean Generator.
Here is what application.properties
looks like in our case.
# FILE: application.properties
#
# DATASOURCE PROPERTIES
# Default DataSource connection properties
spring.datasource.driver-class-name=com.nuodb.jdbc.Driver
spring.datasource.url=jdbc:com.nuodb://localhost/test
spring.datasource.username=dba
spring.datasource.password=goalie
spring.datasource.platform=nuodb
# Schema to use
spring.datasource.hikari.schema=Hockey
Things to note:
-
The
spring.datasource
properties are standard Spring Boot configuration properties, we have simply set them up for NuoDB. -
By default Spring Boot creates a pooled data source using the Hikari open-source connection pool library.
-
The
spring.datasource.hikari.schema
is a Spring Boot property, specific to configuring Hikari, that allows us to set a default schema. Alternativley, putschema=Hockey
in the URL.
The many, many properties that can be used to control Spring Boot are defined in the Common Application Properties section of the Spring Boot documentation. |
Here is the application itself:
/**
* JDBC DataSource using Spring Boot.
*
* @author Paul Chapman
*/
@EnableTransactionManagement(proxyTargetClass = true)
@SpringBootApplication
public class SpringBootJdbcClient {
public static void main(String[] args) {
/* Run Spring Boot, using this class as the starting point for configuration. */
SpringApplication.run(SpringBootJdbcClient.class, args);
}
/**
* Spring Boot recommended way to run code at startup. As this class is marked
* as an {@code @Component}, Spring will automatically create an instance. As it
* implements {@code CommandLineRunner}, Spring Boot will automatically invoke
* its run method after all configuration is complete.
*/
@Component
public class ClientRunner implements CommandLineRunner {
/* Spring Boot will automatically create the data source and set this */
/* data-member (even though it is private). */
@Autowired
private DataSource dataSource;
@Override
@Transactional
public void run(String... args) throws Exception {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
Map<String, Object> result = jdbcTemplate
.queryForMap("SELECT firstname, lastname FROM Players WHERE playerid=?", "aaltoan01");
/* The map is an instance of org.springframework.util.LinkedCaseInsensitiveMap */
/* The keys are case insensitive. */
System.out.println("Found: " + result.get("FIRSTNAME") + ' ' + result.get("lastname"));
/* This is not a long-running application but Spring Boot doesn't know that, */
/* quit now */
System.exit(0);
}
}
}
How it works:
-
Spring automatically creates an instance (Spring Bean) of any class annotated with
@Component
- likeClientRunner
. -
Spring Boot automatically creates a
DataSource
Spring Bean using thespring.datasource
property values. -
Any Spring Bean can be injected (set) into any other Spring Bean using
@Autowired
- in this case Spring injects the data source into the instance ofClientRunner
immediately after creation. -
Spring Boot automatically invokes
ClientRunner.run()
because it implementsCommandLineRunner
.
Using NuoDB’s DataSource with Spring Boot
To use a NuoDB DataSource instead of Hikari:
-
Override the data source creation by adding a Bean method to
SpringBootJdbcClient
or (as below) to a dedicated configuration class -
The
@Bean
annotation tells Spring that this is a method it should use to create a Spring Bean. -
Spring automatically invokes all
@Bean
methods at startup. -
As this produces a bean of a type known to Spring Boot (a
DataSource
instance), Spring Boot will use it and not create its default Hikari data source.-
The
spring.datasource
properties inapplication.properties
will no longer be used. In the example below we provide NuoDB properties via a dedicated properties file (nuodb.properties
).
-
/**
* Configuration classes are used to create Spring Beans. All methods
* annotated with @Bean are invoked automatically. In this case it creates
* our DataSource bean. Provided this class is in the same package as
* SpringBootJdbcClient (or a sub-package) it will be found and instantiated.
*/
@Configuration
public class DbSetup {
...
@Bean
public DataSource dataSource() throws IOException {
/* Using a Java properties file */
Properties properties = new Properties();
properties.load(new FileInputStream("nuodb.properties"));
return new com.nuodb.jdbc.DataSource(properties);
}
}
Spring and Database Initialization
Spring can automatically initialize your database by running schema-${platform}.sql
and data-${platform}.sql
if present at the root of the classpath.
This is especially useful for testing, ensuring the database is always the same before every test.
When using Spring Boot, the platform
value should be set in application.properties
using one of these properties:
-
spring.sql.init.platform=nuodb
(Spring Boot 2.7 or later) -
spring.datasource.platform=nuodb
(Spring Boot 2.6 or earlier).
Spring Boot will look for and run schema-nuodb.sql
and data-nuodb.sql
.
In schema-nuodb.sql
remember to first use DROP TABLE IF EXISTS …
to remove any tables from a previous test before recreating them.
If using Hibernate, set spring.jpa.hibernate.ddl-auto=none
to prevent Hibernate also performing initialization.
Defining a JNDI Resource (Tomcat)
Here is the XML configuration to enable an application container to create a NuoDB DataSource.
<Resource name="jdbc/nuoDB"
auth="Container"
type="com.nuodb.jdbc.DataSource"
initialSize="2" maxActive="100" maxIdle="30"
maxWait="10000" maxAge="60000"
username="dba"
password="goalie"
defaultSchema="hockey"
defaultReadOnly="false"
defaultAutoCommit="true"
testOnReturn="false"
testOnBorrow="true"
testWhileIdle="false"
validationQuery="SELECT 1 /* Your App Name */ FROM DUAL"
validationInterval="30000"
timeBetweenEvictionRunsMillis="5000"
url-delimiter="|"
url="jdbc:com.nuodb://localhost/test" />
-
The JNDI name,
jdbc/nuoDB
, can be anything you want although there is a naming convention ofresource-type/resource-name
. -
The use of
testOnBorrow
is optional, specifyfalse
to disable. See Validating Connections Example for more details of using a validation query, in particular the cautionary note. -
This resource definition goes in the
META-INF/context.xml
of your web application. You can also put it inCATALINA_HOME/conf/server.xml
(whereCATALINA_HOME
is the Tomcat installation directory), which allows multiple web applications (WAR files) to share the same database. -
For information on the properties shown in the example above, see DataSource Properties.
In your web-application, you need to add this in your WEB-INF/web.xml
:
<resource-ref>
<description>
Resource reference to a factory for java.sql.Connection
instances that may be used for talking to a particular
database that is configured in the <Context>
configuration for the web application.
</description>
<res-ref-name>
jdbc/nuoDB
</res-ref-name>
<res-type>
javax.sql.DataSource
</res-type>
<res-auth>
Container
</res-auth>
</resource-ref>
To access this resource, use code like this:
try {
envContext = new InitialContext();
Context initContext = (Context)envContext.lookup("java:/comp/env");
DataSource ds = (DataSource)initContext.lookup("jdbc/nuoDB");
... Use the data source
} catch(SQLException sqle) {
sqle.printStackTrace();
} catch (NamingException ne) {
ne.printStackTrace();
}
Alternatively, if using Spring Boot 1.2 or later, all you need to do is set the following property in your applications.properties
file and Spring Boot will auto-create a DataSource
in the usual way.
spring.datasource.jndi-name=jdbc/nuoDB
Connection Pooling Example
This example can be found on GitHub at: ServletSample
Although writing servlets is no longer common, the code is simple enough to review and demonstrates both connection pooling and High Availability (HA).
-
This example uses the
HOCKEY
schema and assumes that you have run the NuoDB QuickStart to setup the database. -
The
jdbc/nuodb
JNDI resource is defined incontext.xml
and referred to byweb.xml
. -
The sample program,
HockeyServlet
, is found insrc/com/nuodb/servletsample
.
When you are running the NuoDB JDBC driver in an application, you can use DataSource
to benefit from its implementation of High Availability.
-
The set of properties for the Hockey servlet in this example enables connections via two APs, on
localhost
andbackuphost
:-
JDBC URL:
jdbc:com.nuodb://localhost/test,backuphost/test
-
-
If one AP goes down, the
DataSource
automatically connects to the database by means of the other available AP.