JPA를 활용한 스케줄러 시작하기

말하는 감자·2025년 4월 1일

내일배움캠프

목록 보기
33/73

이번 과제는 저번과제에서 여러 유용한 JPA기능으 ㄹ이용해보고
저번과제에서 부족했던 부분을 과제 피드백 받은 토대로 개선해보고자 한다.

ERD 및 흐름도

일단 필터에서는 로그인 되어있는지(세션여부)만 참고할 계획이다.

솔직히 기능들은 과제 1이랑 비슷하다고 생각해서 기초만 일단 ㅇㅇ..
ERD도 직접 테이블작성하고 하는게 없어서 일단 만들어놓기만 했다.

필요한 기능은 이정도로하고 ,
명세서 작성전에 저번해 했던 표를 고쳐서 쓰려고했는데
너무 네모가 너무많아서 글씨가 잘 안보인다..
어떻게 고칠지 고민중임

(여기서 더 추가가 되어야한다..)



필수

🔎JPA Auditing (감사 기능)


과제 요구사항을 보면 JPA Auditing 활용이 있다.
JPA Auditing은 엔티티의 생성 및 수정 정보를 자동으로 기록하는 기능이다.



✂️주요 기능

1. 엔티티 생성 시간 자동 저장 (@CreatedDate)
2. 엔티티 수정 시간 자동 저장 (@LastModifiedDate)
3. 엔티티 생성자 자동 저장 (@CreatedBy)
4. 엔티티 수정자 자동 저장 (@LastModifiedBy)



🛠️JPA Auditing 설정

1️⃣ @EnableJpaAuditing 활성화

~Application이 있는 클래스에서


@EnableJpaAuditing 추가

2️⃣ Auditing 적용할 엔티티

<조건>

  • 변수명은 자유롭게 설정 가능
  • 필드 타입이 LocalDateTime, Instant, Date날짜 타입이어야 함
  • Auditing 기능이 활성화되어 있어야 함 (@EnableJpaAuditing)
  • @EntityListeners(AuditingEntityListener.class) 설정 필요

나는 이렇게 해줬따
요구사항을 보다싶이 사용자와 할일 두곳에서 해당 어노테이션이 필요하기 때문에 실습에서 했던거 참고해서 extends 해줄거다.



사용 어노테이션

@CreatedDate : 엔티티가 생성될 때 날짜 자동 저장
@LastModifiedDate : 엔티티가 수정될 때 날짜 자동 저장
@CreatedBy : 엔티티 생성한 사용자 자동 저장
@LastModifiedBy : 엔티티 수정한 사용자 자동 저장
@EnableJpaAuditing : Auditing 기능 활성화






필요 Entity 작성

모두가 필수값으로 빈값 들어오면 다 오류뱉을 예정이다.
마찬가지로 과제 실습했던거 + 이전에 했떤거 참고...

📌점검할 것 @@@@@


스키마 이름, mysql 아이디 비밀번호 자기가 설정한거 맞는지 다시체크하기 ㅠㅠ

User


이런식으로 만들어줬고, ERD대로 잘 만들어진다.
나머지 스케줄러도 후다닥



Scheduler - 단방향 연관관계 설정

사용 어노테이션

@ManyToOne : 한명의 유저가 다수의 스케줄을 생성할 수 있다.
@JoinColumn : SQL의 JOIN ON 에 들어가는 키값!

스케줄러같은경우는 ERD에서도 표시해놨지만 연관관계 설정이 있다.
한명의 유저가 다수의 스케줄을 생성할 수 있어서
@ManyToOne 어노테이션 사용해줬다.

다대일 연관관계에서 가장 많이 사용되는 방법으로,
외래키가되는 uesrId의 Long값 대신에 해당 User객체를 찾아서 저장해준다.

하지만@JoinColumn 어노테이션으로 PK를 가져와서 DB에 저장할 때 FK 컬럼으로 만들어줄 수 있는데,
ㅇ위에 사진을 보면 uesr 테이블의 id값을 uesr_id로 받아왔다.
어노테이션의 파라미터를 name="user_id"로 해줬기 때문임!!

자바입장에서는 객체를 가져오는거고 DB입장에서는 FK의 키값을 저장하는게 된다.






그 뒤로 뭐 과제 랑 실습 따라가다보면서 느낀건데
이번에는 용도에 맞게 dto를 여러개로 나누는 것이 좋겠다는 생각이 들었다.

