🍀Spring Boot를 함께 공부하고 기록해나가는 공간입니다🍀

Overview

Spring Boot Study

Spring Boot Fighter

Since 2022.02.11

스터디 진행 상황

coding

Collaborator



조용은


김동윤


현수빈


윤영진


이진


정광수

Repository-Rule

  1. Github 가이드
  2. 미션 올리는 방법
  • Commit convention rule :
    • => peer/성명 : Chapter1 실습 코드 OR peer/성명 : Chapter1 내용 정리
    • (ex) peer/성명 : 첫번째 미션 수행 or peer/성명 : Chapter1 내용 정리

  • Branch naming convention :
    • => peer/성명
    • (ex) peer/김동윤

  • Pull Request rule :

    • Pull Request 제목 : [peer/성명] : 수행한 내용
      • (ex) [peer/성명] Chapter1 실습 코드 수행 or [peer/성명] Chapter1 내용 정리

    • Pull Request 내용 :
      • 자유롭게 적으셔도 좋고! (다만 자신이 어떤 부분 공부했는지 등에 대해서는 넣어주시면 다른 분들이 볼 때 편하실 것 같네욥 ㅎㅎ)
      • image
      • 위의 이미지에서 템플릿 복붙해서 작성해주시면 됩니다!



Table of Contents

First-Study

1) The Origin : 스프링부트 강의 (2/11 ~ 4/5)

  • [Chapter 1. Basics]
  • [Chapter 2. Spring Boot Basics (1)]
  • [Chapter 3. Spring Boot Basics (2)]
  • [Chapter 4. CRUD & Data (1)]
  • [Chapter 5. CRUD & Data (2)]
  • [Chapter 6. Spring Boot 기능활용 (1)]
  • [Chapter 7. Spring Boot 기능활용 (2)]
  • [Chapter 8. Spring Security]
  • [Chapter 9. Spring과 Middlewares]



Second-Study

2) 스프링 부트 쇼핑몰 프로젝트 with JPA (4/12 ~ )

스프링 부트 쇼핑몰 프로젝트 공부 계획 정리 노션

coding

  • [1장 개발 환경 구축]
  • [2장 Spring Data JPA]
  • [3장 Thymeleaf 학습하기]
  • [4장 스프링 시큐리티를 이용한 회원 가입 및 로그인]
  • [5장 연관 관계 매핑]
  • [6장 상품 등록 및 조회하기]
  • [7장 주문]
  • [8장 장바구니]

Reference

