일자 관련 자동 처리를 위한 Entity - EntityListener

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Data
public abstract class AuditableEntity {

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;
}

1. 1:1 단방향 관계

@Entity
@NoArgsConstructor
@Data
public class User extends AuditableEntity {

    @Id 
    @GeneratedValue
    private Long id;

    private String name;

    @OneToOne
    private Address address;
}
@Entity
@NoArgsConstructor
@Data
public class Address extends AuditableEntity {

    @Id 
    @GeneratedValue
    private Long id;

    private String city;
    private String street;
}
  • User가 관계의 주인
  • address_id 외래키가 User 테이블에 생성됨
  • Address에서는 User 참조가 없음 (단방향)

2. 양방향 관계 + mappedBy 사용

@Entity
@NoArgsConstructor
@Data
public class User extends AuditableEntity {

    @Id 
    @GeneratedValue
    private Long id;

    private String name;

    @OneToOne(mappedBy = "user")
    @ToString.Exclude 
    private Address address;
}
@Entity
@NoArgsConstructor
@Data
public class Address extends AuditableEntity {

    @Id 
    @GeneratedValue
    private Long id;

    private String city;
    private String street;

    @OneToOne
    private User user;
}
  • Address가 관계의 주인 (외래키 보유)
  • User는 mappedBy = "user"로 연관관계 주인이 아님
  • 양방향 관계이지만 외래키는 Address에 있음
  • 양방향 관계를 사용할 때 순환 참조를 조심해야한다. 따라서 사용할 이유가 있을 경우에는 한쪽에 @ToString.Exclude 를 사용하여 순환 참조를 방지

'Spring' 카테고리의 다른 글

JPA - N:1 연관 관계  (1) 2025.07.18
JPA - 1:N 연관 관계  (0) 2025.06.16
JPA - Entity Listener의 활용  (1) 2025.06.12
JPA  (1) 2025.06.06
Jacoco - 테스트 커버리지 확인하기  (1) 2025.04.21

데이터베이스에서 엔터티(데이터 단위) 간의 관계를 시각적으로 표현한 다이어그램이다. 시스템이 어떤 데이터를 저장하고, 그것들이 어떻게 연결되어 있는지를 한눈에 보여준다.

 

구성 요소

구성 요소  설명  예시
Entity (엔터티) 정보를 저장할 대상(테이블) 사용자, 주문, 상품
Attribute (속성) 엔터티가 가진 세부 정보(컬럼) 사용자ID, 이메일
Relationship (관계) 엔터티 간의 연결성 사용자 - 주문 관계

 

관계(Cardinality)의 유형

관계  설명  예시
1:1 (일대일) 한 엔터티가 다른 엔터티와 1:1로 연결 사용자 ↔ 기본주소
1:N (일대다) 한 엔터티가 다른 엔터티 여러 개와 연결 사용자 ↔ 주문
N:M (다대다) 여러 엔터티가 서로 다수와 연결 주문 ↔ 상품

 

N:M 관계는 직접 구현되지 않음 → 중간 테이블 생성 필수 (예: 주문상품)

관계 설명 예시
식별자 실선으로 표현
부모 자식 관계에서 자식이 부모의 주 식별자를 외래 식별자로 참조하여 자신의 주 식별자로 설정
학생-신체정보(학생 PK=신체정보 PK)
비식별자 점선으로 표현
부모 자식 관계에서 부모의 주 식별자를 외래 식별자로 참조해서 일반 속성으로 사용
사원-사원증(각 사원은 반드시 하나의 사원증만 가짐)

 

ERD 작성 방법

① 요구사항 분석

  • 비즈니스 로직을 정리하고 저장해야 할 정보를 추출
  • 핵심 엔터티 도출 (예: 사용자, 주문, 상품 등)

② 엔터티 도출 및 속성 정의

  • 각 엔터티의 속성(컬럼)을 식별 (ID, 이름, 날짜 등)
  • Primary Key, Unique, Not Null 등 제약 조건 함께 정의

