How do I use JDBC with Spring?

You can use JDBC with Spring through Spring’s JDBC support, especially JdbcTemplate. It removes much of the repetitive JDBC boilerplate such as opening connections, closing resources, handling PreparedStatement, iterating ResultSet, and translating SQLException into Spring’s DataAccessException hierarchy.

The typical setup is:

  1. Configure a DataSource
  2. Create a JdbcTemplate
  3. Inject it into a repository/DAO class
  4. Use it to run queries and updates

1. Add Spring JDBC and a database driver

For a Maven project, you usually need spring-jdbc and your database driver.

Example for PostgreSQL:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>6.2.8</version>
    </dependency>

    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>42.7.7</version>
    </dependency>
</dependencies>

If you use Spring Boot, you would usually use:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

plus the database driver.


2. Configure a DataSource

In plain Spring Java configuration, you can define a DataSource bean.

A common choice is HikariCP:

package org.kodejava.spring;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.time.Duration;

@Configuration
public class DatabaseConfig {

    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();

        config.setJdbcUrl("jdbc:postgresql://localhost:5432/app");
        config.setUsername("postgres");
        config.setPassword("postgres");

        config.setMaximumPoolSize(10);
        config.setMinimumIdle(2);
        config.setConnectionTimeout(Duration.ofSeconds(5).toMillis());
        config.setPoolName("AppHikariPool");

        return new HikariDataSource(config);
    }
}

You would also need the HikariCP dependency if you are not using Spring Boot:

<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>6.3.0</version>
</dependency>

3. Create a JdbcTemplate bean

Spring can create JdbcTemplate from the configured DataSource.

package org.kodejava.spring;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

@Configuration
public class JdbcConfig {

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

If you are using Spring Boot, Boot usually autoconfigures JdbcTemplate for you as long as a DataSource exists.


4. Create a model class

For example, suppose you have a users table:

CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(150) NOT NULL
);

You can map rows to a Java object:

package org.kodejava.spring;

public class User {
    private Long id;
    private String name;
    private String email;

    public User() {
    }

    public User(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }
}

5. Use JdbcTemplate in a repository

A repository class can receive JdbcTemplate through constructor injection.

package org.kodejava.spring;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class UserRepository {
    private final JdbcTemplate jdbcTemplate;

    public UserRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public User findById(Long id) {
        String sql = """
                SELECT id, name, email
                FROM users
                WHERE id = ?
                """;

        return jdbcTemplate.queryForObject(
                sql,
                (rs, rowNum) -> new User(
                        rs.getLong("id"),
                        rs.getString("name"),
                        rs.getString("email")
                ),
                id
        );
    }

    public List<User> findAll() {
        String sql = """
                SELECT id, name, email
                FROM users
                ORDER BY id
                """;

        return jdbcTemplate.query(
                sql,
                (rs, rowNum) -> new User(
                        rs.getLong("id"),
                        rs.getString("name"),
                        rs.getString("email")
                )
        );
    }

    public int insert(User user) {
        String sql = """
                INSERT INTO users (id, name, email)
                VALUES (?, ?, ?)
                """;

        return jdbcTemplate.update(
                sql,
                user.getId(),
                user.getName(),
                user.getEmail()
        );
    }

    public int update(User user) {
        String sql = """
                UPDATE users
                SET name = ?, email = ?
                WHERE id = ?
                """;

        return jdbcTemplate.update(
                sql,
                user.getName(),
                user.getEmail(),
                user.getId()
        );
    }

    public int deleteById(Long id) {
        String sql = "DELETE FROM users WHERE id = ?";

        return jdbcTemplate.update(sql, id);
    }
}

6. Enable component scanning

If you are using plain Spring, your configuration class should scan for repositories and services.

package org.kodejava.spring;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("org.kodejava.spring")
public class AppConfig {
}

Then you can bootstrap Spring:

package org.kodejava.spring;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringJdbcExample {
    public static void main(String[] args) {
        try (AnnotationConfigApplicationContext context =
                     new AnnotationConfigApplicationContext(AppConfig.class, DatabaseConfig.class, JdbcConfig.class)) {

            UserRepository userRepository = context.getBean(UserRepository.class);

            User user = new User(1L, "Alice", "[email protected]");
            userRepository.insert(user);

            User savedUser = userRepository.findById(1L);
            System.out.println(savedUser.getName());
        }
    }
}

7. Handling query results safely

queryForObject() is convenient, but it throws an exception when no row is found. You can handle that explicitly:

package org.kodejava.spring;

import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.Optional;

public class UserRepository {
    private final JdbcTemplate jdbcTemplate;

    public UserRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public Optional<User> findOptionalById(Long id) {
        String sql = """
                SELECT id, name, email
                FROM users
                WHERE id = ?
                """;

        try {
            User user = jdbcTemplate.queryForObject(
                    sql,
                    (rs, rowNum) -> new User(
                            rs.getLong("id"),
                            rs.getString("name"),
                            rs.getString("email")
                    ),
                    id
            );

            return Optional.ofNullable(user);
        } catch (EmptyResultDataAccessException e) {
            return Optional.empty();
        }
    }
}

