스프링 복습 그만!

프로젝트전공부·2024년 8월 25일

스피링기본

목록 보기
1/8

포스팅 이유

공부를 기록하면서 만들었지만, 1년간 하루에 3포스팅씩 만들니까, 너무많은 공부를 하고있어서, 정리가 안됩니다.
더이상 복습하기에는 시간이 너무 아까워서 스프링 관련한 내용을 올립니다.

영속상태

  • 비영속(Transient)
  • 연속(Persistent)
  • 중영속(Detached)
  • 삭제(Removed)
  1. 비영속(Transient)
    설명: 엔티티가 JPA의 관리 대상이 아니며, 데이터베이스와 연관되지 않은 상태입니다. 즉, 새롭게 생성된 엔티티 객체로, 아직 EntityManager에 의해 관리되지 않거나, 데이터베이스에 저장되지 않은 상태입니다.
예제:
java
코드 복사
// 비영속 상태의 엔티티 객체 생성
Entity entity = new Entity();
entity.setName("Example");

// 이 상태에서 데이터베이스에 저장되지 않음
2. 연속(Persistent)
설명: 엔티티가 EntityManager에 의해 관리되고 있는 상태입니다. 이 상태에서는 데이터베이스에 저장되거나, 데이터베이스에서 조회된 객체이며, 이후 변경된 사항이 자동으로 데이터베이스에 반영됩니다.

예제:
java
코드 복사
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();

Entity entity = new Entity();
entity.setName("Example");


// 연속 상태로 전환
em.persist(entity); // 데이터베이스에 저장됨
em.getTransaction().commit(); // 트랜잭션 커밋
  1. 중영속(Detached)
    설명: 엔티티가 EntityManager에 의해 더 이상 관리되지 않는 상태입니다. 하지만 여전히 데이터베이스에 저장된 기록이 있는 상태입니다. 예를 들어, 트랜잭션이 끝난 후, EntityManager가 닫히면 엔티티는 중영속 상태로 전환됩니다.
예제:
java
코드 복사
em.getTransaction().begin();
Entity entity = em.find(Entity.class, 1L);
em.getTransaction().commit();

// 트랜잭션 종료 후, EntityManager가 닫힘
em.close();

// entity는 중영속 상태가 됨
entity.setName("New Name");
// 하지만 변경사항은 자동으로 반영되지 않음
  1. 삭제(Removed)
    설명: 엔티티가 데이터베이스에서 삭제될 예정이거나, 이미 삭제된 상태입니다. EntityManager의 remove 메서드를 호출하면 엔티티가 이 상태로 전환됩니다. 삭제된 엔티티는 더 이상 EntityManager에 의해 관리되지 않습니다.
예제:
java
코드 복사
em.getTransaction().begin();
Entity entity = em.find(Entity.class, 1L);

// 삭제 상태로 전환
em.remove(entity); // 데이터베이스에서 삭제됨
em.getTransaction().commit();

요약
비영속(Transient): EntityManager에 관리되지 않고, 데이터베이스에 저장되지 않은 상태.
연속(Persistent): EntityManager에 의해 관리되며, 데이터베이스에 저장된 상태.
중영속(Detached): 데이터베이스에는 저장되어 있지만, EntityManager에 의해 더 이상 관리되지 않는 상태.
삭제(Removed): 데이터베이스에서 삭제될 예정이거나 이미 삭제된 상태.
각 상태는 JPA가 엔티티 객체를 어떻게 다루는지에 대한 중요한 개념이므로, JPA를 사용한 애플리케이션 개발에서 중요한 역할을 합니다.


엔티티와 컬럼매핑

기본 개념
엔티티(Entity) 클래스:

데이터베이스의 테이블과 매핑되는 자바 클래스입니다. 각 엔티티 클래스는 보통 데이터베이스의 한 테이블에 대응됩니다.
엔티티 클래스는 @Entity 어노테이션을 통해 선언됩니다.
컬럼(Column):