Comments
  • Cascade REMOVE vs. orphanRemoval = true

    Cascade REMOVE vs. orphanRemoval = true

    Cascade REMOVE vs. orphanRemoval = true

    Cascade REMOVE

    Cascade.REMOVE는 부모 엔티티가 삭제되면 자식 엔티티도 삭제된다. 즉, 부모가 자식의 삭제 생명 주기를 관리한다. 만약 CascadeType.PERSIST도 함께 사용하면, 부모가 자식의 전체 생명 주기를 관리하게 된다.

    해당 옵션의 경우에는 부모 엔티티가 자식 엔티티와의 관계를 제거해도 자식 엔티티는 삭제되지 않고 그대로 남아있다.

    Cascade.ALL = Cascade.PERSIST + Cascade.REMOVE

    @Entity
    @NoArgsConstructor
    @Setter @Getter
    public class Member {
    
        @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String name;
    
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn
        private Team team;
    }
    
    public class Team {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String name;
    
        @OneToMany(
                mappedBy = "team",
                fetch = FetchType.LAZY)
                cascade = CascadeType.ALL)
        private List<Member> members = new ArrayList<>();
    
        public void addMember(Member member) {
            members.add(member);
            member.setTeam(this);
        }
    }
    
    

    부모 엔티티를 삭제하는 경우

    @DataJpaTest
    class TeamTest {
    
        @Autowired
        private TeamRepository teamRepository;
    
        @Autowired
        private MemberRepository memberRepository;
    
        @DisplayName("CascadeType.REMOVE - 부모 엔티티(Team)을 삭제하는 경우")
        @Test
        void cascadeType_Remove_InCaseOfTeamRemoval() {
            // given
            Member member1 = new Member();
            Member member2 = new Member();
    
            Team team = new Team();
    
            team.addMember(member1);
            team.addMember(member2);
    
            teamRepository.save(team);
    
            // when
            teamRepository.delete(team);
    
            // then
            List<Team> teams = teamRepository.findAll();
            List<Member> members = memberRepository.findAll();
    
            assertEquals(0, teams.size());
            assertEquals(0, members.size());
        }
    }
    

    image

    Member에 대한 save를 해주지 않았음에도 불구하고 team에 속한 Member에 대한 insert sql이 실행됨을 볼 수 있다. -> CascadeType.PERSIST

    image

    Member에 대한 delete를 해주지 않았음에도 불구하고 team에 속해있던 member에 대한 delete sql이 실행됨을 볼 수 있다. -> CascadeType.REMOVE

    부모 엔티티에서 자식 엔티티를 제거하는 경우

    @DisplayName("CascadeType.REMOVE - 부모 엔티티(Team)에서 자식 엔티티(Member)를 제거하는 경우")
        @Test
        void cascadeType_Remove_InCaseOfMemberRemovalFromTeam() {
            // given
            Member member1 = new Member();
            Member member2 = new Member();
    
            Team team = new Team();
    
            team.addMember(member1);
            team.addMember(member2);
    
            teamRepository.save(team);
    
            // when
            team.getMembers().remove(0);
    
            // then
            List<Team> teams = teamRepository.findAll();
            List<Member> members = memberRepository.findAll();
    
            assertEquals(1, teams.size());
            assertEquals(2, members.size());
        }
    

    image

    부모 엔티티(=Team)에서 자식 엔티티(=Member)를 삭제했음에도 불구하고 delete sql이 실행되지 않았다. 영속성 전이 삭제 옵션은 부모와 자식의 관계가 끊어졌다 해서 자식을 삭제하지 않기 때문이다.

    orphanRemoval = true

    orphanRemoval = true 또한 부모 엔티티가 삭제되면 자식 엔티티도 삭제된다. 따라서 CascadeType.PERSIST를 함께 사용하면, 이때도 부모가 자식의 전체 생명 주기를 관리하게 된다.

    @Entity
    public class Team {
    
        @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String name;
    
        @OneToMany(
            mappedBy = "team",
            fetch = FetchType.LAZY,
            cascade = CascadeType.PERSIST,
            orphanRemoval = true
        )
        private List<Member> members = new ArrayList<>();
    }
    

    부모 엔티티를 삭제하는 경우

    @DisplayName("orphanRemoval = true - 부모 엔티티(Team)을 삭제하는 경우")
    @Test
    void orphanRemoval_True_InCaseOfTeamRemoval() {
    // given
    Member member1 = new Member();
    Member member2 = new Member();
    
            Team team = new Team();
    
            team.addMember(member1);
            team.addMember(member2);
    
            teamRepository.save(team);
    
            // when
            teamRepository.delete(team);
    
            // then
            List<Team> teams = teamRepository.findAll();
            List<Member> members = memberRepository.findAll();
    
            assertEquals(0, teams.size());
            assertEquals(0, members.size());
        }
    

    image

    CascadeType.REMOVE와 마찬가지로 부모 엔티티를 삭제함으로써 연관된 Member에 대한 delete sql이 실행된다.

    부모 엔티티에서 자식 엔티티를 제거하는 경우

    @DisplayName("orphanRemoval = true - 부모 엔티티(Team)에서 자식 엔티티(Member)를 제거하는 경우")
        @Test
        void orphanRemoval_True_InCaseOfMemberRemovalFromTeam() {
            // given
            Member member1 = new Member();
            Member member2 = new Member();
    
            Team team = new Team();
    
            team.addMember(member1);
            team.addMember(member2);
    
            teamRepository.save(team);
    
            // when
            team.getMembers().remove(0);
    
            // then
            List<Team> teams = teamRepository.findAll();
            List<Member> members = memberRepository.findAll();
    
            assertEquals(1, teams.size());
            assertEquals(1, members.size());
        }
    

    image

    이전과는 다르게 부모에서 삭제한 자식에 대해서 delete sql이 1번 실행된다. 고아 객체 옵션은 부모와 자식의 관계가 끊어지면 자식을 고아로 취급하고 자식을 삭제하기 때문이다.

    비교 결과

    • 부모 엔티티 삭제

      • CascadeType.REMOVE와 orphanRemoval = true는 부모 엔티티를 삭제하면 자식 엔티티도 삭제한다.
    • 부모 엔티티에서 자식 엔티티 제거

      • CascadeType.REMOVE는 자식 엔티티가 그대로 남아있는 반면, orphanRemoval = true는 자식 엔티티를 제거한다.

    주의점

    두 케이스 모두 자식 엔티티에 딱 하나의 부모 엔티티가 연관되어 있는 경우에만 사용해야 한다.

    예를 들어 Member(자식)을 Team(부모)도 알고 Parent(부모)도 알고 있다면, CascadeType.REMOVE 또는 orphanRemoval = true를 조심할 필요가 있다. 자식 엔티티를 삭제할 상황이 아닌데도 어느 한쪽의 부모 엔티티를 삭제했거나 부모 엔티티로부터 제거됐다고 자식이 삭제되는 불상사가 일어날 수 있기 때문이다.


    저는 완전 반대로 알고 있었네요.

    opened by yoon-youngjin 3
  • JPQL 컴파일 에러 잡는 여부 (feat . QueryDSL)

    JPQL 컴파일 에러 잡는 여부 (feat . QueryDSL)

    @yoon-youngjin 님께서 공유해주신 의문사항

    1) @Query (JPQL)

    image image

    • where -> wheree 로 오타를 내더라도 단순 typo 에러로만 나타남, 컴파일 에러는 X (run 누르면 돌아감)

    2) QueryDSL

    image image

    • compile 에러로 확실하게 나타내줌, run 누르면 안 돌아감
    opened by myway00 3
  • Cascade 개념 미숙지로 인해 발생했던 에러

    Cascade 개념 미숙지로 인해 발생했던 에러

    image

    하나의 라우트 프로덕트는 여러 멤버를 가질 수 있고 한 멤버는 여러 라우트 프로덕트에 속할 수 있는 다대다 관계 맵핑을 위한 엔티티를 작성했습니다. 다대다 맵핑을 위해서 전 다대다 테이블을 만들어 위의 erd 테이블과 같이 구조를 짰습니다.

    그래서 저는 라우트 프로덕트를 생성할 때, createrequest로 들어온 멤버들의 아이디를 검사하면서 멤버아이디 - 라우트프로덕트 아이디로 다대다테이블에 관계를 등록해주고 싶었습니다.

    그러나 라우트 프로덕트를 생성을 해도 라우트프로덕트멤버 (다대다 테이블)에 전혀 관계가 저장이 안되는 것입니다.

    에러 당시 RouteProduct.java 코드의 문제 부분입니다.

        @OneToMany(
                mappedBy = "routeProduct",
                orphanRemoval = true, 
                fetch = FetchType.LAZY
        )
        private List<RouteProductMember> members;
    

    그리고 생성자입니다

        public RouteProduct(
                Integer sequence,
    
                Integer origin_seq,
    
                String name,
                RouteType type,
                String comments,
                boolean passed,
                boolean rejected,
                boolean show,
                boolean disabled,
                List<Member> member,
                RouteOrdering newRoute
    
        ) {
            this.sequence = sequence;
    
            this.origin_seq = origin_seq;
    
    
            this.route_name = name;
    
            this.type = type;
            this.comments = comments;
            this.passed = passed;
            this.rejected = rejected;
            this.route_show = show;
            this.disabled = disabled;
            this.members =
                    member.stream().map(
                            r -> new RouteProductMember(
                                    this, r)
                    )
                    .collect(toList());
            this.routeOrdering = newRoute;
        }
    
    
            this.members =
                    member.stream().map(
                            r -> new RouteProductMember(
                                    this, r)
                    )
                    .collect(toList());
    

    위와 같이 new로만 해주어, 당연히 영속성 컨텍스트에 저장이 되지 않는 비영속 상태여서 저장이 되지 않는다는 문제를 파악했으나, 어떻게 이를 일일히 영속성 컨텍스트에 반영을 해주냐..? 라는 깊은 고민 속에서 몇 시간을 날렸습니다. ^^ (매번 저 친구를 빼서 하나하나 save() 해줘야 하나...분명 다른 방법이 있을 것인데 하며 말이죠~,,)

    그러던 중 구글링을 통해 cascade 속성이 단순히 삭제 때만 적용되는 아이가 아닌, 부모 객체가 저장될 때 자식 객체도 저장해준다는 것을 알게 되었습니다. (저는 당시 cascade 개념을 전혀 알지 못해서 단순 삭제 때만 적용되는 속성인 줄로 알고 있었습니다. )

    따라서 부모가 저장될 때 자식이 저장되는 ALL 옵션을 주어 라우트 프로덕트가 생성이 될 때 이의 자식인 라우트프로덕트멤버 에도 관계가 생성되게 해주었더니, 말끔히 정보가 잘 저장되는 것이었습니다.

        @OneToMany(
                mappedBy = "routeProduct",
                cascade = CascadeType.ALL,//이거 없애면 안돼 동윤아...
                orphanRemoval = true, 
                fetch = FetchType.LAZY
        )
        private List<RouteProductMember> members;
    

    cascade 속성이 삭제할 때만 연관이 있겠지, 하고 전혀 이에 대한 학습을 하지 않고 코드를 짰다가 이 문제로 많은 시간을 허비하고 나서 스프링부트의 개념을 단단히 다져야 하는 것에 대한 중요성과 필요성을 다시 한번 느꼈었습니다.

    다대다를 위한 테이블 생성 시 때 꼭 cascade 유무를 확인해봅시다!

    opened by myway00 1
  • 패키지 구조(도메인형 구조 vs. 계층형 구조)

    패키지 구조(도메인형 구조 vs. 계층형 구조)

    패키지 구조는 크게 계층형, 도메인형 2가지 유형

    계층형

    └── src
        ├── main
        │   ├── java
        │   │   └── com
        │   │       └── example
        │   │           └── demo
        │   │               ├── DemoApplication.java
        │   │               ├── config
        │   │               ├── controller
        │   │               ├── dao
        │   │               ├── domain
        │   │               ├── exception
        │   │               └── service
        │   └── resources
        │       └── application.properties
    
    • 계층형 구조는 각 계층을 대표하는 디렉토리를 기준으로 코들들이 구성
    • 계층형 구조의 장점은 해당 프로젝트에 이해가 상대적으로 낮아도 전체적인 구조를 빠르게 파악할 수 있다.
    • 계층형 구조의 단점은 디렉토리에 클래스들이 너무 많이 모이게 된다.

    도메인형

    └── src
        ├── main
        │   ├── java
        │   │   └── com
        │   │       └── example
        │   │           └── demo
        │   │               ├── DemoApplication.java
        │   │               ├── coupon
        │   │               │   ├── controller
        │   │               │   ├── domain
        │   │               │   ├── exception
        │   │               │   ├── repository
        │   │               │   └── service
        │   │               ├── member
        │   │               │   ├── controller
        │   │               │   ├── domain
        │   │               │   ├── exception
        │   │               │   ├── repository
        │   │               │   └── service
        │   │               └── order
        │   │                   ├── controller
        │   │                   ├── domain
        │   │                   ├── exception
        │   │                   ├── repository
        │   │                   └── service
        │   └── resources
        │       └── application.properties
    
    
    • 도메인 디렉토리 기준으로 코드를 구성
    • 도메인 구조의 장점은 관련된 코드들이 응집해 있다.
    • 도메인 구조의 단점은 프로젝트의 이해도가 낮을 경우 전체적인 구조를 파악하기 어렵다.

    현재 책에서 제공하는 코드는 계층형 패키지 구조로 되어있는데, 실제 현업에서는 도메인형 구조를 많이 선호한다고 합니다. 계층형 패키지 구조를 사용하면 프로젝트의 크기가 커졌을 경우에 Controller 패키지, Service 패키지에 너무 많은 클래스들이 모이게 되어서 불편하다고 하네요.

    opened by yoon-youngjin 1
  • QueryDSL-build.gradle 설정

    QueryDSL-build.gradle 설정

    @gdakate

    plugins {
     id 'org.springframework.boot' version ‘2.2.2.RELEASE'
     id 'io.spring.dependency-management' version '1.0.8.RELEASE'
     //querydsl 추가
     id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
     id 'java'
    }
    
    group = 'study'
    version = '0.0.1-SNAPSHOT'
    sourceCompatibility = '1.8'
    configurations {
     compileOnly {
     extendsFrom annotationProcessor
     }
    }
    repositories {
     mavenCentral()
    }
    dependencies {
     implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
     implementation 'org.springframework.boot:spring-boot-starter-web'
     //querydsl 추가
     implementation 'com.querydsl:querydsl-jpa'
     compileOnly 'org.projectlombok:lombok'
     runtimeOnly 'com.h2database:h2'
     annotationProcessor 'org.projectlombok:lombok'
     testImplementation('org.springframework.boot:spring-boot-starter-test') {
     exclude group: ‘org.junit.vintage’, module: ‘junit-vintage-engine'
     }
    }
    test {
     useJUnitPlatform()
    }
    //querydsl 추가 시작
    def querydslDir = "$buildDir/generated/querydsl"
    querydsl {
     jpa = true
    
    querydslSourcesDir = querydslDir
    }
    sourceSets {
     main.java.srcDir querydslDir
    }
    configurations {
     querydsl.extendsFrom compileClasspath
    }
    compileQuerydsl {
     options.annotationProcessorPath = configurations.querydsl
    }
    //querydsl 추가 끝
    
    
    opened by myway00 1
  • Spring Boot Devtools - Applitacion 자동 재시작(Automatic restart 설정)

    Spring Boot Devtools - Applitacion 자동 재시작(Automatic restart 설정)

    P.115 Springboot Devtools 관련 compiler.automake.allow.when.app.running 항목이 registry 없는 경우 해결 방법 공유드립니다! IDEA 2021.2버전 부터 해당 설정이 레지스트리에서 Advanced Setting으로 옮겨진 것으로 확인됩니다

    1. Build Project automatically 체크 image

    File - Settings - Build, Execution, Deployment - Complier - Build Project automatically 체크

    1. Allow auto-make to start even if developed application is currently running 체크 image

    윈도우 File - Setting - Advanced Settings - Allow auto-make to start even if developed application is currently running 체크

    (맥은 Preference)

    1,2 두가지 설정을 완료하시면 됩니다!

    opened by hehahihang 1
  • @Enumerated - EnumType.STRING

    @Enumerated - EnumType.STRING

    @Enumerated을 이용할 때 반드시 EnumType은 String을 사용하자!

    • @Enumerated 애너테이션에는 두 가지 EnumType이 존재
      • EnumType.ORDINAL: enum 순서 값을 DB에 저장
      • EnumType.STRING: enum 이름을 DB에 저장
    enum OrderStatus{
        READY, FINISH
            }
    
    • 이와 같이 OrderStatus의 enum타입이 명시된 경우라고 가정하자.
    @Enumerated(EnumType.ORDINAL)
    private OrderStatus orderstatus;
    
    • EnumType.ORDINAL인 경우 DB에 READY는 1, FINISH는 2인 상태로 저장
    @Enumerated(EnumType.STRING)
    private OrderStatus orderstatus;
    
    • EnumType.STRING인 경우 DB에 READY, FINISH로 저장

    • 만약 OrderStatus에 필드가 추가 된다면?

    enum OrderStatus{
        READY, DELIVERY,  FINISH
            }
    
    • EnumType.ORDINAL인 경우에 기존 DB의 OrderStatus 속성값의 2는 DELEVERY가 되어 큰 문제가 발생
    • @Enumerated의 기본 값은 EnumType.ORDINAL이므로 반드시 EnumType.STRING으로 사용하자!
    opened by yoon-youngjin 1
  • 윈도우 -Dspring 코드

    윈도우 -Dspring 코드

    지금 chapter 6 을 듣고 있던 중, Spring Boot Properties 강의 30분 경에서 강사님이 써주신

    java -jar -Dspring.profiles.active=local -jar build/libs/jpa-0.0.1-SNAPSHOT.jar
    

    이 코드를 터미널에 치면 자꾸 클래스를 찾을 수 없다고 에러가 떴습니다. 이와 같은 사례가 있나 검색해보니깐 https://stackoverflow.com/questions/46614338/issue-run-spring-boot-profiles-jar 요론 스택오버플로우를 발견했어요~

    이걸 참고해서 코드를 아래와 같이 수정한 뒤에

    java -jar "-Dspring.profiles.active=local" -jar build/libs/jpa-0.0.1-SNAPSHOT.jar
    

    실행을 하니깐 작동하더라구요 ! 만약 저와 같은 에러가 일어나면 한번 시도해보시면 좋을 것 같아서 이슈에 남깁니다 ㅎㅎ 모두 열공하세요!

    opened by myway00 1
  • Fetch Join

    Fetch Join

    Fetch Join

    현재 상황

    image

    상품아이디(Item->id), 상품명(Item->itemNm), 상태(Item->itemSellStatus), 등록자(Item->createdBy), 등록일(Item->regTime)

    모두 Item에서 가져올 수 있는 데이터 -> Item과 연관된 Member를 조회할 필요가 없음

    따라서 지연로딩으로 설정하였기 때문에 SELELT SQL에는 item에 관한 쿼리문만 존재한다.

    image

    **등록자 이름(Item -> Member -> name)**과 같은 Item에 연관된 필드에서 데이터를 조회해야 하는 경우에는?

    변경된 상황

    image

    image

    지연로딩으로 설정하였기 때문에 SELECT SQL에 Item과 연관된 Member를 조회하기 위한 쿼리문이 하나 더 생긴걸 확인할 수 있다.

    만약 연관된 데이터가 굉장히 많고 모든 연관된 데이터에서 데이터를 조회해야 한다면 연관된 데이터의 개수만큼 SELECT SQL이 추가될 것이다.

    Fetch Join 적용하기

    public class ManageItemRepositoryImpl implements ManageItemRepository {
        
       ...
        @Override
        public Page<Item> getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable) {
    
            List<Item> content = queryFactory
                    .selectFrom(QItem.item)
                    /**
                     * fetch join 적용 
                     */
                    .join(QItem.item.member, QMember.member).fetchJoin() 
                    .where(regDtsAfter(itemSearchDto.getSearchDateType()),
                            searchSellStatusEq(itemSearchDto.getSearchSellStatus()),
                            searchByLike(itemSearchDto.getSearchBy(),
                                    itemSearchDto.getSearchQuery()))
                    .orderBy(QItem.item.id.desc())
                    .offset(pageable.getOffset())
                    .limit(pageable.getPageSize())
                    .fetch();
    
            int totalSize = queryFactory
                    .selectFrom(QItem.item)
                    .where(regDtsAfter(itemSearchDto.getSearchDateType()),
                            searchSellStatusEq(itemSearchDto.getSearchSellStatus()),
                            searchByLike(itemSearchDto.getSearchBy(),
                                    itemSearchDto.getSearchQuery()))
                    .fetch().size();
    
            return new PageImpl<>(content, pageable, totalSize);
        }
    }
    

    image

    QueryDsl에 fetch join을 적용하고 실행하면 하나의 SELECT SQL안에 inner join을 통해서 Item과 연관된 Member를 한 번에 가져옴을 볼 수 있다.

    opened by yoon-youngjin 0
  • @Embedded

    @Embedded

    @Embedded

    현재 코드

    
    @Entity
    @Table(name = "member")
    @Getter
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    @ToString
    public class Member {
    
        @Id
        @Column(name = "member_id")
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String name;
    
        private String email;
    
        private String password;
      ...
    }
    

    만약 Email의 Host부분, Domain부분을 가져와야 하는 경우, xxxxService에서 해당 일을 대신해서 처리하게 된다.

    만약 Password의 만기기간, 중복 횟수 처리, ... 기능이 있는 경우에도 마찬가지로 xxxxService에서 해당 일을 하게 된다. -> 책임을 미루게 된다.

    @Embedded를 사용한 코드

    
    @Entity
    @Table(name = "member")
    @Getter
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    @ToString
    public class Member extends BaseTimeEntity {
    
        @Id
        @Column(name = "member_id")
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String name;
    
        @Embedded
        private Email email;
    
        @Embedded
        private Password password;
        ...
    }
    
    @Embeddable
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    @Getter
    @JsonIgnoreProperties(value = {"id","host"})
    @ToString
    public class Email {
    
        @javax.validation.constraints.Email(message = "NOT_VALID_EMAIL")
        @Column(name = "email", nullable = false, unique = true, length = 50)
        private String value;
    
        @Builder
        public Email(String value) {
            this.value = value;
        }
    
        public static Email of(String email) {
            return new Email(email);
        }
    
        public String getHost() {
            int index = value.indexOf("@");
            return value.substring(index);
        }
    
        public String getId() {
            int index = value.indexOf("@");
            return value.substring(0, index);
        }
    }
    
    @Embeddable
    @Getter
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    public class Password {
    
        @Column(name = "password", nullable = false)
        private String value;
    
        @Column(name = "password_expiration_date")
        private LocalDateTime expirationDate;
    
        @Column(name = "password_failed_count", nullable = false)
        private int failedCount;
    
        @Column(name = "password_ttl")
        private long ttl;
    
        @Builder
        public Password(final String value) {
            this.ttl = 1209_604; // 1209_604 is 14 days
            this.value = encodePassword(value);
            this.expirationDate = extendExpirationDate();
        }
    
        public boolean isMatched(final String rawPassword) {
            if (failedCount >= 5)
                throw new PasswordFailedExceededException();
    
            final boolean matches = isMatches(rawPassword);
            updateFailedCount(matches);
            return matches;
        }
    
        public void changePassword(final String newPassword, final String oldPassword) {
            if (isMatched(oldPassword)) {
                value = encodePassword(newPassword);
                extendExpirationDate();
            }
        }
    
        public boolean isExpiration() {
            return LocalDateTime.now().isAfter(expirationDate);
        }
    
        private LocalDateTime extendExpirationDate() {
            return LocalDateTime.now().plusSeconds(ttl);
        }
    
        private String encodePassword(final String password) {
            return new BCryptPasswordEncoder().encode(password);
        }
    
        private void updateFailedCount(boolean matches) {
            if (matches)
                resetFailedCount();
            else
                increaseFailCount();
        }
    
        private void resetFailedCount() {
            this.failedCount = 0;
        }
    
        private void increaseFailCount() {
            this.failedCount++;
        }
    
        private boolean isMatches(String rawPassword) {
            return new BCryptPasswordEncoder().matches(rawPassword, this.value);
        }
    
    }
    

    Email, Password 객체를 만들어서 관리하는 것이 항상 중요하다고 말하는 객체지향적인 프로그래밍을 의미하는 것 같다.

    opened by yoon-youngjin 0
  • Principal vs. @AuthenticationPrincipal

    Principal vs. @AuthenticationPrincipal

    Principal vs. @AuthenticationPrincipal

    Principal

    로그인한 사용자의 정보를 파라미터로 받고 싶을 때 기존에는 다음과 같이 Principal 객체로 받아서 사용

    @GetMapping("/")
    public String index(Model model,Principal principal){
            ...
            }
    

    위의 Principal 객체는 Spring Security가 제공하는 객체가 아닌, 자바에 정의되어있는 Principal 객체를 바인딩 해주는 것이라 사용할 수 있는 메소드가 getName() 밖에 없다.

    @AuthenticationPrincipal

    @PostMapping("/new")
    private String itemNew(
    @Valid @ModelAttribute("insertItemDto") InsertItemDto dto,
            BindingResult bindingResult,
    @AuthenticationPrincipal UserDetailsImpl userDetails,
            RedirectAttributes redirectAttributes
            ){
            ...
            }
    

    실제 SecurityContextHolder 내 Principal(SecurityContextHolder.getContext().getAuthentication().getPrincipal())를 가져오기 위해서 @AuthenticationPrincipal를 사용한다.

    opened by yoon-youngjin 0
