프로젝트 진행 중 패스워드 저장시 암호화를 하기위해 찾다가 PasswordEncoder 인터페이스에 대해 공부하게 되었다.

 

PasswordEncoder란?

 

자바에서 비밀번호 암호화를 하기 위한 인터페이스이다. 

public interface PasswordEncoder {


    String encode(CharSequence rawPassword);


    boolean matches(CharSequence rawPassword, String encodedPassword);


    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }

1. encode

이 메소드는 비밀번호를 입력 받아 해당 비밀번호를 인코딩된 문자열로 반환한다.

인코딩된 비밀번호는 저장되며 사용자 인증을 위해 사용한다.

 

2.matches

 

이 메소드는 입력받은 비밀번호와 인코딩된 비밀번호를 받아 두개가 일치하는지 확인하고

그 결과값을 boolean으로 반환한다. 사용자 인증 과정중 비밀번호 검증을 위해 사용한다.

 

3.upgradeEncoding

 

이 메소드는 비밀번호가 업그레이드가 필요한지 여부를 결정하는데 사용한다.

기본적으로 false를 반환 하여 업그레이드가 필요하지 않다고 가정한다.

또 한 오버라이딩하여 다른 로직을 제공할 수 있다. 그러므로 비밀번호 업그레이드할 때 

기존의 비밀번호를 새로운 인코딩 방식으로 업그레이드가 가능하다.

 

비밀번호 업그레이드란?

시스템이 사용하는 비밀번호 인코딩 방식이 변경되었을 때 기존에 저장된 비밀번호를

새로운 인코딩 방식으로 변경하는 것을 의미한다.

보통 사용자가 로그인 시도할 때 업그레이드를 시도하는데 upgradeEncoding을 사용하여 업그레이드가 필요한지 확인하며 필요하면 새로인 인코딩 방식으로 업그레이드 한 후 DB에 저장된다.

 

 

그렇다면 비밀번호가 동일하면 암호화값도 같다라고 가정하면 보안에 취약하지 않나?

그래서 등장 한 것이 솔트(salt) 개념이다. 

솔트는 무작위로 생성된 데이터이며, 비밀번호에 추가되어 해시 함수를 거치게 된다.

이를 통해 동일한 비밀번호라도 솔트값의 차이로 다른 암호화값을 가지게 된다.

 

 

솔트는 passwordEncoder에 적용되어 있는가?

 

솔트는 passwordEncoder에 적용되어 있지는 않지만 SpringSecurity 프레임워크에서 지원하는

BCryptPasswordEncoder 클래스에 적용되어있다. BCryptPasswordEncoder 클래스는 PasswordEncoder 인터페이스를 

구현하며 솔트를 사용하여 비밀번호를 인코딩 하여 보안을 강화시킬 수 있다.  

 

 

연관관계를 매핑할 떄 고려해야하는 3가지 사항

  1. 다중성
  2. 단방향, 양방향
  3. 연관관계의 주인

 

다중성의 종류

  1. 다대일: @ManyToOne
  2. 일대다: @OneToMany
  3. 일대일: @OneToOne
  4. 다대다: @ManyToMany

 

 

@OneToMany

 

 

외래키를 매핑하는 방법

@JoinColumn 

속성 기능 기본 값
name 매핑할 외래 키 이름 "필드명" + "_"  을 참조하는 테이블의 기본 키 컬럼 명
referencedColumnName 외래키가 참조하는 대상 테이블의 컬럼명 참조하는 테이블의 기본 키 컬럼 명
foreignKey(DDL) 외래 키 제약조건을 직접 지정
테이블을 생성할 때만 사용
 
unique @Column의 속성과 같음  
nullable @Column의 속성과 같음  
insertable @Column의 속성과 같음  
updateable @Column의 속성과 같음  
columnDefinition @Column의 속성과 같음  
table @Column의 속성과 같음

 

 

@OneToOne

 

일대일 관계는 그 반대도 일대일이다.

일대일 관계는 주 테이블이나 대상테이블 중 외래 키를 넣을 테이블을 선택할 수 있다.

외래 키에 DB 유니크 제약 조건을 추가해야 일대일 관계가 된다.

 

다대일 단방향 관계 매핑과 JPA어노테이션만 달라지고 거의 유사하다

 

@OneToOne 단방향 예시 코드

일대일 관계에서 대상 테이블에 외래 키를 지정하는 단방향 관계는 JPA에서 지원하지 않는다.

 

    class Member{
        //연관관계 주인 클래스, One

        @Id
        @Column(name = "MEMBER_ID")
        private Long id;

        private String username;

        @OneToOne
        @JoinColumn(name="TEAM_ID")
        private Team team; //Team 객체와 연관관계를 맺음
    }
    class Team{
        //One

        @Id
        @Column(name = "TEAM_ID")
        private Long id;

        private String name;

    }

 

@OneToOne 양방향 예시 코드

 

주 테이블은 멤버 테이블이지만, 외래 키를 대상 테이블에서 관리하고 주 테이블의 team 필드는 읽기 전용이 된다.

 

class Member{
    //연관관계 주인 클래스, One

    @Id
    @Column(name = "MEMBER_ID")
    private Long id;

    private String username;

    @OneToOne
    @JoinColumn(name="TEAM_ID")
    private Team team; //Team 객체와 연관관계를 맺음
}
class Team{
    //One

    @Id
    @Column(name = "TEAM_ID")
    private Long id;

    private String name;

    @OneToOne(mappedBy = "team") //Member 클래스의 team과 연관관계 맺음
    private Member member; //양방향 매핑
}

 

@ManyToMany

 

실무에서는 사용을 최대한 하지않는다.

관계형 DB에서는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다.

연결 테이블을 추가하여 일대다 또는 다대일 관계로 변경해야 한다.

 

객체는 컬렉션을 사용해 객체 2개로 다대다 관계가 가능하다.