엔티티 클래스의 필드는 데이터베이스 테이블의 컬럼과 매핑됩니다.
필드와 컬럼 간의 매핑은 @Column 어노테이션을 사용하여 설정할 수 있습니다.
주요 어노테이션과 매핑 방법
1. @Entity
설명: 클래스를 엔티티로 선언하며, 해당 클래스는 데이터베이스의 테이블에 매핑됩니다.

예제:
java
코드 복사
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String email;
}
  1. @Table
    설명: 엔티티가 매핑될 테이블의 이름을 지정합니다. @Table 어노테이션을 생략하면 엔티티 클래스의 이름이 테이블 이름으로 사용됩니다.
예제:
java
코드 복사
@Entity
@Table(name = "users") // "users" 테이블과 매핑
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String email;
}
  1. @Column
    설명: 엔티티의 필드를 테이블의 컬럼에 매핑합니다. 이 어노테이션을 사용하여 컬럼의 이름, 데이터 유형, 제약 조건 등을 설정할 수 있습니다.

속성들:

name: 매핑할 테이블의 컬럼 이름을 지정합니다.
nullable: 컬럼이 NULL 값을 허용할지 여부를 설정합니다.
unique: 컬럼의 값이 고유해야 하는지를 지정합니다.
length: 문자열 타입의 컬럼에 대해 최대 길이를 설정합니다.
columnDefinition: SQL의 컬럼 정의를 직접 지정할 수 있습니다.
예제:

java
코드 복사
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "username", nullable = false, unique = true)
    private String name;

    @Column(name = "user_email", nullable = false, length = 100)
    private String email;
}
  1. @Id
    설명: 엔티티의 기본 키(primary key)를 지정합니다. 이 필드는 데이터베이스에서 고유한 값을 가지며, 엔티티를 식별하는 데 사용됩니다.
    예제:
java
코드 복사
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
}
  1. @GeneratedValue
    설명: 기본 키의 값을 자동으로 생성할 전략을 지정합니다.
    속성들:
    strategy: 기본 키 생성 전략을 지정합니다. GenerationType.IDENTITY, GenerationType.SEQUENCE, GenerationType.TABLE, GenerationType.AUTO 중 하나를 사용할 수 있습니다.
예제:
java
코드 복사
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
}
  1. @Enumerated
    설명: enum 타입의 필드를 데이터베이스에 매핑할 때 사용합니다. EnumType.STRING 또는 EnumType.ORDINAL로 매핑할 수 있습니다.
예제:
java
코드 복사
public enum Status {
    ACTIVE, INACTIVE
}

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Enumerated(EnumType.STRING)
    private Status status;
}

예제: 간단한 엔티티 매핑
다음은 User 엔티티가 데이터베이스의 users 테이블에 매핑되는 예입니다.


java
코드 복사
@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "username", nullable = false, unique = true)
    private String name;

    @Column(name = "user_email", nullable = false, length = 100)
    private String email;

    @Enumerated(EnumType.STRING)
    @Column(name = "user_status")
    private Status status;

    // Getters and Setters
}

위 예제에서 User 클래스는 users라는 데이터베이스 테이블과 매핑됩니다. name, email, status 필드는 각각 username, user_email, user_status 컬럼과 매핑되며, 각 컬럼의 속성은 @Column 어노테이션을 통해 정의되었습니다.


1:N N:M N:1

  1. 1대N 관계 (One-to-Many)
    설명: 하나의 엔티티가 여러 개의 다른 엔티티와 연관될 때 사용됩니다. 예를 들어, 한 명의 사용자가 여러 개의 주문을 가질 수 있는 관계입니다.

매핑 방법:

@OneToMany 어노테이션을 사용하여 설정합니다.
@JoinColumn을 사용하여 외래 키를 지정할 수 있습니다.
기본적으로, 1대N 관계는 연관된 엔티티를 List, Set 등의 컬렉션으로 표현합니다.
예제:


java
코드 복사
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "user")
    private List<Order> orders = new ArrayList<>();
}

@Entity
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String product;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}

설명: 위 예제에서 User 엔티티는 Order 엔티티와 1대N 관계를 가집니다. 즉, 한 명의 사용자는 여러 개의 주문을 가질 수 있습니다. Order 엔티티의 user 필드는 @ManyToOne 관계로, 주문이 한 명의 사용자에 종속됨을 나타냅니다.

