Spring Data JPA
SkillDatabases & dataSpring Data JPA for database access in Spring Boot applications. Covers repositories, entities, relationships, queries, pagination, and auditing. Based on production patterns from castellino and gestionale-presenze projects.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Spring Data JPA skill
What this skill tells your AI
The instructions your AI receives, as published by claude-dev-suite/claude-dev-suite in skills/backend-frameworks/spring-data-jpa/SKILL.md and read by ahel’s review.
Deep Knowledge: Use
mcp__documentation__fetch_docswith technology:spring-data-jpafor comprehensive documentation.
Entity with Auditing
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EntityListeners(AuditingEntityListener.class)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String name;
@Column(unique = true, nullable = false)
private String email;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private UserRole role = UserRole.USER;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private UserStatus status = UserStatus.ACTIVE;
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
@CreatedBy
@Column(updatable = false)
private String createdBy;
@LastModifiedBy
private String updatedBy;
}
Repository Interface
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Derived query methods
Optional<User> findByEmail(String email);
boolean existsByEmail(String email);
List<User> findByStatus(UserStatus status);
List<User> findByRoleIn(List<UserRole> roles);
// Query with JPQL
@Query("SELECT u FROM User u WHERE u.status = :status AND u.role = :role")
List<User> findByStatusAndRole(
@Param("status") UserStatus status,
@Param("role") UserRole role
);
// Native query
@Query(value = "SELECT * FROM users WHERE email LIKE %:domain", nativeQuery = true)
List<User> findByEmailDomain(@Param("domain") String domain);
// Pagination
Page<User> findByNameContainingIgnoreCase(String name, Pageable pageable);
// Sorting
List<User> findByStatus(UserStatus status, Sort sort);
// Modifying queries
@Modifying
@Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
int updateStatus(@Param("id") Long id, @Param("status") UserStatus status);
@Modifying
@Query("DELETE FROM User u WHERE u.status = :status")
int deleteByStatus(@Param("status") UserStatus status);
}
Relationships
// One-to-Many
@Entity
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Employee> employees = new ArrayList<>();
}
@Entity
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id", nullable = false)
private Department department;
}
// Many-to-Many
@Entity
public class User {
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
}
Pagination & Sorting
@Service
public class UserService {
public Page<UserResponse> findAll(int page, int size, String sortBy, String direction) {
Sort sort = Sort.by(Sort.Direction.fromString(direction), sortBy);
Pageable pageable = PageRequest.of(page, size, sort);
return userRepository.findAll(pageable)
.map(userMapper::toResponse);
}
public Page<UserResponse> search(String query, Pageable pageable) {
return userRepository.findByNameContainingIgnoreCase(query, pageable)
.map(userMapper::toResponse);
}
}
// Controller
@GetMapping
public ResponseEntity<Page<UserResponse>> findAll(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "createdAt") String sortBy,
@RequestParam(defaultValue = "desc") String direction) {
return ResponseEntity.ok(userService.findAll(page, size, sortBy, direction));
}
Specifications (Dynamic Queries)
public class UserSpecifications {
public static Specification<User> hasStatus(UserStatus status) {
return (root, query, cb) ->
status == null ? null : cb.equal(root.get("status"), status);
}
public static Specification<User> hasRole(UserRole role) {
return (root, query, cb) ->
role == null ? null : cb.equal(root.get("role"), role);
}
public static Specification<User> nameContains(String name) {
return (root, query, cb) ->
name == null ? null : cb.like(cb.lower(root.get("name")),
"%" + name.toLowerCase() + "%");
}
}
// Repository extends JpaSpecificationExecutor
public interface UserRepository extends
JpaRepository<User, Long>,
JpaSpecificationExecutor<User> {}
// Usage
Specification<User> spec = Specification
.where(UserSpecifications.hasStatus(UserStatus.ACTIVE))
.and(UserSpecifications.hasRole(UserRole.ADMIN))
.and(UserSpecifications.nameContains("john"));
List<User> users = userRepository.findAll(spec);
Enable Auditing
@Configuration
@EnableJpaAuditing
public class JpaConfig {
@Bean
public AuditorAware<String> auditorProvider() {
return () -> Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getName);
}
}
Key Annotations
| Annotation | Purpose |
|---|---|
@Entity | JPA entity |
@Table | Table mapping |
@Id | Primary key |
@GeneratedValue | Auto-generation strategy |
@Column | Column mapping |
@ManyToOne / @OneToMany | Relationships |
@Query | Custom JPQL/SQL |
@Modifying | Update/Delete queries |
@CreatedDate / @LastModifiedDate | Auditing |
When NOT to Use This Skill
- Spring Boot application setup → Use
spring-bootskill - REST API patterns → Use
spring-webskill - MongoDB operations → Use
mongodb-expertskill - Security configuration → Use
spring-securityskill - Raw SQL optimization → Use
sql-expertskill - Reactive database access → Use
spring-r2dbcskill
Anti-Patterns
| Anti-Pattern | Why It's Bad | Correct Approach |
|---|---|---|
| N+1 queries | Poor performance | Use @EntityGraph or fetch joins |
Missing @Transactional | Data inconsistency | Always use for write operations |
| Bidirectional relations without care | Infinite recursion | Use @JsonManagedReference/@JsonBackReference |
| Fetch EAGER everywhere | Loads unnecessary data | Use LAZY, fetch only when needed |
| No pagination | Memory issues | Always paginate large results |
| Query in loop | Performance killer | Use batch fetch or single query |
Quick Troubleshooting
| Problem | Likely Cause | Solution |
|---|---|---|
| LazyInitializationException | Accessing lazy field outside transaction | Fetch in transaction or use @EntityGraph |
| MultipleBagFetchException | Multiple @OneToMany EAGER fetch | Use @EntityGraph or separate queries |
| Slow queries | Missing indexes or N+1 | Add indexes, check query logs |
| No query results | Wrong method name | Follow naming convention or use @Query |
| Constraint violation | Entity state mismatch | Check cascade and orphanRemoval |
| DetachedEntityException | Entity not managed | Use merge() or reload entity |
Reference Documentation
Signals
- GitHub stars
- 33
- Forks
- 6
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
spring-data-jpa-claude-dev-suite- Source
- github.com/claude-dev-suite/claude-dev-suite