개발 할 때 연결 테이블이 단순 연결만 하는 것이 아닌 추가 데이터가 많이 들어갈 수 있는데

이때 매핑 정보만 넣는 것이 가능하고 추가 정보를 넣는 것 자체가 불가능하다(확장 불가)

또 한 중간 테이블이 숨어져 있기 때문에 예상하지 못한 쿼리문들이 나갈 수 있다.

아래는 사용을 하지 말아야할 @ManyToMany코드이다.

 

@ManyToMany 단방향

 

class Member{
    //연관관계 주인 클래스, MANY

    @Id
    @Column(name = "MEMBER_ID")
    private Long id;

    private String username;

    @ManyToMany
    @JoinTable(name = "Member_Team",//연결 테이블 지정
                joinColumns = @JoinColumn(name = "MEMBER_ID"), //현재 방향인 회원
                inverseJoinColumns = @JoinColumn(name = "TEAM_ID"))//반대 방향인 TEAM
    private List<Team> teams = new ArrayList<>();
}
class Team{
    //MANY

    @Id
    @Column(name = "TEAM_ID")
    private Long id;

    private String name;


}

 

@ManyToMany 양방향

class Member{
    //연관관계 주인 클래스, MANY

    @Id
    @Column(name = "MEMBER_ID")
    private Long id;

    private String username;

    @ManyToMany
    @JoinTable(name = "Member_Team",//연결 테이블 지정
                joinColumns = @JoinColumn(name = "MEMBER_ID"), //현재 방향인 회원
                inverseJoinColumns = @JoinColumn(name = "TEAM_ID"))//반대 방향인 TEAM
    private List<Team> teams = new ArrayList<>();
}
class Team{
    //MANY

    @Id
    @Column(name = "TEAM_ID")
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "teams")
    private List<Member> members = new ArrayList<>();
}

 

@ManyToMany 한계 극복

 

연결 테이블용 엔티티를 추가하여 연결 테이블을 엔티티로 만들어 준다

각 두 테이블을 일대다, 다대일로 관계를 설정 해 준다.

 

class Member{
    //One

    @Id
    @Column(name = "MEMBER_ID")
    private Long id;

    private String username;

    @OneToMany(mappedBy = "member")
    private List<Member_Team> member_teams = new ArrayList<>(); //중간 테이블 연결
}
class Team{
    //One

    @Id
    @Column(name = "TEAM_ID")
    private Long id;

    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member_Team> members = new ArrayList<>(); //중간 테이블 연결
}


class Member_Team{
    //Many
    //연관관계 주인

    @Id
    @GeneratedValue
    private Long id;

    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;

    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;
}

연관관계를 매핑할 떄 고려해야하는 3가지 사항

  1. 다중성
  2. 단방향, 양방향
  3. 연관관계의 주인

 

다중성의 종류

  1. 다대일: @ManyToOne
  2. 일대다: @OneToMany
  3. 일대일: @OneToOne
  4. 다대다: @ManyToMany

 

 

@OneToMany

 

 

외래키를 매핑하는 방법

@JoinColumn 

속성 기능 기본 값
name 매핑할 외래 키 이름 "필드명" + "_"  을 참조하는 테이블의 기본 키 컬럼 명
referencedColumnName 외래키가 참조하는 대상 테이블의 컬럼명 참조하는 테이블의 기본 키 컬럼 명
foreignKey(DDL) 외래 키 제약조건을 직접 지정
테이블을 생성할 때만 사용
 
unique @Column의 속성과 같음  
nullable @Column의 속성과 같음  
insertable @Column의 속성과 같음  
updateable @Column의 속성과 같음  
columnDefinition @Column의 속성과 같음  
table @Column의 속성과 같음

 

 

@OneToMany 단방향

 

@ManyToOne과 비슷한 구조이다.

    class Member{
        //연관관계 주인 클래스, One

        @Id
        @Column(name = "MEMBER_ID")
        private Long id;

        private String username;

        @OneToMany
        @JoinColumn(name="TEAM_ID")
        private Team team; //Team 객체와 연관관계를 맺음
    }
    class Team{
        //Many

        @Id
        @Column(name = "TEAM_ID")
        private Long id;

        private String name;

    }

 

 

 

@OneToMany 양방향

 

이런 매핑은 공식적으로 존재 하지 않는다!

@JoinColumn(name = "team_id", insertable = false, updatable = false) 

코드를 사용한다.

@ManyToOne과 @JoinColumn을 사용해서 연관관계를 매핑하면, 다대일 단반향 매칭이 되어버리는데

반대쪽 Team에서 이미 일대다 단방향 매핑이 설정되어 있어 두 테이블 모두 FK를 관리하게 된다.

 

그걸 방지하기 위해 insertable과 updateable을 false로 설정해준다.

그외에도 읽기 전용으로 사용이 가능하다 

하지만 다대일 양방향을 사용하는 편이 낫다!

 

연관관계를 매핑할 떄 고려해야하는 3가지 사항

  1. 다중성
  2. 단방향, 양방향
  3. 연관관계의 주인

 

다중성의 종류

  1. 다대일: @ManyToOne
  2. 일대다: @OneToMany
  3. 일대일: @OneToOne
  4. 다대다: @ManyToMany

 

 

외래키를 매핑하는 방법

@JoinColumn 

속성 기능 기본 값
name 매핑할 외래 키 이름 "필드명" + "_"  을 참조하는 테이블의 기본 키 컬럼 명
referencedColumnName 외래키가 참조하는 대상 테이블의 컬럼명 참조하는 테이블의 기본 키 컬럼 명
foreignKey(DDL) 외래 키 제약조건을 직접 지정
테이블을 생성할 때만 사용
 
unique @Column의 속성과 같음  
nullable @Column의 속성과 같음  
insertable @Column의 속성과 같음  
updateable @Column의 속성과 같음  
columnDefinition @Column의 속성과 같음  
table @Column의 속성과 같음  

 

 