. N대1 관계 (Many-to-One)
설명: 여러 개의 엔티티가 하나의 엔티티에 연관될 때 사용됩니다. 예를 들어, 여러 주문이 하나의 사용자에 속할 수 있는 관계입니다.

매핑 방법:

@ManyToOne 어노테이션을 사용하여 설정합니다.
일반적으로 외래 키가 있는 쪽에 @ManyToOne을 사용합니다.
예제:


java
코드 복사
@Entity
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String product;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
}

설명: 이 예제에서는 Order 엔티티가 User 엔티티와 N대1 관계를 가집니다. 즉, 여러 개의 주문이 하나의 사용자에 연관될 수 있습니다. @JoinColumn을 사용하여 user_id라는 외래 키 컬럼을 지정합니다.

  1. N대M 관계를 1대N과 N대1로 풀기 중간 엔티티를 추가하여 N대M 관계를 두 개의 1대N과 N대1 관계로 나눕니다. 예를 들어 StudentCourse라는 중간 엔티티를 생성합니다.

java
코드 복사
@Entity
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "student")
    private List<StudentCourse> studentCourses = new ArrayList<>();
}

@Entity
public class Course {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @OneToMany(mappedBy = "course")
    private List<StudentCourse> studentCourses = new ArrayList<>();
}

@Entity
public class StudentCourse {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "student_id")
    private Student student;

    @ManyToOne
    @JoinColumn(name = "course_id")
    private Course course;
}

N+1

N+1 문제 해결 방법
Fetch Join 사용:

@Query나 JPQL을 사용할 때 fetch join을 통해 연관된 엔티티를 한 번에 가져오도록 합니다.

java
코드 복사
@Query("SELECT s FROM Student s JOIN FETCH s.studentCourses sc JOIN FETCH sc.course WHERE s.id = :id")
Student findStudentWithCourses(@Param("id") Long id);

Entity Graph 사용:

@EntityGraph를 사용하여 연관된 엔티티를 함께 가져올 수 있습니다.

java
코드 복사
@EntityGraph(attributePaths = {"studentCourses", "studentCourses.course"})
@Query("SELECT s FROM Student s WHERE s.id = :id")
Student findStudentWithCourses(@Param("id") Long id);

Batch Size 설정:

hibernate.default_batch_fetch_size를 설정하여 N+1 문제를 완화할 수 있습니다.
properties
코드 복사
spring.jpa.properties.hibernate.default_batch_fetch_size=10
JSON Ignore로 순환 참조 방지:

@JsonIgnore를 사용하여 양방향 관계에서 순환 참조(Circular Reference)를 방지할 수 있습니다.

java
코드 복사
@Entity
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "student")
    @JsonIgnore
    private List<StudentCourse> studentCourses = new ArrayList<>();
}

@Entity
public class StudentCourse {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "student_id")
    private Student student;

    @ManyToOne
    @JoinColumn(name = "course_id")
    private Course course;
}

설명:

@JsonIgnore

를 사용하여 JSON 직렬화 중에 특정 필드를 무시하도록 할 수 있습니다. 이는 엔티티 간의 순환 참조를 방지하는 데 유용합니다.
이렇게 하면 N+1 문제는 해결되지 않지만, JSON 직렬화 과정에서의 무한 루프는 방지할 수 있습니다.

단일테이블전략 조인전략

  1. 단일 테이블 전략 (Single Table Strategy)
    설명:

단일 테이블 전략은 상속 구조에 있는 모든 엔티티를 하나의 테이블에 매핑하는 방법입니다. 즉, 부모 클래스와 자식 클래스의 모든 필드를 포함하는 하나의 테이블이 생성됩니다.
각 레코드는 어느 자식 클래스에 해당하는지 식별하기 위해 구분 컬럼(Discriminator Column)이 사용됩니다.
장점:

성능이 빠릅니다. 조회 시 하나의 테이블만 참조하면 되므로, 조인 연산이 필요하지 않습니다.
구조가 단순합니다. 모든 데이터를 하나의 테이블에 저장하므로 관리하기 쉽습니다.
단점:

불필요한 데이터가 많아질 수 있습니다. 자식 클래스에 따라 사용되지 않는 컬럼들이 존재하게 됩니다.
테이블의 컬럼 수가 많아질 수 있습니다.
예제:


java
코드 복사
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Vehicle {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
}

@Entity
public class Car extends Vehicle {
    private int numberOfDoors;
}

@Entity
public class Motorcycle extends Vehicle {
    private boolean hasSidecar;
}
  1. 조인 전략 (Joined Strategy)
    설명:

조인 전략은 부모 클래스에 해당하는 테이블과 각 자식 클래스에 해당하는 테이블을 따로 생성하고, 이들을 JOIN을 통해 결합하여 데이터를 조회하는 방법입니다.
부모 클래스의 공통 속성들은 부모 테이블에 저장되고, 자식 클래스의 속성들은 자식 클래스별로 각각의 테이블에 저장됩니다.
장점:

정규화된 테이블 구조를 가집니다. 각 테이블은 해당 엔티티에 필요한 속성만을 가집니다.
자식 클래스가 많아져도 테이블 구조가 복잡해지지 않습니다.
단점:

조인 연산이 필요하므로 조회 성능이 저하될 수 있습니다.
설계와 관리가 상대적으로 복잡합니다.
예제:


java
코드 복사
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Vehicle {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
}

@Entity
public class Car extends Vehicle {
    private int numberOfDoors;
}

@Entity
public class Motorcycle extends Vehicle {
    private boolean hasSidecar;
}

매핑상속

  1. 구체 클래스 전략 (Table per Class Strategy)
    설명:

구체 클래스 전략은 각 자식 클래스가 독립적인 테이블에 매핑되고, 부모 클래스에 해당하는 테이블이 없습니다.
자식 클래스는 부모 클래스의 모든 속성을 포함한 테이블로 매핑되며, 각 자식 클래스는 독립적인 테이블에 저장됩니다.
예제:


java
코드 복사
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Vehicle {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
}

@Entity
public class Car extends Vehicle {
    private int numberOfDoors;
}

@Entity
public class Motorcycle extends Vehicle {
    private boolean hasSidecar;
}

설명:

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)로 구체 클래스 전략을 사용함을 나타냅니다.
부모 클래스 Vehicle에 대한 테이블은 생성되지 않으며, 자식 클래스 Car와 Motorcycle 각각에 대해 독립적인 테이블이 생성됩니다.

지연로딩

  • MANYONE
  • ONEONE

CASECADE

주요 Cascade 유형
JPA에서는 다음과 같은 케스케이드 옵션을 제공합니다:

CascadeType.PERSIST: 부모 엔티티가 저장될 때 연관된 자식 엔티티도 함께 저장됩니다.
CascadeType.MERGE: 부모 엔티티가 병합될 때(업데이트될 때) 연관된 자식 엔티티도 함께 병합됩니다.
CascadeType.REMOVE: 부모 엔티티가 삭제될 때 연관된 자식 엔티티도 함께 삭제됩니다.
CascadeType.REFRESH: 부모 엔티티가 새로고침될 때 자식 엔티티도 함께 새로고침됩니다.
CascadeType.DETACH: 부모 엔티티가 영속성 컨텍스트에서 분리될 때 연관된 자식 엔티티도 함께 분리됩니다.
CascadeType.ALL: 모든 케스케이드 작업을 포함합니다. (PERSIST, MERGE, REMOVE, REFRESH, DETACH)
Cascade의 사용 예제
1. CascadeType.PERSIST


java
코드 복사
@Entity
public class Parent {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToMany(cascade = CascadeType.PERSIST)
    private List<Child> children = new ArrayList<>();
    
    // Getters and Setters
}

@Entity
public class Child {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    // Getters and Setters
}
java
코드 복사
Parent parent = new Parent();
Child child1 = new Child();
Child child2 = new Child();

parent.getChildren().add(child1);
parent.getChildren().add(child2);

entityManager.persist(parent);

임베디드타입(타입 공유)

