This doc explains the main application class that uses Spring's JdbcTemplate to interact with the database.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;| Import | What It Does |
|---|---|
Logger, LoggerFactory |
SLF4J logging - prints messages to console |
CommandLineRunner |
Interface that runs code after Spring starts |
SpringApplication |
Bootstraps and launches the Spring app |
@SpringBootApplication |
Marks this as the main Spring Boot class |
JdbcTemplate |
Spring's helper for database operations |
List, Stream, Collectors |
Java collections and stream API |
@SpringBootApplication
public class RelationalDataAccessApplication implements CommandLineRunner {@SpringBootApplication is a shortcut that combines three annotations:
| Annotation | What It Does |
|---|---|
@Configuration |
This class can define Spring beans |
@EnableAutoConfiguration |
Spring Boot auto-configures based on dependencies (sees H2, sets up datasource) |
@ComponentScan |
Scans this package for other Spring components |
public class RelationalDataAccessApplication implements CommandLineRunner {
@Override
public void run(String... strings) throws Exception {
// This code runs after Spring Boot starts
}
}CommandLineRunner is an interface with one method: run().
Why use it?
- Code in
run()executes after Spring Boot fully starts - The application context is ready (beans are created, database is connected)
- Good for demo apps, data seeding, or one-time startup tasks
The String... strings parameter:
- These are command-line arguments passed to the app
- We don't use them in this example
private static final Logger log = LoggerFactory.getLogger(RelationalDataAccessApplication.class);What this does:
- Creates a logger for this class
LoggerFactory.getLogger()creates a logger named after the classprivate static final- one logger shared by all instances, never changes
Using the logger:
log.info("Creating tables");
log.info("Inserting customer record for {} {}", name[0], name[1]);log.info()prints an INFO level message{}are placeholders that get filled with the values after the string- Better than
System.out.println()- gives timestamps, log levels, class names
private final JdbcTemplate jdbcTemplate;
public RelationalDataAccessApplication(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}Constructor Injection:
- Spring sees the constructor needs a
JdbcTemplate - Spring auto-creates a
JdbcTemplate(because we havespring-boot-starter-jdbc) - Spring passes it to our constructor
- We save it in a field to use later
Why private final?
private- only this class can access itfinal- can't be reassigned after construction (immutable reference)
jdbcTemplate.execute("DROP TABLE IF EXISTS customers");
jdbcTemplate.execute("CREATE TABLE customers(" +
"id SERIAL, first_name VARCHAR(255), last_name VARCHAR(255))");execute() runs SQL that doesn't return data (DDL statements).
| SQL | What It Does |
|---|---|
DROP TABLE IF EXISTS customers |
Deletes the table if it exists (clean slate) |
CREATE TABLE customers(...) |
Creates a new table |
The table structure:
id SERIAL- auto-incrementing integer (H2 syntax)first_name VARCHAR(255)- string up to 255 characterslast_name VARCHAR(255)- string up to 255 characters
List<Object[]> splitUpNames = Stream.of("John Woo", "Jeff Dean", "Josh Bloch", "Josh Long")
.map(name -> name.split(" "))
.collect(Collectors.toList());Step by step:
Stream.of("John Woo", "Jeff Dean", ...)- creates a stream of full names.map(name -> name.split(" "))- splits each name into["John", "Woo"].collect(Collectors.toList())- collects into aList<Object[]>
Result:
[
["John", "Woo"],
["Jeff", "Dean"],
["Josh", "Bloch"],
["Josh", "Long"]
]
Why Object[]?
batchUpdate()expectsList<Object[]>for the parameter values- Each
Object[]is one row's worth of parameters
splitUpNames.forEach(name -> log.info("Inserting customer record for {} {}", name[0], name[1]));forEach()loops through each name arrayname[0]is first name,name[1]is last name{}placeholders get replaced with the values
Output:
Inserting customer record for John Woo
Inserting customer record for Jeff Dean
Inserting customer record for Josh Bloch
Inserting customer record for Josh Long
jdbcTemplate.batchUpdate("INSERT INTO customers(first_name, last_name) VALUES (?,?)", splitUpNames);batchUpdate() inserts multiple rows efficiently in one database call.
Parameters:
- SQL with
?placeholders:INSERT INTO customers(first_name, last_name) VALUES (?,?) - List of parameter arrays:
splitUpNames
How it works:
- For each
Object[]in the list, it runs the INSERT ?placeholders get replaced with array values["John", "Woo"]becomesINSERT INTO customers(first_name, last_name) VALUES ('John', 'Woo')
Why use ? placeholders?
- Prevents SQL injection attacks
- JDBC properly escapes the values
- Never concatenate user input into SQL strings!
jdbcTemplate.query(
"SELECT id, first_name, last_name FROM customers WHERE first_name = ?",
(rs, rowNum) -> new Customer(rs.getLong("id"), rs.getString("first_name"), rs.getString("last_name")),
"Josh")
.forEach(customer -> log.info(customer.toString()));query() parameters:
- SQL query with
?placeholder - RowMapper lambda - converts each row to a Customer
"Josh"- value for the?placeholder
The RowMapper lambda:
(rs, rowNum) -> new Customer(
rs.getLong("id"),
rs.getString("first_name"),
rs.getString("last_name")
)| Parameter | What It Is |
|---|---|
rs |
ResultSet - the current row from the database |
rowNum |
Row number (0, 1, 2...) - we don't use it here |
| Method | What It Does |
|---|---|
rs.getLong("id") |
Gets the id column as a long |
rs.getString("first_name") |
Gets first_name column as a String |
The result:
query()returns aList<Customer>- We call
.forEach()to log each one
Output:
Customer[id=3, firstName='Josh', lastName='Bloch']
Customer[id=4, firstName='Josh', lastName='Long']
- Spring Boot starts - auto-configures H2 database and JdbcTemplate
- Constructor injection - Spring passes JdbcTemplate to our class
- run() executes - CommandLineRunner kicks in after startup
- Create table -
execute()runs DDL - Prepare data - Stream splits names into arrays
- Insert rows -
batchUpdate()inserts all customers - Query data -
query()finds customers named Josh - Log results - RowMapper converts rows to Customer objects, we log them
| Method | Use Case | Returns |
|---|---|---|
execute(sql) |
DDL (CREATE, DROP, ALTER) | void |
update(sql, params...) |
Single INSERT, UPDATE, DELETE | int (rows affected) |
batchUpdate(sql, list) |
Multiple INSERTs efficiently | int[] (rows affected per statement) |
query(sql, rowMapper, params...) |
SELECT returning multiple rows | List |
queryForObject(sql, rowMapper, params...) |
SELECT returning one row | T |