다대일(@ManyToOne)

다대일 단방향과 다대일 양방향이 있다.

 

다대일 단방향: 가장 많이 사용하는 연관관계이며 이에 반대되는 관계는 일대다 이다.

    class Member{
        //연관관계 주인 클래스, Many

        @Id
        @Column(name = "MEMBER_ID")
        private Long id;

        private String username;

        @ManyToOne
        @JoinColumn(name="TEAM_ID")
        private Team team; //Team 객체와 연관관계를 맺음
    }
    class Team{
        //One

        @Id
        @Column(name = "TEAM_ID")
        private Long id;

        private String name;

    }

 

 

 

다대일 양방향: 서로가 서로를 참조함

반대편에 @OneToMany를 사용하여 양방향 설정

    class Member{
        //연관관계 주인 클래스, Many

        @Id
        @Column(name = "MEMBER_ID")
        private Long id;

        private String username;

        @ManyToOne
        @JoinColumn(name="TEAM_ID")
        private Team team; //Team 객체와 연관관계를 맺음
    }
    class Team{
        //One

        @Id
        @Column(name = "TEAM_ID")
        private Long id;

        private String name;

        @OneToMany(mappedBy = "team")//양방향 연관관계 설정
        private List<Member> memberList = new ArrayList<>();

    }

 

@ManyToOne 속성

 

속성 기능 기본값
optional false로 설정하면 연관된 엔티티가 항상 있어야 한다 true
fetch  글로벌 패치 전략을 설정한다 FetchType.EAGER(즉시 로딩)(default)
FetchType.LAZY(지연 로딩)(권장 설정)
cascade 영속성 전이 기능을 사용한다  
targetEntity 연관된 엔티티의 타입 정보를 설정한다.  

 

 

출처

https://www.inflearn.com/course/ORM-JPA-Basic/dashboard

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

방향(Direction) : 단방향, 양방향

연관관계 주인(Owner): 객체 양방향 연관관계는 관리 주인이 필요

 

 

객체를 테이블에 맞추어 데이터 중심으로 모델링시 협력 관계를 만들 수 없다.

테이블: 외래 키로 조인을 사용해서 연관된 테이블을 찾는다 객체: 참조를 사용해서 연관된 객체를 찾는다.
테이블과 객체 사이에는 이런 큰 간격이 있다.

 

단방향 연관관계

 

단방향 : 엔티티의 관계가 한 쪽만 참조하는 것

 

    class Member{

        @Id
        @Column(name = "MEMBER_ID")
        private Long id;

        private String username;

        @ManyToOne
        @JoinColumn(name="TEAM_ID")
        private Team team; //Team 객체와 연관관계를 맺음
    }
    class Team{

        @Id
        @Column(name = "TEAM_ID")
        private Long id;
        private String name;
        //팀은 회원을 알 수 없다.
    }
}
  1. 회원은 하나의 팀에만 소속될 수 있다.
  2. 회원과 팀은 다대일 관계이다.

member -> team: member.getTeam() 은 가능하지만

team -> member : 불가능하다.

 

만약 @JoinColumn을 생략하게 된다면 외래 키를 찾을 때 기본 전략을 사용한다.

  • 기본 전략: 필드명 + _ + 참조하는 테이블의 컬럼 명
  • 필드명 ( team) + _ + 참조하는 테이블의 컬럼명(TEAM_ID)

team_TEAM_ID 외래키를 사용한다.

 

 

인스턴스 관계

public static void main(String[] args) {

    //생성자(id, 이름)
    Member member1 = new Member("member1", "회원1");
    Member member2 = new Member("member2", "회원2");
    Team team1 = new Team("team1", "팀1");

    //member1과 member2를 team1에 소속
    member1.setTeam(team1);
    member2.setTeam(team1);

    //객체 그래프 탐색을 통한 연관관계 탐색
    Team findTeam = member1.getTeam();

}

 

 

양방향 연관 관계

 

양방향 연관관계: 엔티티의 관계가 서로를 참조하는 것

    class Member{
        //연관관계 주인 클래스

        @Id
        @Column(name = "MEMBER_ID")
        private Long id;

        private String username;

        @ManyToOne
        @JoinColumn(name="TEAM_ID")
        private Team team; //Team 객체와 연관관계를 맺음
    }
    class Team{

        @Id
        @Column(name = "TEAM_ID")
        private Long id;

        private String name;

        @OneToMany(mappedBy = "team")//양방향 연관관계 설정
        private List<Member> memberList = new ArrayList<>();
    }

Team 엔티티는 컬렉션을 추가하면 된다.

 

  1. Team의 입장에서 바라보는 일대다, @OneToMany를 설정한다
  2. mappedBy로 team과 연관이 있는 것을 알려준다
  3. 컬렉션을 매핑한다. 관례로 ArrayList로 초기화하여 NPE방지한다.

NPE: Null Pointer Exception

 

반대 방향으로 객체 그래프 탐색이 가능하다.

//팀 조회
Team findTeam = em.find(Team.class, team.getId());
​
// 역방향으로 멤버들 조회
int memberSize = findTeam.getMembers().size(); 

 

@OneToMany의 mappedBy가 필요한 이유

객체에서는 양방향 연관관계가 없고 서로 다른 단방향 연관관계를 2개 묶어 양방향인것 처럼 보이게한다.

양방향으로 매핑하면 서로를 참조하게 되는데 이때 객체의 연관관계를 관리하는 포인트가 2곳으로 늘어난다.

객체의 참조는 둘인데 외래키는 하나이므로 둘 사이의 차이가 발생한다.

그래서 JPA에서는 두 객체 연관관계 중 하나를 정해서 테이블의 외래키를 관리해야하는데 이것이