Owner
null
该仓库中主要是 Spring Boot 的入门学习教程以及一些常用的 Spring Boot 实战项目教程,包括 Spring Boot 使用的各种示例代码,同时也包括一些实战项目的项目源码和效果展示,实战项目包括基本的 web 开发以及目前大家普遍使用的线上博客项目/企业大型商城系统/前后端分离实践项目等,摆脱各种 hello world 入门案例的束缚,真正的掌握 Spring Boot 开发。

Spring Boot Projects 该仓库中主要是 Spring Boot 的入门学习教程以及一些常用的 Spring Boot 实战项目教程,包括 Spring Boot 使用的各种示例代码,同时也包括一些实战项目的项目源码和效果展示,实战项目包括基本的 web 开发以及目前大家普遍使用的前

十三 4.5k Dec 30, 2022
一个涵盖六个专栏:Spring Boot 2.X、Spring Cloud、Spring Cloud Alibaba、Dubbo、分布式消息队列、分布式事务的仓库。希望胖友小手一抖,右上角来个 Star,感恩 1024

友情提示:因为提供了 50000+ 行示例代码,所以艿艿默认注释了所有 Maven Module。 胖友可以根据自己的需要,修改 pom.xml 即可。 一个涵盖六个主流技术栈的正经仓库: 《Spring Boot 专栏》 《Spring Cloud Alibaba 专栏》 《Spring Clou

