You are viewing a preview of this lesson. Sign in to start learning
Back to Java

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:

  1. DriverManager: Manages database drivers and establishes connections
  2. Connection: Represents a session with a specific database
  3. Statement: Executes SQL queries
  4. PreparedStatement: Pre-compiled SQL with parameters (prevents SQL injection)
  5. 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:

  1. HikariCP (fastest, most popular)
  2. Apache DBCP (Apache Commons)
  3. C3P0 (older, still used)
  4. 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.0 uniquely identifies this project
  • Properties: Sets Java version to 17 and UTF-8 encoding
  • Dependencies: Includes PostgreSQL driver, HikariCP, and JUnit
  • Scope: test scope 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: java and application plugins provide necessary tasks
  • Repositories: mavenCentral() is where dependencies are downloaded from
  • Dependencies: implementation for runtime, testImplementation for tests only
  • Application: Specifies main class for run task

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(), and rollback() 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 🎯

  1. Build Tools Automate Complexity

    • Maven uses XML and convention over configuration
    • Gradle uses DSL and offers better performance
    • Both manage dependencies and build lifecycles
  2. JDBC Provides Database Connectivity

    • Always use PreparedStatement for security
    • Close resources with try-with-resources
    • Manage transactions explicitly when needed
  3. 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
  4. Security is Critical

    • Never concatenate SQL strings with user input
    • Use parameterized queries exclusively
    • Store credentials securely (environment variables, vaults)
  5. 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

  1. Maven Official Documentation: https://maven.apache.org/guides/ - Comprehensive guides on Maven usage and best practices

  2. Gradle User Manual: https://docs.gradle.org/current/userguide/userguide.html - Complete reference for Gradle build tool

  3. 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.