TIL 41 - [ Spring 심화 ( API 예외처리, JPQL, Fetch Join ) ]

2025. 4. 21. 14:46·[내일배움캠프-Sparta]/Spring 6기
반응형

14:00 ~ 15:00 Spring 심화 개인과제 해설 

 

3일간의 예비군 이슈로 부재를 하여 과제를 손도 대지 못하였다. 주말동안 강의와 특강 세션에 집중을 하다보니 시간이 남아 도질 않았다. 주말에는 (1:1, 1:N, N:1, N:M) 연관관계, proxy, 지연로딩/즉시로딩, Bean 생명주기 등 강의를 집중적으로 들었고 따로 포스팅은 하지 않을 예정이다.

1️⃣ API 예외처리

✅ @ExceptionHandler

컨트롤러 클래스 내 특정 예외 처리하는 메소드를 정의하는 Annotation

  • 특정 컨트롤러에서 발생한 예외를 처리하기 위한 메서드를 지정한다.
  • 단일 컨트롤러 내 예외 처리
  • 계층별로 알맞은 예외를 발생 ( throw new ) 시키기만 하면 됨.
@RestController
@RequiredArgsConstructor
public class ExceptionHandlerController {

    private final ExceptionHandlerService exceptionHandlerService;

    @RequestMapping("/v1/exception")
    public void illegalArgumentException() {

        throw new IllegalArgumentException("IllegalArgumentException 발생");

    }

    @RequestMapping("/v2/exception")
    public void nullPointerException() {

        throw new NullPointerException("NPE 발생");
    }

    @RequestMapping("/v3/exception")
    public void serviceLayerException() {

        exceptionHandlerService.throwNewIllegalArgumentException();
    }

    // IllegalArgumentException을 처리하는 메서드
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<Map<String, String>> handleIllegalArgumentException(IllegalArgumentException ex) {

        Map<String, String> response = new HashMap<>();
        response.put("error", ex.getMessage());

        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(response);
    }

}
@RestController
public class NonExceptionHandlerController {

    @RequestMapping("/v4/exception")
    public void illegalArgumentException() {

        throw new IllegalArgumentException("IllegalArgumentException 발생");

    }

}

서비스

@Service
public class ExceptionHandlerService {

    public void throwNewIllegalArgumentException() {
        throw new IllegalArgumentException("ServiceLayer Exception");
    }

}

500에러가 최대한 뜨지 않게 하는것이 안전한 서버다.

💥 문제점

  1. Controller 코드에 Exception 처리를 위한 책임이 추가된다.(단일 책임 원칙 위반)
  2. 단일 컨트롤러 내의 예외만 처리가 가능하다.(컨트롤러 예외처리 중복코드)
  3. 코드 재사용, 유지보수성 부족

✅ @ControllerAdvice

@ControllerAdvice와 동일한 기능을 제공하지만 @RestController를 포함하고 있어 반환 값이 자동으로 JSON 형태로 변환되며 REST API에서 발생하는 예외를 처리할 때 사용

Application 전역에서 발생한 예외를 처리한다.

⚡  @RestControllerAdvice

  • REST API의 예외를 처리
  • 반환 값이 JSON 이다.
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<Map<String, String>> handleIllegalArgumentException(IllegalArgumentException ex) {

        Map<String, String> response = new HashMap<>();
        response.put("error", ex.getMessage());

        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(response);
    }

}

@ExceptionHandler 이 선언되지 않은 컨트롤러의 예외처리 가능

Annotation 용도 반환 형식 예시 처리 범위
@ExceptionHandler 특정 컨트롤러 내 발생한 예외 처리 뷰 이름, String 단일 컨트롤러 내
@ControllerAdvice 애플리케이션 전체에서 발생한 예외 처리 뷰 이름, String 모든 컨트롤러 전역에 적용
@RestControllerAdvice REST API에서 발생한 예외를 JSON 형식으로 처리 ResponseEntity(JSON) 모든 컨트롤러 전역에 적용 
(RESTful 서비스)

