JDBC

Basics

JDBC (Java Database Connectivity) - API in java.sql package for creating database connections and executing queries on relational databases.

The 5 interfaces of JDBC API:

  • Driver (eastablish a protocol to communicate with the database)
  • Connection (send commands to the database)
  • PreparedStatement (send an SQL query with parameters to the database)
  • CallableStatement (call a stored procedure from the database)
  • ResultSet (read results of SQL query)
// complete JDBC demo code
public class MyFirstDatabaseConnection {
	public static void main(String[] args) throws SQLException {
 	 	String url = "jdbc:postgres:localhost:8000/food";
 		try (Connection conn = DataSource.getConnection(url);
 			PreparedStatement ps = conn.prepareStatement("SELECT name FROM vegetarian");
 			ResultSet rs = ps.executeQuery()) {
 				while (rs.next())
 					System.out.println(rs.getString(1));
			} 
	}
}

Building Blocks

Include the Driver

Driver are a set of classes that contain logic on how Java code and the database understand each other. They are vendor specific.

Traditionally we used to place a driver JAR in our project.

Nowadays, with build systems like Maven and Gradle we include it as a dependency.

<!-- MySQL Driver Dependency -->
<dependency>
	<groupId>com.mysql</groupId>
	<artifactId>mysql-connector-j</artifactId>
	<scope>runtime</scope>
</dependency>

Get the JDBC URL

jdbc:<dbprovider>://<serverURL>/<databaseName>

jdbc:postgres://localhost:8000/food

many other forms...

Create a Connection

Either use DriverManager or DataSource (both are in-built in Java). The latter is better as it has more features and can take input from external sources.

Connection conn = DataSource.getConnection(url);

Build & Execute SQL Queries

Use Statement interface or its subinterfaces: PreparedStatement or CallableStatement.

Statement doesnโ€™t take any parameters, just executes whatever query we supply to it.

// for INSERT, UPDATE, DELETE
try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM vegetarian")) {
 	int result = ps.executeUpdate();
 	System.out.println(result);	 	// 1
}

// for SELECT
try (var ps = conn.prepareStatement("SELECT * FROM vegetarian");
	 ResultSet rs = ps.executeQuery()) {
 	// work with rs
}

// for both
boolean isResultSet = ps.execute();
// returns "true" if SELECT query is passed to it and ResultSet is there; otherwise "false"
if (isResultSet) {
	try (ResultSet rs = ps.getResultSet()) {	// get result set
		System.out.println("ran a query");
	}
} 
else {
	int result = ps.getUpdateCount();		// get updated row count
	System.out.println("ran an update");
}
  • ps.executeUpdate()
  • ps.executeQuery()
  • ps.execute()

Parameterized Statements: Indexing starts from 1 and not 0.

String sql = "INSERT INTO names VALUES(?, ?, ?)";
try (var ps = conn.prepareStatement(sql)) {
	ps.setInt(1, x);
	ps.setString(3, y);
	ps.setInt(2, z);
	ps.executeUpdate();
}

// use ps.setObject() for any type

Read ResultSet

ResultSet has a cursor, and it is indexed from 1 and not 0, just like a PreparedStatement.

String sql = "SELECT id, name FROM food";
try (var ps = conn.prepareStatement(sql);
	ResultSet rs = ps.executeQuery()) {

	while (rs.next()) {
		int id = rs.getInt("id");				// can use .getInt(1) here
		String name = rs.getString("name");		// can use .getString(2) here

		// process both here...
	}
}

// use rs.getObject() for any type

Transactions

conn.setAutoCommit(false);	// to let the database know that we'll handle transactions ourselves

conn.commit();
conn.rollback();

Savepoint sp1 = conn.setSavepoint();
Savepoint sp2 = conn.setSavepoint("second savepoint");		// savepoint with a name
conn.rollback(sp2);
conn.rollback(sp1);

Closing Resources

  • Closing a Connection closes PreparedStatement and ResultSet too
  • Closing a PreparedStatement closes ResultSet too

All the above examples on this page used try-with-resources to close resources, this is how its done traditionally.

Although, with modern frameworks such as Spring, it is not recommended to close resources manually unless you know what youโ€™re doing, since a connection maybe required to continue the same transaction in another class, etc. Spring provides in-built classes like JdbcTemplate to run the query that can automatically close the connection later.