③ 관계(Relationship) 설정

  • 각 엔터티 간의 연관성 식별 (1:1, 1:N, N:M)
  • 다대다 관계는 중간 엔터티로 분해 (예: 주문상품)

④ ERD 도식화

  • 시각적 다이어그램으로 구성
  • 관계선에 관계명, 방향성, 카디널리티(1:1, 1:N, N:M 등) 표시

 

1:1 관계 (One-to-One Cardinality)

도서의 평점,리뷰수 등은 책 한권에 한 정보만을 가진다.

 

1:N 관계 (One-to-Many Cardinality)

한 사용자는 여러 리뷰(1개 이상)를 작성할 수 있다.

 

 

한 도서에 대해서 여러 리뷰(0개 이상)를 가질 수 있다.

 

N:M 관계 (Many -to-Many Cardinality)

도서는 여러 저자(1개 이상)를 가질 수 있다.

저자 또한 여러 권의 책(1개 이상)을 쓸 수 있다

.

'개발지식' 카테고리의 다른 글

프로세스와 쓰레드  (0) 2025.03.06
RAM 영역  (0) 2025.03.04

JPA 엔티티의 생명주기 이벤트(생성, 수정 등)에 반응하여 자동으로 특정 메서드를 실행할 수 있게 해주는 기능이다.

→ 핵심 목적: 반복되는 엔티티 전처리/후처리 로직을 재사용 가능하게 분리.

 

어노테이션명  호출시점
@PrePersist insert 쿼리 실행 전
@PostPersist insert 쿼리 실행 후
@PreUpdate update 쿼리 실행 전
@PostUpdate update 쿼리 실행 후
@PreRemove delete 쿼리 실행 전
@PostRemove delete 쿼리 실행 후
@PostLoad select 쿼리 실행 후
import java.time.LocalDateTime;

public interface Auditable {
		LocalDataTime getCreateAt();
		LocalDataTime getUpdateAt();

    void setCreatedAt(LocalDateTime createdAt);
    void setUpdatedAt(LocalDateTime updatedAt);
}

import jakarta.persistence.*;

import java.time.LocalDateTime;

public class AuditListener {

    @PrePersist
    public void perPersist(Object o) {
        if (o instanceof Auditable) {
            ((Auditable) o).setCreateAt(LocalDateTime.now());
            ((Auditable) o).setUpdateAt(LocalDateTime.now());
        }
    }

    @PreUpdate
    public void perUpdate(Object o) {
        if (o instanceof Auditable) {
            ((Auditable) o).setUpdateAt(LocalDateTime.now());
        }
    }
}

import jakarta.persistence.*;
import java.time.LocalDateTime;

@Entity
@NoArgsConstructor
@Data
@EntityListeners(AuditListener.class)
public class Post implements Auditable {

    @Id
    @GeneratedValue
    private Long id;

    private String title;
    
    private LocalDateTime createdAt;
    
    private LocalDateTime updatedAt;

  
    // getter/setter 생략 가능
}

 

AuditingEntityListener

Spring Boot에서 제공하는 AuditingEntityListener는 Spring Data JPA의 감사(Auditing) 기능을 자동화해주는 리스너 클래스다. 이 리스너를 통해 createdDate, lastModifiedDate, createdBy, lastModifiedBy 같은 필드를 자동으로 관리할 수 있다.

import jakarta.persistence.*;
import lombok.Data;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Data
public class BaseEntity {

    @CreatedDate
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;
}
import jakarta.persistence.*;
import java.time.LocalDateTime;

@Entity
@NoArgsConstructor
@Data
@ToString(callSuper= true)
@EqualsAndHashCode(callSuper= true)
@EntityListeners(AuditListener.class)
public class Post extends BaseEntity implements Auditable {

    @Id
    @GeneratedValue
    private Long id;

    private String title;
    
}

'Spring' 카테고리의 다른 글

JPA - 1:N 연관 관계  (0) 2025.06.16
JPA - 1:1 연관 관계  (0) 2025.06.16
JPA  (1) 2025.06.06
Jacoco - 테스트 커버리지 확인하기  (1) 2025.04.21
JUnit  (0) 2025.04.21

 

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}