2️⃣ JPQL

✅ JPQL

JPA의 SQL 쿼리 지원

  1. ⭐ JPQL (Java Persistence Query Language)
    • 객체지향 쿼리 언어
    • Entity 객체를 대상으로 SQL Query를 작성하도록 도와줌.
  2. ⭐ QueryDSL
    • Java 기반 ORM 쿼리 빌더 라이브러리
    • 동적 쿼리를 지원함
  3. JPA Criteria
    • JPQL와 유사한 쿼리를 코드로 생성할 수 있다.
    • 복잡하고 실용성 ❌ 
  4. Native SQL
    1. JPA가 제공하는 SQL을 직접 사용하는 기능
    2. DB 종속적인 Query가 필요시 사용

JPQL  등장배경

  • JPA 사용 ➡ 객체 중심적인 설계
  • 조회 ( em.find() ) ➡ Entity 객체 그래프 탐색 ( tutor.getCompany() )
    • 모든 DB 데이터를 객체로 변환해 조회하는 경우는 사용 ❌
    • 특정 조건을 가진 데이터를 검색 ❌
  • 실제로 필요한 데이터를 조회하기 위한 조건이 포함된 SQL이 필요
String jpql = "SELECT p FROM Product p WHERE p.price > :minPrice";
List<Product> products = em.createQuery(jpql, Product.class)
                            .setParameter("minPrice", 1000)
                            .getResultList();
  • SELECT p FROM Product p ( SQL의 SELECT * FROM Product 와 대응 )
  • 최소 가격 (minPrice) 이상의 Product 를 조회하는 SQL Query

특징

  • 객체를 대상으로 검색하는 객체 지향 쿼리 ( 테이블 ❌ )
  • SQL 추상화 ( DB종속 ❌ )
    • 다양한 DB에서 사용 가능
    • JPA 영속성 컨텍스트를 사용해 1차 캐시, 지연 로딩등의 기능을 활용할 수 있다.
  • 타입 안정성
    • 컴파일 시점 Entity와 필드의 타입을 확인해 오류를 줄인다.
특징 JPQL SQL
대상 Entity와 필드 테이블과 컬럼
표현 방식 객체 지향 관계형
DB 독립성 높음 특정 DB에 종속
호출 방식 EntityManager.createQuery() 직접 DB와 연결

✅ 동적 쿼리

JPQL은 SQL 문법과 유사한 SELECT, WHERE, GROUP BY, HAVING, JOIN 등을 지원

String jpql = "SELECT p FROM Product p WHERE 1=1";

if (name != null) {
    jpql += " AND p.name = :name";
}
if (price != null) {
    jpql += " AND p.price = :price";
}
  • 특정 조건에 따라 실행되는 SQL이 변함.

JPQL 동적 쿼리

  • JPQL은 정적 쿼리에 최적화 되어있다.
  • JPQL은 문자열 SQL을 직접 다루어야 하기때문에 문자열을 동적으로 조합해야 한다.
    • 가독성과 유지보수가 어려워지고 다루기 까다롭다.
    • 오타나 문법 오류가 발생하기 쉽고 컴파일 시점에 검증되지 못한다.
  • 조건이 많아지면 많아질수록 JPQL을 사용하는것은 한계가 존재

✅ 기본 문법

JPQL 문법 규칙

  1. 테이블 이름이 아닌 Entity 이름을 사용한다.(클래스 이름이 Default)
  2. Entity와 필드는 대소문자를 구분한다.
  3. JPQL 키워드(SELECT, from, Where)는 대소문자를 구분하지 않는다.
  4. 별칭(alias)은 필수이고 as 는 생략이 가능
SELECT <별칭> FROM <엔티티 이름> [AS <별칭>] [WHERE 조건] [GROUP BY 속성] [HAVING 조건] [ORDER BY 속성]
select
	SUM(p.price), // 상품 가격의 총합
	AVG(p.price), // 평균 가격
	MAX(p.price), // 최대 가격
	MIN(p.price)  // 최소 가격
	COUNT(p),     // 상품 수