연관관계의 주인(Owner)이다.

  1. 주인은 mappedBy를 사용하지 않는다
  2. 주인이 아닐 경우 mappedBy를 사용하여 주인을 지정한다.
  3. 외래키가 있는 곳을 주인으로 설정한다.
  4. 객체에 둘다 정보를 업데이트해도 연관관계 주인것만 DB에 영향을 준다
  5. 주인이 아닌쪽은 읽기만 가능하다.
  6. 객체지향적, 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자!
  7. 양방향 매핑시 무한루프를 주의한다.( Lombok이 자동으로 만드는 toString()사용하지말자)
  8. 컨트롤러에서는 엔티티를 절대 직접 반환하지 않는다.

 

 

 

 

 

 

출처: https://www.inflearn.com/course/ORM-JPA-Basic

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

https://ict-nroo.tistory.com/122

 

[JPA] 양방향 연관관계

양방향 연관관계와 연관관계의 주인Team을 통해서도 getMemberList()로 특정 팀에 속한 멤버 리스트를 가져오고 싶다.객체 설계는 위와 같이 Member에서는 Team을 가지고 있고, Team에서는 Members를 가지고

ict-nroo.tistory.com

https://cornswrold.tistory.com/350

 

[JPA] 연관관계 매핑 기초(단방향/양방향)

객체 관계 매핑(ORM)에서 가장 어려운 부분이 객체 연관관계와 테이블 연관관계를 매핑하는 일이다. 연관관계를 매핑할 때 다음 3가지를 고려해야 한다. 방향(Direction) [단방향, 양방향] 존재 회원,

cornswrold.tistory.com

 

Entity: 테이블에 대응하는 하나의 클래스이다.

Entity Manager : 관리하는 엔티티 객체들을 영속 컨텍스트에 넣어두고 객체들을 관리한다.

영속 컨텍스트: JPA가 엔티티 객체들을 모아두는 공간

 

영속 컨텍스트 상태

new(비영속) Java영역에만 존재하고, DB에 연동된 적 없는 상태이다. 
순수한 Java객체로 엔티티 매니저가 관리하지 않는다.
Managed(영속) DB에 저장되고, 메모리 상에서도 같은 상태로 존재하는 상태이다
PK값을 통해 필요한 엔티티 객체를 꺼내 사용할 수 있게 됩니다.
Removed(삭제) DB상에서 삭제된 상태이다.
객체는 영속 컨텍스트에 존재하지 않는다.
Detached(준영속) 영속 컨텍스트에서 엔티티 객체를 꺼내 사용하는 상태이다.
아직 DB와 동기화가 이루어 지지 않는 상태이다.
영속성 켄텍스트가 제공하는 기능을 사용하지 못한다.

준영속 상태로 만드는 방법

  1. em.detach(entity): 특정 엔티티만 준영속 상태로 전환
  2. em.clear() : 영속성 컨텍스트를 완전히 초기화
  3. em.close() : 영속성 컨텍스트를 종료

 

 

JPA는 자동으로 테이블을 생성할 수 있는 기능이 있다. 다음은 2가지 방식이다.

  1. SQL을 이용해서 테이블을 먼저 생성하고 엔티티 클래스를 만드는방식
  2. JPA를 이용해서 클래스만 설계하고 자동으로 테이블 생성하는 방식

엔티티 설계시 주의점

1.엔티티에는 가급적 Setter를 사용하지 말자!

Setter가 모두 열려있다. 변경 포인트가 너무 많아서 유지보수가 어렵다. 나중에 리펙토링으로 Setter제거한다.

 

2.모든 연관관계는 지연로딩으로 설정!

즉시로딩(EAGER)은 예측이 어렵고, 어떤 SQL이 실행될 지 추적하기 어렵다. 특히 JPQL을 실행할 때 N+1의 문제가 자주 발생한다. 그러므로 실무에서 모든 연관관계는 지연로딩(LAZY)방식으로 설계해야한다.

연관된 엔티티를 함께 DB조회시 fetch join 또는 엔티티 그래프 기능을 사용한다.

@OneToOne, @ManyToOne 관계는 기본값이 즉시로딩이므로 지연로딩으로 설정 해야 한다.

 

3.컬렉션은 필드에서 초기화 하자

컬렉션은 필드에서 바로 초기화 하는 것이 null문제에서 안전하다.

임의의 메서드에서 컬렉션을 잘못 생성하면 하이버네이트 내부 메커니즘에 문제가 발생할 수 있다. 

따라서 필드 레벨에서 생성하는 것이 가장 안전하고 코드도 간결하다.

 

 

 

스프링 부트 신규 설정 (엔티티(필드) 테이블(컬럼))

  1. 카멜 케이스 -> 언더 스코어(memberPoint -> member_point)
  2. .(점) -> _(언더 스코어)
  3. 대문자 -> 소문자

 

 

플러시: 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.

  1. 영속성 컨텍스트를 비우지 않는다
  2. 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화시킨다.
  3. 트랜잭션이라는 작업 단위가 중요하다( 커밋 직전에만 동기화 하면된다.)

 

 

플러시 발생

  1. 변경 감지
  2. 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
  3. 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송(등록, 수정, 삭제 쿼리)

 

 

영속성 컨텍스트를 플러시하는 방법

  1. em.flush() - 직접호출
  2. 트랜잭션 커밋 - 플러시 자동 호출
  3. JPQL 쿼리 실행 - 플러시 자동 호출

 

 

플러시 모드 옵션

em.setFlushMode(FlushModeType.AUTO)
커밋이나 쿼리를 실행할 때 플러시(기본값)
em.setFlushMode(FlushModeType.COMMIT)
커밋할 때만 플러시

 

 

 

 

 

 

 

 

 

출처: https://velog.io/@jayjay28/%EC%97%94%ED%8B%B0%ED%8B%B0Entity

 

엔티티(Entity)

엔티티? JPA에서는 엔티티는 테이블에 대응하는 하나의 클래스라고 생각하시면 편합니다.엄청나게 간단한 코드와 함께 같이 보겠습니다. 엔티티 클래스 데이터베이스의 테이블 |username|passoword| |