spring data jpa 사용을 위해 의존성 추가

 

ORM(Object Relational Mapping)

ORM은 Object Relational Mapping의 약자로, 객체 지향 프로그래밍 언어(예: Java)에서 사용하는 객체(엔티티)와 관계형 데이터베이스(RDB)에서 사용하는 테이블 간의 불일치를 해결하고, 이 둘을 자동으로 매핑해주는 기술입니다. 즉, 자바의 객체와 데이터베이스의 테이블을 1:1로 연결해주는 중계자 역할을 하며, 개발자가 객체를 조작하면 그에 맞춰 자동으로 데이터베이스에 반영되도록 해줍니다

@GeneratedValue는 JPA(Java Persistence API)에서 엔티티 클래스의 주요 키(Primary Key) 값을 자동으로 생성하기 위해 사용하는 어노테이션입니다. 주로 @Id와 함께 사용되어, 데이터베이스에 엔티티를 저장할 때 해당 필드의 값을 자동으로 생성하도록 지시합니다

엔티티 정의

package com.example.bookmanager.domain;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;

import java.time.LocalDateTime;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NonNull
    private String name;

    @NonNull
    private String email;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
}

 

전략명 설명 주요 사용 DBMS 언제 사용해야 하는가
IDENTITY DB의 auto-increment/identity 기능으로 PK 생성. DB가 직접 값 할당. MySQL, MariaDB, PostgreSQL(SERIAL), SQL Server 등 DB가 PK를 자동 증가로 관리할 때. 단순하고, 별도 시퀀스/테이블 없이 DB 기본 기능을 그대로 쓸 때 적합. 대량 배치/성능이 중요하지 않을 때.
SEQUENCE DB의 시퀀스 오브젝트에서 PK 값을 가져와 할당. Oracle, PostgreSQL, H2 등 DB가 시퀀스를 지원할 때. 대량 데이터 삽입, JPA 배치 처리 등 고성능이 필요할 때. 시퀀스를 커스터마이즈할 때.
TABLE 별도 테이블에 PK 값을 저장·증가시켜 관리. 모든 DBMS 시퀀스/identity 미지원 DB에서 DB 독립적 키 생성이 필요할 때. 하지만 성능 저하로 대규모 트랜잭션에는 비권장.
AUTO JPA 구현체가 DB에 맞는 전략을 자동 선택. 모든 DBMS DB 종류가 바뀔 수 있거나, 특별한 요구사항이 없을 때. 그러나 동작이 DB/버전에 따라 달라질 수 있어 일관성이 중요할 때.

요약

  • IDENTITY: DB가 PK를 자동 증가로 관리할 때(간단, 소규모, DB에 맞춘 설계).
  • SEQUENCE: 시퀀스 지원 DB에서 성능과 확장성을 원할 때(대량 데이터, 커스텀 시퀀스).
  • TABLE: DB 독립성이 우선이거나, 시퀀스/identity 없는 DB에서(성능 저하 감수).
  • AUTO: JPA가 알아서 결정(프로토타입, DB 변경 가능성 있을 때, 단 일관성 주의).

Repository 정의

package com.example.bookmanager.repository;

import com.example.bookmanager.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

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

JpaRepository<{Entity 클래스} , {PK 데이터 타입}> 을 상속 받는다.

Application.yml 설정

spring:  
	  jpa:
	    show-sql: true #jpa 사용 시 sql 로그 출력
      properties:
		      hibernate:
			        format_sql: true #  sql 로그를 sql 포맷형식으로 출력
	    defer-datasource-initialization: true
	  h2:
	    console:
	      enabled: true

Spring Boot 2.5 이상부터는 기본적으로 data.sql과 같은 SQL 스크립트가 Hibernate(JPA)의 DDL(테이블 생성 등) 작업보다 먼저 실행된다. 이 때문에 Hibernate가 아직 초기화 되지 않았는데 data.sql이 실행되어 에러가 발생할 수 있다.

