반응형

클라이언트 측에서 넘어온 데이터를 객체에 바인딩할 때 @Valid와 @Validated로 유효성 검사를 할 수 있습니다.

 

우선 각 빌드에 맞게 의존성을 추가합니다.

 

Gradle일 경우, build.gradle에 의존성 추가

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

 

Maven일 경우, pom.xml에 의존성 추가

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    <version>2.6.3</version>
</dependency>

 

유효성 검사를 위한 어노테이션 적용

객체에 유효성 검사를 적용할 필드에 어노테이션을 적용합니다.

 

public class Article {

    private Long id;
    
    @NotEmpty
    private String title;
    
    @NotEmpty
    private String content;
    
    ...

}

 

어노테이션 종류

Anotation 제약조건
@NotNull Null 불가
@Null Null만 입력 가능
@NotEmpty Null, 빈 문자열 불가
@NotBlank Null, 빈 문자열, 스페이스만 있는 문자열 불가
@Size(min= , max= ) 문자열, 배열등의 크기가 만족하는가?
@Pattern(regex= ) 정규식을 만족하는가?
@Max(숫자) 지정 값 이하인가?
@Min(숫자) 지정 값 이상인가
@Future 현재 보다 미래인가?
@Past 현재 보다 과거인가?
@Positive 양수만 가능
@PositiveOrZero 양수와 0만 가능
@Negative 음수만 가능
@NegativeOrZero 음수와 0만 가능
@Email 이메일 형식만 가능
@Digits(integer= , fraction = ) 대상 수가 지정된 정수와 소수 자리 수 보다 작은가?
@DecimalMax(value= )  지정된 값(실수) 이하인가?
@DecimalMin(value= ) 지정된 값(실수) 이상인가?
@AssertFalse false 인가?
@AssertTrue true 인가?

 

Controller에서 유효성 검사

해당 유효성 검사를 할 것인지에 대해 Controller에서 @Valid를 통해 적용할 수 있습니다.

 

@RestController
public class ArticleController {

    ...

    @PostMapping("/article/write")
    public ResponseEntity articleWrite(@RequestBody @Valid Article article) {
        ...
    }

}

 

@Validated

@Validated 어노테이션은 스프링에서 제공하는 @Valid 기능을 확장한 어노테이션이고 그룹핑 목적으로 사용합니다. 예를 들어 위 예제에서 본 Article 클래스에서 @NotEmpty를 통해 게시글 제목과 내용에 null값과 빈 문자열이 들어가지 못하도록 설정했는데 @Valid를 사용하면 필드에 적용된 모든 어노테이션에 대한 검증이 이뤄지므로 특정 필드만 유효성 검사를 하고 싶을 경우에는 필드를 그룹핑하여 일부만 유효성 검사를 할 수 있습니다.

 

그룹을 지정하기 위한 용도의 클래스를 하나 생성하고 그룹별 interface를 클래스 내에 생성합니다.

 

public class ValidationGroups {

    public interface groupA {};
    public interface groupB {};

}

 

 

그리고 적용할 필드에 그룹을 적용하면 그룹핑이 끝납니다.

 

public class Article {

    private Long id;
    
    @NotEmpty(group = {ValidationGroups.groupA.class})
    private String title;
    
    @NotEmpty(group = {ValidationGroups.groupB.class})
    private String content;
    
    ...

}

 

위에서 본 예제 Controller에 유효성 검사 시 게시글 내용만 null값을 체크하고 싶으면 아래 코드처럼 적용하시면 됩니다.

 

@RestController
public class ArticleController {

    ...

    @PostMapping("/article/write")
    public ResponseEntity articleWrite(@RequestBody 
                                       @Validated(ValidationGroup.groupB.class) Article article) {
        ...
    }

}
반응형
반응형

엔티티(Entity) 설계 시 주의사항

1. Setter를 사용하지 말 것

Setter가 모두 열려있을 경우 변경 포인트가 많아서 유지 보수가 어려워집니다.

 

2. 모든 연관 관계는 지연로딩(LAZY)로 설정할 것 * 중요

즉시로딩(EAGER)은 예측이 어렵고 어떤 SQL이 실행될 지 예측하기 어렵습니다. 특히 JPQL을 실행할 때 N+1 문제가 자주 발생하게 됩니다.

 

3. 컬렉션은 필드에서 초기화할 것

컬렉션은 필드에서 바로 초기화해야 NullPointerException으로부터 안전합니다.

반응형
반응형

persistence.xml 설정

resources 디렉토리 밑에 META-INF 디렉토리를 생성하고 그 안에 persistence.xml을 만든 다음 아래 내용을 자신의 개발 환경에 맞게 세팅하시면 됩니다.

 