velog.io

https://www.inflearn.com/course/ORM-JPA-Basic

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

JPA:자바에서 ORM(Object-Relational Mapping) 기술 표준으로 사용하는 인터페이스 모음이다.

 

@Entity

해당클래스를 테이블과 매핑한다고 JPA에게 알린다.

 

@Table

엔티티 클래스에 매핑할 테이블 정보를 지정한다.

만약 생략하게 되면 클래스 이름을 테이블 이름으로 매핑한다. 

 

@Table 옵션

name 테이블 명
catalog 테이블 카테고리
schema 테이블 스키마
uniqueConstraints 컬럼값 유니크 제약조건
indexes 인덱스 생성

 

@Access

JPA가 엔티티 데이터에 접근하는 방식을 지정합니다.

 

@Entity
@Access(AccessType.FIELD)
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
}

@Entity
@Access(AccessType.PROPERTY)
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
}

 

@Access 옵션

AccessType.Field 필드에 직접 접근
AccessType.PROPERTY getter을 통해 접근

 

@Id

엔티티 클래스의 필드를 테이블의 기본키(PK)에 매핑합니다.

사용된 필드를 식별자 필드라 하며 주로 @GenerateValue와 같이 이용해서 식별키를 어떤 전략으로 생성하는지 명시한다.

 

 

@Column

엔티티 클래스의 필드를 컬럼에 매핑한다.

name 컬럼 이름
unique 유니크 여부(DDL)
nullable null 허용 여부(DDL)
insertable 추가 가능 여부(DDL, DML)
updateable 수정 가능 여부(DDL, DML)
table 테이블 이름(하나의 엔티티를 두개 이상의 테이블에 매핑할 때 사용
columnDefinition 데이터베이스 컬럼 정보 직접 부여(DDL)
length 컬럼 사이즈(default:225) (DDL)
percision 소수 정밀도(default:0) (DDL)
scale 소수점 이하 자리수(default:0) (DDL)

* DDL: 테이블, 뷰, 인덱스 및 프로시저와 같은 데이터베이스 및 개체의 구조를 정의

* DML: 데이터베이스 내의 데이터를 조작하는 데 사용

 

@GenerateValue

데이터베이스에 의해 자동으로 생성될 값에 대한 전략을 지정

보통 식별키 생성 전략으로 많이 사용

 

@GenerateValue 옵션AUTO: 특정 데이터베이스에 맞게 자동으로 생성되는 방식

    @Id //primary key 설정
    @GeneratedValue(strategy = GenerationType.AUTO) 
    private Long id;

 

IDENTITY: 기본키 생성 방식 자체를 데이터베이스에 위임하는 방식 데이터베이스에 의존 방식(MySQL MariaDB 등)

    @Id //primary key 설정
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

 

TABLE: 데이터베이스 종류 상관없이 별도의 키를 생성해주는 채번 테이블을 이용하는 방법

성능이 좋지않아 권장하진않는다.

@TableGenerator가 필요하다

@Entity
@TableGenerator(name = "MEMBER_SEQ_GENERATOR",
        table = "MY_SEQUENCES",
        pkColumnValue = "MEMBER_SEQ",
        allocationSize = 1) 
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,
            generator = "MEMBER_SEQ_GENERATOR")
    private Long id;
}
속성 설명 기본
name 식별자 생성기 이름 필수
table 키생성 테이블명 hibernate_sequence
pkColumnName 시퀸스 컬럼명 sequence_name
valueColumnName 시퀸스 값 컬럼명 next_val
pkColumnValue 키로 사용할 값 이름 엔티티이름
initialValue 초기 값, 마지막으로 생성된 값이 기준 0
allocationSize 시퀸스 한 번 호출에 증가하는 수(성능 최적화에 사용) 50
catalog, schema DB catalog, schema 이름  
uniqueConstraints(DDL) 유니크 제약 조건을 지정할 수 있음  

 

SEQUENCE: 데이터베이스의 시퀸스를 이용해서 식별키 생성(ORACLE 등 시퀸스 제공 DB)

@SequenceGenerator가 필요하다

@Entity
@SequenceGenerator(name = “MEMBER_SEQ_GENERATOR",
        sequenceName = “MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
        initialValue = 1,
        allocationSize = 1)
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
            generator = "MEMBER_SEQ_GENERATOR")
    private Long id;
}
name 식별자 생성기 이름(필수)
sequenceName DB에 등록되어 있는 시퀸스 이
initialValue DDL 생성 시에만 사용됨, 시퀀스 DDL을 생성할 때 처음 시작하는 수를 지정(Default:1)
allocationSize 시퀀스 한 번 호출에 증가하는 수(성능 최적화에 사용됨)
데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값 을 반드시 1로 설정해야 한다(Defaule: 50)
catalog, schema 데이터베이스 catalog, schema 이름

 

@Index

인덱스를 지정합니다

@Entity
@Table(name = "MEMBER", indexes={
        @Index(name = "idx__name__age", columnList = "name, age", unique = true)
})
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
}
name 인덱스 이름
columnList 인덱스를 지정할 컬럼들
unique 중복값 어용 여부(default:false[중복 허용])

 

@Enumerted

자바 enum타입을 매핑할 때 사용한다.

@Enumerated(EnumType.ORDINAL)
private Gender gender;

@Enumerated(EnumType.STRING)
private Gender gender;
Original enum순서를 DB에 저장(default)
STRING enum이름을 DB에 저장

 

@Temporal

날짜 타입을 매핑할 때 사용한다.

    @Temporal(TemporalType.DATE)
    Date regDate;

    @Temporal(TemporalType.TIME)
    Date regTime;

    @Temporal(TemporalType.TIMESTAMP)
    Date regTimestamp;