8. Using NamedParameterJdbcTemplate

For more readable SQL parameters, use NamedParameterJdbcTemplate.

package org.kodejava.spring;

import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class NamedUserRepository {
    private final NamedParameterJdbcTemplate jdbcTemplate;

    public NamedUserRepository(NamedParameterJdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public User findById(Long id) {
        String sql = """
                SELECT id, name, email
                FROM users
                WHERE id = :id
                """;

        MapSqlParameterSource params = new MapSqlParameterSource()
                .addValue("id", id);

        return jdbcTemplate.queryForObject(
                sql,
                params,
                (rs, rowNum) -> new User(
                        rs.getLong("id"),
                        rs.getString("name"),
                        rs.getString("email")
                )
        );
    }
}

You can define it as a bean:

package org.kodejava.spring;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import javax.sql.DataSource;

@Configuration
public class NamedJdbcConfig {

    @Bean
    public NamedParameterJdbcTemplate namedParameterJdbcTemplate(DataSource dataSource) {
        return new NamedParameterJdbcTemplate(dataSource);
    }
}

9. Transactions

For multiple database operations that should succeed or fail together, use Spring transactions.

Add a transaction manager:

package org.kodejava.spring;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration
@EnableTransactionManagement
public class TransactionConfig {

    @Bean
    public TransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

Then use @Transactional in a service:

package org.kodejava.spring;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional
    public void registerUser(User user) {
        userRepository.insert(user);

        // Other related database operations can go here.
        // If a RuntimeException occurs, the transaction is rolled back.
    }
}

10. Typical Spring Boot configuration

If you are using Spring Boot, the configuration is simpler.

application.properties:

spring.datasource.url=jdbc:postgresql://localhost:5432/app
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.hikari.maximum-pool-size=10

Repository:

package org.kodejava.spring;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class UserRepository {
    private final JdbcTemplate jdbcTemplate;

    public UserRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public int countUsers() {
        return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM users", Integer.class);
    }
}

Summary

To use JDBC with Spring:

  1. Add spring-jdbc and your database driver.
  2. Configure a DataSource.
  3. Create or autoconfigure JdbcTemplate.
  4. Inject JdbcTemplate into repository classes.
  5. Use query(), queryForObject(), and update() for database operations.
  6. Use @Transactional for operations that need transaction boundaries.

For most applications, prefer JdbcTemplate over raw JDBC because it keeps the code shorter, safer, and easier to test.

How do I use DataSource instead of DriverManager?

Switching from DriverManager to DataSource is a best practice in modern Java applications because it supports connection pooling, is more configurable, and decouples your code from the specific database driver implementation.

While DriverManager creates a physical connection every time you call getConnection(), a DataSource (specifically a pooling one) maintains a set of open connections that can be reused, significantly improving performance.

1. The DriverManager Approach (Old way)

You are likely used to this pattern:

// Hardcoded driver details and physical connection creation
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/db", "user", "pass");

2. The DataSource Approach (Modern way)

With DataSource, you configure the object once and then use it to get connections throughout your application.

Using Apache Commons DBCP (Connection Pooling)

To use a DataSource with pooling, you can use a library like Apache Commons DBCP’s BasicDataSource.

package org.kodejava.jdbc;

import org.apache.commons.dbcp2.BasicDataSource;
import javax.sql.DataSource;

public class DatabaseConfig {
    private static final BasicDataSource dataSource;

    static {
        dataSource = new BasicDataSource();
        dataSource.setUrl("jdbc:mysql://localhost/musicdb");
        dataSource.setUsername("music");
        dataSource.setPassword("s3cr*t");

        // Optional: Configure pooling parameters
        dataSource.setInitialSize(5);
        dataSource.setMaxTotal(10);
    }

    public static DataSource getDataSource() {
        return dataSource;
    }
}

3. Using the Connection in your Code

Once you have the DataSource instance, getting a connection is consistent regardless of the underlying implementation:

public void fetchData() {
    DataSource ds = DatabaseConfig.getDataSource();

    // The try-with-resources ensures the connection is "closed" 
    // (returned to the pool) automatically.
    try (Connection conn = ds.getConnection()) {
        // Use the connection as usual
        var stmt = conn.prepareStatement("SELECT * FROM record");
        var rs = stmt.executeQuery();
        // ... process results
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

Why use DataSource?

  • Connection Pooling: Reusing connections is much faster than opening/closing them for every request.
  • Decoupling: Your business logic only knows about the javax.sql.DataSource interface. You can switch from BasicDataSource to HikariCP (another popular pool) without changing your data-access code.
  • JNDI Support: In Jakarta EE environments, you can look up a DataSource configured in the application server via JNDI, keeping credentials out of your source code.
  • Spring Integration: If you use Spring Framework, JdbcTemplate is designed to work directly with a DataSource.

Maven Dependency

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-dbcp2</artifactId>
    <version>2.13.0</version>
</dependency>

How do I select records from database using JdbcTemplate?

In this example you will learn how to select records from the database using JdbcTemplate.queryForList() method. This method returns a List object which stores information selected from the table in a HashMap object. The key of the map is the table’s field names while the value of the map contains the corresponding table’s field value.

package org.kodejava.spring.jdbc;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import java.util.List;
import java.util.Map;

public class SelectDemo {
    public static void main(String[] args) {
        // Creates a DataSource object.
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost/musicdb");
        ds.setUsername("root");
        ds.setPassword("");

        // Creates an instance of JdbcTemplate.
        JdbcTemplate template = new JdbcTemplate(ds);

        // Executes a select query using queryForList() method. This
        // method returns a List containing HashMap object. The key
        // of the map is the table's field name and the value is
        // the table's field value.
        String query = "SELECT * FROM record";
        List<Map<String, Object>> results = template.queryForList(query);
        for (Map<String, Object> result : results) {
            for (String key : result.keySet()) {
                System.out.print(key + " = " + result.get(key) + "; ");
            }
            System.out.println();
        }

    }
}

Maven Dependencies

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>6.1.10</version>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>8.4.0</version>
    </dependency>
</dependencies>

Maven Central Maven Central

How do I delete records from database using JdbcTemplate?

The code below demonstrates on how to delete some records from database using the JdbcTemplate.

package org.kodejava.spring.jdbc;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;

public class DeleteDemo {
    public static final String DRIVER = "com.mysql.cj.jdbc.Driver";
    public static final String URL = "jdbc:mysql://localhost/musicdb";
    public static final String USERNAME = "root";
    public static final String PASSWORD = "";

    public static final String QUERY = "DELETE FROM record WHERE id = ?";

    private final DataSource dataSource;

    public DeleteDemo(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * Returns a DataSource object.
     *
     * @return a DataSource.
     */
    public static DataSource getDataSource() {
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName(DeleteDemo.DRIVER);
        ds.setUrl(DeleteDemo.URL);
        ds.setUsername(DeleteDemo.USERNAME);
        ds.setPassword(DeleteDemo.PASSWORD);
        return ds;
    }

    public static void main(String[] args) {
        DataSource ds = getDataSource();
        DeleteDemo demo = new DeleteDemo(ds);

        Long id = 1L;
        demo.deleteRecord(id);
    }

    public void deleteRecord(Long id) {
        // Creates an instance of JdbcTemplate and supply a data
        // source object.
        JdbcTemplate template = new JdbcTemplate(this.dataSource);

        // Delete a record from database where the record
        // id matches with the specified parameter.
        Object[] params = {id};
        int rows = template.update(DeleteDemo.QUERY, params);
        System.out.println(rows + " row(s) deleted.");
    }
}

Maven Dependencies

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>6.1.10</version>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>8.4.0</version>
    </dependency>
</dependencies>

Maven Central Maven Central

How do I update records in the database using JdbcTemplate?

The example demonstrated below will show you how to use the JdbcTemplate.update() method for updating records in database. This method returns an integer value indicating number of records updated in the database when the query is executed.

package org.kodejava.spring.jdbc;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;
import java.sql.Types;
import java.util.Date;

public class UpdateDemo {
    public static final String DRIVER = "com.mysql.cj.jdbc.Driver";
    public static final String URL = "jdbc:mysql://localhost/musicdb";
    public static final String USERNAME = "root";
    public static final String PASSWORD = "";

    public static final String QUERY =
            "UPDATE record SET title = ?, release_date = ? WHERE id = ?";

    private final DataSource dataSource;

    public UpdateDemo(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public static void main(String[] args) {
        DataSource ds = getDataSource();
        UpdateDemo demo = new UpdateDemo(ds);

        Long id = 2L;
        String title = "The Beatles 1967 - 1970";
        Date releaseDate = new Date();
        demo.updateRecord(id, title, releaseDate);
    }

    /**
     * Returns a data source object.
     *
     * @return a DataSource.
     */
    public static DataSource getDataSource() {
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName(UpdateDemo.DRIVER);
        ds.setUrl(UpdateDemo.URL);
        ds.setUsername(UpdateDemo.USERNAME);
        ds.setPassword(UpdateDemo.PASSWORD);
        return ds;
    }

    public void updateRecord(Long id, String title, Date releaseDate) {
        // Creates an instance of JdbcTemplate and set the DataSource.
        // We can use the template update() method to update records
        // in the database. Below we use an update() method that accepts
        // three parameters: the sql query, the parameter values and
        // the parameter data types.
        JdbcTemplate template = new JdbcTemplate(this.dataSource);

        Object[] params = {title, releaseDate, id};
        int[] types = {Types.VARCHAR, Types.DATE, Types.BIGINT};

        int rows = template.update(UpdateDemo.QUERY, params, types);
        System.out.println(rows + " row(s) updated.");
    }
}

Maven Dependencies

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>6.1.10</version>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>8.4.0</version>
    </dependency>
</dependencies>

Maven Central Maven Central