芋道源码 15.7k Dec 31, 2022
参考 DDD/Clean Architecture 设计理念,整合 Spring Boot/Spring Security/Mybatis Plus/Vavr 的 Spring Realworld 应用案例

Demo · 更多项目 · 参考资料 ms-spring-ddd-examples Unified Domain-driven Layered Architecture for MicroService Apps,试图探索一套切实可行的应用架构规范,可以复制、可以理解、可以落地、可以控制复杂性的指导

王下邀月熊 19 Sep 23, 2022
Spring Kurulumundan Başlayarak, Spring IOC ve Dependency Injection, Hibernate, Maven ve Spring Boot Konularına Giriş Yapıyoruz.

Spring Tutorial for Beginners File Directory Apache Tomcat Apache Tomcat - Eclipse Bağlantısı Spring Paketlerinin İndirilmesi ve Projeye Entegrasyonu

İbrahim Can Erdoğan 11 Apr 11, 2022
Spring Boot JdbcTemplate example with SQL Server: CRUD Rest API using Spring Data JDBC, Spring Web MVC

Spring Boot JdbcTemplate example with SQL Server: Build CRUD Rest API Build a Spring Boot CRUD Rest API example that uses Spring Data Jdbc to make CRU

null 7 Dec 20, 2022
Spring Boot & MongoDB Login and Registration example with JWT, Spring Security, Spring Data MongoDB