spring.jpa.defer-datasource-initialization: true를 설정하면 Hibernate의 DDL 작업(엔티티 기반 테이블 생성 등)이 모두 끝난 뒤에 data.sql, schema.sql 등 SQL 스크립트가 실행된다

JPA 주요 메소드 및 네이밍 규칙 정리

JpaRepository가 기본적으로 제공하는 주요 메소드와 Spring Data JPA 쿼리 메소드 네이밍 규칙을 아래와 같이 정리할 수 있습니다.


주요 기본 메소드

  • save(S entity): 새로운 엔티티는 저장, 이미 존재하는 엔티티는 병합.
  • delete(T entity): 엔티티 하나 삭제.
  • findById(ID id): ID로 엔티티 하나 조회, Optional<T> 반환.
  • findAll(): 모든 엔티티 조회, 정렬(Sort)·페이징(Pageable) 지원.
  • count(): 전체 엔티티 개수 반환.
  • existsById(ID id): 해당 ID 엔티티 존재 여부 반환.
  • saveAll(Iterable<S> entities): 여러 엔티티 저장.

쿼리 메소드 네이밍 규칙 및 예시

Spring Data JPA는 메소드 이름만으로도 다양한 쿼리를 자동 생성합니다.

기능 메소드 네이밍 시작 반환 타입  예시 및 설명
조회 findBy, readBy, queryBy, getBy List<T>, Optional<T> findByName(String name)
카운트 countBy long countByAgeGreaterThan(int age)
존재여부 existsBy boolean existsByEmail(String email)
삭제 deleteBy, removeBy long, void deleteByName(String name)
중복제거 findDistinctBy List<T> findDistinctByEmail(String email)

연산자 및 키워드 조합

  • And, Or: findByNameAndAge, findByNameOrEmail
  • Between: findByAgeBetween(int start, int end)
  • LessThan, GreaterThan: findByAgeLessThan(int age), findByAgeGreaterThan(int age)
  • Like, NotLike: findByNameLike(String pattern)
  • In, NotIn: findByAgeIn(List<Integer> ages)
  • OrderBy: findByAgeOrderByNameDesc(int age)
  • IsNull, IsNotNull: findByEmailIsNull(), findByEmailIsNotNull()
  • True, False: findByActiveTrue(), findByActiveFalse()
  • IgnoreCase: findByNameIgnoreCase(String name)
  • Limit: findFirst3ByOrderByAgeDesc(), findTopByOrderByScoreAsc()
public interface UserRepository extends JpaRepository<User, Long> {
    List<User> findByName(String name);
    List<User> findByNameAndAge(String name, int age);
    List<User> findByAgeLessThan(int age);
    List<User> findByNameContaining(String part);
    List<User> findByNameOrderByAgeAsc(String name);
    List<User> findByAgeBetween(int start, int end);
}

data.sql로 사전 데이터 정의 하기

resources 하위에 data.sql을 생성하면 spring boot가 구동되면 자동으로 data.sql를 실행한다.

-- PK가 GenerationType.IDENTITY 인 경우
insert into users (`name`, `email`,`created_at`,`updated_at`) values ('jun','jun@gmail.com',now(),now());
insert into users (`name`, `email`,`created_at`,`updated_at`) values ('steve','steve@naver.com',now(),now());
insert into users (`name`, `email`,`created_at`,`updated_at`) values ('dennis','dennis@hanmail.net',now(),now());
insert into users (`name`, `email`,`created_at`,`updated_at`) values ('queen','queen@daum.com',now(),now());
insert into users (`name`, `email`,`created_at`,`updated_at`) values ('jun','jun@yahoo.com',now(),now());

JPA save() 등 ORM 코드로 데이터 삽입 (권장)

  • JPA가 시퀀스와 PK 할당을 완전히 관리하므로, 시퀀스 값 불일치, 음수 PK, 중복 오류 등 예기치 못한 문제가 발생하지 않습니다.
  • 테스트 데이터가 필요한 경우, 테스트 코드의 @BeforeEach 또는 별도 초기화 메서드에서 save()로 엔티티를 저장합니다.
  • 예시:
  • @BeforeEach void setUp() { userRepository.save(new User("jun", "[jun@gmail.com](<mailto:jun@gmail.com>)", ...)); userRepository.save(new User("steve", "[steve@naver.com](<mailto:steve@naver.com>)", ...)); }