from Product p

✅ 반환 타입 및 결과

JPQL에서 데이터베이스 쿼리를 실행하기 위해 Query와 TypedQuery 두 가지 타입을 제공하며 이들은 JPA의 쿼리 API로 작성된 쿼리를 실행하고 결과를 반환받는 데 사용

TypeQuery

  • 반환 타입이 명확할 때 사용한다.
    • 주로 Entity나 특정 타입의 결과를 반환할 때 사용
  • 컴파일 시 타입 검사를 할 수 있다.
TypedQuery<Tutor> typeQuery1 = em.createQuery("select t from Tutor t", Tutor.class);
TypedQuery<String> typeQuery2 = em.createQuery("select t.name from Tutor t", String.class);

Query

  • 반환 타입이 명확하지 않을 때 사용한다.
    • 결과를 알 수 없거나 다양한 타입의 데이터를 반환할 때 사용
  • 결과를 처리할 때 형 변환(casting)이 필요할 수 있다.
Query query = em.createQuery("select t.name, t.age from Tutor t");
특징 Query TypeQuery
타입 안정성 낮음 (결과를 Object로 처리) 높음 (지정한 타입으로 변환)
사용 목적 결과 타입이 명확하지 않을때  결과 타입이 명확할때
형 변환 필요성 수동 형 변환 필요 ( Casting )  자동 형 변환 지원
결과 처리 방식 Object, Object[] 지정한 클래스 타입

결과 조회

  • getResultList()
    • 결과가 하나 이상일 때 사용한다.
    • List 반환
      • 결과가 없다면 빈 List 반환
List resultList = em.createQuery("select t from Tutor t").getResultList();

 

  • getSingleResult()
    • 결과가 딱 하나일 때 사용한다.
    • 결과가 없거나 여러개라면 예외 발생
      • NoResultException, NonUniqueResultException
Tutor singleResult = em.createQuery("select t from Tutor t where t.id = 1L", Tutor.class).getSingleResult();

✅ 파라미터 바인딩

파라미터 바인딩은 쿼리에 동적으로 값을 전달해 SQL 인젝션을 방지, 쿼리를 재사용할 수 있도록 돕는 기능

Tutor wonuk = em.createQuery("select t from Tutor t where t.name = :name", Tutor.class)
                    .setParameter("name", "wonuk")
                    .getSingleResult();
System.out.println("wonuk.getName() = " + wonuk.getName());
System.out.println("wonuk.getAge() = " + wonuk.getAge());

메소드 체이닝 사용 가능

✅ Embedded Type

JPA는 새로운 값 타입을 @Embedded , @Embeddable 을 사용하여 직접 정의해서 사용할 수 있고 이것을 임베디드 타입이라 한다. 직접 정의한 임베디드 타입도 int, String과 같은 값 타입

Embeded Type

@Entity
public class Tutor {
		
    @Id
    @GeneratedValue
    private Long id;

    private String name;
    // Embedded 사용
    @Embedded
    private Period workPeriod;

}

// Embedded 정의
@Embeddable
public class Period {
  
  @Temporal(TemporalType.DATE)
  Date startDate;
  
  @Temporal(TemporalType.Date)
  Date endDate;
  
  public boolean isWork (Date date) {
	    // startDate <= date <= endDate 확인
      return (startDate == null || !date.before(startDate)) &&
             (endDate == null || !date.after(endDate));
  }
}
  • 하나의 임베디드 타입으로 정의하여 여러 Entity에서 재사용 가능
  • 응집도 증가 → 단일 책임 원칙 준수
  • 독립적인 로직 작성 가능

✅ 프로젝션

Entity 특정 필드만 선택하여 조회하는 방식으로 필요한 데이터만 조회하여 성능을 최적화하고 네트워크 비용을 줄일 수 있다.

  • 프로젝션
    • SELECT 문에 조회할 대상(Entity, Embedded, Scala)을 지정할 수 있다.
    • DISTINCT 중복 제거가 가능