일단 유저서비스가 제공되어야할 것들을 정리햇음..
오늘 이거까지만해도 성공적일듯






다시돌아와서!!

회원가입 정보 처리하기

여기서 해야할 것은
1️⃣ 이메일 형식 지정하기
2️⃣비밀번호 암호화

비밀번호 암호화같은 경우는 도전과제이긴한데
지금 만드는김에 할 수 잇으면 해보려고 한다!!!
못하게되면 뒤에 (보류)가 적힐 것이다..



1️⃣ 이메일 형식 지정하기 - @Email

이건 저번 과제에서도 정규표현식으로 했었는데, 스프링에서 제공하는 어노테이션으로도 이메일 형식을 지정할 수 있다고 해서 한번 해보겠다!!


< 기존코드 >

//이메일 형식 소문자, 대문자, 숫자 _.- << 이게 1글자 이상 @ 소문자, 대문자, 숫자 1자 이상이고 .으로 끝나면 안됨.
        if(!email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$")) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid email format.");
        }

먼저 @validation 을 사용하려면 build.gradle에서 의존성을 추가해줘야 한다!
implementation 'org.springframework.boot:spring-boot-starter-validation' 요거 복사

필요한 곳에 @Email을 붙이고 컨트롤러에서 사용할 수 있다.

검증 필요한 데이터가잇으면 DTO에서 validation 넣어주고
값을 받아올 때 자동으로 검증해준다고 한다!!

🚨 주의할 점

  • @Email은 널(null) 값을 허용하므로, 반드시 @NotBlank 또는 @NotNull과 같이 써야 함.
  • 이메일 형식은 검증하지만, 실제 사용 가능한 이메일인지는 체크하지 않음.

validation 받은 김에 필요한 곳 여기저기에 다 붙여줬다 ^^




2️⃣ 비밀번호 암호화

암호화부분은.........................따로빼서 글 작성해야할 것 같다.
일단 검색하면 제일많이 SHA-256이 나오는것 같은데 이건 공격자가 비밀번호를 유추할 수 잇기 때문에 예전 프로젝트에서 사용했던 BCrypt 다시 써보겠다.

그때는 php라서 자바에서도 이런게 잇겠지!!하고 찾아보니 있다.

참고할만한 블로그 : https://st-lab.tistory.com/100

BCryptPasswordEncoder

스프링 시큐리티(Spring Seurity) 프레임워크에서 제공하는 클래스 중 하나로 비밀번호를 암호화하는 데 사용할 수 있는 메서드를 가진 클래스다.

보면 필요한 정보들이 다있다.

encode로 암호화하고, matches로 맞는지 확인하면 된다!!
이거두개만쓸거다..

암호화하느 방식이다.
비밀번호에 복호화 못하게 솔트 넣어주고 (=> 같은 비번이라도 다른 해시값 나옴) 해시를 여러번 돌려서 더 알아보기 힘들게 함!!



먼저 필요한 정보 import해줘야함.

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

의존성 추가

implementation 'org.springframework.security:spring-security-core:5.7.0'
이거 build.gradle에 추가

<암호화하기> - encode(java.lang.CharSequence rawPassword)

  • 패스워드를 암호화해주는 메서드
  • 매개변수는 java.lang.CharSequence타입의 데이터를 입력
    (String, StringBuffer, StringBuilder 중 ㅇㅇ)
  • 반환 타입 : String
  • 똑같은 비밀번호를 해당 메서드를 통하여 인코딩하더라도 매번 다른 인코딩 된 문자열을 반환
    // 비밀번호 암호화
    public static String hashPassword(String password) {
        return encoder.encode(password);
    }

<비교하기> - matchers(java.lang.CharSequence rawPassword, java.lang.String encodePassword)

  • 제출된 인코딩 되지 않은 패스워드(일치 여부를 확인하고자 하는 패스워드)와 인코딩 된 패스워드의 일치 여부를 확인
  • 첫 번째 매개변수 : 인코딩 되지 않은 패스워드
  • 두 번째 매개변수 : 인코딩 된 패스워드
  • 반환 타입 : boolean
    // 비밀번호 비교
    public static boolean matchPassword(String rawPassword, String encodedPassword) {
        return encoder.matches(rawPassword, encodedPassword);
    }




암호화, 비교 모두 서비스에서 해줄 것 같으니
서비스에서 객체를 생성해줄 것 같다. !!

