Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Build Spring Boot 3.x REST APIs, microservices, and reactive WebFlux apps with Spring Security 6 and JPA.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/data.md
1# Data Access - Spring Data JPA23## JPA Entity Pattern45```java6@Entity7@Table(name = "users", indexes = {8@Index(name = "idx_email", columnList = "email", unique = true),9@Index(name = "idx_username", columnList = "username")10})11@EntityListeners(AuditingEntityListener.class)12@Getter @Setter13@NoArgsConstructor14@AllArgsConstructor15@Builder16public class User {1718@Id19@GeneratedValue(strategy = GenerationType.IDENTITY)20private Long id;2122@Column(nullable = false, unique = true, length = 100)23private String email;2425@Column(nullable = false, length = 100)26private String password;2728@Column(nullable = false, unique = true, length = 50)29private String username;3031@Column(nullable = false)32@Builder.Default33private Boolean active = true;3435@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)36@Builder.Default37private List<Address> addresses = new ArrayList<>();3839@ManyToMany40@JoinTable(41name = "user_roles",42joinColumns = @JoinColumn(name = "user_id"),43inverseJoinColumns = @JoinColumn(name = "role_id")44)45@Builder.Default46private Set<Role> roles = new HashSet<>();4748@CreatedDate49@Column(nullable = false, updatable = false)50private LocalDateTime createdAt;5152@LastModifiedDate53@Column(nullable = false)54private LocalDateTime updatedAt;5556@Version57private Long version;5859// Helper methods for bidirectional relationships60public void addAddress(Address address) {61addresses.add(address);62address.setUser(this);63}6465public void removeAddress(Address address) {66addresses.remove(address);67address.setUser(null);68}69}70```7172## Spring Data JPA Repository7374```java75@Repository76public interface UserRepository extends JpaRepository<User, Long>,77JpaSpecificationExecutor<User> {7879Optional<User> findByEmail(String email);8081Optional<User> findByUsername(String username);8283boolean existsByEmail(String email);8485boolean existsByUsername(String username);8687@Query("SELECT u FROM User u LEFT JOIN FETCH u.roles WHERE u.email = :email")88Optional<User> findByEmailWithRoles(@Param("email") String email);8990@Query("SELECT u FROM User u WHERE u.active = true AND u.createdAt >= :since")91List<User> findActiveUsersSince(@Param("since") LocalDateTime since);9293@Modifying94@Query("UPDATE User u SET u.active = false WHERE u.lastLoginAt < :threshold")95int deactivateInactiveUsers(@Param("threshold") LocalDateTime threshold);9697// Projection for read-only DTOs98@Query("SELECT new com.example.dto.UserSummary(u.id, u.username, u.email) " +99"FROM User u WHERE u.active = true")100List<UserSummary> findAllActiveSummaries();101}102```103104## Repository with Specifications105106```java107public class UserSpecifications {108109public static Specification<User> hasEmail(String email) {110return (root, query, cb) ->111email == null ? null : cb.equal(root.get("email"), email);112}113114public static Specification<User> isActive() {115return (root, query, cb) -> cb.isTrue(root.get("active"));116}117118public static Specification<User> createdAfter(LocalDateTime date) {119return (root, query, cb) ->120date == null ? null : cb.greaterThanOrEqualTo(root.get("createdAt"), date);121}122123public static Specification<User> hasRole(String roleName) {124return (root, query, cb) -> {125Join<User, Role> roles = root.join("roles", JoinType.INNER);126return cb.equal(roles.get("name"), roleName);127};128}129}130131// Usage in service132@Service133@RequiredArgsConstructor134public class UserService {135private final UserRepository userRepository;136137public Page<User> searchUsers(UserSearchCriteria criteria, Pageable pageable) {138Specification<User> spec = Specification139.where(UserSpecifications.hasEmail(criteria.email()))140.and(UserSpecifications.isActive())141.and(UserSpecifications.createdAfter(criteria.createdAfter()));142143return userRepository.findAll(spec, pageable);144}145}146```147148## Transaction Management149150```java151@Service152@RequiredArgsConstructor153@Transactional(readOnly = true)154public class OrderService {155private final OrderRepository orderRepository;156private final PaymentService paymentService;157private final InventoryService inventoryService;158private final NotificationService notificationService;159160@Transactional161public Order createOrder(OrderCreateRequest request) {162// All operations in single transaction163Order order = Order.builder()164.customerId(request.customerId())165.status(OrderStatus.PENDING)166.build();167168request.items().forEach(item -> {169inventoryService.reserveStock(item.productId(), item.quantity());170order.addItem(item);171});172173order = orderRepository.save(order);174175try {176paymentService.processPayment(order);177order.setStatus(OrderStatus.PAID);178} catch (PaymentException e) {179order.setStatus(OrderStatus.PAYMENT_FAILED);180throw e; // Transaction will rollback181}182183return orderRepository.save(order);184}185186@Transactional(propagation = Propagation.REQUIRES_NEW)187public void logOrderEvent(Long orderId, String event) {188// Separate transaction - will commit even if parent rolls back189OrderEvent orderEvent = new OrderEvent(orderId, event);190orderEventRepository.save(orderEvent);191}192193@Transactional(noRollbackFor = NotificationException.class)194public void completeOrder(Long orderId) {195Order order = orderRepository.findById(orderId)196.orElseThrow(() -> new ResourceNotFoundException("Order not found"));197198order.setStatus(OrderStatus.COMPLETED);199orderRepository.save(order);200201// Won't rollback transaction if notification fails202try {203notificationService.sendCompletionEmail(order);204} catch (NotificationException e) {205log.error("Failed to send notification for order {}", orderId, e);206}207}208}209```210211## Auditing Configuration212213```java214@Configuration215@EnableJpaAuditing216public class JpaAuditingConfig {217218@Bean219public AuditorAware<String> auditorProvider() {220return () -> {221Authentication authentication = SecurityContextHolder222.getContext()223.getAuthentication();224225if (authentication == null || !authentication.isAuthenticated()) {226return Optional.of("system");227}228229return Optional.of(authentication.getName());230};231}232}233234@MappedSuperclass235@EntityListeners(AuditingEntityListener.class)236@Getter @Setter237public abstract class AuditableEntity {238239@CreatedDate240@Column(nullable = false, updatable = false)241private LocalDateTime createdAt;242243@CreatedBy244@Column(nullable = false, updatable = false, length = 100)245private String createdBy;246247@LastModifiedDate248@Column(nullable = false)249private LocalDateTime updatedAt;250251@LastModifiedBy252@Column(nullable = false, length = 100)253private String updatedBy;254}255```256257## Projections258259```java260// Interface-based projection261public interface UserSummary {262Long getId();263String getUsername();264String getEmail();265266@Value("#{target.firstName + ' ' + target.lastName}")267String getFullName();268}269270// Class-based projection (DTO)271public record UserSummaryDto(272Long id,273String username,274String email275) {}276277// Usage278public interface UserRepository extends JpaRepository<User, Long> {279List<UserSummary> findAllBy();280281<T> List<T> findAllBy(Class<T> type);282}283284// Service usage285List<UserSummary> summaries = userRepository.findAllBy();286List<UserSummaryDto> dtos = userRepository.findAllBy(UserSummaryDto.class);287```288289## Query Optimization290291```java292@Service293@RequiredArgsConstructor294@Transactional(readOnly = true)295public class UserQueryService {296private final UserRepository userRepository;297private final EntityManager entityManager;298299// N+1 problem solved with JOIN FETCH300@Query("SELECT DISTINCT u FROM User u " +301"LEFT JOIN FETCH u.addresses " +302"LEFT JOIN FETCH u.roles " +303"WHERE u.active = true")304List<User> findAllActiveWithAssociations();305306// Batch fetching307@BatchSize(size = 25)308@OneToMany(mappedBy = "user")309private List<Order> orders;310311// EntityGraph for dynamic fetching312@EntityGraph(attributePaths = {"addresses", "roles"})313List<User> findAllByActiveTrue();314315// Pagination to avoid loading all data316public Page<User> findAllUsers(Pageable pageable) {317return userRepository.findAll(pageable);318}319320// Native query for complex queries321@Query(value = """322SELECT u.* FROM users u323INNER JOIN orders o ON u.id = o.user_id324WHERE o.created_at >= :since325GROUP BY u.id326HAVING COUNT(o.id) >= :minOrders327""", nativeQuery = true)328List<User> findFrequentBuyers(@Param("since") LocalDateTime since,329@Param("minOrders") int minOrders);330}331```332333## Database Migrations (Flyway)334335```sql336-- V1__create_users_table.sql337CREATE TABLE users (338id BIGSERIAL PRIMARY KEY,339email VARCHAR(100) NOT NULL UNIQUE,340password VARCHAR(100) NOT NULL,341username VARCHAR(50) NOT NULL UNIQUE,342active BOOLEAN NOT NULL DEFAULT true,343created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,344updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,345version BIGINT NOT NULL DEFAULT 0346);347348CREATE INDEX idx_users_email ON users(email);349CREATE INDEX idx_users_username ON users(username);350CREATE INDEX idx_users_active ON users(active);351352-- V2__create_addresses_table.sql353CREATE TABLE addresses (354id BIGSERIAL PRIMARY KEY,355user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,356street VARCHAR(200) NOT NULL,357city VARCHAR(100) NOT NULL,358country VARCHAR(2) NOT NULL,359created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP360);361362CREATE INDEX idx_addresses_user_id ON addresses(user_id);363```364365## Quick Reference366367| Annotation | Purpose |368|------------|---------|369| `@Entity` | Marks class as JPA entity |370| `@Table` | Specifies table details and indexes |371| `@Id` | Marks primary key field |372| `@GeneratedValue` | Auto-generated primary key strategy |373| `@Column` | Column constraints and mapping |374| `@OneToMany/@ManyToOne` | One-to-many/many-to-one relationships |375| `@ManyToMany` | Many-to-many relationships |376| `@JoinColumn/@JoinTable` | Join column/table configuration |377| `@Transactional` | Declares transaction boundaries |378| `@Query` | Custom JPQL/native queries |379| `@Modifying` | Marks query as UPDATE/DELETE |380| `@EntityGraph` | Defines fetch graph for associations |381| `@Version` | Optimistic locking version field |382