flush : DB 반영 시점 조절. 로그상의 크게 변화 없음

delete 관련 일반 함수들은 먼저 해당 엔티티가 DB상에 있는지 각각 select쿼리로 확인 한 후 삭제 처리를 한다

delete관련 Batch 함수들은 where 속성 in 으로 확인 후 where 속성 or 절로 한번에 삭제 처리를 한다.

allInBatch 같은 경우는 select 쿼리를 거치지 않고 바로 삭제 처리를 한다.

 

pagenation 기능이 필요한 경우 JPA find 함수의 인자로 PageRequest.of(페이지번호, 페이지 사이즈)를 넣어주면 Page<Entitiy> 객체를 반환 받아서 활용할 수 있다.

@Test
    void crud(){
        Page<User> users = userRepository.findAll(PageRequest.of(1,3));

        users.forEach(System.out::println);

    }
   @Test
    void crud(){
        Page<User> users = userRepository.findAll(PageRequest.of(1,3));

        System.out.println("pages :" + users);
        System.out.println("totalElements :" + users.getTotalElements());
        System.out.println("totalPages :" + users.getTotalPages());
        System.out.println("Number of elements :" + users.getNumberOfElements());
        System.out.println("sort :"+users.getSort());
        System.out.println("size : "+users.getSize());

        users.getContent().forEach(System.out::println);
    }

// 콘솔 결과
pages :Page 2 of 2 containing com.example.bookmanager.domain.User instances
totalElements :5
totalPages :2
Number of elements :2
sort :UNSORTED
size : 3

QueryByExampleExecutor란?

QueryByExampleExecutor는 Spring Data JPA에서 제공하는 인터페이스로, 엔티티의 일부 필드 값(예: name, email 등)을 예시로 삼아 동적 쿼리(Example Query)를 쉽게 생성할 수 있게 해줍니다. 즉, “이런 값이 들어있는 엔티티를 찾아줘”라는 식의 검색을 코드 몇 줄로 구현할 수 있다.

위 방식은 특히 문자열에 관련된 것만 쓸 수 있다는 한계점이 있어서 조금 복잡한 쿼리를 만들 때는

Query DSL과 같은 별도의 방식으로 구현을 하게 된다.

@Test
void crud() {
    ExampleMatcher matcher = ExampleMatcher.matching()
            .withIgnorePaths("name")
            .withMatcher("email", endsWith());

    Example<User> example = Example.of(new User("j", "gmail.com"), matcher);

    userRepository.findAll(example).forEach(System.out::println);
}

주요 개념

Probe: 실제로 값을 채운 엔티티 객체(예: new User("j", "gmail.com")). 이 객체의 값이 쿼리 조건이 됩니다.

ExampleMatcher: 어떤 필드는 무시하고, 어떤 필드는 어떻게 비교할지(예: endsWith, contains 등) 지정하는 도구.

Example: Probe와 ExampleMatcher를 합쳐서, 실제로 쿼리를 만들 때 사용하는 객체.

QueryByExampleExecutor: findAll(Example<S> example) 등 Example 기반 쿼리 메서드를 제공하는 인터페이스. Spring Data JPA의 Repository가 이 인터페이스를 상속받으면 QBE 기능을 쓸 수 있습니다

'Spring' 카테고리의 다른 글

JPA - 1:1 연관 관계  (0) 2025.06.16
JPA - Entity Listener의 활용  (1) 2025.06.12
Jacoco - 테스트 커버리지 확인하기  (1) 2025.04.21
JUnit  (0) 2025.04.21
Server to Server - RestTemplate  (1) 2025.04.16

