Core Java

How to Reuse PreparedStatement in Java

Java Database Connectivity (JDBC) remains the standard foundation for interacting with relational databases in Java applications. One of its key components is the PreparedStatement interface, which simplifies the execution of parameterized SQL queries while enhancing security and performance. Prepared statements help prevent SQL injection and reduce query parsing overhead by allowing reuse with different parameter values.

This article explores the correct way to reuse a PreparedStatement and shows how minor adjustments can greatly enhance performance.

1. Why Should You Reuse a PreparedStatement?

Each time an SQL statement is prepared, the database must parse the query, compile it, and generate an execution plan. However, when you reuse a PreparedStatement without closing it between executions, the database can bypass these steps by reusing the previously compiled plan, significantly reducing computational overhead and improving performance. The benefits of reusing a PreparedStatement include:

  • Improved performance: Avoids the cost of SQL compilation for every execution.
  • Better security: Shields against SQL injection via parameterization.
  • Reduced database load: Fewer round-trips and lighter parsing tasks.
  • Cleaner code: Reduces duplication of boilerplate code.

2. Inefficient Usage: Preparing Inside a Loop

Let’s begin by examining a poor practice: creating a new PreparedStatement within a loop. In this scenario, we are working with a table named BOOKS.

CREATE TABLE BOOKS (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255),
    author VARCHAR(255)
);

Here is inefficient Java code inserting multiple book records:

public class InefficientPreparedStatement {

    private static final Logger logger = Logger.getLogger(InefficientPreparedStatement.class.getName());

    public static void main(String[] args) throws Exception {
        Connection connection = DriverManager.getConnection("jdbc:h2:mem:testdb", "sa", "password");

        // Create table
        try (Statement stmt = connection.createStatement()) {
            stmt.execute("CREATE TABLE BOOKS (id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255), author VARCHAR(255))");
        }

        // Inefficient: preparing and closing the statement in each loop iteration
        for (int i = 1; i <= 5; i++) {
            try (PreparedStatement pstmt = connection.prepareStatement("INSERT INTO books (title, author) VALUES (?, ?)")) {
                pstmt.setString(1, "Book " + i);
                pstmt.setString(2, "Author " + i);
                pstmt.executeUpdate();
                logger.log(Level.INFO, "Inserted Book {0} by Author {1}", new Object[]{i, i});
            }
        }

        // Verify insertions
        try (Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM books")) {
            rs.next();
            logger.log(Level.INFO, "Books inserted: {0}", rs.getInt(1));
        }
        connection.close();
    }
}

What’s wrong with this approach is that it creates a new PreparedStatement during each loop iteration, which forces the database to parse and compile the SQL statement every time. This prevents the JDBC driver and database from reusing the execution plan, thereby defeating the entire purpose of using a PreparedStatement. As the volume of data increases, this inefficiency can lead to significantly slower performance and higher resource consumption.

3. Efficient Methods to Reuse a PreparedStatement

Next let’s look at how to do it right. Below are some patterns for reusing PreparedStatement properly.

3.1 Reuse Within a Loop Without Batch

Let’s improve the previous code by creating the PreparedStatement once and reusing it in the loop.

public class ReusedPreparedStatement {
    private static final Logger logger = Logger.getLogger(ReusedPreparedStatement.class.getName());

    public static void main(String[] args) throws Exception {
        Connection connection = DriverManager.getConnection("jdbc:h2:mem:testdb", "sa", "password");

        try (Statement stmt = connection.createStatement()) {
            stmt.execute("CREATE TABLE BOOKS (id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255), author VARCHAR(255))");
        }

        String query = "INSERT INTO BOOKS (title, author) VALUES (?, ?)";
        try (PreparedStatement pstmt = connection.prepareStatement(query)) {
            for (int i = 1; i <= 5; i++) {
                pstmt.setString(1, "Efficient Book " + i);
                pstmt.setString(2, "Efficient Author " + i);
                pstmt.executeUpdate();
                logger.log(Level.INFO, "Inserted Efficient Book {0} by Efficient Author {1}", new Object[]{i, i});
            }
        }

        // Verify insertions
        try (Statement stmt = connection.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM books")) {
            rs.next();
            logger.log(Level.INFO, "Books inserted: {0}", rs.getInt(1));
        }

        connection.close();
    }
}

A better approach here was to prepare the PreparedStatement once and reuse it across multiple executions. This allows the database to reuse the execution plan, improving performance and reducing overhead. Additionally, this method leads to cleaner, faster, and more maintainable code.

3.2 Batch Insert with PreparedStatement

Batch processing is the best method when inserting or updating a large number of records.

public class BatchedPreparedStatement {

    private static final Logger logger = Logger.getLogger(BatchedPreparedStatement.class.getName());

    public static void main(String[] args) throws Exception {
        Connection connection = DriverManager.getConnection("jdbc:h2:mem:testdb", "sa", "password");

        try (Statement stmt = connection.createStatement()) {
            stmt.execute("CREATE TABLE BOOKS (id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255), author VARCHAR(255))");
        }

        String query = "INSERT INTO BOOKS (title, author) VALUES (?, ?)";
        try (PreparedStatement pstmt = connection.prepareStatement(query)) {
            for (int i = 1; i <= 15; i++) {
                pstmt.setString(1, "Batched Book " + i);
                pstmt.setString(2, "Batched Author " + i);
                pstmt.addBatch();
            }
            int[] results = pstmt.executeBatch();
            logger.log(Level.INFO, "Batch inserted {0} books", results.length);
        }

        // Verify insertions
        try (Statement stmt = connection.createStatement(); 
            ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM books")) {
            rs.next();
            logger.log(Level.INFO, "Books inserted: {0}", rs.getInt(1));
        }

        connection.close();
    }
}

Output:

Sample output demonstrating batch processing with Java SQL PreparedStatement reuse

This approach is better because all SQL commands are sent to the database in a single batch, which reduces network traffic and takes advantage of the database engine’s batch processing capabilities. It is highly efficient for handling large volumes of data and significantly lowers JDBC overhead, resulting in improved overall throughput.

4. Conclusion

In this article, we explored the concept of reusing PreparedStatement objects in Java’s JDBC API to improve both performance and code quality. We began by examining an inefficient pattern by creating a new PreparedStatement inside a loop, which leads to unnecessary parsing and compilation of SQL statements, increased overhead, and degraded performance. We then demonstrated better approaches such as preparing the statement once and reusing it with different parameters, as well as leveraging JDBC batch updates to send multiple statements in a single execution.

In conclusion, reusing a PreparedStatement in Java is an effective way to improve performance and write cleaner code. Depending on your use case, choose the right reuse strategy. For bulk inserts, use batch processing with executeBatch() and addBatch().

5. Download the Source Code

This article explored how to reuse PreparedStatement in Java SQL for efficient and optimized database operations.

Download
You can download the full source code of this example here: java sql reuse preparedstatement

Omozegie Aziegbe

Omos Aziegbe is a technical writer and web/application developer with a BSc in Computer Science and Software Engineering from the University of Bedfordshire. Specializing in Java enterprise applications with the Jakarta EE framework, Omos also works with HTML5, CSS, and JavaScript for web development. As a freelance web developer, Omos combines technical expertise with research and writing on topics such as software engineering, programming, web application development, computer science, and technology.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button