속성 설명 결과값 예시
DATE 날짜, DB date타입과 매핑 2021-07-27
TIME 시간, DB time타입과 매핑 12:30:50
TIMESTAMP 날짜와 시간 DB timestamp타입과 매핑 2021-07-27 12:30:50

 

@Lob

DB BLOB, CLOB타입과 매핑

  • 필드 타입이 문자열일 경우(CLOB 매핑)
  • 그외 자료형(BLOB 매핑)
    @Lob
    @Column(name="photo", columnDefinition = "BLOB")
    private String photo2;

    @Lob
    @Column(name="photo", columnDefinition = "CLOB")
    private byte[] photo1;

 

@Transient

해당 어노테이션이 명시된 필드는 DB에 저장하지도 조회하지도 않습니다.

  • 객체에 임시로 값을 보관하고 싶을 때 사용한다.

 

@CreationTimestamp

해당 어노테이션이 명시되면 INSERT 쿼리가 발생할 때, 현재 시간을 자동으로 만들어 저장합니다.

    @CreationTimestamp // INSERT 시 자동으로 값을 채워줌
    @Column(name = "created_at")
    private LocalDateTime createdAt = LocalDateTime.now();

 

@UpdateTimeStamp

해당 어노테이션이 명시되면 UPDATE쿼리가 발생할 때, 현재 시간을 자동으로 만들어 저장합니다.

    @UpdateTimestamp // UPDATE 시 자동으로 값을 채워줌
    @Column(name = "updated_at")
    private LocalDateTime updatedAt = LocalDateTime.now();

 

 

 

 

 

출처: https://velog.io/@c65621/JPA-annotation-%EC%A0%95%EB%A6%AC

 

JPA annotation 정리

1.@Entity해당 클래스를 테이블과 매핑한다고 JPA에게 알려줍니다.@Entity가 사용된 클래스를 엔티티 클래스라고 합니다.2.@Table엔티티 클래스에 매핑할 테이블 정보를 지정합니다.@Table 어노테이션을

velog.io

출처: https://yoonbing9.tistory.com/19

 

기본키 매핑 어노테이션 정리 @Id, @GeneratedValue, @SequenceGenerator, @TableGenerator

기본키 매핑은 직접할당과 자동할당 방법으로 나뉜다. @Id 직접할당. 엔티티의 기본키 필드에 값을 직접 넣어 등록한다. @GeneratedValue 자동할당 속성값 설명 대표DBMS strategy = GenerationType.IDENTITY 기

yoonbing9.tistory.com

 

'Spring' 카테고리의 다른 글

[Spring JPA] 단방향, 양방향 연관관계  (0) 2023.04.09
[Spring JPA] 엔티티 설계  (0) 2023.04.08
[SpringBoot] 검증2-Bean Validation  (0) 2023.04.03
[SpringBoot] 검증1-Validation  (0) 2023.03.30
[SpringBoot] 메시지 국제화  (0) 2023.03.28

스프링은 EJB의 무겁고 복잡한 플랫폼에서 벗어나 POJO기반의 경량화된 개발 환경을 제공하는 오픈소스 프레임워크

 

좋은 객체 지향 설계의 5가지 원칙(SOLID)

 

SOLID

-SRP : 단일 책임원칙(Single Responsibility Principle)

한클래스는 하나의 책임만 가져야 한다.

중요한 기준은 변경! 변경이 있을때 파급효과 적으면 단일 책침 원칙을 잘 따른것!

 

-OCP: 개방-폐쇄의 원칙(Open/Closed Principle)

소프트웨어 요소는 확장에는 열려있고 변경에는 닫혀있어야 한다.

다형성 활용!

 

-LSP: 리스코프 치환 원칙(Liskov Substitution Principle)

프로그램의 객체는 프로그램의 정확성을 깨지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다

 

-ISP: 인터페이스 분리 원칙(Interface Segregation Principle)

특정 클라이언트를 위한 여러개의 인터페이스가 범용 인터페이스 하나보다 낫다

인터페이스가 명확해지고, 대체 가능성이 높아진다

 

-DIP: 의존관계 역전 원칙(Dependency Inversion Principle)

구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻

역할에 의존하게 해야한다

 

-----------------------------------------------------------------------------------

 

Bean Validation은 특정한 구현체가 아니라 Bean Validation 2.0이라는 기술 표준이며 검증 애노테이션과 여러 인터페이스의 모음이다. 

 

Bean Validation 의존관계 추가

 

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-validation'

spring-boot-starter-validation을 의존관계를 추가하면 라이브러리가 추가된다.

 

@NotBlank 빈값 + 공백만 있는 경우를 허용하지 않는다.
@NotNull null을 허용하지 않는다.
@Range(min=?, max=?) ?에 숫자를 넣으며 범위 안의 값이어야 한다.
@Max(?) 최대 ? 까지만 허용된다.

 

스프링 부트는 spring-boot-starter-validation 라이브러리를 넣으면 자동으로 Bean Validator를 인지하고 스프링에 통합

 

스프링 부트는 자동으로 글로벌 Validator로 등록한다.

LocalValidatorFactoryBean 을 글로벌 Validator로 등록한다. 이 Validator는 @NotNull 같은 애노테이션을 보고 검증을 수행한다. 이렇게 글로벌 Validator가 적용되어 있기 때문에, @Valid , @Validated 만 적용하면 된다.

검증 오류가 발생하면, FieldError , ObjectError 를 생성해서 BindingResult 에 담아준다.

하지만 직접 글로벌 Validator를 직접 등록하면 스프링 부트는 Bean Validator를 글로벌 Validator 로 등록하지 않는다.

 

@Bean Validation은 등록할때와 수정할때 요구사항이 다를수있어 이를 해결하기 위해 groups, Form 전송 객체 분리 를 사용한다.

 

BeanValidation groups 기능 

 

인터페이스를 생성하고 어노테이션에 groups를 추가한다.

groups는 실무에서 잘 사용하지 않는다고 한다.

