はじめに
Spring Data JPA は、Spring Boot でリレーショナル データベースと対話するための最も一般的な抽象化レイヤーです。データ アクセス層の定型コードを最小限に抑え、強力なクエリ機能を提供します。この記事では、エンティティ マッピングから高度なクエリまでを説明します。
1. データベースの構成
1.1 PostgreSQL と Docker Compose
# docker-compose.yaml
services:
postgres:
image: postgres:17
environment:
POSTGRES_DB: springboot_demo
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
docker compose up -d
1.2 アプリケーション構成
# application.yaml
spring:
datasource:
url: jdbc:postgresql://localhost:5432/springboot_demo
username: admin
password: secret
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: update # create, create-drop, update, validate, none
show-sql: true
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.PostgreSQLDialect
注意:
ddl-auto: update開発用途のみ。プロダクションを使用する必要がありますvalidateFlyway/Liquibase を使用したスキーマ管理。
2. JPAエンティティマッピング
2.1 基本的なエンティティ
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 200)
private String name;
@Column(unique = true, nullable = false, length = 100)
private String slug;
@Column(columnDefinition = "TEXT")
private String description;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal price;
@Column(name = "stock_quantity", nullable = false)
private Integer stockQuantity = 0;
@Column(nullable = false)
private Boolean active = true;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private ProductStatus status = ProductStatus.DRAFT;
@CreatedDate
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// Constructors, Getters, Setters
protected Product() {} // JPA required
public Product(String name, String slug, BigDecimal price) {
this.name = name;
this.slug = slug;
this.price = price;
}
}
public enum ProductStatus {
DRAFT, ACTIVE, ARCHIVED
}
2.2 基本エンティティ (DRY)
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@CreatedDate
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// Getters, Setters
}
// Enable JPA Auditing
@Configuration
@EnableJpaAuditing
public class JpaConfig { }
// Entities kế thừa
@Entity
@Table(name = "products")
public class Product extends BaseEntity {
private String name;
private BigDecimal price;
// ...
}
@Entity
@Table(name = "categories")
public class Category extends BaseEntity {
private String name;
private String slug;
// ...
}
2.3 ID 生成戦略
// IDENTITY - Database auto-increment (khuyến nghị cho PostgreSQL)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// UUID - Unique across distributed systems
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
// SEQUENCE - Database sequence (tốt cho batch insert)
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "product_seq")
@SequenceGenerator(name = "product_seq",
sequenceName = "product_sequence",
allocationSize = 50)
private Long id;
3. リポジトリインターフェイス
3.1 JPリポジトリ
public interface ProductRepository extends JpaRepository<Product, Long> {
// JpaRepository cung cấp sẵn:
// save(entity), saveAll(entities)
// findById(id), findAll(), findAllById(ids)
// count(), existsById(id)
// deleteById(id), delete(entity), deleteAll()
// flush(), saveAndFlush(entity)
}
3.2 派生クエリメソッド
Spring Data はメソッド名からクエリを自動的に作成します。
public interface ProductRepository extends JpaRepository<Product, Long> {
// SELECT * FROM products WHERE name = ?
List<Product> findByName(String name);
// SELECT * FROM products WHERE slug = ?
Optional<Product> findBySlug(String slug);
// SELECT * FROM products WHERE price BETWEEN ? AND ?
List<Product> findByPriceBetween(BigDecimal min, BigDecimal max);
// SELECT * FROM products WHERE name LIKE '%keyword%'
List<Product> findByNameContainingIgnoreCase(String keyword);
// SELECT * FROM products WHERE active = true ORDER BY created_at DESC
List<Product> findByActiveTrueOrderByCreatedAtDesc();
// SELECT * FROM products WHERE status = ? AND price < ?
List<Product> findByStatusAndPriceLessThan(ProductStatus status, BigDecimal price);
// SELECT * FROM products WHERE category_id IN (?, ?, ?)
List<Product> findByCategoryIdIn(List<Long> categoryIds);
// SELECT COUNT(*) FROM products WHERE status = ?
long countByStatus(ProductStatus status);
// SELECT EXISTS(SELECT 1 FROM products WHERE slug = ?)
boolean existsBySlug(String slug);
// DELETE FROM products WHERE active = false
void deleteByActiveFalse();
// SELECT * FROM products WHERE name = ? LIMIT 1
Optional<Product> findFirstByName(String name);
// SELECT * FROM products ORDER BY price DESC LIMIT 5
List<Product> findTop5ByOrderByPriceDesc();
}
3.3 クエリメソッドのキーワード
| キーワード | SQL | 例 |
|---|---|---|
And | そして | findByNameAndPrice |
Or | または | findByNameOrSlug |
Between | 間 | findByPriceBetween |
LessThan | < | findByPriceLessThan |
GreaterThan | > | findByPriceGreaterThan |
Like | いいね | findByNameLike |
Containing | %x% のように | findByNameContaining |
StartingWith | x% のように | findByNameStartingWith |
In | 印刷 | findByStatusIn |
OrderBy | 注文方法 | findByOrderByPriceAsc |
Not | <> | findByStatusNot |
IsNull | NULL | findByDeletedAtIsNull |
4. カスタムクエリ
4.1 JPQL を使用した @Query
public interface ProductRepository extends JpaRepository<Product, Long> {
@Query("SELECT p FROM Product p WHERE p.price > :minPrice AND p.status = :status")
List<Product> findExpensiveActiveProducts(
@Param("minPrice") BigDecimal minPrice,
@Param("status") ProductStatus status);
@Query("SELECT p FROM Product p WHERE LOWER(p.name) LIKE LOWER(CONCAT('%', :keyword, '%'))")
List<Product> searchByKeyword(@Param("keyword") String keyword);
@Query("SELECT p.status, COUNT(p) FROM Product p GROUP BY p.status")
List<Object[]> countByStatusGrouped();
@Query("UPDATE Product p SET p.active = false WHERE p.id = :id")
@Modifying
@Transactional
int softDelete(@Param("id") Long id);
}
4.2 ネイティブ SQL を使用した @Query
@Query(value = """
SELECT p.* FROM products p
JOIN categories c ON p.category_id = c.id
WHERE c.slug = :categorySlug
AND p.price BETWEEN :minPrice AND :maxPrice
ORDER BY p.created_at DESC
""", nativeQuery = true)
List<Product> findByCategoryAndPriceRange(
@Param("categorySlug") String categorySlug,
@Param("minPrice") BigDecimal minPrice,
@Param("maxPrice") BigDecimal maxPrice);
4.3 予測
// Interface-based projection
public interface ProductSummary {
Long getId();
String getName();
BigDecimal getPrice();
}
public interface ProductRepository extends JpaRepository<Product, Long> {
List<ProductSummary> findByActiveTrue();
}
// Record-based projection (Spring Boot 4.x)
public record ProductInfo(Long id, String name, BigDecimal price) {}
@Query("SELECT new com.example.dto.ProductInfo(p.id, p.name, p.price) FROM Product p WHERE p.active = true")
List<ProductInfo> findActiveProductInfo();
5. 監査
5.1 監査構成
@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class JpaAuditConfig {
@Bean
public AuditorAware<String> auditorProvider() {
return () -> {
// Lấy username từ Security Context
return Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getName)
.or(() -> Optional.of("system"));
};
}
}
5.2 監査可能なエンティティ
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AuditableEntity extends BaseEntity {
@CreatedBy
@Column(name = "created_by", updatable = false)
private String createdBy;
@LastModifiedBy
@Column(name = "updated_by")
private String updatedBy;
}
概要
- JPA エンティティは @Entity、@Table、@Column を使用して Java オブジェクトをデータベース テーブルにマップします
- JpaRepository は組み込みの CRUD 操作を提供し、派生クエリ メソッドはメソッド名から SQL を自動的に作成します
- @Query は、複雑なクエリに対して JPQL とネイティブ SQL をサポートし、プロジェクションによりデータ転送のオーバーヘッドを削減します
- JPA 監査は、createdAt、updatedAt、createdBy、updatedBy を自動的に追跡します。
演習
1.エンティティの作成 Article フィールド: ID、タイトル、スラッグ (一意)、コンテンツ (TEXT)、ステータス (列挙)、viewCount、createdAt、updatedAt。 BaseEntityから拡張
2. 少なくとも 5 つの派生クエリ メソッドを備えたリポジトリを作成します: ステータスによる検索、タイトルのキーワードによる検索、ステータスによるカウント、viewCount による上位 10 の検索
3. 2 つのカスタム @Query を作成します。viewCount > N の記事を検索する JPQL クエリ、カテゴリ テーブルに結合するネイティブ SQL クエリ