Careeroza
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) vsFETCH 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 JDBC | With JPA / Hibernate |
|---|---|
| Write SQL for every CRUD operation | Generated from the mapping |
| Map each column by hand | Automatic, from annotations |
| Track changes yourself | Dirty checking: changed fields are saved at commit |
| Database-specific SQL | Dialect generates correct SQL for each database |
| Manual joins for relationships | order.getItems() loads them |
| No caching | First-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();