등록시 폼에서 전달하는 데이터가 Item 도메인 객체와 딱 맞지 않기 때문이다.

 

 SaveCheck 인터페이스

package hello.itemservice.domain.item;
    public interface SaveCheck {
    }

 

groups 사용

 @NotBlank(groups = {SaveCheck.class})
     private String itemName;

 

Controller에 SaveCheck Groups적용

@PostMapping("/add")
public String addItemV2(@Validated(SaveCheck.class) @ModelAttribute Item item,
BindingResult bindingResult, RedirectAttributes redirectAttributes) {
   //...
}

 

 

Form 전송 객체 분리

 

폼데이터 잔달에 Item 도메인 객체 사용

HTML Form -> Item -> Controller -> Item -> Repository

장점: Item 도메인 객체를 컨트롤러, 리포지토리 까지 직접 전달해서 중간에 Item을 만드는 과정이 없어서 간단하다.

단점: 간단한 경우에만 적용할 수 있다. 수정시 검증이 중복될 수 있고, groups를 사용해야 한다.

 

 

폼 데이터 전달을 위한 별도의 객체 사용

HTML Form -> ItemSaveForm -> Controller -> Item 생성 -> Repository

 

장점: 전송하는 폼 데이터가 복잡해도 거기에 맞춘 별도의 폼 객체를 사용해서 데이터를 전달 받을 수 있다. 보통 등록과, 수정용으로 별도의 폼 객체를 만들기 때문에 검증이 중복되지 않는다.

단점: 폼 데이터를 기반으로 컨트롤러에서 Item 객체를 생성하는 변환 과정이 추가된다.

 

 

저장과 수정의 폼을 분리해서 사용한다.

 

Controller 예시

public String addItem(@Validated @ModelAttribute("item") ItemSaveForm form,
BindingResult bindingResult, RedirectAttributes redirectAttributes) {
    //...
}

ItemSaveForm 를 받아오고 @ModelAttribute("item") 를 통해 item이란 명으로 바꿔준다. 

 

 

 

@ModelAttribute vs @RequestBody

HTTP 요청 파리미터를 처리하는 @ModelAttribute 는 각각의 필드 단위로 세밀하게 적용도어서 특정 필드에 타입이 맞지 않는 오류가 발생해도 나머지 필드는 정상 처리할 수 있다.

HttpMessageConverter는 @ModelAttribute와 다르게 각각의 필드 단위로 적용되는 것이 아니라 전체 객체 단위로 적용된다.

 

@ModelAttribute 는 필드 단위로 정교하게 바인딩이 적용된다. 특정 필드가 바인딩 되지 않아도 나머지 필드는 정상 바인딩 되고, Validator를 사용한 검증도 적용할 수 있다.

@RequestBody 는 HttpMessageConverter 단계에서 JSON 데이터를 객체로 변경하지 못하면 이후 단계 자체가 진행되지 않고 예외가 발생한다. 컨트롤러도 호출되지 않고, Validator도 적용할 수 없다.

 

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

 

'Spring' 카테고리의 다른 글

[Spring JPA] 엔티티 설계  (0) 2023.04.08
[Spring JPA] 어노테이션 정리  (0) 2023.04.05
[SpringBoot] 검증1-Validation  (0) 2023.03.30
[SpringBoot] 메시지 국제화  (0) 2023.03.28
스프링 JUnit5 어노테이션 정리  (0) 2022.12.06

스프링은 EJB의 무겁고 복잡한 플랫폼에서 벗어나 POJO기반의 경량화된 개발 환경을 제공하는 오픈소스 프레임워크

 

좋은 객체 지향 설계의 5가지 원칙(SOLID)

 

SOLID

-SRP : 단일 책임원칙(Single Responsibility Principle)

한클래스는 하나의 책임만 가져야 한다.

중요한 기준은 변경! 변경이 있을때 파급효과 적으면 단일 책침 원칙을 잘 따른것!

 

-OCP: 개방-폐쇄의 원칙(Open/Closed Principle)

소프트웨어 요소는 확장에는 열려있고 변경에는 닫혀있어야 한다.

다형성 활용!

 

-LSP: 리스코프 치환 원칙(Liskov Substitution Principle)

프로그램의 객체는 프로그램의 정확성을 깨지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다

 

-ISP: 인터페이스 분리 원칙(Interface Segregation Principle)

특정 클라이언트를 위한 여러개의 인터페이스가 범용 인터페이스 하나보다 낫다

인터페이스가 명확해지고, 대체 가능성이 높아진다

 

-DIP: 의존관계 역전 원칙(Dependency Inversion Principle)

구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻

역할에 의존하게 해야한다

 

-----------------------------------------------------------------------------------

 

컨트롤러의 중요한 역할중 하나는 HTTP 요청이 정상인지 검증하는 것

 

고객이 입력하지 않거나 검증 범위를 넘어서면 서버 검증 로직이 실패해야 하며 이럴경우 다시 폼을 보여주고 어떤값을 잘못 입력했는지 알려주어야한다.

 

thymeleaf 예시

 

글로벌 오류 처리

<div th:if="${#fields.hasGlobalErrors()}">

 <p class="field-error" th:each="err : ${#fields.globalErrors()}" th:text="${err}">전체 오류 메시지</p>

</div>

필드 오류 처리

<input type="text" id="itemName" th:field="*{itemName}"
 th:errorclass="field-error" class="form-control" placeholder="이름을 입력하세요">
<div class="field-error" th:errors="*{itemName}"> 상품명 오류 </div>

#fields: BindingResult를 활용해서 편리하게 검증 오류를 표현하는 기능 제공

th:errors: 해당 필드에 오류가 있을 경우 태그를 출력, th:if의 편의버전

th:errorclass: th:field에서 지정한 필드에 오류가 있을 경우  class정보 추가

 