SELECT t FROM Tutor t                      // Entity
SELECT t.company FROM Tutor t              // Entity
SELECT t.period                            // Embedded
SELECT t.name, t.age FROM Tutor t          // Scala
SELECT DISTINCT t.name, t.age FROM Tutor t // 중복 제거
  • Entity 프로젝션
    • Entity 프로젝션을 사용하면 영속성 컨텍스트가 관리한다.
    • 연관된 Entity를 JOIN하여 조회할 수 있다
  • Embedded 프로젝션
    • 나머지 코드 주석 필요
    • select period from Period p 는 불가능하다.
    • select t from Tutor t where t.period.startDate < ? Embedded
    • select t from Tutor t where t.startDate < ? 상속(@MappedSuperclass)
      • 상속을 사용하는것이 편리하고 직관적
  • Scala 프로젝션
    • Scala 프로젝션은 DTO 형태로 반환 받을 수 있다.

묵시적 조인을 사용하면 실행되는 SQL Query를 예측하기 어렵다. 명시적 조인을 사용하자.

✅ Paging

DB의 쿼리 결과를 특정 범위로 제한하는 기능, 대량의 데이터 중 원하는 페이지의 데이터만 조회하기 위해 사용된다.

  • JPQL Paging
    • setFirstResult(int startPosition)
      • 조회 시작 위치
    • setMaxResult(int maxResult)
      • 조회할 데이터 수
List<Tutor> tutorList = em.createQuery("select t from Tutor t order by t.age desc", Tutor.class)
                    .setFirstResult(5)
                    .setMaxResults(10)
                    .getResultList();

for (Tutor tutor : tutorList) {
    System.out.println("tutor = " + tutor.getId() + ", " + tutor.getName() + ", " + tutor.getAge());
}

✅ JOIN

JPQL의 JOIN은 대상을 필터링(ON) 하거나 연관관계가 없는 Entity와 JOIN이 가능

💡 Hibernate 5.1 버전 이상에서 연관관계가 없는 Entity와 JOIN이 가능하도록 지원.

JOIN ON

String query = "select t from Tutor t left join Subject s on t.name = s.name";
List<Tutor> tutorList = em.createQuery(query, Tutor.class).getResultList();

Tutor findTutor = tutorList.get(0);
System.out.println("findTutor.getAge() = " + findTutor.getAge());
  • 연관관계가 없지만 on을 사용하여 JOIN이 가능

✅ CASE

 JPQL에서 CASE 표현식은 조건에 따라 다른 값을 반환할 때 사용된다. SQL의 CASE 구문과 유사하며 주로 조건에 따라 계산된 값을 반환하거나 쿼리 결과를 조정하는 데 사용

💡 COALESCE : null이 아니면 반환, NULLIF : 같으면 null, 다르면 첫번째 값 반환

String query =
        "select" +
                " case when t.age < 10 then '어린이'" +
                " when t.age >= 80 then '노약자'" +
                " else '청년'" +
                " end " +
        "from Tutor t";
        
List<String> resultList = em.createQuery(query, String.class).getResultList();

for (String range : resultList) {
		System.out.println("range = " + range);
}

문자열 SQL 띄어쓰기 유의

✅ 함수

  • 문자열 함수
    1. CONCAT('a', 'b')
      • a, b 문자열을 더한다.
    2. SUBSTRING('abcde', 2, 3)
      • 두번째 글자부터 세글자 문자열을 자른다.
      • bcd
    3. TRIM(' a ')
      • 문자열 양쪽 공백제거
    4. LOWER('A'), UPPER('a')
      • 각각 문자열을 소문자, 대문자로 변환한다.
    5. LENGTH('abcde')
      • 문자열의 길이를 반환한다.
  • 숫자 함수
    1. ABS(-100)
      • 절대값을 반환한다.
    2. MOD(100, 2)
      • 나머지를 계산한다.
    3. SIZE(t.company)
      • 연관된 컬렉션의 크기를 반환
