Build Tools & Databases
Automate builds and integrate data persistence
Build Tools & Databases in Java
Master build automation and database integration with free flashcards and hands-on coding practice. This lesson covers Maven and Gradle build tools, JDBC for database connectivity, and connection poolingβessential skills for professional Java development. You'll learn how to manage dependencies, automate builds, and integrate databases into production applications.
Welcome to Build Tools & Databases π»
In professional Java development, manually compiling code and managing libraries quickly becomes unmanageable. Build tools automate compilation, dependency management, testing, and packaging. Similarly, most real-world applications need to persist data, making database connectivity a fundamental skill.
This lesson equips you with:
- Build automation using Maven and Gradle
- Database connectivity through JDBC
- Connection pooling for performance optimization
- Best practices for production systems
Core Concepts
Build Tools: Maven & Gradle π¨
Build tools automate the software build process, including:
- Compiling source code
- Managing dependencies (external libraries)
- Running tests
- Packaging applications (JAR, WAR files)
- Deploying to servers
Maven
Maven is a declarative build tool that uses an XML configuration file called pom.xml (Project Object Model). It follows convention over configurationβif you organize your project according to Maven's standard directory structure, minimal configuration is needed.
Standard Maven Directory Structure:
my-project/ βββ pom.xml βββ src/ β βββ main/ β β βββ java/ (source code) β β βββ resources/ (config files) β βββ test/ β βββ java/ (test code) β βββ resources/ (test resources) βββ target/ (compiled output)
Key Maven Concepts:
- Coordinates: Uniquely identify a project using
groupId:artifactId:version - Dependencies: External libraries your project needs
- Plugins: Extend Maven's capabilities
- Build Lifecycle: Standard phases like
compile,test,package,install
Maven Build Lifecycle Phases:
βββββββββββββββββββββββββββββββββββββββββββ
β MAVEN BUILD LIFECYCLE β
βββββββββββββββββββββββββββββββββββββββββββ
validate β compile β test β package β install β deploy
β β β β β β
βΌ βΌ βΌ βΌ βΌ βΌ
Check Compile Run Create Install Deploy
setup sources tests JAR to local to
repo remote
π‘ Tip: Running mvn package automatically executes all previous phases (validate, compile, test) before packaging.
Gradle
Gradle is a more flexible build tool using Groovy or Kotlin DSL instead of XML. It's faster than Maven thanks to incremental builds and build caching.
Key Gradle Concepts:
- build.gradle: Main configuration file
- Tasks: Units of work (similar to Maven phases)
- Plugins: Add functionality (Java, application, Spring Boot)
- Dependency configurations:
implementation,testImplementation, etc.
Gradle Build Flow:
ββββββββββββββββββββββββββββββββββββββββββ
β GRADLE BUILD PHASES β
ββββββββββββββββββββββββββββββββββββββββββ
Initialization β Configuration β Execution
β β β
βΌ βΌ βΌ
Load project Configure Run tasks
settings tasks & based on
dependencies task graph
Maven vs Gradle Comparison:
| Feature | Maven π | Gradle β‘ |
|---|---|---|
| Configuration | XML (pom.xml) | Groovy/Kotlin DSL (build.gradle) |
| Philosophy | Convention over configuration | Flexibility & performance |
| Build Speed | Slower | Faster (incremental builds) |
| Learning Curve | Easier for beginners | Steeper but more powerful |
| Industry Usage | Extremely widespread | Growing (Android standard) |
JDBC: Java Database Connectivity ποΈ
JDBC (Java Database Connectivity) is the standard API for connecting Java applications to relational databases. It provides a uniform interface regardless of the underlying database (MySQL, PostgreSQL, Oracle, etc.).
JDBC Architecture:
βββββββββββββββββββββββββββββββββββββββββββββββ
β Java Application β
ββββββββββββββββ¬βββββββββββββββββββββββββββββββ
β Uses JDBC API
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββ
β JDBC Driver Manager β
ββββββββββββββββ¬βββββββββββββββββββββββββββββββ
β Loads appropriate driver
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββ
β JDBC Driver (MySQL, PostgreSQL, etc.) β
ββββββββββββββββ¬βββββββββββββββββββββββββββββββ
β Database-specific protocol
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββ
β Database Server β
βββββββββββββββββββββββββββββββββββββββββββββββ
Key JDBC Interfaces:
- DriverManager: Manages database drivers and establishes connections
- Connection: Represents a session with a specific database
- Statement: Executes SQL queries
- PreparedStatement: Pre-compiled SQL with parameters (prevents SQL injection)
- ResultSet: Contains data retrieved from queries
JDBC Workflow:
ββββββββββββββββββββββββββββββββββββ
β 1. Load Driver (automatic) β
ββββββββββββββ¬ββββββββββββββββββββββ
βΌ
ββββββββββββββββββββββββββββββββββββ
β 2. Establish Connection β
β DriverManager.getConnection()β
ββββββββββββββ¬ββββββββββββββββββββββ
βΌ
ββββββββββββββββββββββββββββββββββββ
β 3. Create Statement β
β connection.prepareStatement()β
ββββββββββββββ¬ββββββββββββββββββββββ
βΌ
ββββββββββββββββββββββββββββββββββββ
β 4. Execute Query β
β statement.executeQuery() β
ββββββββββββββ¬ββββββββββββββββββββββ
βΌ
ββββββββββββββββββββββββββββββββββββ
β 5. Process ResultSet β
β while (rs.next()) { ... } β
ββββββββββββββ¬ββββββββββββββββββββββ
βΌ
ββββββββββββββββββββββββββββββββββββ
β 6. Close Resources β
β (connection, statement, rs) β
ββββββββββββββββββββββββββββββββββββ
β οΈ Critical: Always close JDBC resources to prevent memory leaks and connection exhaustion. Use try-with-resources for automatic cleanup.
PreparedStatement vs Statement
PreparedStatement should be your default choice:
| Feature | Statement β | PreparedStatement β |
|---|---|---|
| SQL Injection | Vulnerable | Protected |
| Performance | Slower (re-parsed each time) | Faster (pre-compiled) |
| Readability | String concatenation messy | Clean with placeholders |
| Use Case | Rare (static queries only) | Default choice |
π‘ Security Tip: Never concatenate user input into SQL strings. Always use PreparedStatement with parameters.
Connection Pooling π
Connection pooling reuses database connections instead of creating new ones for each request. Establishing a database connection is expensive (network handshake, authentication), so pooling dramatically improves performance.
Without Connection Pooling:
Request 1: Create β Use β Close (100ms overhead) Request 2: Create β Use β Close (100ms overhead) Request 3: Create β Use β Close (100ms overhead) Total overhead: 300ms for 3 requests β
With Connection Pooling:
Initialization: Create pool of 10 connections (1000ms once) Request 1: Borrow β Use β Return (1ms overhead) Request 2: Borrow β Use β Return (1ms overhead) Request 3: Borrow β Use β Return (1ms overhead) Total overhead: 3ms + 1000ms initialization β (Initialization cost amortized over thousands of requests)
Connection Pool Lifecycle:
ββββββββββββββββββββββββββββββββββββββββββββ
β CONNECTION POOL β
β β
β ββββββ ββββββ ββββββ ββββββ ββββββ β
β β C1 β β C2 β β C3 β β C4 β β C5 β β C = Connection
β ββββββ ββββββ ββββββ ββββββ ββββββ β
β β β idle idle idle β
ββββββΌβββββββΌβββββββββββββββββββββββββββββββ
β β
βΌ βΌ
App1 App2 (borrowed)
β β
ββββ¬ββββ
βΌ
(returned after use)
Popular Connection Pool Libraries:
- HikariCP (fastest, most popular)
- Apache DBCP (Apache Commons)
- C3P0 (older, still used)
- Tomcat JDBC Pool
Key Connection Pool Settings:
| Setting | Description | Typical Value |
|---|---|---|
| minimumIdle | Minimum connections kept alive | 5-10 |
| maximumPoolSize | Maximum connections allowed | 10-50 |
| connectionTimeout | Wait time for available connection | 30 seconds |
| idleTimeout | Time before idle connection is removed | 10 minutes |
| maxLifetime | Maximum lifetime of a connection | 30 minutes |
π‘ Performance Tip: Set maximumPoolSize based on your database's connection limit divided by the number of application instances.
Detailed Examples
Example 1: Maven Project Configuration
Here's a complete pom.xml for a Java application using PostgreSQL:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Project Coordinates -->
<groupId>com.example</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<!-- Properties -->
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!-- Dependencies -->
<dependencies>
<!-- PostgreSQL JDBC Driver -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
</dependency>
<!-- HikariCP Connection Pool -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version>
</dependency>
<!-- JUnit for Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- Build Configuration -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
</plugins>
</build>
</project>
Explanation:
- Coordinates:
com.example:my-app:1.0.0uniquely identifies this project - Properties: Sets Java version to 17 and UTF-8 encoding
- Dependencies: Includes PostgreSQL driver, HikariCP, and JUnit
- Scope:
testscope means JUnit is only available during testing
Common Maven Commands:
mvn clean # Delete target directory
mvn compile # Compile source code
mvn test # Run unit tests
mvn package # Create JAR file
mvn install # Install to local repository
mvn dependency:tree # Show dependency hierarchy
Example 2: Gradle Project Configuration
Equivalent build.gradle using Kotlin DSL:
plugins {
java
application
}
group = "com.example"
version = "1.0.0"
repositories {
mavenCentral()
}
dependencies {
// PostgreSQL JDBC Driver
implementation("org.postgresql:postgresql:42.6.0")
// HikariCP Connection Pool
implementation("com.zaxxer:HikariCP:5.0.1")
// JUnit for Testing
testImplementation("org.junit.jupiter:junit-jupiter:5.9.3")
}
application {
mainClass.set("com.example.Main")
}
tasks.test {
useJUnitPlatform()
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
Explanation:
- Plugins:
javaandapplicationplugins provide necessary tasks - Repositories:
mavenCentral()is where dependencies are downloaded from - Dependencies:
implementationfor runtime,testImplementationfor tests only - Application: Specifies main class for
runtask
Common Gradle Commands:
./gradlew build # Compile and run tests
./gradlew test # Run unit tests
./gradlew run # Run the application
./gradlew clean # Delete build directory
./gradlew dependencies # Show dependency tree
Example 3: JDBC Database Operations
Complete example showing CRUD operations with proper resource management:
import java.sql.*;
public class UserRepository {
private final String url = "jdbc:postgresql://localhost:5432/mydb";
private final String user = "dbuser";
private final String password = "dbpass";
// CREATE - Insert new user
public void createUser(String name, String email) throws SQLException {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, name);
stmt.setString(2, email);
int rowsAffected = stmt.executeUpdate();
System.out.println("Inserted " + rowsAffected + " row(s)");
}
}
// READ - Retrieve user by ID
public void getUserById(int userId) throws SQLException {
String sql = "SELECT id, name, email FROM users WHERE id = ?";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, userId);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
System.out.printf("User: id=%d, name=%s, email=%s%n",
id, name, email);
} else {
System.out.println("User not found");
}
}
}
}
// UPDATE - Modify user email
public void updateUserEmail(int userId, String newEmail) throws SQLException {
String sql = "UPDATE users SET email = ? WHERE id = ?";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, newEmail);
stmt.setInt(2, userId);
int rowsAffected = stmt.executeUpdate();
System.out.println("Updated " + rowsAffected + " row(s)");
}
}
// DELETE - Remove user
public void deleteUser(int userId) throws SQLException {
String sql = "DELETE FROM users WHERE id = ?";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, userId);
int rowsAffected = stmt.executeUpdate();
System.out.println("Deleted " + rowsAffected + " row(s)");
}
}
// Transaction example
public void transferMoney(int fromUserId, int toUserId, double amount)
throws SQLException {
String debit = "UPDATE accounts SET balance = balance - ? WHERE user_id = ?";
String credit = "UPDATE accounts SET balance = balance + ? WHERE user_id = ?";
Connection conn = null;
try {
conn = DriverManager.getConnection(url, user, password);
conn.setAutoCommit(false); // Start transaction
// Debit from sender
try (PreparedStatement stmt = conn.prepareStatement(debit)) {
stmt.setDouble(1, amount);
stmt.setInt(2, fromUserId);
stmt.executeUpdate();
}
// Credit to receiver
try (PreparedStatement stmt = conn.prepareStatement(credit)) {
stmt.setDouble(1, amount);
stmt.setInt(2, toUserId);
stmt.executeUpdate();
}
conn.commit(); // Commit transaction
System.out.println("Transfer successful");
} catch (SQLException e) {
if (conn != null) {
conn.rollback(); // Rollback on error
}
throw e;
} finally {
if (conn != null) {
conn.setAutoCommit(true);
conn.close();
}
}
}
}
Key Points:
- Try-with-resources: Automatically closes Connection, Statement, and ResultSet
- PreparedStatement: All queries use parameters (no SQL injection risk)
- Transactions:
setAutoCommit(false),commit(), androllback()ensure atomicity - Error handling: SQLException is propagated to caller
Example 4: HikariCP Connection Pool Setup
Production-ready connection pooling configuration:
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class DatabaseConfig {
private static HikariDataSource dataSource;
static {
HikariConfig config = new HikariConfig();
// Database connection settings
config.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb");
config.setUsername("dbuser");
config.setPassword("dbpass");
// Pool configuration
config.setMinimumIdle(5);
config.setMaximumPoolSize(20);
config.setConnectionTimeout(30000); // 30 seconds
config.setIdleTimeout(600000); // 10 minutes
config.setMaxLifetime(1800000); // 30 minutes
// Performance optimizations
config.setAutoCommit(true);
config.setConnectionTestQuery("SELECT 1");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
// Pool name for monitoring
config.setPoolName("MyAppHikariPool");
dataSource = new HikariDataSource(config);
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public static void closePool() {
if (dataSource != null && !dataSource.isClosed()) {
dataSource.close();
}
}
}
Using the Connection Pool:
public class UserService {
public void createUser(String name, String email) throws SQLException {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
// Get connection from pool (fast!)
try (Connection conn = DatabaseConfig.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, name);
stmt.setString(2, email);
stmt.executeUpdate();
} // Connection automatically returned to pool
}
public List<User> getAllUsers() throws SQLException {
List<User> users = new ArrayList<>();
String sql = "SELECT id, name, email FROM users";
try (Connection conn = DatabaseConfig.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
User user = new User(
rs.getInt("id"),
rs.getString("name"),
rs.getString("email")
);
users.add(user);
}
}
return users;
}
}
Benefits of This Setup:
- β‘ Fast: Connection reuse eliminates connection overhead
- π Safe: Prepared statement caching prevents SQL injection
- π Monitored: Named pool appears in JMX monitoring tools
- π― Optimized: Settings tuned for typical web application load
Common Mistakes β οΈ
1. Forgetting to Close Resources
β Wrong:
Connection conn = DriverManager.getConnection(url, user, pass);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
// Forgot to close! Memory leak!
β Correct:
try (Connection conn = DriverManager.getConnection(url, user, pass);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {
// Use resources
} // Automatically closed
2. SQL Injection Vulnerability
β Wrong:
String username = request.getParameter("username");
String sql = "SELECT * FROM users WHERE username = '" + username + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
// Attacker can input: ' OR '1'='1
β Correct:
String username = request.getParameter("username");
String sql = "SELECT * FROM users WHERE username = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, username); // Safe!
ResultSet rs = stmt.executeQuery();
3. Creating Connections Instead of Using Pool
β Wrong:
// Every request creates new connection (slow!)
public void handleRequest() {
try (Connection conn = DriverManager.getConnection(url, user, pass)) {
// Use connection
}
}
β Correct:
// Reuse pooled connections (fast!)
public void handleRequest() {
try (Connection conn = dataSource.getConnection()) {
// Use connection
} // Returns to pool
}
4. Incorrect Maven Dependency Scope
β Wrong:
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
<scope>test</scope> <!-- Wrong! Needed at runtime too -->
</dependency>
β Correct:
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
<!-- Default 'compile' scope is correct -->
</dependency>
5. Not Handling Transactions Properly
β Wrong:
// No transaction - partial updates possible!
stmt1.executeUpdate("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
stmt2.executeUpdate("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
// What if second update fails?
β Correct:
conn.setAutoCommit(false);
try {
stmt1.executeUpdate("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
stmt2.executeUpdate("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
conn.commit();
} catch (SQLException e) {
conn.rollback(); // Undo all changes
throw e;
}
6. Incorrect Connection Pool Size
β Wrong:
config.setMaximumPoolSize(1000); // Way too many!
// Database can only handle 100 connections total
β Correct:
// Database max connections: 100
// Number of app instances: 5
// Pool size per instance: 100 / 5 = 20
config.setMaximumPoolSize(20);
π‘ Formula: maxPoolSize = (database max connections) / (number of app instances)
Key Takeaways π―
Build Tools Automate Complexity
- Maven uses XML and convention over configuration
- Gradle uses DSL and offers better performance
- Both manage dependencies and build lifecycles
JDBC Provides Database Connectivity
- Always use PreparedStatement for security
- Close resources with try-with-resources
- Manage transactions explicitly when needed
Connection Pooling Improves Performance
- Reusing connections is 100x faster than creating new ones
- HikariCP is the fastest and most popular pool
- Configure pool size based on database limits
Security is Critical
- Never concatenate SQL strings with user input
- Use parameterized queries exclusively
- Store credentials securely (environment variables, vaults)
Professional Projects Require Both
- Build tools manage dependencies and builds
- Database connectivity is essential for data persistence
- Connection pooling is mandatory for production systems
π Quick Reference Card
| Concept | Key Command/Code |
|---|---|
| Maven Build | mvn clean install |
| Gradle Build | ./gradlew build |
| JDBC Connection | DriverManager.getConnection(url, user, pass) |
| PreparedStatement | conn.prepareStatement("SELECT * FROM t WHERE id = ?") |
| Execute Query | stmt.executeQuery() β returns ResultSet |
| Execute Update | stmt.executeUpdate() β returns row count |
| Start Transaction | conn.setAutoCommit(false) |
| Commit Changes | conn.commit() |
| Rollback | conn.rollback() |
| Connection Pool | dataSource.getConnection() |
π Further Study
Maven Official Documentation: https://maven.apache.org/guides/ - Comprehensive guides on Maven usage and best practices
Gradle User Manual: https://docs.gradle.org/current/userguide/userguide.html - Complete reference for Gradle build tool
HikariCP GitHub: https://github.com/brettwooldridge/HikariCP - Documentation and configuration examples for the fastest connection pool
π Congratulations! You now understand build tools and database connectivityβessential skills for professional Java development. Practice by building a complete application using Maven/Gradle and connecting to a real database with HikariCP.