복잡한 검증 로직을 별도로 분리하면 컨트롤러의 코드부분을 간소화 시킬 수 있으며 재사용도 가능하다.

 

검증로직 분리 예시

ItemValidation

package hello.itemservice.web.validation;
import hello.itemservice.domain.item.Item;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
    @Component
    public class ItemValidator implements Validator {

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

     @Override
     public void validate(Object target, Errors errors) {
        Item item = (Item) target;
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "itemName","required");

     if (item.getPrice() == null || item.getPrice() < 1000 ||item.getPrice() > 1000000) {
        errors.rejectValue("price", "range", new Object[]{1000, 1000000},null);
     }
        if (item.getQuantity() == null || item.getQuantity() > 10000) {
            errors.rejectValue("quantity", "max", new Object[]{9999}, null);
        }
        //특정 필드 예외가 아닌 전체 예외
        if (item.getPrice() != null && item.getQuantity() != null) {
            int resultPrice = item.getPrice() * item.getQuantity();
            if (resultPrice < 10000) {
                errors.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
            }
        }
     }
}

 

스프링에서 검증을 체계적으로 제공하기위한 인터페이스

public interface Validator {
    boolean supports(Class<?> clazz);
    void validate(Object target, Errors errors);
}

validate(Object target, Errors errors) : 검증 대상 객체와 BindingResult

 

 

아래는 ItemValidation 을 직접 호출하기 위한 컨트롤러

private final ItemValidator itemValidator;
@PostMapping("/add")
    public String addItemV5(@ModelAttribute Item item, BindingResult bindingResult,RedirectAttributes redirectAttributes) {
         itemValidator.validate(item, bindingResult);
        if (bindingResult.hasErrors()) {
             log.info("errors={}", bindingResult);
             return "validation/v2/addForm";
     }
       //성공 로직
       Item savedItem = itemRepository.save(item);
       redirectAttributes.addAttribute("itemId", savedItem.getId());
       redirectAttributes.addAttribute("status", true);
       return "redirect:/validation/v2/items/{itemId}";
}

private final ItemValidator itemValidator;를 통해 주입받아서 직접 호출한다.

 

하지만 WebDataBinder를 사용하면 스프링의 파라미터 바인딩 역할을 해주며 검증 기능도 내부에 포함한다.

WebDataBinder예시

@InitBinder
public void init(WebDataBinder dataBinder) {
 log.info("init binder {}", dataBinder);
 dataBinder.addValidators(itemValidator);
}

@Validation을 적용하여 validator를 직접 호출하는 부분이 사라진다.

Controller예시

@PostMapping("/add")
    public String addItemV5((@Validated @ModelAttribute Item item, BindingResult bindingResult,RedirectAttributes redirectAttributes) {
        if (bindingResult.hasErrors()) {
             log.info("errors={}", bindingResult);
             return "validation/v2/addForm";
     }
       //성공 로직
       Item savedItem = itemRepository.save(item);
       redirectAttributes.addAttribute("itemId", savedItem.getId());
       redirectAttributes.addAttribute("status", true);
       return "redirect:/validation/v2/items/{itemId}";
}

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

 

'Spring' 카테고리의 다른 글

[Spring JPA] 어노테이션 정리  (0) 2023.04.05
[SpringBoot] 검증2-Bean Validation  (0) 2023.04.03
[SpringBoot] 메시지 국제화  (0) 2023.03.28
스프링 JUnit5 어노테이션 정리  (0) 2022.12.06
스프링 Lombok 어노테이션  (0) 2022.12.06

메세지 파일(messages.properties)을 각 나라별로 별도로 관리하면 서비스를 국제화 가능하다.

 

영어는 messages_en.properties, 한글은 messages_kr.properties로 사용해서 개발한다.

 

Http의 accept-language헤더값을 사용하거나 사용자가 직접 언어를 선택하게 하고 쿠키등을 사용해서 처리한다.

기본적인 메시지 관리 기능은 MessageSource라는 인터페이스를 사용해서 MessageSource를 스프링빈으로 등록하지만 스프링 부트가 자동으로 스프링빈으로 등록해준다.

 

스프링 부트 메시지 소스 기본값

spring.messages.basename=messages

MessageSource를 스프링빈으로 등록하지 않고 별도의 설정 안할시 messages가 기본등록되며  messages_en.properties등이 자동으로 인식된다.

 

messages_kr.properties 예시

hello=안녕
hello.name=안녕 {0}

thymeleaf의 경우 예시

<h2 th:text="#{hello}">안녕</h2>
<h2 th:text="#{hello.name}">안녕</h2>

 

MessageSource 예시

public interface MessageSource {
    String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);

    String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;
}

메시지가 없는경우 NoSuchMessageException이 발생하며 defaultMessage를 사용하면 기본메시지를 사용할 수 있다.

locale부분은 Locale.ENGLISH를 작성해주면 messages_en을 찾아서 사용해준다.

 

Accept-Language를 사용하여 국제화를 진행하며 LocaleResolver를 사용하여 Locale방식을 선택할 수 있다.

스프링부트는 기본적으로  AcceptHeaderLocaleResolver를 사용한다.

 

public interface LocaleResolver {
  Locale resolveLocale(HttpServletRequest request);
  void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);
}

Locale변경시 LocaleResolver의 구현체를  변경해서 쿠키나 세션기반의 Locale선택 기능을 사용할 수 있다. 

예로는 고객이 직접 Locale을 선택하는것이다.

 

 

출처: 김영한의 스프링MVC2편

링크: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard

 

'Spring' 카테고리의 다른 글

[SpringBoot] 검증2-Bean Validation  (0) 2023.04.03
[SpringBoot] 검증1-Validation  (0) 2023.03.30
스프링 JUnit5 어노테이션 정리  (0) 2022.12.06
스프링 Lombok 어노테이션  (0) 2022.12.06
스프링 주요 어노테이션2  (1) 2022.12.06

+ Recent posts