<?xml version="1.0" encoding="UTF-8" ?>
<persistence version="2.2"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/">
    
    <!-- EntityManagerFactory 생성 시 unit name과 일치해야 합니다 -->
    <persistence-unit name="hello">
        <properties>
            <!-- 필수 속성 -->
            <!-- DB Driver -->
            <property name="javax.persistence.jdbc.driver" value="org.mariadb.jdbc.Driver"/>
            
            <!-- DB user info -->
            <property name="javax.persistence.jdbc.user" value="user"/>
            <property name="javax.persistence.jdbc.password" value="password"/>
            
            <!-- DB url -->
            <property name="javax.persistence.jdbc.url" value="jdbc:mariadb://localhost:3306/schema"/>
            
            <!-- DB Dialect 설정 -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.MariaDB103Dialect"/>
            
            <!-- 옵션 -->
            <!-- SQL show -->
            <property name="hibernate.show_sql" value="true"/>
            
            <!-- SQL 정렬 -->
            <property name="hibernate.format_sql" value="true"/>
            
            <!-- SQL에 관한 주석 처리 -->
            <property name="hibernate.use_sql_comments" value="true"/>
            
            <!-- application 실행 시 ddl 전략 -->
            <property name="hibernate.hbm2ddl.auto" value="create"/>
        </properties>
        
    </persistence-unit>
</persistence>
반응형
반응형

연관 관계 편의 메소드를 사용하기 전에

이 부분을 이해하시려면 우선 영속성 컨텍스트(Persistence Context)에 대한 이해가 필요합니다. 관련 글은 다른 블로그에 자세히 설명된 게 있어서 링크로 첨부하니 확인해보세요!

 

JPA / 영속성 컨텍스트 / 1차 캐시 / 쓰기 지연

"T아카데미 / JPA 프로그래밍 기본기 다지기 - 김영한 " 강좌를 듣고 정리한 글입니다. 영속성 컨텍스트를 영속성 컨텍스로 관리하면 어떤 이점을 갖을까 1. 1차 캐시 / 엔티티 동일성 보장 2. 쓰기

www.ecsimsw.com

 

연관 관계 편의 메소드

JPA에서 양방향 연관 관계를 설정했을 때 연관 관계의 주인의 반대쪽 필드는 mappedby를 설정하여 읽기 전용으로 쓰게 됩니다. 그런데 일대다 중 다 쪽에만 연관 관계를 설정하면 객체 지향적이지 않을뿐더러 DB에서 데이터를 조회하기 전까지는 일대다 중 다 쪽에만 값이 세팅되어 일 쪽 Entity에서는 연관 관계를 모르기 때문에 일 쪽의 값을 출력해보면 데이터가 나오지 않습니다.

 

예제 코드

아래 코드는 teamA를 생성하고 memberA에 teamA를 넣어 연관 관계를 설정하는 코드입니다. 이 코드는 문제가 없어보일 수 있지만 실행하면 마지막 줄은 출력되지 않습니다.

 

memberA에는 teamA가 들어갔지만 반대인 teamA는 memberA와의 연관 관계가 설정되지 않았기 때문에 teamA에는 member와 관련된 아무런 값도 설정되지 않았기 때문입니다.

 

따라서 teamA가 memberA를 알려면 em.flush() 후 em.clear()를 통해 영속성 컨텍스트를 깨끗하게 비워 DB에서 값을 찾을 수 있도록 해주면 되는데 이보다 객체 지향적이면서, 1차 캐시의 영속성 컨텍스트와 실제 DB 데이터와의 데이터 차이 문제를 해결하기 위해 일대다 양쪽에 데이터를 넣어주는 게 필수적입니다. 아래 예제를 통해 확인해봅시다!

 

* 반드시 영속성 컨텍스트에 대한 내용을 숙지하고 보세요!

 

Team teamA = new Team();
team.setName("teamA");
em.persist(teamA);

Member memberA = new Member();
memberA.setName("memberA");

// memberA에 연관 관계 설정
memberA.setTeam(teamA);
em.persist(memberA);

System.out.println("memberA.getTeam : " + memberA.getTeam());
System.out.println("teamA.getMembers() : " + teamA.getMembers(0));

 

teamA에도 연관 관계 설정

teamA.getMember().add(memberA); 한 줄만 추가해주면 됩니다.

 

Team teamA = new Team();
team.setName("teamA");
em.persist(teamA);

Member memberA = new Member();
memberA.setName("memberA");

// memberA에 연관 관계 설정
memberA.setTeam(teamA);
em.persist(memberA);

// teamA에 연관 관계 설정
teamA.getMembers().add(memberA);

System.out.println("memberA.getTeam : " + memberA.getTeam());
System.out.println("teamA.getMembers() : " + teamA.getMembers(0));

 

Member Entity에 연관 관계 편의 메소드 생성

위 방법으로 해결할 수 있지만 같은 코드 반복을 방지하기 위해 Member Entity에서 연관 관계 편의 메소드를 생성합니다. 이렇게 연관 관계 편의 메소드를 사용하면 코드 중복을 방지하고 양방향 연관 관계 매핑에서 한쪽에만 연관 관계가 설정되는 것을 방지할 수 있습니다.

 

@Entity
@Getter
public Member {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;
    private String name;
    private Team team;
    
    public changeTeam(Team team) {
        
        // Member에 이미 Team이 설정되어 있을 경우
        if(this.team != null) {
         
            // team에서 해당 Entity를 제거
            this.team.getMembers().remove(this);
        }
        
        // 해당 member Entity에 파라미터로 들어온 team 연관 관계 설정
        this.team = team;
        
        // 파라미터로 들어온 team Entity에 member 연관 관계 설정
        team.getMembers().add(this);
    }
}
반응형

+ Recent posts