JDBC — Connecting Java to a Database
Use JDBC to connect Java to a real database and run safe queries with PreparedStatement, the foundation of data-driven apps.
What you will learn
- Connect to a database with JDBC
- Run an insert and a query safely with PreparedStatement
- Read results from a ResultSet
Letting Java talk to a database
Files are fine for small data, but real applications store information in a database — software built to hold, search and update large amounts of structured data reliably. JDBC (Java Database Connectivity) is the standard built-in way for Java to talk to a database such as MySQL, PostgreSQL or SQLite.
The flow is always the same: connect, send a command (insert, query, update), read any results, then close the connection. We will use a users table with columns id, name and age as our example.
Connecting
You connect with DriverManager.getConnection(...), giving it a connection URL (which database and where), plus a username and password. Like file work, JDBC can throw a checked SQLException, so it lives in try / catch. We again use try-with-resources so the connection closes automatically.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Main {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/mydb";
try (Connection conn = DriverManager.getConnection(url, "root", "password")) {
System.out.println("Connected to the database!");
} catch (SQLException e) {
System.out.println("Connection failed: " + e.getMessage());
}
}
}Note: Output:
Connected to the database!
The URL jdbc:mysql://localhost:3306/mydb says: use JDBC, talk to MySQL, on this computer (localhost) at port 3306, and use the database named mydb. If the details are right and the database is running, the connection opens — and closes itself at the end because it was declared in the try ( ... ) brackets.
Inserting data safely with PreparedStatement
To run SQL, we use a PreparedStatement — a command with placeholders written as ?. We fill those placeholders with real values afterwards. This is the safe way: it stops a dangerous attack called SQL injection, where a user types sneaky SQL into an input to break or read your database. With placeholders, user input can never be mistaken for SQL commands.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Main {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/mydb";
String sql = "INSERT INTO users (name, age) VALUES (?, ?)";
try (Connection conn = DriverManager.getConnection(url, "root", "password");
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, "Asha"); // fill the first ?
stmt.setInt(2, 25); // fill the second ?
int rows = stmt.executeUpdate();
System.out.println("Rows inserted: " + rows);
} catch (SQLException e) {
System.out.println("Insert failed: " + e.getMessage());
}
}
}Note: Output:
Rows inserted: 1
The ? placeholders were filled by setString(1, "Asha") and setInt(2, 25) — the numbers are the placeholder positions, counting from 1. executeUpdate() runs commands that change data (INSERT, UPDATE, DELETE) and returns how many rows changed: here, 1. Because the values went in through set... calls, not by gluing text into the SQL, the query is safe from SQL injection.
Watch out: Never build SQL by joining strings with user input, like "... VALUES ('" + name + "')". A malicious name could inject extra SQL and damage or steal your data. Always use ? placeholders and the set... methods — it is both safer and cleaner.
Querying and reading results
To read data we use executeQuery(), which returns a ResultSet — a cursor over the rows that match. We call next() to step to each row (it returns false when there are no more), and getter methods like getString and getInt to read each column by name.
import java.sql.*;
public class Main {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/mydb";
String sql = "SELECT name, age FROM users WHERE age >= ?";
try (Connection conn = DriverManager.getConnection(url, "root", "password");
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, 18); // adults only
ResultSet rs = stmt.executeQuery();
while (rs.next()) { // step through each row
String name = rs.getString("name");
int age = rs.getInt("age");
System.out.println(name + " is " + age);
}
} catch (SQLException e) {
System.out.println("Query failed: " + e.getMessage());
}
}
}Note: Output (with two matching rows in the table):
Asha is 25
Ravi is 30
The query asked for everyone aged 18 or more. executeQuery() gave back a ResultSet, and the while loop called rs.next() to move to each row in turn, reading the name and age by column name. When no rows are left, next() returns false and the loop ends.
The JDBC steps in order
- Connect with
DriverManager.getConnection(url, user, password), inside try-with-resources. - Prepare a statement with
?placeholders viaconn.prepareStatement(sql). - Fill the placeholders with
setString,setInt, and so on (positions count from 1). - Run it:
executeUpdate()for INSERT/UPDATE/DELETE, orexecuteQuery()for SELECT. - Read a SELECT result by looping
while (rs.next())and usinggetString/getIntper column. - Closing is automatic — try-with-resources closes the statement and connection for you.
Tip: In bigger apps, database code is usually tucked into a DAO (Data Access Object) — a class whose only job is to read and write one kind of data (for example a UserDao with save(user) and findById(id)). It keeps the messy SQL in one place, away from the rest of your program.
Q. Why use a PreparedStatement with ? placeholders instead of building SQL by joining strings?
✍️ Practice
- Write the JDBC code to insert one product (name and price) into a
productstable using a PreparedStatement. - Write a query that selects all products under a given price and prints each name and price from the ResultSet.
🏠 Homework
- Sketch (or build, if you have a database handy) a
UserDaoclass with two methods:addUser(name, age)using executeUpdate, andlistAdults()that queries and prints everyone aged 18 or older. Explain in 3 to 4 lines why PreparedStatement is safer than string-built SQL.