Tutor tutor = new Tutor("wonuk", 100);
em.persist(tutor);

ArrayList<String> resultList = new ArrayList<>();

String concatQuery = "select concat(t.name, ' is good') from Tutor t";
List<String> concatResult = em.createQuery(concatQuery, String.class).getResultList();
resultList.add(concatResult.get(0));

String substringQuery = "select substring(t.name, 2, 3) from Tutor t";
List<String> substringResult = em.createQuery(substringQuery, String.class).getResultList();
resultList.add(substringResult.get(0));

Tutor tutor2 = new Tutor(" wonukgap ", 101);
em.persist(tutor2);

String trimQuery = "select trim(t.name) from Tutor t";
List<String> trimResult = em.createQuery(trimQuery, String.class).getResultList();
resultList.add(trimResult.get(1));

String upperQuery = "select upper(t.name) from Tutor t";
List<String> upperResult = em.createQuery(upperQuery, String.class).getResultList();
resultList.add(upperResult.get(0));

String lengthQuery = "select length(t.name) from Tutor t";
List<Integer> lengthResult = em.createQuery(lengthQuery, Integer.class).getResultList();
resultList.add(lengthResult.get(0).toString());

for (String result : resultList) {
		System.out.println("result = " + result);
}

상황에 맞게 함수를 사용하면 됨.

3️⃣ Fetch Join

✅ N + 1 문제

JPA가 연관된 엔티티를 조회할 때 추가적인 쿼리를 반복적으로 실행하기 때문에 발생하는 문제

  • 반복문 1
    1. tutor1~3 조회 및 1차 캐시에 저장
    2. tutor1 출력
    3. sparta 조회 및 1차 캐시에 저장
    4. sparta 출력
  • 반복문 2
    1. 1차 캐시의 tutor2 출력
    2. etc 조회 및 1차 캐시에 저장
    3. etc 출력
  • 반복문 3
    1. 1차 캐시의 tutor3 출력
    2. 1차 캐시의 sparta 출력
  • 튜터(N), 회사(1)가 많아질수록 더 많은 N + 1 문제가 생긴다.

💡 N + 1 문제는 지연 로딩, 즉시 로딩 모두 발생한다.

 

✅ Entity fetch join

JPQL에서 성능 최적화를 위해 fetch join을 제공, 연관된 엔티티나 컬렉션을 SQL 한번에 조회 해주는 기능

fetch join은 SQL의 JOIN과는 다른 종류이고 객체 그래프를 SQL 한 번에 조회함. 실무에서 굉장히 많이 활용

  • N:1 fetch join(Entity)
    • Tutor N : 1 Comapny 연관관계

일반 JOIN
Fetch Join

  • 튜터 조회시 연관된 회사도 함께 조회
String query = "select t from Tutor t join fetch t.company";
  • drop table 로 데이터 중복 제거
  • 지연로딩을 설정해도 fetch join이 우선권을 가진다.
  • fetch join 후 모든 Entity가 영속성 컨텍스트로 관리된다.
    • Proxy 객체가 아닌, 진짜 Entity 객체를 조회

✅ Collection fetch join

💡 @OneToMany 의 기본 FetchType은 LAZY

String query = "select c from Company c join fetch c.tutorList";
List<Company> companyList = em.createQuery(query, Company.class).getResultList();

for (Company company : companyList) {
    System.out.println("company.getName() = " + company.getName());
    System.out.println("company.getTutorList().size() = " + company.getTutorList().size());
}

데이터 중복이 발생하지 않는다.

 

SQL Query의 조회 결과는 데이터가 중복된다.

  • Hibernate 6.0 이상 부터는 DISTINCT가 자동으로 적용

주의점 ❗

  • Collection 에 fetch join을 사용하면 페이징을 메모리에서 수행한다.
