Spring: Spring Data JPA
Entities & Repositories
// Entity
@Entity
@Table(name = "users", indexes = {
@Index(columnList = "email", unique = true)
})
@EntityListeners(AuditingEntityListener.class)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String name;
@Column(nullable = false, unique = true)
private String email;
@Enumerated(EnumType.STRING)
private UserStatus status = UserStatus.ACTIVE;
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<Post> posts = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organization_id")
private Organization organization;
}
// Repository — Spring generates implementation at runtime
public interface UserRepository extends JpaRepository<User, Long> {
// Derived queries — Spring generates SQL from method name
Optional<User> findByEmail(String email);
List<User> findByStatusAndOrganizationId(UserStatus status, Long orgId);
boolean existsByEmail(String email);
long countByStatus(UserStatus status);
void deleteByEmail(String email);
// Custom query
@Query("SELECT u FROM User u WHERE u.name ILIKE %:term% OR u.email ILIKE %:term%")
Page<User> search(@Param("term") String term, Pageable pageable);
// Native SQL
@Query(value = "SELECT * FROM users WHERE created_at > :since", nativeQuery = true)
List<User> findRecentUsers(@Param("since") LocalDateTime since);
// Projection — return subset of fields
List<UserSummary> findByStatus(UserStatus status);
}Queries & Pagination
// Service layer — use repositories
@Service
@Transactional
public class UserService {
public Page<UserDto> getAll(int page, int size, String search) {
var pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
var userPage = search != null
? userRepository.search(search, pageable)
: userRepository.findAll(pageable);
return userPage.map(userMapper::toDto);
}
// Specification (dynamic filtering)
public List<User> filter(UserFilter filter) {
return userRepository.findAll(
Specification.where(UserSpec.hasStatus(filter.status()))
.and(UserSpec.inOrganization(filter.orgId()))
.and(UserSpec.createdAfter(filter.since()))
);
}
}
// Specification — composable query predicates
public class UserSpec {
public static Specification<User> hasStatus(UserStatus status) {
return (root, query, cb) ->
status == null ? null : cb.equal(root.get("status"), status);
}
public static Specification<User> createdAfter(LocalDate date) {
return (root, query, cb) ->
date == null ? null : cb.greaterThan(root.get("createdAt"), date.atStartOfDay());
}
}
// Avoid N+1 — use JOIN FETCH for associations
@Query("SELECT u FROM User u LEFT JOIN FETCH u.posts WHERE u.id = :id")
Optional<User> findByIdWithPosts(@Param("id") Long id);
// Or use @EntityGraph
@EntityGraph(attributePaths = {"posts", "organization"})
Optional<User> findWithDetailsById(Long id);Transactions & Migrations
// @Transactional — Spring manages transaction boundaries
@Service
@Transactional(readOnly = true) // default: read-only (optimizes SELECT)
public class UserService {
@Transactional // override: writable
public User create(CreateUserRequest req) {
var user = new User(req.name(), req.email());
userRepository.save(user);
emailService.sendWelcome(user); // if this throws, user creation rolls back
return user;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void auditLog(String action) { } // always runs in own transaction
@Transactional(noRollbackFor = EmailException.class)
public void createWithoutRollingBackOnEmail(CreateUserRequest req) { }
}
// Flyway (recommended) or Liquibase — database migrations
// Add flyway-core dependency; create src/main/resources/db/migration/
// V1__init_schema.sql, V2__add_users_table.sql, V3__add_posts.sql
# application.yml
spring:
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: trueKeep your own version of these notes — editable, searchable, and organised by your stack.
Start free