Spring Boot Login and Registration example with MongoDB Build a Spring Boot Auth with HttpOnly Cookie, JWT, Spring Security and Spring Data MongoDB. Y

null 15 Dec 30, 2022
Spring Boot Login and Registration example with MySQL, JWT, Rest Api - Spring Boot Spring Security Login example

Spring Boot Login example with Spring Security, MySQL and JWT Appropriate Flow for User Login and Registration with JWT Spring Boot Rest Api Architect

null 58 Jan 5, 2023
Demo microservice architecture with Spring ,Spring Cloud Gateway , Spring Cloud config server , Eureuka , keycloak and Docker.

spring-microservice Demo microservice architecture with Spring ,Spring Cloud Gateway , Spring Cloud config server , Eureuka , keycloak and Docker. Arc

null 4 Sep 13, 2022
Spring Boot JWT Authentication example with Spring Security & Spring Data JPA

Spring Boot JWT Authentication example with Spring Security & Spring Data JPA

null 1 Jan 26, 2022
Spring REST service built with Spring initializr and Spring Data.

Spring REST Service Generated with start.spring.io, using Spring Data. Documented using Spring REST Docs. Spring Initializr - Generate new Spring Rest

null 1 Jan 28, 2022
mall-swarm是一套微服务商城系统,采用了 Spring Cloud Hoxton & Alibaba、Spring Boot 2.3、Oauth2、MyBatis、Docker、Elasticsearch、Kubernetes等核心技术,同时提供了基于Vue的管理后台方便快速搭建系统。mall-swarm在电商业务的基础集成了注册中心、配置中心、监控中心、网关等系统功能。文档齐全,附带全套Spring Cloud教程。