String query = "select c from Company c join fetch c.tutorList";
List<Company> companyList = em.createQuery(query, Company.class)
        .setFirstResult(0)
        .setMaxResults(1)
        .getResultList();
  • 전체를 조회하는 SQL이 실행된다.
    • setFirstResult(), setMaxResult() 가 SQL에 반영되지 않는다.
  • 모든 데이터를 조회하여 메모리에서 페이징을 처리한다.
    • 필요없는 데이터까지 전체 로드한 후 필터링

✅ @BatchSize

JPA에서 N+1 문제를 해결하기 위해 사용되는 설정

지연 로딩시 한 번에 로드할 엔티티의 개수를 조정하여 여러 개의 엔티티를 효율적으로 조회할 수 있다.

@Entity
@Table(name = "company")
public class Company {

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

    private String name;

    @BatchSize(size = 100)
    @OneToMany(mappedBy = "company")
    private List<Tutor> tutorList = new ArrayList<>();

    public Company() {
    }

    public Company(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public List<Tutor> getTutorList() {
        return tutorList;
    }

}
  • 한 번의 IN Query에 식별자(PK)를 조회된 개수만큼 넣어준다.
  • 설정 파일의 hibernate.jdbc.batch_size 를 통해 Global 적용이 가능하다.
    • xml, yml, properties 모두 가능

💡 Global 설정보다 필드에 직접 선언한 @BatchSize 의 우선순위가 더 높다.

✅ 정리

JPQL의 한계

  1. 동적 쿼리를 사용하기 어렵다.
  2. SQL을 문자열로 작성하여 사용하기 까다롭다.
  3. SQL의 모든 기능을 사용할 수 없다(Native Query 사용).

fetch join 정리

  1. SQL의 JOIN과 비슷하지만 연관된 엔티티나 컬렉션을 한 번의 쿼리로 함께 로드하는 기능.
    • 지연 로딩 설정된 엔티티도 fetch join으로 사용할 수 있다.
  2. N + 1 문제를 해결할 수 있다.
  3. 데이터의 중복이 발생할 수 있다. (DISTINCT)
  4. 컬렉션 페이징 처리가 힘들다.
    • @BatchSize 적용
  5. 조회 시 여러 테이블이 JOIN 된다면 일반 JOIN을 사용하면 된다.
    • JPQL 또는 QueryDSL로 필요한 데이터만 조회하여 DTO로 반환한다.

4️⃣ Spring Data JPA와 JPQL

✅ JPQL 적용

  • Spring Data JPA
    • Query Methods 만으로는 조회하기 힘든 경우가 많다.
    • N+1 문제가 발생한다.
    • Spring Data JPA에도 JPQL을 적용할 수 있다.
    • @Query Annotation을 사용한다.
    • @Param 을 사용하여 파라미터 바인딩한다.
  • yml, properties
    • application.properties
spring.application.name=SpringJPQL

spring.datasource.url=jdbc:mysql://localhost:3306/example
spring.datasource.username=아이디
spring.datasource.password=비밀번호
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.jpa.hibernate.ddl-auto=create

spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
  • key-value 형태로 구문이 간단하다.
  • 동일 접두사의 키가 많은 경우 중복이 빈번 ( spring.datasource , spring.jpa 등등 )
  • application.yml
spring:
  application:
    name: SpringJPQL
  datasource:
    url: jdbc:mysql://localhost:3306/example
    username: 아이디
    password: 비밀번호
    driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        show_sql: true
        format_sql: true
        use_sql_comments: true
        dialect: org.hibernate.dialect.MySQLDialect
  • 계층적 구조로 작성이 가능하여 가독성이 높다.
  • 중복을 줄일 수 있다.

💡 properties, yml을 함께 사용할 수 있지만 properties의 우선순위가 높다. 하나의 방법을 선택해서 사용하면 된다.

✅ @PostConstruct 활용

  • 프로필 설정하기
    • application.yml
profiles:
  active: dev

프로필 환경별로 다른 설정을 적용할 수 있다.

@Slf4j
@Component
@Profile("dev")
public class DataInitializer {

    @Autowired
    private TutorRepository tutorRepository;