임베디드 타입의 기본 개념
임베디드 타입을 정의하면, JPA는 해당 타입의 모든 필드를 엔티티의 테이블에 포함시킵니다. 이는 @Embeddable 애노테이션을 사용하여 정의된 클래스를 엔티티의 필드로 사용함으로써 이루어집니다.

주요 애노테이션
@Embeddable

임베디드 타입으로 사용할 클래스를 정의할 때 사용합니다.
이 클래스는 데이터베이스 테이블에 매핑되지 않고, 다른 엔티티 클래스의 필드로서 매핑됩니다.
@Embedded

임베디드 타입을 엔티티의 필드로 포함할 때 사용합니다.
@AttributeOverrides 및 @AttributeOverride

임베디드 타입의 속성에 대해 추가적인 매핑 구성을 할 수 있습니다.
임베디드 타입 사용 예제
1. 임베디드 타입 정의

java
코드 복사
@Embeddable
public class Address {
    private String street;
    private String city;
    private String zipCode;

    // Getters and Setters
}

@Embeddable을 사용하여 Address 클래스를 임베디드 타입으로 정의합니다. 이 클래스는 자체적으로 데이터베이스 테이블에 매핑되지 않고, 다른 엔티티 클래스의 필드로 사용됩니다.
2. 임베디드 타입 사용

java
코드 복사
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @Embedded
    private Address address;

    // Getters and Setters
}

@Embedded를 사용하여 User 엔티티에 Address 임베디드 타입을 포함시킵니다.
3. 임베디드 타입의 속성 오버라이드
때때로 임베디드 타입의 속성을 엔티티의 테이블에서 다르게 매핑하고 싶을 때가 있습니다. 이럴 때는 @AttributeOverrides 및 @AttributeOverride를 사용할 수 있습니다.


java
코드 복사
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "street", column = @Column(name = "home_street")),
        @AttributeOverride(name = "zipCode", column = @Column(name = "postal_code"))
    })
    private Address address;

    // Getters and Setters
}

위 예제에서 Address 임베디드 타입의 street과 zipCode 필드를 각각 home_street과 postal_code로 매핑하였습니다.
임베디드 타입의 장점
재사용성: 동일한 임베디드 타입을 여러 엔티티에서 재사용할 수 있습니다.
코드의 재사용과 관리 용이: 공통적으로 사용되는 필드를 그룹화하여 관리할 수 있습니다.
테이블 구조 단순화: 복잡한 엔티티 구조를 보다 간단하게 정의할 수 있습니다.
임베디드 타입의 주의사항
변경 감지: 임베디드 타입의 속성 값을 수정하면, JPA는 전체 객체를 변경된 것으로 간주하고 관리합니다. 따라서 임베디드 타입의 필드가 많거나 복잡한 경우 성능에 영향을 줄 수 있습니다.
비즈니스 로직: 임베디드 타입은 비즈니스 로직을 포함하지 않도록 하는 것이 좋습니다. 비즈니스 로직은 엔티티 자체에 포함시켜야 합니다.


페이징 서브쿼리 JPQL

  1. 페이징 (Paging)
    페이징은 대량의 데이터를 효율적으로 처리하기 위해 데이터를 페이지 단위로 나누어 조회하는 방법입니다. JPA에서는 Query나 TypedQuery의 setFirstResult와 setMaxResults 메서드를 사용하여 페이징을 구현할 수 있습니다.
예제:

java
코드 복사
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.List;

public class UserService {
    private final EntityManager entityManager;

    public UserService(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public List<User> getUsers(int pageNumber, int pageSize) {
        TypedQuery<User> query = entityManager.createQuery("SELECT u FROM User u", User.class);
        query.setFirstResult((pageNumber - 1) * pageSize);  // 시작 위치
        query.setMaxResults(pageSize);  // 페이지 사이즈
        return query.getResultList();
    }
}

설명:

setFirstResult 메서드는 결과 리스트에서 조회를 시작할 첫 번째 위치를 설정합니다. (pageNumber - 1) * pageSize를 통해 시작 위치를 계산합니다.
setMaxResults 메서드는 한 페이지에서 반환할 최대 결과 수를 설정합니다.
2. 서브쿼리 (Subquery)
서브쿼리는 쿼리 내에서 또 다른 쿼리를 사용하는 방법입니다. JPA의 JPQL에서는 서브쿼리를 SELECT, WHERE, HAVING 절에서 사용할 수 있습니다.

예제:

java
코드 복사
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.List;

public class UserService {
    private final EntityManager entityManager;