mall-swarm 友情提示 快速体验项目:在线访问地址。 全套学习教程:《mall学习教程》。 Spring Cloud全套教程:《SpringCloud学习教程》。 专属学习路线:学习不走弯路,整理了套非常不错的《mall专属学习路线》。 项目交流:想要加群交流项目的朋友,可以加入mall项目

macro 9.7k Jan 3, 2023
芋道 mall 商城,基于微服务的思想,构建在 B2C 电商场景下的项目实战。核心技术栈,是 Spring Boot + Dubbo 。未来,会重构成 Spring Cloud Alibaba 。

[toc] 友情提示:近期在升级和优化该项目,建议先 Star 本项目。主要在做几个事情: 1、微服务技术选型以 Spring Cloud Alibaba 为中心。 2、修改项目分层,并合并部分服务,简化整体服务的复杂性。 3、将管理后台从 React 重构到 Vue 框架。 交流群:传送门 前言

芋道源码 7k Jan 6, 2023
about learning Spring Boot via examples. Spring Boot 教程、技术栈示例代码,快速简单上手教程。

Spring Boot 学习示例 Spring Boot 使用的各种示例,以最简单、最实用为标准,此开源项目中的每个示例都以最小依赖,最简单为标准,帮助初学者快速掌握 Spring Boot 各组件的使用。 Spring Boot 中文索引 | Spring Cloud学习示例代码 | Spring

