자바 웹 디비 관련 보안

Dear·2025년 7월 8일

TIL

목록 보기
57/74

💙 Errors 인터페이스

org.springframework.validation.Errors
스프링에서 유효성 검사(validation) 결과를 저장하고 처리하기 위한 인터페이스

BindingResult가 상속하고 있는 상위 인터페이스이다.
검증 대상 객체에 발생한 에러 메시지, 필드 오류 정보, 글로벌 오류 정보를 담을 수 있다.

자주 사용하는 메소드

메서드설명
reject(String errorCode)글로벌 오류 등록
rejectValue(String field, String errorCode)특정 필드에 대한 오류 등록
hasErrors()전체 오류 여부 확인
hasFieldErrors(String field)특정 필드에 오류가 있는지 확인
getAllErrors()모든 오류 가져오기
getFieldErrors()필드별 오류 가져오기

커스텀 Validator

@Component
public class UserFormValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return UserForm.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        UserForm form = (UserForm) target;

        if (!form.getPassword().equals(form.getConfirmPassword())) {
        	// 유효성 검사 실패를 직접 등록
            errors.rejectValue("confirmPassword", "PasswordsNotMatch", "비밀번호가 일치하지 않습니다");
        }
    }
}

💙 BindingResult

@Valid@Validated로 유효성 검사를 수행할 때, 검사 결과(에러 여부, 에러 메시지 등)를 담는 객체

에외로 처리되지 않고, 컨트롤러에서 직접 오류를 처리할 수 있다.
만약 BindingResult 없이 유효성 검사에 실패하면 MethodArgumentNotValidException 발생
BindingResult 사용한다면 예외 대신 결과를 확인하고 직접 처리 할 수 있다.

순서

@Valid@Validated 뒤에 바로 BindingResult가 와야 제대로 작동

// 올바른 순서
public String submit(@Valid UserDto dto, BindingResult bindingResult)

// 잘못된 순서 - 예외 발생
public String submit(BindingResult bindingResult, @Valid UserDto dto)

선호 이유

  1. BindingResult는 Errors보다 더 많은 정보와 기능을 제공한다.
    어떤 필드에서 오류가 발생했는지, 어떤 메시지를 보여줄지 등
  2. IDE 자동완성 지원이 더 잘된다.
  3. 이름 자체가 "검증 결과와 바인딩" 을 의미해 의도가 명확하다

💙 @Valid

Java Bean Validation 을 수행하는 어노테이션으로 DTO나 폼 객체에 붙여서 자동 유효성 검사를 수행한다.

@PostMapping("/save")
public String save(@Valid UserForm form, BindingResult bindingResult) { ... }
구분설명
@Valid + Errors✔ 사용 가능. Spring MVC 초기 예제에서 종종 등장.
@Valid + BindingResult권장 방식. 현재 Spring Boot/JPA 기반 예제에서 표준처럼 사용됨.
Spring FrameworkErrors 또는 BindingResult 모두 사용 가능
Spring Boot기본적으로 BindingResult를 사용함

💙 연관 관계 매핑

객체와 객체 사이의 관계를 데이터베이스의 테이블 관계에 맞춰 매핑하는 것
JPA나 Hibernate 같은 ORM 프레임워크에서 핵심적인 개념이다.

  • 캑체지향 : 클래스 간의 참조 (Member -> Team)
  • 관계형 데이터베이스 : 테이블 간의 외래 키(Foreign Key) 관계 (member.team_id -> team.id)

이 둘을 연결시켜주는 게 연관 관계 매핑이다.

객체(자바)

연관 관계 종류

연관 관계방향설명예시비고
@OneToOne단방향 or 양방향한 객체가 하나의 객체만 참조. 서로 1:1 관계사람 ↔ 주민등록증
PersonIdCard
@JoinColumn 필수
지연로딩(LAZY) 기본값은 EAGER
@OneToMany보통 양방향하나의 객체가 여러 객체를 참조팀 → 팀원들
TeamList<Member>
연관관계의 주인이 아님 (mappedBy 사용)
단방향은 비효율적 (중간 테이블 생성됨)
@ManyToOne단방향 or 양방향여러 객체가 하나의 객체를 참조여러 팀원 → 한 팀
MemberTeam
가장 많이 사용됨
외래키 가짐 = 연관관계 주인
@ManyToMany거의 양방향여러 객체가 여러 객체를 참조학생 ↔ 수업
StudentSubject
중간 테이블 자동 생성됨
실무에선 거의 사용 안 함, 중간 엔티티로 풀어냄

// @OneToOne
// 단방향 or 양방향
@Entity
public class Person {
    @OneToOne
    @JoinColumn(name = "id_card_id")
    private IdCard idCard;
}

// @OneToMany + mappedBy
// 보통 양방향
@Entity
public class Team {
    @OneToMany(mappedBy = "team")
    private List<Member> members;
}

