OCP Review 10 - JDBC
JDBC: Java Database Connectivity
Building Database Applications with JDBC
-
JDBC 4.0 and its later version support automatic loading and registration of all JDBC drivers accessible via an application’s classpath. For all earlier versions of JDBC, you must manually load the JDBC drivers using Class.forName(), before you can access the driver.
// JDBC 4.0+
-
DriverManager.getConnection(“jdbc:h2:mem:test”); JDBC transactions:
-
When connection’s auto-commit mode is changed during a transaction, the transaction is committed. So when auto-commit is enabled, all the “pending” changes in the current transaction are commited.
BASIC SQL
- DML (Data Manipulation Language)
- SELECT
- INSERT
- DELETE
- UPDATE
- COUNT
- DDL (Data Definition Language)
- DROP
- ALTER
- ETC.
JDBC: Interfaces inside java.sql (for the exam)
- Driver
- Connection
- Statement
- ResultSet
Others have been added in javax.sql
JDBC URL
protocol:vendor:database specific connection details
jdbc:postgres://localhost:5432/mydb
jdbc:oracle:thin:@localhost:1521:orcl
jdbc:oracle:thin:@[HOST][:PORT]:SID
- used to connect to the DB
- Several samples: https://www.ibm.com/support/knowledgecenter/en/SSEP7J_10.2.0/com.ibm.swg.ba.cognos.vvm_ag_guide.10.2.0.doc/c_ag_samjdcurlform.html
Connect to a DB
- Interfaces:
java.sql.DriverManager
vsjavax.sql.DataSource
DataSource
: factory, pooled, distributed- not covered in the exam!
DriverManager
covered in the exam- uses Factory pattern
Creating a connection using DriverManager
public static void main(String[] args) throws SQLException {
String url = "jdbc:derby:peopledb;create=true";
try (Connection connection = DriverManager.getConnection(url)) {
System.out.println(connection);
}
}
- also exists:
getConnection(url, user, password)
For reference only: using DataSource
EmbeddedDataSource ds = new EmbeddedDataSource();
ds.setDatabaseName("test/mydb");
ds.setCreateDatabase("create");
try (Connection connection = ds.getConnection();
Statement statement = connection.createStatement()) {
}
Manually loading database Driver
- DriverManager search JARs that contain a
Driver
class - looks for
META-INF/services/java.sql.Driver
- what if our (old) driver does not have
META-INF/services/java.sql.Driver
?- Since JDBC 4.0 (Java 6) should be there
try {
Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Creating a test DB from Java
String url = "jdbc:derby:peopledb;create=true";
try (Connection connection = DriverManager.getConnection(url); // this Throws Exception
Statement statement = connection.createStatement()) {
statement.executeUpdate("Drop table person");
statement.executeUpdate("create table PERSON " +
"(" +
"id integer primary key," +
"name varchar(255)" +
")");
statement.executeUpdate("insert into PERSON VALUES (1, 'Diego')");
}
Statement: sending SQL to the DB
Statement statement = connection.createStatement()
executeUpdate
- should be called executedSomethingThatsGonnaChangeTheDB
- returns the number of inserted, deleted or updated rows (or 0 if it’s a DDL SQL sentence)
i = statement.executeUpdate("insert into PERSON VALUES (1, 'Diego')"); // i == 1
i = statement.executeUpdate("Drop table person"); // i == 0
System.out.println(statement.getUpdateCount()); // getUpdateCount also returns this
Inserting more than one record
INSERT INTO PERSON VALUES (0, 'Person 0'), (1, 'Person 1'), ...
statement.executeUpdate("insert into PERSON VALUES (0, 'Person 0'), (1, 'Person 1')");
Inserting more than one record
StringBuffer insertSQL = new StringBuffer("insert into PERSON VALUES ");
for (int j = 0; j < 10; j++) {
insertSQL.append(String.format("(%d, '%s')", j, "Person " + j));
if (j < 9) {
insertSQL.append(", ");
}
}
System.out.println(insertSQL);
i = statement.executeUpdate(insertSQL.toString());
System.out.println(i);
Deleting
Updating
Query
- a
executeQuery
call returns aResultSet
(a cursor)
ResultSet rs = statement.executeQuery("SELECT * FROM PERSON");
while (rs.next()) {
System.out.println(rs.getString(2)); // columns start in 1!
}
Query
- we can also use
execute
and check if it returns aResultSet
boolean isResultSet = statement.execute("SELECT * FROM PERSON");
if (isResultSet) {
ResultSet resultSet = statement.getResultSet();
System.out.println(resultSet);
} else {
int result = statement.getUpdateCount();
System.out.println(result);
}
Types of ResultSets
Statement statement = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)
ResultSet.TYPE_FORWARD_ONLY
: can move –> only, can’t see last data (snapshop), supported
ResultSet.TYPE_SCROLL_INSENSITIVE
: can move <–>, can’t see last data (snapshop), supported
ResultSet.TYPE_SCROLL_SENSITIVE
: can move <–>, can see last data (snapshop), not well supported
- createStatement does graceful degradation and returns the available ResultSet
Concurrency modes
ResultSet.CONCUR_READ_ONLY: read only cursor
ResultSet.CONCUR_UPDATABLE: read/write cursor. Not required to be supported
- to change things in the DB we use Statements, not ResultSets
ResultSet resultSet = statement.getResultSet();
System.out.println(resultSet.getConcurrency() == ResultSet.CONCUR_UPDATABLE);
System.out.println(resultSet.getConcurrency() == ResultSet.TYPE_SCROLL_SENSITIVE);
Forward-only means that!
Statement statement = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE)) {
ResultSet rs = statement.executeQuery("SELECT * FROM PERSON");
while (rs.next()) {
System.out.println(rs.getString(2));
}
rs.first(); // Exception in thread "main" java.sql.SQLException: The 'first()' method is only allowed on scroll cursors.
System.out.println(rs.getString(2));
Statement vs PreparedStatement
- Only
Statement
in the exam - use
PreparedStatement
always- no SQL injection
- best performance
Using the wrong method
- a QUERY inside
executeUpdate
statement.executeUpdate("SELECT * FROM PERSON");
// Exception in thread "main" java.sql.SQLException:
// Statement.executeUpdate() cannot be called with a statement that returns a ResultSet.
- a INSERT, UPDATE, DELETE inside
executeQuery
ResultSet rs = statement.executeQuery("DELETE FROM PERSON");
// Exception in thread "main" java.sql.SQLException:
// Statement.executeQuery() cannot be called with a statement that returns a row count.
while (rs.next()) {
System.out.println(rs.getString(2));
}
Statement Method | Ret | DELETE | INSERT | UPDATE | SELECT |
---|---|---|---|---|---|
execute() |
boolean |
👍 | 👍 | 👍 | 👍 |
execute Query() |
ResultSet |
👎 | 👎 | 👎 | 👍 |
execute Update() |
int |
👍 | 👍 | 👍 | 👎 |
Looping through a ResultSet
- check ResultSet type: remember
ResultSet.TYPE_FORWARD_ONLY
, etc. - initial position “beforeFirst”
rs.beforeFirst()
,rs.isBeforeFirst()
- last position “after last”
rs.afterLast()
,rs.isAfterLast()
- rs.previous(),
rs.next()
-> returns true if not at the end
Absolute
- moves to the absolute index provided
- returns true if there’s a row there
- absolute(0) == before first
- absolute(1) == first row (if exists)
- absolute(-1) == last element
- absolute(-2) == element before last one
Relative
- skips n rows from current position
Accessing fields
- using name: getInt(“id”), getString(“name”)
- using field position: getInt(1), getString(2) // starts with 1
Counting
ResultSet rsCount = statement.executeQuery("SELECT COUNT(*) FROM PERSON");
if (rsCount.next()) {
String count = rsCount.getString(1);
System.out.println(count);
}
Times & Dates
JDBC | Java 8 | Contains |
---|---|---|
java.sql.Date | java.time.LocalDate | Date |
java.sql.Time | java.time.LocalTime | Time |
java.sql.TimeStamp | java.time. LocalDateTime | Date & Time |
Inserting TimeStamps
- convert into
java.sql.TimeStamp
// modify table
statement.executeUpdate("create table PERSON " +
"(" +
"id integer primary key," +
"name varchar(255)," +
"birthDate timestamp" + // added
")");
// ...
StringBuffer insertSQL = new StringBuffer("insert into PERSON VALUES ");
for (int j = 0; j < 10; j++) {
java.sql.Timestamp timestamp = new Timestamp(LocalDateTime.now().toEpochSecond(ZoneOffset.UTC));
insertSQL.append(String.format("(%d, '%s', '%s')", j, "Person " + j, timestamp.toString()));
if (j < 9) {
insertSQL.append(", ");
}
}
Closing database
try (Connection connection = DriverManager.getConnection(url);
Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE)) {
// try-with-resources: closes resources in the opposite order as they were created
}
- closing Connection closes statement (and resultsets)
- closing Statement closes resultsets
Closing resultsets
ResultSet rs = statement.executeQuery("SELECT * FROM PERSON");
while (rs.next()) {
System.out.println(rs.getString("name"));
}
boolean isResultSet = statement.execute("SELECT * FROM PERSON"); // closes here
JDBC
Questions
-
java.sql.Savepoint interface of JDBC is tailor made for this purpose. At the end of each step, the process can create a save point. Later on, when the process encounters the special condition, it can roll back the transaction up to any of the previous save point and continue from there, instead of rolling back the whole transaction and losing all the values.
- A PreparedStatement is used for SQL statements that are executed multiple times with different values. For example, if you want to insert several values into a table, one after another, it is a lot easier with PreparedStatement
-
A CallableStatement is meant for executing a stored procedure, which has already been created in the database.
-
Applications with JDBC 4.0, no longer need to explicitly load JDBC drivers using Class.forName(). The DriverManager methods getConnection and getDrivers have been enhanced to support the Java Standard Edition Service Provider mechanism. JDBC 4.0 Drivers must include the file META-INF/services/java.sql.Driver. This file contains the name of the JDBC drivers implementation of java.sql.Driver. For example, to load the my.sql.Driver class, the META-INF/services/java.sql.Driver file.
-
PreparedStatement offers better performance when the same query is to be run multiple times with different parameter values. If you are going to run a query only once, it may not offer better performance because it requires multiple trips to the database (one to get it pre compiled, and one to execute with the parameters) and also requires multiple method calls to set the parameters. reparedStatement has specific methods for additional SQL column type such as setBlob(int parameterIndex, Blob x) and setClob(int parameterIndex, Clob x).
-
ne advantage of CallableStatement is that it allows IN/OUT parameters. CallableStatement is the only way for a JDBC program to execute stored procedures in the database if the procedure has in and out parameters.
-
“enabling the transactions”. In JDBC, transactions are always enabled. Every statement is a part of a transaction. If auto-commit is set to true, the transaction is committed after every statement (and a new transaction is started with the next query), if it is set to false, the transaction is committed when connection.commit() is called explicitly. That is why there is no begin() method in Connection.
-
First, the connection’s auto commit mode is set to false and then two rows are inserted. However, since autocommit is false, they are not committed yet. Now, c.rollback() is called. This will cause the whole transaction to rollback. Although we created a Savepoint, we did not pass it to rollback and so the save point will have not be respected and both the rows will be lost. This transaction ends here. Next, row 3 is inserted in a new transaction and since autocommit is still false at this point, it is not commited yet. The transaction is still running. Now, when c.setAutoCommit(true) is called, the auto-commit status changes from false to true and this causes the existing transaction to commit thereby committing row 3. Finally, row 4 is inserted and committed immediately because autocommit is true.
-
con.setAutoCommit(true);
since auto-commit has been disabled in the given code (by calling c.setAutoCommit(false)), you have to explicitly commit the transaction to commit the changes to the database. The regular way to do this is to call con.commit(). Notice that commit method does not take any arguments. Another way is to utilize the side effect of changing the auto-commit mode of the connection. If the setAutoCommit method is called during a transaction and the auto-commit mode is changed, the transaction is committed. If setAutoCommit is called and the auto-commit mode is not changed, the call is a no-op. In this question, con.setAutoCommit(true) changes the auto-commit mode of the connection from false to true and therefore this call commits the changes. -
The actual classes for Connection, Statement, and ResultSet interfaces are provided by the JDBC driver and are therefore driver dependent.
The RowSet interface is unique in that it is intended to be implemented using the rest of the JDBC API. In other words, a RowSet implementation is a layer of software that executes “on top” of a JDBC driver. Implementations of the RowSet interface can be provided by anyone, including JDBC driver vendors who want to provide a RowSet implementation as part of their JDBC products. Oracle provides sample implementations of various RowSet interfaces with the JDK as well. So it is not clear whether this option is a correct option or not but we have included it because a few candidates have reported getting such a question.