纯洁的微笑 28.3k Jan 1, 2023
spring boot 实践学习案例,是 spring boot 初学者及核心技术巩固的最佳实践。另外写博客,用 OpenWrite。

推荐工具: 微信公众号 Markdown 编辑器 - OpenWrite:Markdown 微信编辑器是一款专业强大的微信公众平台在线编辑排版工具,提供手机预览功能,让用户在微信图文 、文章、内容排版、文本编辑、素材编辑上更加方便。 - 更多介绍 博客群发平台 一、支持泥瓦匠 Spring Boot

泥瓦匠BYSocket 15.2k Jan 5, 2023
Spring Boot基础教程,Spring Boot 2.x版本连载中!!!

Spring Boot基础教程 本项目内容为《Spring Boot基础教程》的程序样例。 专题目标:打造全网内容最全,比收费教程更好的Spring Boot免费教程! 加入社群:如果你正在学习Spring Boot,不妨加入我们的Spring技术交流群,一起成长! 如何支持: 关注我的公众号”程序

程序猿DD 14.9k Jan 6, 2023
Not only Spring Boot but also important knowledge of Spring(不只是SpringBoot还有Spring重要知识点)

在线阅读 : https://snailclimb.gitee.io/springboot-guide (上面的地址访问速度缓慢的建议使用这个路径访问) 重要知识点 基础 Spring Boot 介绍 第一个 Hello World 第一个 RestFul Web 服务 Spring 如何优雅读取配

Guide哥 4.7k Jan 3, 2023
Spring-Boot-Plus is a easy-to-use, high-speed, high-efficient,feature-rich, open source spring boot scaffolding

Everyone can develop projects independently, quickly and efficiently! What is spring-boot-plus? A easy-to-use, high-speed, high-efficient, feature-ric

geekidea 2.3k Dec 31, 2022
开源论坛、问答系统,现有功能提问、回复、通知、最新、最热、消除零回复功能。功能持续更新中…… 技术栈 Spring、Spring Boot、MyBatis、MySQL/H2、Bootstrap

码问社区 在线演示地址 www.mawen.co 功能列表 开源论坛、问答系统,现有功能提问、回复、通知、最新、最热、消除零回复功能。功能持续更新中…… 技术栈 技术 链接 Spring Boot http://projects.spring.io/spring-boot/#quick-start

小匠 2.3k Dec 30, 2022