문제점

회원가입하면서부터 암호화된 비밀번호를 넣어주는데
저기서 where절로 비교하기가 어렵다.

해결

그냥 email을 키값으로 찾고(결국 테이블에 email도 primary해지는군), password를 읽어와서 비교를 해봐야 할 것 같다.

이런식으로 검사하게 해주었따.
앞으로 비밀번호 검증할 곳이 많은 것 같은데 BCryptPasswordEncoder 를 관리하는 클래스만 하나 둬도 괜찮을 것 같다.






로그인 구현하기

서비스에서 1. 이메일+비밀번호 해당되는 row찾기
-> 있으면 그 row를 UserResponseDto으로 만들어서 반환
-> 없으면 throw 하면 된다.

Spring Data JPA의 쿼리 메소드는 메소드 이름을 기반으로 쿼리를 자동으로 생성한다.
JpaRepository를 상속받은 인터페이스에서 메소드를 정의해놓으면 이름을 토대로 알아서 쿼리를 자동으로 만들어준다.

나는 로그인을 해야하니까 이메일, 비밀번호 << 두가지를 가지고 find를 하면되깄지


친절하게 추천도 해준다.



📌 Jpa 조건 사용 예제

where절에서 일치 말고도 범위나 like도 쓸 수 있어서 예제 가져왔다.

1️⃣LIKE 조건 사용

List<TestEntity> findByNameContaining(String name);

name 컬럼에 특정 문자열이 포함된 엔티티를 검색
SQL 쿼리 : WHERE name LIKE %name%

2️⃣BETWEEN 조건 사용

List<TestEntity> findByDateBetween(LocalDate startDate, LocalDate endDate);

특정 날짜 범위 내에 있는 엔티티를 검색
SQL 쿼리: WHERE date BETWEEN startDate AND endDate

3️⃣정렬 조건 사용

List<TestEntity> findByNameOrderByCodeDesc(String name);

name 컬럼이 일치하는 엔티티를 code 컬럼을 기준으로 내림차순 정렬하여 검색
SQL 쿼리: ORDER BY code DESC

유의할 점

  • JPA의 findBy... 메서드는 기본적으로 Optional<T>이 아니라 T를 반환하면 null을 반환할 수 있음
    • User 객체를 찾지 못하면 null이 리턴됨.
    • null을 반환하는 경우가 있으니깐 Optional을 사용하는 것이 명확하다.

그래서 함수를 Optional<User> 반환하도록 고쳐줬다!!

완?성된 로그인

컨트롤러같은 경우는 서비스에서 받아온 값을 세션에다가 저장하면 된다.
예외나올만한 것들은 validation이나 서비스에서 throw으로 처리함

        HttpSession session = request.getSession(true);
        session.setAttribute("loginUser",responseDto);

여기서 세션없으면 만들어주고 로긘을 보통 저장하는 키값은 loginUser에다가 해당 내용 달아주면 된다.

< controller >

    @PostMapping("/login")
    public ResponseEntity<UserResponseDto> login(
            @RequestBody LogInRequestDto requestDto,
            HttpServletRequest request
    ){
        UserResponseDto responseDto = userService.login(requestDto.getEmail(), requestDto.getPassword());

        HttpSession session = request.getSession();
        session.setAttribute("loginUser",responseDto);

        return new ResponseEntity<>(responseDto, HttpStatus.OK);
    }

< service >

    public UserResponseDto login(String email, String password) {
        Optional<User> loginUser = userRepository.findByEmail(email);

        if( loginUser.isEmpty()){
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "이메일과 비밀번호를 확인해주세요");
        }

        User user = userRepository.findByIdOrElseThrow(loginUser.get().getId());

        if( ! encoder.matches(password, user.getPassword()) ) { // 비밀번호 불일치
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "이메일과 비밀번호를 확인해주세요");
        }

        return new UserResponseDto(user.getId(), user.getUsername(), user.getEmail());
    }

잘된다잉!!



현재 로그인 정보 불러오기

이건 서비스까지 갈 필요없이 세션값이 있으면 바로 출력하도록 했다. 간단!

로그인 필터도 만들엇따.
이건 실습때 따라한거랑 거의 똑같은데
앞에 @Component 어노테이션을 붙여주면 자동으로 필터 등록이 된다!

오늘은 여기가찌만..............

profile
대충 데굴데굴 굴러가는 개발?자

0개의 댓글