Handbooks / Spring Boot / Chapter 5
Spring Data JPA
7 pages · ~5 min✓ Reviewed
Builds on Spring AOP and Proxies. Next up: Database Migrations.
Entities and Relationships
1. Entity Basics
- @Entity class maps to one table
- @Id marks the primary key
@GeneratedValue(strategy = IDENTITY): the DB assigns the id- A no-arg constructor is required
java
@Entity
public class Item {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
protected Item() {}
}2. Owning Side and Link Tables
| @OneToMany | @ManyToOne | |
|---|---|---|
| Side | inverse | owning |
| Marker | mappedBy = "order" | @JoinColumn |
| Writes the FK? | No | Yes |
- @ManyToMany needs a
@JoinTable - Extra columns on the link: use a join entity with two
@ManyToOne
3. Cascade and Sync
cascade = ALL+orphanRemoval = true: children follow the parent's save and delete- Use it only for owned children
java
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Item> items = new ArrayList<>();
public void addItem(Item i) {
items.add(i);
i.setOrder(this);
}helper keeps both sides in sync
Sync both sides
Setting only the inverse side persists nothing. Always go through order.addItem(item).
Repositories and Derived Queries
1. Repository Interface
JpaRepository<T, ID>extends CrudRepository and PagingAndSortingRepository- Declare an interface only; Spring generates the implementation at startup
java
interface UserRepo extends JpaRepository<User, Long> {}| Core method | Returns |
|---|---|
save | insert or merge |
findById | Optional<T> |
findAll | List<T> |
deleteById | void |
existsById |
@Query, Pagination and Sorting
1. @Query and Native SQL
- JPQL uses entity and field names, not table or column names
- Bind with
:emailplus@Param("email") nativeQuery = trueruns raw SQL for DB-specific features- Native queries get no entity-name translation
java
@Query("select u from User u where u.email = :email") Optional<User> find(@Param("email") String email); @Query(value = "select * from users where email = ?1", nativeQuery = true) Optional<User> findRaw(String email);
2. Modify, Page and Sort
@Modifying+@Queryruns update or delete- Add clearAutomatically = true so the context is not stale
- Pages are zero-indexed, sort inside
PageRequest
java
@Modifying(clearAutomatically = true) @Query("update User u set u.active = false where u.id = :id") int deactivate(@Param("id") Long id); Pageable p = PageRequest.of(0, 20, Sort.by("createdAt").descending());
Transactions with @Transactional
1. Proxy and Rollback
- Callercalls bean
- Proxybegins tx
- Methodyour code
- Commit / Rollbackon exit
| Outcome | Default |
|---|---|
| Success | commit |
Lazy vs Eager Loading and the N+1 Problem
1. Fetch Defaults and Proxies
| Association | Default |
|---|---|
@ManyToOne, @OneToOne | EAGER |
@OneToMany, @ManyToMany | LAZY |
- Safe habit: set fetch = LAZY on everything
- A lazy association is a proxy, loaded on first access
- Touch it after the transaction closes: LazyInitializationException
2. The N+1 Problem
- 1 queryloads N parents
- Looptouch lazy items
- N queriesone per parent
Spring Data JPA Cheat Sheet
1. Annotations and Derived Queries
| Annotation | Job |
|---|---|
@Entity @Id | class = table, primary key |
@GeneratedValue | DB assigns the id |
@OneToMany(mappedBy) | inverse side, writes nothing |
@ManyToOne @JoinColumn | owns the foreign key |
@Query @Param | JPQL, named bind |
@Modifying | update or delete query |
@Transactional | transaction via proxy |
@EntityGraph |
Check yourself
order.getItems().add(item); orderRepo.save(order); with cascade ALL, but item.setOrder never called. What is stored?
- The item row is inserted with order_id NULL
- Only the owning @ManyToOne side writes the FK; use
order.addItem(item)
run() calls this.saveAll(), which is @Transactional. It throws a RuntimeException after 2 of 5 saves. What rolls back?
- Nothing: self-invocation bypasses the proxy, so no transaction starts
- The 2 saves already committed on their own
100 orders are loaded with findAll(), then a loop calls o.getItems().size() on lazy items. How many queries?
- 101: one for orders, then one per order
- Fix with
join fetchor @EntityGraph, not EAGER