Blog Spring Boot

Repository Pattern in Java: Decoupling Business Logic from Persistence

When designing software systems, maintaining a clean separation of concerns is crucial. One common pitfall is mixing database interaction code with business logic, which leads to tightly coupled components. The Repository Pattern is an effective design strategy to address this by abstracting the persistence layer from the business logic. This approach makes your application more maintainable, testable, and adaptable to future changes (like switching from MySQL to MongoDB).

Why Avoid Mixing Database Code with Business Logic?

  1. Flexibility: If the database technology changes, you only need to update the repository implementation, leaving the business logic untouched.
  2. Maintainability: Separation of concerns makes the code easier to read, debug, and update.
  3. Testability: Mock repositories can replace actual database interactions during unit testing, simplifying the process.

Repository Pattern Design

The repository interface defines the contract for data operations, while concrete classes implement these operations for specific databases. The service layer depends on the repository interface, not its implementation.

Example: Implementing the Repository Pattern

Step 1: Define the Repository Interface

Java
public interface ProductRepository {
    void save(Product product);
    Product findProductByTitle(String title);
    Product findProductById(long id);
}

Step 2: Implement the Repository for MySQL

Java
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class MySqlProductRepository implements ProductRepository {

    private Connection connection;

    public MySqlProductRepository(Connection connection) {
        this.connection = connection;
    }

    @Override
    public void save(Product product) {
        String query = "INSERT INTO products (title, description) VALUES (?, ?)";
        try (PreparedStatement statement = connection.prepareStatement(query)) {
            statement.setString(1, product.getTitle());
            statement.setString(2, product.getDescription());
            statement.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public Product findProductByTitle(String title) {
        String query = "SELECT * FROM products WHERE title = ?";
        try (PreparedStatement statement = connection.prepareStatement(query)) {
            statement.setString(1, title);
            ResultSet rs = statement.executeQuery();
            if (rs.next()) {
                return new Product(rs.getLong("id"), rs.getString("title"), rs.getString("description"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public Product findProductById(long id) {
        String query = "SELECT * FROM products WHERE id = ?";
        try (PreparedStatement statement = connection.prepareStatement(query)) {
            statement.setLong(1, id);
            ResultSet rs = statement.executeQuery();
            if (rs.next()) {
                return new Product(rs.getLong("id"), rs.getString("title"), rs.getString("description"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

Step 3: Implement the Repository for MongoDB

Java
import com.mongodb.client.MongoCollection;
import org.bson.Document;

public class MongoDbProductRepository implements ProductRepository {

    private MongoCollection<Document> collection;

    public MongoDbProductRepository(MongoCollection<Document> collection) {
        this.collection = collection;
    }

    @Override
    public void save(Product product) {
        Document doc = new Document("title", product.getTitle())
                        .append("description", product.getDescription());
        collection.insertOne(doc);
    }

    @Override
    public Product findProductByTitle(String title) {
        Document query = new Document("title", title);
        Document doc = collection.find(query).first();
        if (doc != null) {
            return new Product(doc.getObjectId("_id").toHexString(), doc.getString("title"), doc.getString("description"));
        }
        return null;
    }

    @Override
    public Product findProductById(long id) {
        // Assume id is a String for MongoDB's ObjectId
        Document query = new Document("_id", id);
        Document doc = collection.find(query).first();
        if (doc != null) {
            return new Product(doc.getObjectId("_id").toHexString(), doc.getString("title"), doc.getString("description"));
        }
        return null;
    }
}

Step 4: Service Layer Using the Repository

Java
public class ProductService {

    private ProductRepository productRepository;

    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    public void createProduct(String title, String description) {
        Product product = new Product(title, description);
        productRepository.save(product);
    }

    public Product getProductByTitle(String title) {
        return productRepository.findProductByTitle(title);
    }

    public Product getProductById(long id) {
        return productRepository.findProductById(id);
    }
}

Step 5: Switching Between Implementations

Java
public class Main {
    public static void main(String[] args) {
        ProductRepository repository;
        String databaseType = "mongo"; // Change to "mysql" to switch

        if (databaseType.equals("mysql")) {
            Connection connection = DatabaseConnection.getMySqlConnection();
            repository = new MySqlProductRepository(connection);
        } else {
            MongoCollection<Document> collection = DatabaseConnection.getMongoCollection();
            repository = new MongoDbProductRepository(collection);
        }

        ProductService productService = new ProductService(repository);

        // Use the service
        productService.createProduct("Sample Product", "A sample description");
        Product product = productService.getProductByTitle("Sample Product");
        System.out.println(product);
    }
}

Benefits of the Repository Pattern

  • Loose Coupling: The service layer is decoupled from the persistence layer.
  • Ease of Change: Switching databases requires only a change in the repository implementation.
  • Improved Testability: Mock repositories can be used to test the service layer without requiring a database.

By adopting the Repository Pattern, you create a scalable and maintainable architecture that can adapt to future changes with minimal impact on your business logic.

Avatar

Neelabh

About Author

As Neelabh Singh, I am a Senior Software Engineer with 6.6 years of experience, specializing in Java technologies, Microservices, AWS, Algorithms, and Data Structures. I am also a technology blogger and an active participant in several online coding communities.

You may also like

Blog Design Pattern

Understanding the Builder Design Pattern in Java | Creational Design Patterns | CodeTechSummit

Overview The Builder design pattern is a creational pattern used to construct a complex object step by step. It separates
Blog Tech Toolkit

Base64 Decode

Base64 encoding is a technique used to encode binary data into ASCII characters, making it easier to transmit data over