에라토스테네스의 체는 2부터 N까지의 자연수 중에서 소수를 빠르고 효율적으로 찾는 대표적인 알고리즘입니다. 이 방법은 소수가 아닌 수(합성수)를 체로 걸러내듯이 제거해 나가면서 소수만 남기는 방식입니다.

동작 원리 요약
1. 2부터 N까지 모든 수를 나열
2. 남아있는 수 중 가장 작은 수(i)를 소수로 판정
3. i의 배수(2i, 3i, 4i, ...)를 모두 제거
4. 이를 N의 제곱근까지 반복

이 과정을 거치면 남아있는 수가 모두 소수입니다.

 

코드 예시

n = 100  # 2부터 100까지 소수 구하기
prime = [True for _ in range(n + 1)]  # 소수 여부를 저장하는 리스트

for i in range(2, int(n**0.5) + 1):
    if prime[i]:
        for j in range(i * 2, n + 1, i):  # i의 배수들은 소수가 아님
            prime[j] = False

# 소수 출력
for i in range(2, n + 1):
    if prime[i]:
        print(i, end=' ')



시간 복잡도  
O(nloglogn)으로, 매우 효율적

한 번에 여러 소수를 구할 때 특히 빠르다.
특정 범위의 소수 개수, 소수의 합 등 다양한 문제에 응용 가능

'Algorithm' 카테고리의 다른 글

BOJ - 28278번 - 큐 2  (0) 2025.06.30
BOJ - 28278번 - 스택 2  (0) 2025.06.30
BOJ - 13909번 - 창문 닫기  (0) 2025.05.28
[프로그래머스] 제출 내역  (0) 2025.04.14
[프로그래머스] 덧칠하기  (0) 2025.04.14

문제
서강대학교 컴퓨터공학과 실습실 R912호에는 현재 N개의 창문이 있고 또 N명의 사람이 있다. 1번째 사람은 1의 배수 번째 창문을 열려 있으면 닫고 닫혀 있으면 연다.  2번째 사람은 2의 배수 번째 창문을 열려 있으면 닫고 닫혀 있으면 연다. 이러한 행동을 N번째 사람까지 진행한 후 열려 있는 창문의 개수를 구하라. 단, 처음에 모든 창문은 닫혀 있다.

예를 들어 현재 3개의 창문이 있고 3명의 사람이 있을 때,

1번째 사람은 1의 배수인 1,2,3번 창문을 연다. (1, 1, 1)
2번째 사람은 2의 배수인 2번 창문을 닫는다. (1, 0, 1)
3번째 사람은 3의 배수인 3번 창문을 닫는다. (1, 0, 0)
결과적으로 마지막에 열려 있는 창문의 개수는 1개 이다.

입력
첫 번째 줄에는 창문의 개수와 사람의 수 N(1 ≤ N ≤ 2,100,000,000)이 주어진다.

출력
마지막에 열려 있는 창문의 개수를 출력한다.

예제 입력 1 
3
예제 출력 1 
1
예제 입력 2 
24
예제 출력 2 
4


N = int(input())
print(int(N**0.5))

 

정리

 

**완전제곱수(perfect square)**란 어떤 정수 k에 대해 k² 형태로 표현되는 수
예: 1 = 1², 4 = 2², 9 = 3², 16 = 4², ..., k² ≤ N

우리가 궁금한 건:

N 이하의 완전제곱수는 총 몇 개인가?


 

모든 자연수 i (1 ≤ i ≤ k)에 대해 i²를 구했을 때, i² ≤ N을 만족해야 완전제곱수임.
여기서 가장 큰 그런 i는 바로 i = ⌊√N⌋, 즉 int(N**0.5)이다.

 

 

 

 

출처 : https://www.acmicpc.net/problem/13909

'Algorithm' 카테고리의 다른 글

BOJ - 28278번 - 스택 2  (0) 2025.06.30
에라토스테네스의 체  (0) 2025.05.28
[프로그래머스] 제출 내역  (0) 2025.04.14
[프로그래머스] 덧칠하기  (0) 2025.04.14
[프로그래머스] 바탕화면 정리  (0) 2025.04.10