// @ManyToOne
// 단방향 or 양방향
@Entity
public class Member {
    @ManyToOne
    @JoinColumn(name = "team_id") // 외래키
    private Team team;
}

// @ManyToMany
// 거의 양방향
@Entity
public class Student {
    @ManyToMany
    @JoinTable(name = "student_subject",
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "subject_id"))
    private List<Subject> subjects;
}

💙 Fetch 전략 (로딩 전략)

연관 관계가 설정된 다른 엔티티를 언제 DB에서 불러올지 설정하는 것

  • LAZY : 지연 로딩 (Lazy Loading) - 실제로 접근할 때 가져옴 (필요할 때만 쿼리 발생)
  • EAGER : 즉시 로딩 (Eager Loading) - 엔티티를 조회할 때 즉시 함께 로딩 (즉시 쿼리 발생)
@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)  // Fetch 전략
    private Team team;
}

Member member = em.find(Member.class, 1L); // 여기선 아직 Team 조회 안함
Team team = member.getTeam(); // 이 시점에 Team 쿼리가 실행됨 (LAZY일 경우)

기본값 정리

관계 어노테이션기본 Fetch 전략설명
@ManyToOneLAZY실무에서 가장 많이 쓰는 관계 (성능 고려)
@OneToOneEAGER성능 문제 때문에 직접 LAZY로 변경 권장
@OneToManyLAZY리스트는 무거우니 기본이 LAZY
@ManyToManyLAZY역시 기본 LAZY

@OneToOne, @ManyToOne은 기본값이 EAGER여서 성능 저하가 발생할 수 있다.

EAGER의 문제

Member member = em.find(Member.class, 1L);

EAGER일 경우 MemberTeam을 무조건 JOIN해서 한 번에 가져온다.
하지만 나중에 Team이 필요 없을 수도 있는데도 쿼리가 실행된다. (불필요한 성능 낭비)

대부분 fetch = FetchType.LAZY로 설정하고, 필요한 경우에만 JOIN FETCH로 명시적 쿼리를 작성하는 방식이 안정적이다.

JOIN FETCH

JPA의 JPQL(객체지향 쿼리 언어)에서 사용하는 문법으로, 지연 로딩(LAZY)된 연관된 엔티티를 즉시 함께 조회할 때 사용된다.


Member member = em.find(Member.class, 1L);
Team team = member.getTeam(); // 이 시점에 추가 쿼리 발생

getTeam()을 호출할 때 추가 쿼리가 실행됨 → N+1 문제 발생 가능

JPQL에서 JOIN FETCH를 사용하면 연관된 엔티티를 한 번에 함꼐 조회 가능


String jpql = "SELECT m FROM Member m JOIN FETCH m.team";
List<Member> members = em.createQuery(jpql, Member.class).getResultList();

Member를 조회하면서 Member.team도 한 번의 쿼리로 함께 조회
이때 지연 로딩 무시하고 즉시 로딩

-> SQL로 변환되면 INNER JOIN team처럼 조인된 쿼리가 나간다.

💙 N + 1 문제

1개의 쿼리로 N개의 결과를 조죄했는데, 그 결과의 연관된 엔티티를 가져오기 위해 추가로 N개의 쿼리가 발생하는 현상
총 1 + N 번의 쿼리가 실행됨

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    private Team team;
}

@Entity
public class Team {
    @Id @GeneratedValue
    private Long id;
    private String name;
}


List<Member> members = em.createQuery("SELECT m FROM Member m", Member.class)
                         .getResultList();

for (Member member : members) {
    System.out.println(member.getTeam().getName()); // Team 접근
}

실행되는 쿼리

  1. 첫 번째 쿼리 -> Member 리스트 조회 (1건)
    SELECT * FROM member;

  2. 반복문 내부에서 팀 이름 조회 시, 멤버마다 추가 쿼리 (N건)
    SELECT * FROM team WHERE id = ?; <- N번 반복
    1 + N개의 쿼리 발생

해결 방법 : JOIN FETCH 사용

List<Member> members = em. createQuery(
	"SELECT m FROM Member m JOIN FETCH m.team", Member.class
).getResultList();

for (Member member : members) {
    System.out.println(member.getTeam().getName()); // 추가 쿼리 없음
}

-> 실행되는 쿼리

SELECT m.*, t.* FROM member m JOIN team t ON m.team_id = t.id;

🤍 회고

오늘은 @Valid와 연관 관계 매핑에 대해 공부했다.
데이터베이스에서 데이터를 조회하는 방식과 밀접한 개념이라 중요한 부분이라고 느꼈다.
JPQL을 공부하면서 SELECT member FROM Member member 구문을 보게 되었는데,
SQL에서는 보통 SELECT *을 사용하는 반면, JPQL에서는 SELECT member처럼 엔티티 자체를 조회한다.
이는 JPQL이 테이블이 아닌 자바의 엔티티 객체를 대상으로 질의하기 때문이며, 결과로 객체 자체가 반환된다는 의미다.

profile
친애하는 개발자

0개의 댓글