    public UserService(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public List<User> getUsersWithMostPosts() {
        String jpql = "SELECT u FROM User u WHERE (SELECT COUNT(p) FROM Post p WHERE p.author = u) > 5";
        TypedQuery<User> query = entityManager.createQuery(jpql, User.class);
        return query.getResultList();
    }
}

설명:

위 JPQL 쿼리는 User 엔티티에서, 게시글 수가 5개를 초과하는 사용자만 조회합니다. 서브쿼리는 WHERE 절에서 사용되어 각 사용자에 대해 게시글 수를 계산합니다.
3. JPQL 함수
JPQL에서는 여러 유용한 함수들을 제공하여 쿼리 작성 시 편리함을 더합니다. 주요 JPQL 함수는 다음과 같습니다:

집계 함수 (Aggregation Functions)

COUNT(): 행의 수를 계산합니다.
SUM(): 합계를 계산합니다.
AVG(): 평균을 계산합니다.
MAX(): 최대 값을 찾습니다.
MIN(): 최소 값을 찾습니다.

예제:

java
코드 복사
public Long getUserCount() {
    String jpql = "SELECT COUNT(u) FROM User u";
    TypedQuery<Long> query = entityManager.createQuery(jpql, Long.class);
    return query.getSingleResult();
}

문자열 함수 (String Functions)

CONCAT(): 문자열을 결합합니다.
SUBSTRING(): 문자열의 일부를 추출합니다.
LENGTH(): 문자열의 길이를 계산합니다.
TRIM(): 문자열의 공백을 제거합니다.
LOWER(), UPPER(): 문자열을 소문자 또는 대문자로 변환합니다.

예제:

java
코드 복사
public List<User> findUsersWithNameLike(String name) {
    String jpql = "SELECT u FROM User u WHERE LOWER(u.name) LIKE LOWER(:name)";
    TypedQuery<User> query = entityManager.createQuery(jpql, User.class);
    query.setParameter("name", "%" + name.toLowerCase() + "%");
    return query.getResultList();
}

날짜 및 시간 함수 (Date and Time Functions)

CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP: 현재 날짜, 시간, 타임스탬프를 반환합니다.
DATE(), TIME(), TIMESTAMP(): 날짜, 시간, 타임스탬프를 추출합니다.

예제:

java
코드 복사
public List<User> findUsersCreatedAfter(Date date) {
    String jpql = "SELECT u FROM User u WHERE u.creationDate > :date";
    TypedQuery<User> query = entityManager.createQuery(jpql, User.class);
    query.setParameter("date", date);
    return query.getResultList();
}

수학 함수 (Mathematical Functions)

ABS(): 절댓값을 반환합니다.
MOD(): 나머지 값을 반환합니다.
SQRT(): 제곱근을 계산합니다.

예제:

java
코드 복사
public List<Post> findPostsWithTitleLengthGreaterThan(int length) {
    String jpql = "SELECT p FROM Post p WHERE LENGTH(p.title) > :length";
    TypedQuery<Post> query = entityManager.createQuery(jpql, Post.class);
    query.setParameter("length", length);
    return query.getResultList();
}

요약
페이징: setFirstResult와 setMaxResults를 사용하여 데이터를 페이지 단위로 조회할 수 있습니다.
서브쿼리: JPQL의 SELECT, WHERE, HAVING 절에서 서브쿼리를 사용하여 복잡한 조회를 구현할 수 있습니다.
JPQL 함수: 집계 함수, 문자열 함수, 날짜/시간 함수, 수학 함수 등을 사용하여 쿼리를 보다 효율적으로 작성할 수 있습니다.
이러한 기능들을 적절히 활용하면 JPA를 사용하여 데이터베이스와의 상호작용을 보다 효율적이고 유연하게 수행할 수 있습니다.

Named Query
Named Query는 JPQL 쿼리를 사전에 정의하고 이를 이름으로 참조하여 사용하는 방법입니다. 이는 코드의 가독성을 높이고, 쿼리를 재사용 가능하게 만들어 줍니다.

Named Query 정의 및 사용
엔티티 클래스에 Named Query 정의

@NamedQuery 또는 @NamedQueries 애노테이션을 사용하여 엔티티 클래스에 Named Query를 정의합니다.

java
코드 복사
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQuery;

@Entity
@NamedQuery(name = "User.findByName", query = "SELECT u FROM User u WHERE u.name = :name")
public class User {
@Id
private Long id;
private String name;

// Getters and Setters

}
Named Query 호출

EntityManager를 통해 정의된 Named Query를 호출할 수 있습니다.

java
코드 복사
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.List;

public class UserService {
private final EntityManager entityManager;

public UserService(EntityManager entityManager) {
    this.entityManager = entityManager;
}

public List<User> findUsersByName(String name) {
    TypedQuery<User> query = entityManager.createNamedQuery("User.findByName", User.class);
    query.setParameter("name", name);
    return query.getResultList();
}

}
벌크 연산 (Bulk Operations)
벌크 연산은 대량의 데이터를 효율적으로 처리할 때 사용하는 쿼리 작업입니다. JPQL에서는 UPDATE, DELETE 쿼리를 사용하여 벌크 연산을 수행할 수 있으며, 이를 통해 성능을 개선할 수 있습니다.

벌크 연산 예제
벌크 업데이트

벌크 업데이트는 데이터베이스에 저장된 여러 레코드를 동시에 업데이트하는 작업입니다. EntityManager의 createQuery를 사용하여 JPQL을 실행합니다.

java
코드 복사
import javax.persistence.EntityManager;
import javax.transaction.Transactional;

public class UserService {
private final EntityManager entityManager;

public UserService(EntityManager entityManager) {
    this.entityManager = entityManager;
}

@Transactional
public int updateUserStatus(String newStatus) {
    String jpql = "UPDATE User u SET u.status = :status WHERE u.status = :oldStatus";
    return entityManager.createQuery(jpql)
                        .setParameter("status", newStatus)
                        .setParameter("oldStatus", "inactive")
                        .executeUpdate();
}

}
설명:

executeUpdate() 메서드는 업데이트 작업을 수행하고, 영향을 받은 레코드의 수를 반환합니다.
벌크 삭제

벌크 삭제는 데이터베이스의 여러 레코드를 동시에 삭제하는 작업입니다.

java
코드 복사
import javax.persistence.EntityManager;
import javax.transaction.Transactional;

public class UserService {
private final EntityManager entityManager;

public UserService(EntityManager entityManager) {
    this.entityManager = entityManager;
}

@Transactional
public int deleteInactiveUsers() {
    String jpql = "DELETE FROM User u WHERE u.status = :status";
    return entityManager.createQuery(jpql)
                        .setParameter("status", "inactive")
                        .executeUpdate();
}

}
설명:

DELETE 쿼리는 삭제할 레코드 수를 반환합니다. 벌크 삭제는 성능을 높이지만, 엔티티의 상태나 영속성 컨텍스트에 영향을 줄 수 있습니다.
Named Query와 벌크 연산 결합
Named Query와 벌크 연산을 결합하여 사용하면 쿼리를 재사용하면서 대량의 데이터에 대해 효율적인 작업을 수행할 수 있습니다.

Named Query 정의 (벌크 연산 포함)


java
코드 복사
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQuery;

@Entity
@NamedQuery(name = "User.updateStatus", query = "UPDATE User u SET u.status = :status WHERE u.status = :oldStatus")
public class User {
    @Id
    private Long id;
    private String status;