Jacoco
Java코드의 코드 커버리지를 체크 하는 라이브러리 
결과를 html, xml, csv로 확인가능

 

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.4.4'
    id 'io.spring.dependency-management' version '1.1.7'
    id 'jacoco' // 추가
}

bulid.gradle 에서 plugins추가

 

Gradle 창의 verification check 더블클릭

 

 

build - report-tests 폴더 안에 index.html 파일이 생성

 

다음과 같이 확인이 가능하다

 

Gradle 창의 verification jacocoTestReport 더블클릭

 

build- report-jacoco 폴더 안에 index.html 파일이 생성

 

다음과 같이 전체 코드에 대한 테스트 커버리지를 확인 할 수 있다.

'Spring' 카테고리의 다른 글

JPA - Entity Listener의 활용  (1) 2025.06.12
JPA  (1) 2025.06.06
JUnit  (0) 2025.04.21
Server to Server - RestTemplate  (1) 2025.04.16
비동기 처리하기  (1) 2025.04.15

TDD(Test Driven Development, 테스트 주도 개발)

소프트웨어 개발 방법론 중 하나로, 실제 코드 작성에 앞서 테스트 코드를 먼저 작성하고, 그 테스트를 통과하는 코드를 구현하는 개발 방식을 의미

 

단위테스트

작성한 코드가 기대하는 대로 동작을 하는지 검증 하는 절차

 

Jnit

Java기반의 단위 테스트를 위한 프레임워크

Annotation 기반으로 테스트를 지원하며, Assert를 통하여 검증

 

dependencies {
    testImplementation platform('org.junit:junit-bom:5.10.0')
    testImplementation 'org.junit.jupiter:junit-jupiter'

		// mock 관련 의존성 추가
    testImplementation("org.mockito:mockito-core:4.11.0")
    testImplementation("org.mockito:mockito-junit-jupiter:4.11.0")
}

 

@ExtendWith(MockitoExtension.class)
public class DollarCalculatorTest {

    @Mock
    public MarketApi marketApi;

    @BeforeEach
    public void init(){
		    // marketApi.connect()의 결과로 3000 반환
        Mockito.lenient().when(marketApi.connect()).thenReturn(3000);
    }

    @Test
    public void testHello() {
        System.out.println("hello");
    }

    @Test
    public void dollarTest() {
        MarketApi marketApi = new MarketApi();
        DollarCalculator dollarCalculator = new DollarCalculator(marketApi);
        dollarCalculator.init();;

        Calculator calculator = new Calculator(dollarCalculator);
        Assertions.assertEquals(22000,calculator.sum(10,10));
        Assertions.assertEquals(0,calculator.minus(10,10));
    }

    @Test
    public void mockTest() {
        DollarCalculator dollarCalculator = new DollarCalculator(marketApi);
        dollarCalculator.init();;

        Calculator calculator = new Calculator(dollarCalculator);
        Assertions.assertEquals(60000,calculator.sum(10,10));
        Assertions.assertEquals(0,calculator.minus(10,10));
    }

}

 

어노테이션  설명
@ExtendWith(MockitoExtension.class) JUnit 5에서 Mockito 프레임워크의 기능을 테스트 클래스에 통합해주는 확장 어노테이션. Mock 객체 자동 초기화 등 Mockito의 기능을 손쉽게 사용할 수 있게 해준다
@Mock Mockito에서 제공하는 어노테이션으로, 인터페이스나 클래스의 Mock(가짜) 객체를 생성한다. 실제 구현체 대신 가상의 객체를 만들어 테스트 대상 클래스의 격리를 가능하게 한다
@BeforeEach JUnit 5에서 각 테스트 메서드 실행 전에 공통 초기화 작업을 수행하기 위해 사용된다. 예를 들어 Mock 객체 초기화, 테스트 데이터 준비 등에 활용된다
@Test JUnit 5에서 해당 메서드가 테스트 메서드임을 나타내는 어노테이션. 이 어노테이션이 붙은 메서드는 JUnit이 자동으로 실행하여 코드의 특정 동작이 기대한 대로 동작하는지 검증한다
package com.example.springcalculator.component;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;

