Why ORMs Exist

The Pain of Plain JDBC

  • Repetitive mapping: every query repeats rs.getString("name") → setName(...).
  • Resource handling: Connection, PreparedStatement and ResultSet must all be closed.
  • Checked SQLException everywhere.
  • Database-specific SQL: LIMIT (MySQL) vs FETCH FIRST (Oracle), auto-increment vs sequences.
  • Change tracking by hand: you must remember which fields changed and write the UPDATE yourself.
  • Relationships: loading an order with its items means extra queries and manual stitching.

Hand-Written Mapping, Simulated

Example

Here a List of maps plays the role of a ResultSet. Every entity needs its own mapper like this in plain JDBC — this is exactly the code an ORM writes for you:

import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;

public class Main {
    public static void main(String[] args) {
        List<Map<String, Object>> resultSet = List.of(
            row(1L, "Boat Rockerz 450", "1499.00", 120),
            row(3L, "Noise ColorFit Pro", "2299.00", 40));

        // A "RowMapper": one row in, one object out
        Function<Map<String, Object>, Product> mapper = r -> new Product(
            (Long) r.get("id"),
            (String) r.get("name"),
            (BigDecimal) r.get("price"),
            (Integer) r.get("stock"));

        List<Product> products = resultSet.stream().map(mapper).toList();
        products.forEach(System.out::println);

        BigDecimal stockValue = products.stream()
            .map(p -> p.price().multiply(BigDecimal.valueOf(p.stock())))
            .reduce(BigDecimal.ZERO, BigDecimal::add);
        System.out.println("Inventory value: Rs." + stockValue);
    }

    static Map<String, Object> row(Long id, String name, String price, Integer stock) {
        Map<String, Object> m = new LinkedHashMap<>();
        m.put("id", id); m.put("name", name); m.put("price", new BigDecimal(price)); m.put("stock", stock);
        return m;
    }
}

record Product(Long id, String name, BigDecimal price, Integer stock) {}

Output

Product[id=1, name=Boat Rockerz 450, price=1499.00, stock=120]
Product[id=3, name=Noise ColorFit Pro, price=2299.00, stock=40]
Inventory value: Rs.271840.00

What an ORM Gives You

Plain JDBCWith JPA / Hibernate
Write SQL for every CRUD operationGenerated from the mapping
Map each column by handAutomatic, from annotations
Track changes yourselfDirty checking: changed fields are saved at commit
Database-specific SQLDialect generates correct SQL for each database
Manual joins for relationshipsorder.getItems() loads them
No cachingFirst-level cache per transaction, optional second-level cache

Tip: An ORM does not mean "never learn SQL". You still need SQL to read Hibernate logs, design indexes and fix slow queries. See the SQL material on Careeroza.

Exercise

Exercise: Write a Function<Map<String,Object>, Category> mapper for rows with keys id, name and slug, and use it on two sample rows.

Show answer
record Category(Long id, String name, String slug) {}

Function<Map<String, Object>, Category> categoryMapper = r -> new Category(
    (Long) r.get("id"),
    (String) r.get("name"),
    (String) r.get("slug"));

List<Category> categories = List.of(
        Map.<String, Object>of("id", 1L, "name", "Electronics", "slug", "electronics"),
        Map.<String, Object>of("id", 2L, "name", "Books", "slug", "books"))
    .stream().map(categoryMapper).toList();