    // Getters and Setters
}

Named Query 호출


java
코드 복사
import javax.persistence.EntityManager;
import javax.transaction.Transactional;

public class UserService {
    private final EntityManager entityManager;

    public UserService(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    @Transactional
    public int updateUserStatus(String newStatus, String oldStatus) {
        return entityManager.createNamedQuery("User.updateStatus")
                            .setParameter("status", newStatus)
                            .setParameter("oldStatus", oldStatus)
                            .executeUpdate();
    }
}

주의사항
영속성 컨텍스트: 벌크 연산은 JPA의 영속성 컨텍스트와 직접 상호작용하지 않기 때문에, 영속성 컨텍스트에 있는 엔티티는 자동으로 동기화되지 않습니다. 따라서 벌크 연산 후에는 영속성 컨텍스트를 수동으로 갱신하거나 초기화할 필요가 있을 수 있습니다.

트랜잭션 관리: 벌크 연산은 데이터베이스의 상태를 변경하므로, 트랜잭션 내에서 수행해야 합니다. @Transactional 애노테이션을 사용하여 트랜잭션을 관리합니다.


JPQL 쿼리 정의
@Query 애노테이션을 사용하여 직접 JPQL 쿼리를 정의할 수 있습니다.

예제:

java
코드 복사
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

// JPQL 쿼리로 사용자 이름으로 검색
@Query("SELECT u FROM User u WHERE u.name = :name")
List<User> findByName(@Param("name") String name);

// JPQL 쿼리로 사용자 상태 업데이트
@Modifying
@Transactional
@Query("UPDATE User u SET u.status = :status WHERE u.status = :oldStatus")
int updateUserStatus(@Param("status") String newStatus, @Param("oldStatus") String oldStatus);

// JPQL 쿼리로 사용자 삭제
@Modifying
@Transactional
@Query("DELETE FROM User u WHERE u.status = :status")
int deleteUsersByStatus(@Param("status") String status);

}
설명:

@Query 애노테이션으로 JPQL 쿼리를 정의합니다.
@Param 애노테이션으로 쿼리 파라미터를 바인딩합니다.
@Modifying 애노테이션은 데이터베이스의 상태를 변경하는 쿼리에 사용합니다.
@Transactional 애노테이션은 트랜잭션을 관리합니다.
3. 엔티티 그래프 사용하기
EntityGraph를 사용하여 엔티티의 연관 관계를 미리 로딩할 수 있습니다. JPA Repository 인터페이스에서 이를 사용하려면 EntityManager를 통해 접근해야 합니다.

예제:

java
코드 복사
import javax.persistence.EntityManager;
import javax.persistence.EntityGraph;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class UserRepositoryCustomImpl implements UserRepositoryCustom {

@PersistenceContext
private EntityManager entityManager;

@Override
public List<User> findUsersWithDetails() {
    EntityGraph<User> graph = entityManager.createEntityGraph(User.class);
    graph.addAttributeNodes("profile"); // 연관된 엔티티를 미리 로딩하도록 설정

    TypedQuery<User> query = entityManager.createQuery("SELECT u FROM User u", User.class);
    query.setHint("javax.persistence.fetchgraph", graph); // 그래프를 쿼리 힌트로 사용
    return query.getResultList();
}

}
설명:

EntityManager를 사용하여 EntityGraph를 정의하고, 이를 쿼리 힌트로 사용하여 연관 관계를 미리 로딩합니다.
UserRepositoryCustom는 사용자 정의 메서드를 정의할 수 있는 인터페이스입니다.
4. 데이터베이스 락 (Database Lock)
데이터베이스 락을 사용하여 동시성 문제를 처리할 수 있습니다. @Lock 애노테이션을 사용하여 메서드 수준에서 락을 설정할 수 있습니다.

예제:

java
코드 복사
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.data.jpa.repository.LockModeType;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

// 비관계 락을 사용하여 사용자 찾기
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT u FROM User u WHERE u.id = :id")
User findUserByIdForUpdate(@Param("id") Long id);

}
설명:

@Lock 애노테이션을 사용하여 비관계 락을 설정합니다.
LockModeType.PESSIMISTIC_WRITE는 쓰기 락을 설정하여 다른 트랜잭션에서 동시에 수정하지 못하도록 합니다.

profile
프로젝트전에 해당 부분을 깊게 공부한것을 올려두는 벨로그

0개의 댓글