@SpringBootTest
class DollarCalculatorTest {

    @MockitoBean
    private MarketApi marketApi;

    @Autowired
    private DollarCalculator dollarCalculator;

    @Test
    public void dollarCalculatorTest() {
        Mockito.when(marketApi.connect()).thenReturn(3000);
        dollarCalculator.init();

        int sum = dollarCalculator.sum(10,10);
        int minus = dollarCalculator.minus(10,10);

        Assertions.assertEquals(60000, sum);
        Assertions.assertEquals(0, minus);
    }

}

@SpringBootTest

    Spring Boot 애플리케이션의 전체 컨텍스트(모든 Bean)를 로드하여 실제 운영 환경과 유사하게 테스트를 진행

@MockitoBean

    MarketApi 빈을 Mockito mock 객체로 대체

package com.example.springcalculator.controller;

import com.example.springcalculator.component.Calculator;
import com.example.springcalculator.component.DollarCalculator;
import com.example.springcalculator.component.MarketApi;
import com.example.springcalculator.dto.Req;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

@WebMvcTest(CalculatorApiController.class)
@Import({Calculator.class, DollarCalculator.class})
public class CalculatorApiControllerTest {

    @MockitoBean
    private MarketApi marketApi;

    @Autowired
    private MockMvc mockMvc;

    @BeforeEach
    public void init(){
        Mockito.when(marketApi.connect()).thenReturn(3000);
    }

    @Test
    public void sumTest() throws Exception {
        // <http://localhost:8080/api/sum?a=10&b=10>

        mockMvc.perform(MockMvcRequestBuilders.get("<http://localhost:8080/api/sum>")
                        .queryParam("a","10")
                        .queryParam("b","10"))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().string("60000"))
                .andDo(MockMvcResultHandlers.print());

    }

    @Test
    public void minusTest() throws Exception {
        // <http://localhost:8080/api/minus?a=10&b=10>

        Req req = new Req();
        req.setA(10);
        req.setB(10);

        String json = new ObjectMapper().writeValueAsString(req);

        // {"result":0,"response":{"resultCode":"OK"}}
        mockMvc.perform(MockMvcRequestBuilders.post("<http://localhost:8080/api/minus>")
                .contentType(MediaType.APPLICATION_JSON)
                .content(json))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.result").value(0))
                .andExpect(MockMvcResultMatchers.jsonPath("$.response.resultCode").value("OK"))
                .andDo(MockMvcResultHandlers.print());

    }

}

 

어노테이션  주요 목적/용도 로드되는 빈 범위  MockMvc
자동 등록
 테스트 속도/무게감 주 사용 사례  장단점 요약
@WebMvcTest 컨트롤러(Web Layer) 단위 테스트 Controller,
Web 관련 빈만
O 빠름/가벼움 컨트롤러 단위 테스트 장점: 빠르고 가볍다. Web Layer 집중.
단점: Service/Repository 등은 Mock 필요, 실제 환경과 다를 수 있음.
@SpringBootTest 전체 애플리케이션 통합 테스트 모든 스프링 빈 X (직접 추가 필요) 느림/무거움 통합 테스트, 실제 환경 검증 장점: 실제 환경과 유사, 모든 빈 사용 가능.단점: 느리고 무거움, 디버깅 어려움.
@SpringBootTest
@AutoConfigureMockMvc
전체 애플리케이션 + MockMvc 테스트 모든 스프링 빈 O 느림/무거움 통합 테스트 + MockMvc 사용 장점: 실제 환경과 유사, MockMvc로 HTTP 테스트 가능.
단점: 느리고 무거움.

 

'Spring' 카테고리의 다른 글

JPA  (1) 2025.06.06
Jacoco - 테스트 커버리지 확인하기  (1) 2025.04.21
Server to Server - RestTemplate  (1) 2025.04.16
비동기 처리하기  (1) 2025.04.15
Interceptor  (0) 2025.04.15

+ Recent posts