    @Autowired
    private StudentRepository studentRepository;

    @PostConstruct
    public void init() {
        // Tutor 데이터 초기화
        Tutor tutor1 = new Tutor("tutor1");
        Tutor tutor2 = new Tutor("tutor2");
        Tutor tutor3 = new Tutor("tutor3");

        tutorRepository.save(tutor1);
        tutorRepository.save(tutor2);
        tutorRepository.save(tutor3);

        // Student 데이터 초기화
        for (int i = 0; i < 30; i++) {
            Student student = new Student("student" + i, 20 + i);
            // 튜터를 순차적으로 할당
            if (i % 3 == 0) {
                student.setTutor(tutor1);
            } else if (i % 3 == 1) {
                student.setTutor(tutor2);
            } else {
                student.setTutor(tutor3);
            }

            studentRepository.save(student);
        }

        log.info("===== Test Data Initialized =====");
    }

}
  • @PostConstruct 로 Application 최초 실행 시에만 초기화 하도록 설정
  • @Profile("dev") dev 프로필에서만 동작하도록 설정
  • 실행 결과

반응형

'[내일배움캠프-Sparta] > Spring 6기' 카테고리의 다른 글

TIL 42 - [ Spring 심화 프로젝트 ( Day 1 ) - 설계 ( 와이어프레임, ERD, API 명세서 ) ]  (0) 2025.04.22
테스트 코드 ( 이론 )  (0) 2025.04.21
TIL 40 - [ Spring 심화 ( HttpMethodConverter, Converter, Formatter ) ]  (0) 2025.04.15
TIL 39 [ Spring 기초 프로젝트 ( D-day ) - 발표/마무리, 트러블슈팅 ]  (0) 2025.04.14
TIL 38 [ Spring 기초 프로젝트 ( Day 5 ) - 마무리 작업 ( ppt, 대본, ERD/API명세서 리뉴얼) ]  (0) 2025.04.11
'[내일배움캠프-Sparta]/Spring 6기' 카테고리의 다른 글
  • TIL 42 - [ Spring 심화 프로젝트 ( Day 1 ) - 설계 ( 와이어프레임, ERD, API 명세서 ) ]
  • 테스트 코드 ( 이론 )
  • TIL 40 - [ Spring 심화 ( HttpMethodConverter, Converter, Formatter ) ]
  • TIL 39 [ Spring 기초 프로젝트 ( D-day ) - 발표/마무리, 트러블슈팅 ]
dimenshun
dimenshun
한 소년의 개발 일기
    반응형
  • dimenshun
    Dev Life Notes
    dimenshun
  • 전체
    오늘
    어제
    • 분류 전체보기 (268)
      • CS (23)
        • 자료구조 (0)
        • 알고리즘 (0)
        • 컴퓨터 구조 (8)
        • 네트워크 (6)
        • 운영체제 (3)
        • DB ( + SQLD ) (5)
        • SW공학 (1)
      • 프로그래밍 (3)
        • Java (0)
        • Spring (0)
        • HTML,CSS (3)
        • JavaScript (0)
      • 개발 툴 (7)
        • Git(버전관리) (1)
        • Docker (3)
        • AWS (2)
        • JSP (1)
      • 코딩테스트(Algorithm) (125)
        • 백준 (6)
        • 프로그래머스 (119)
      • [내일배움캠프-Sparta] (110)
        • Spring 6기 (106)
        • KPT 회고 (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    OS
    알고리즘
    코딩테스트
    운영체제
    KPT
    백엔드
    It
    web
    db
    세션
    웹
    컴퓨터구조
    CPU
    트랜잭션
    Til
    AWS
    network
    Java
    SQLD
    네트워크
    메모리
    개발자
    Python
    spring
    SQL
    내일배움캠프
    Testcode
    배포
    cs
    docker
  • hELLO· Designed By정상우.v4.10.3
dimenshun
TIL 41 - [ Spring 심화 ( API 예외처리, JPQL, Fetch Join ) ]
상단으로

티스토리툴바