https://velog.io/@soopy368/web-Get%EA%B3%BC-Post%EC%9D%98-%EC%B0%A8%EC%9D%B4%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90

 

[web] Get과 Post의 차이를 알아보자

GET과 POST는 HTTP 메서드로 클라이언트에서 서버에 정보를 요청할 때 사용한다. 사용 목적에 맞게 HTTP 통신을 제대로 사용할 수 있도록 두 메서드의 차이에 대해서 자세히 알아보자.

velog.io

 

get은 단순 조회용, post는 생성 및 업데이트와 같이 db의 값이 변화하는 경우에 사용 

 

 

 

 


API 

 

이메일 인증 후 회원 가입 

// 이메일 인증 확인 후 회원가입
@PostMapping("/verify-email")
public ResponseEntity<ApiResponse>verifyEmail(@RequestParam @Valid  UUID emailTokenId){
    UserTemp userTemp = userService.checkEmailToken(emailTokenId);
    User signUpUser = userService.signUpUser(userTemp);
    ApiResponse response = new ApiResponse( "회원 가입 완료",signUpUser);

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

 

이메일 인증을 받은 다음 회원 가입을 처리해주는 메서드인 verifyEmail은 회원을 생성하는 거니까 get X post방식 

문제상황

스케쥴러로 미인증 회원들 삭제기능 만들었는데  임시회원이 인증 받으면 이메일 토큰 테이블이랑 임시회원 테이블에서 데이터 삭제하고 , 미인증 받은 상태로 일정 시간지나면 두 테이블에 있는 데이터들 전부 삭제한다.  근데  테이블에 있는 데이터 들을 전부 삭제한 상태에서 새로운 임시 회원이 들어오면 id값이 1 부터 시작되는게 아니라 이전 id값 다음값 부터 시작하게 된다.  이런 경우 데이터가 쌓이면 쌓일수록 계속 id값이 계속 커지기만 한다.  이때 db의 성능과 관련한 문제가 생기지 않을까?  계속해서 id값이 커지더라도 상관 없을까? 

 

 

테이블이 전부 삭제된 후에 새로운 회원이 들어왔는데 이전 회원의 id값 다음값이 생성된다면 그 앞에 있는 id값들이 낭비 되는거 아닐까? 

예를 들어서 100명이 임시 회원 가입을 했다가 인증을 받아서 회원가입해서 삭제되어 테이블이 전부 비워진 상태에서 새로운 데이터가 생성되는데  id값이 101부터 시작하면 1~100의 값은 사용되지 못하는 거니까 테이블 낭비 아닐까?? 

 

 

원인 분석 

@Table(name = "USERTEMP_TABLE")
public class UserTemp {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="USERTEMP_ID")
    private Long userTempId;
  }

 

현재 내 userTemp 테이블의 pk는 자동으로 값이 증가하도록 설정해놨다. 

이 설정은 데이터베이스가 자동 증가된 값을 관리하도록 하고, 삭제된 레코드의 ID를 재사용하지 않는다.

그래서 삭제된 후 새로운 레코드를 삽입하면 연속되지 않는 ID를 얻게 되는 것이다. 

 

해결 방법 

id값이 계속해서 커지거나 연속적이지 않은 id값이 만들어진다고 해서 성능에 큰 차이가 발생하지는 않는다고 한다. 

오히려 연속적인 id 값을 만들기 위해 아래와 같은 설정을 했다가 트랜잭션과 데이터 무결성 부분에서 문제가 발생할 수 있다 

 

굳이 연속적으로 id값을 생성해야하는 이유가 아니라면 다른 설정을 해줄 필요는 없을 것 같다. 

 

TRUNCATE TABLE user_temp RESTART IDENTITY;
ALTER SEQUENCE user_temp_seq RESTART WITH 1;

 

참고자료 

 

https://transferhwang.tistory.com/112

 

[Spring Boot] h2 Database 인덱스 auto_increment 초기화

스프링 부트 공부를 하면서 h2 DB를 이용해 실습하던 도중 다음과 같은 문제를 만났습니다. 임의의 DB의 테이블에 인덱스 값이 1,2,3,4 인 4개의 레코드가 있다고 가정했을때, 4번 인덱스의 레코드를

transferhwang.tistory.com

 

https://stackoverflow.com/questions/10065386/resetting-autoincrement-in-h2

 

Resetting autoincrement in h2

I'm testing a controller that returns a json response but the tests fail after the first time because the h2 database does not reset the auto increment id. Using fixtures or creating objects manual...

stackoverflow.com

 

 

 

 

-m Option

The -m (or --create-home) option tells useradd to create the user’s home directory if it doesn’t already exist. This option ensures that the specified home directory is created and that the default configuration files from /etc/skel are copied into it.

-d Option

The -d (or --home) option specifies the path to the home directory for the new user. However, by itself, -d does not create the home directory. It merely sets the path for the home directory that should be created.

 

Combined Usage

When you use both -m and -d together, useradd will create the home directory specified by the -d option. If you only use the -d option without -m, the home directory will not be created automatically.

Summary of Differences

  • -m: Creates the home directory and copies default files from /etc/skel.
  • -d: Specifies the path for the home directory but does not create it unless used with -m.

 


 

혼자해보기 

 

 

 

 

sudo useradd -m -u 1500 -G 1000 -s /bin/sh  -c "han01 user"  han01

sudo useradd -m -u 15001 -G 1001 -s /bin/bash  -c "han02 user"  han02

 

grep han0 /etc/passwd

 

sudo usermod -l han11 han01

sudo usermod -u 1600 han02 

 

grep han0 /etc/passwd

 

sudo passwd -n 3-x 150 -w 5 han02

sudo usermod -f 7 -e 2020-10-31 han02

 

grep han02 /etc/shadow

 

sudo chage -l han02

 

 


 

 

 

 

sudo groupadd grphan

sudo groupmod -n grpbook grphan

 

grep grp /etc/group

 

sudo gpasswd -a user1 grpbook

sudo gpasswd -a user2 grpbook 

 

grep grp /etc/group

 

sudo gpasswd -d user2 grpbook 

grep grp /etc/group 

 

 

user1의 소속 그룹 변경하는 것은 현재 사용하고 있는 계정이 user1이기 때문에 바로 newgrp를 사용할 수 있다. 

newgrp grpbook

id 

 

하지만 user2의 소속그룹을 변경하기 위해서는 사용자를 user1 -> user2로 바꿔줘야한다. 

앞에서 실습할때 sudo useradd user2 명령어로 사용자 계정을 만들어서 현재 홈 디렉터리 아래에 user2라는 디렉터리가 없는 상태이다. 그리고 useradd 명령어를 이용하면 사용자 계정과 동일한 group이 생성되므로 user2라는 그룹이 생성되어 있다. 

그래서 su -user2 명령을 사용하면 /home/user2 가 없다는 오류가 나오고 원하는 대로 사용자 계정이 전환되지가 않는다 

 

 

그래서 나는 user2 를 삭제하기 위해  첫번째 명령어를 수행하니 user2는 존재하지 않는다고 나왔다. 

sudo userdel user2

 

그래서 두번째 명령어를 이용하여 사용자 계정의 삭제 여부와 관계없이 /home/user2 홈 디렉터리를 무조건 삭제하도록 한 뒤 

sudo rm -rf /home/user2

 

아래 명령어를 이용해 user2 계정을 새로 만들었다. 이때 처음에 user2를 만들었을때 user2라는 그룹이 만들어져 있으므로 -g 옵션을 이용하여 존재하고 있던 user2라는 그룹에 새롭게 생성한 계정 user2를 추가 하도록 했다. 

sudo useradd -s /bin/bash -m -d /home/user2 -g user2 user2

 

 

그 이후에 user1에서 user2로 사용자 계정을 변환하기 위해 

 

sudo passwd user2 // user2 새로운 비밀번호 설정 
su - user2  // user2로 계정 전환하기

 

su - (바꾸고 싶은 사용자 ID) 를 하면 아래와 같이 사용자의 계정이 변경된다 .... 

 

 

 

 

 

이후 계정을 변경하고 newgrp grpbook 명령어를 수행하면 된다. 

 

 

위의 사진에서 보이듯이 gid가 user2 -> grpbook 으로 변경된 것을 확인 할 수 있다. 

 

이후에 다시 user1 계정으로 돌아와 sudo groupdel grpbook 을 실행한다. 

마지막으로 newgrp user1을 한다. 

 

 


 

 

 

sudo useradd -m -d /home2/qtest3 qtest3 

ls /home2

sudo edquota -g qtest3 

sudo quota -g qtest3 

sudo edquota -p qtest3 qtest2 sudo 

 

사용자에게 이메일 인증을 보내면 이메일 토큰을 생성하고  사용자가 해당 인증 링크를 클릭하면 이메일 토큰을 검증한다. 

검증하는 것은 두가지로 1. 해당 uuid 값이 존재하는 값인지 2. 해당 이메일 토큰이 만료된 토큰인지 를 검사한다 

 

이메일 토큰을 생성할때 생성시간+5분 으로 만료시간을 설정해서 값을 저장해둔다. 

이후에 사용자가 전달해온 uuid로 이메일 토큰을 찾고, 해당 이메일 토큰에 저장되어있는 만료시각과 현재 시각을 비교하여 만료 여부를 판단한다. 

 

그래서 만료 되었다면 isEmailExpired = true 로 필드의 값을 수정한다. 이후 오류 메세지를 반환한다

 

 

문제 상황

오류 메세지는 잘 반환이 되는데 해당 이메일 토큰의 필드값이 업데이트 되지 않았다. (sql 쿼리가 작성되지 않았다) 

그런데 이상한건 원래 설정값이 false라서 true가 아닌 이상 오류 메세지를 출력할 수 없는데 오류 메세지는 잘 출력 되었다. 

 

 

원인 파악 

일단 오류 메세지가 나왔다는 것은 필드의 값이 수정은 되었다는 것, 그런데 바뀐값이 db에 반영되지 않았다는 것이다. 

아마 문제의 원인은 트랜잭션에 있는 것 같다. 

 

 

UserService 

@Transactional
public class UserService {

@SuppressWarnings("all")
    public UserTemp checkEmailToken(UUID emailTokenId){

        // 토큰 검증
        EmailToken emailToken = emailService.checkEmailToken(emailTokenId);

        // 임시 회원의 이메일 인증 완료
        Optional<UserTemp> findUserTemp = userTempRepository
                .findById(emailToken.getUserTempId());
        findUserTemp.ifPresent(UserTemp::emailVerifiedSuccess);

        return findUserTemp.get();

    }
    }

 

  • checkEmailToken

url을 통해 받아온 uuid를 이용하여 토큰을 검증하고  검증이 완료되었다면 임시회원의 이메일 인증 완료 처리를 한 후 userTemp 객체를 반환한다. 

이때  userService 트랜잭션이 설정되어있기 때문에 checkEmailToken 메서드가 성공적으로 끝나야 그 변화가 db에 저장된다. 

 

 

EmailService

 public class EmailService {
 
 // 유효한 토큰 가져오기
    @SuppressWarnings("all")
    public EmailToken checkEmailToken(UUID emailTokenId) {

        // emailToken 이 존재하는지 검사
        EmailToken emailToken = emailTokenRepository.findByEmailTokenId(emailTokenId)
                .orElseThrow(() -> new NoSuchElementException("해당 emailTokenId 를 가진 회원이 없습니다"));

        // 해당 토큰의 만료 시간 검사
        if (!emailToken.isValid()) {
            expired(emailToken);
            throw new IllegalStateException("해당 이메일은 만료되었습니다. 이메일을 재인증 해주세요");
        }

        emailToken.usedToken();

        return emailToken;
    }
    
    @Transactional
    public void expired(EmailToken emailToken){
        emailToken.setEmailTokenExpired(true);
        emailTokenRepository.save(emailToken);
    }

    }

 

 

EmailToken 

public class EmailToken {
// 토큰 만료 시간 검증
    public boolean isValid() {
        return !LocalDateTime.now().isAfter(certificationTime);
 }
}

 

 

이메일 토큰의 만료 시간을 검증할때 isValid 메서드를 호출하여 만료 시간이 지난뒤 인증 요청이 들어온 경우 

expired 메서드를 호출하여 해당 이메일 토큰의 필드값을 수정한 후 오류 메세지를 띄우려고 했다. 

 

 

 

expired 메서드에 트랜잭션을 설정했기 때문에 expired 메서드를 통해 생긴 변화( 필드값 변경후 다시 db에 저장) 가 
db에 반영될 것이라 생각했다. 그런데 로그를 보면 sql이 작성되지 않았다. 

 

 

물론 userService의 checkEmailToken 에 트랜잭션이 설정되어있지만 checkEmail에서 호출한 emailService 안의 expired에도 트랜잭션이 설정되어있기 때문에 expired의 트랜잭션이 먼저 끝나서 해당 변화가 db에 저장될 것이라고 생각했다. 

 


GPT 답변

Transaction Committing:

  • Since UserService.checkEmailToken is a transactional method, the transaction will only be committed after this method completes. Any changes made within this transaction, including changes made in EmailService.expired, will not be committed to the database until the UserService.checkEmailToken method completes successfully.

 

Transaction Rollback:

  • If an exception is thrown before the transaction commits, all changes will be rolled back. Since you're throwing an IllegalStateException after marking the token as expired, this might cause the transaction to roll back if not handled properly.

 

 

 

해결 방법 

 

 

@Transaction 어노테이션을 클래스 범위에서 삭제했다. ( 굳이 클래스단에 붙여줄 이유가 있을까? ) 

불필요한 중첩이 일어나지 않도록 필요한 메서드들에만 따로 설정해주자 

 

의문점 

분명히 이메일 토큰을 만들고, db 에 저장하고 나서 이메일 링크를 만들고 전송하는 기능으로 코드를 짰는데 

실제 동작 결과 이메일 링크를 전송하고 나서, 이메일 토큰을 저장한다. 

 

 

왜 이런 모순이 생겼을까?  그 이유는 @Transaction에 있다. 

 

 

 @Transactional
 public class UserService {
 
 // 이메일 전송
    public UUID sendAuthEmail(UserTemp userTemp) throws MessagingException {
        // 이메일 토큰 생성
        EmailToken emailToken = emailService.createmailToken(userTemp);
        // 이메일 전송
        MimeMessage message = emailService.createAuthLink(userTemp, emailToken);
        emailService.sendEmail(message);

        return emailToken.getEmailTokenId();
    }
    
    }

 

 @Transactional
    public  EmailToken createmailToken(UserTemp userTemp) {
        EmailToken emailToken = EmailToken.createEmailToken(userTemp);
        return emailTokenRepository.save(emailToken);
    }

 

 

트랜잭션이란 하나의 작업이 성공적으로 완료 될 때 까지 값을 db에 저장 하지 않는것을 말한다. 트랜잭션은 하나의 작업이 성공하면 저장하고, 작업 진행 도중 실패 했다면 진행되었던 작업들을 롤백시킨다 

즉, 변화된 값이 있더라도 트랜잭션으로 설정해둔 작업이 모두 끝마칠때 까지 변화된 값을 db에 바로 커밋(저장)하지 않겠다는 것이다. 

 

위의 코드를 보면 이메일 전송을 하는 userService에 트랜잭션이 설정되어있다. 이 뜻은 sendAuthEmail 의 작업이 성공적으로 끝날때 까지 이메일 토큰이 생성되어도 해당 이메일 토큰을 db에 저장하지 않겠다는 것을 의미한다. 

 

따라서 이메일이 성공적으로 보내졌을때 sendAuthEmail의 트랜잭션이 끝나고 이후에 커밋하지 못했던 이메일 토큰의 변화를 db에 저장하는 것이다. 

 

사용자에게 이메일 인증 메세지를 보내지 못했는데 이메일 토큰을 만들어서 미리 저장하는 것이 의미가 있을까? 
의미가 없다!!  이메일 토큰을 생성했다는 것 자체가 회원의 이메일 인증을 처리 하려고 하는 것이기 때문에 인증 메일이 보내지지 않은 상황에서는 이메일 토큰이 만들어져봤자 무의미 하다 

 

 

결론: 이메일이 전송되고 나서 이메일 토큰이 저장되도록 하는 것이 맞다 

https://velog.io/@jiyaho/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8B%9C%EA%B0%84-%EC%A0%9C%ED%95%9C%EA%B3%BC-%EC%8B%9C%EA%B0%84-%EB%B3%B5%EC%9E%A1%EB%8F%84

 

[알고리즘] 시간 제한과 시간 복잡도

코딩 테스트에서 문제의 제한 시간은 보통 1~5초 정도이다.일반적인 CPU 기반의 PC나 채점용 컴퓨터에서 1초에 실행할 수 있는 최대 연산 횟수는 <span style="color:- 따라서 문제를 풀 때 먼저 시간 복

velog.io

 

선택 정렬 

선택 정렬이란 정렬 해야하는 값들 중에서 가장 작은수를 선택하여 맨 앞으로 보내는 방법이다. 

 

 

 

예를 들어  3 2 4 6 8 1 5 가 있다고 하자. 그렇다면 여기서 가장 작은 수는 1 이므로 1을 맨 앞으로 보낸다. 

가장 앞에 있는 1은 정렬이 되었으므로 이제 두번째 자리부터 가장 작은 수를 찾는다. 

 

3  2 4 6 8 1 5  

1  2 4 6 8 3 5  

1  2 4 6 8 3 5  

 

다음으로 가장 작은 수는 2이다. 가장 작은 수인 2가 맨 앞에 있으므로 수열값은 변화하지 않는다. 

이제 2번째 자리 까지 가장 작은 수를 찾았으므로 3번째 자리 부터 끝까지 가장 작은 수를 찾아 세번째 자리에 넣어준다. 

이렇게 한번씩 반복할때 마다 가장 작은 수를  가장 앞에 위치 하게 하는 방식이 선택 정렬이다. 

 

처음 반복문을 수행할때는 첫번째 자리 부터 끝까지 돌고,  그중 가장 작은 수를 찾아 첫번째 자리와 바꾼다.  
두번째 반복문을 수행할때는 두번째 자리부터 끝까지 돌고, 그중 가장 작은 수를 찾아 두번째 자리와 바꾼다.  
세번째 반복문을 수행할때는 세번째 자리부터 끝까지 돌고, 그중 가장 작은 수를 찾아 세번째 자리와 바꾼다.  

 

 

이런식으로 계속 반복하여  모든 수를 정렬한다. 

#include <iostream>
using namespace std;

int main(){

    int idx; // 최솟값을 가진 원소의 인덱스

    int arr[10]={2,6,3,8,4,5,9,1,10,7};

    for(int i=0;i<9;i++){
        int min=999;
        for(int j=i;j<10;j++){
            // 최솟값 찾기
            if(min>arr[j]){
                min=arr[j];
                idx=j;
            }
        }
        // 최솟값을 맨 앞의 원소의 값과 바꾸기
        int temp=arr[i];
        arr[i]=arr[idx];
        arr[idx]=temp;
    }
    
    
    for(int i=0;i<10;i++){
        cout<<arr[i]<<" ";
    }



}

 

 

선택 정렬의 시간 복잡도 : o(N^2) 

 

 

선택 정렬로 N개의 수를 정렬하기 위해서는 몇 번의 반복문이 수행되어야 할까? 

선택 정렬은 한번 반복문이 수행 될때 마다 한개씩 정렬이 일어나며, 그 값을 제외하고 맨 마지막 값 까지 반복문을 수행한다. 

즉, n+(n-1)+(n-2)+ ... +2+1 = n(n-1)/2 번의 수행 시간이 필요하다. (이때 n^2에 비해 상수값들은 무의미 하므로 무시한다) 

 

예를 들어 10개의 수를 정렬하기 위해서는 대략 100번의 계산을 하고, 10,000개라면 약 1억번의 계산을 해야하는 셈이다.

지수함수의 특성 처럼 n의 값(x값)이 증가할때 계산값인 y값의 증가 속도는 기하급수적이다. 

 

그렇다면 과연 이런 정렬 방법이 효율적이라고 할 수 있을지 고민해보자.  

 


 

버블 정렬 

#include <stdio.h>

int length=10; 
int data[10]={1,4,2,3,5,10,9,8,6,7}; 

void bubble_sort(int *data,int size){
    for(int i=0;i<size-1;i++){
        for(int j=0;j<size-1-i;j++){
            if(data[j]>data[j+1]){
                int temp=data[j];
                data[j]=data[j+1];
                data[j+1]=temp; 
            }
        }
}
}

void show(){
    for(int i=0;i<length;i++){
        printf("%d ",data[i]); 
    }
}

int main() {
   bubble_sort(data,length);
   show(); 

   return 0;
}

삽입 정렬 

#include <iostream>
using namespace std;


int main() {

    int arr[10]={1,10,5,8,7,6,4,3,2,9}; 

    for(int i=1;i<10;i++){
        for(int j=i-1;j>=0;j--){
            if(arr[j]>arr[j+1]) {
                int temp=arr[j+1];
                arr[j+1]=arr[j];
                arr[j]=temp; 
            }
            else break; 
        }
    }

    for(int i=0;i<10;i++){
        cout<<arr[i]<<' ';
    }
    return 0;
}

퀵 정렬 

 

피벗값을 이용하여 큰 값과 작은값을 반복적으로 바꿔준다. 

분할 정복 

 

#include <stdio.h>

int number=10;
int data[10]={1,10,5,8,7,6,4,3,2,9}; 

void quickSort(int *data,int start,int end){
    if(start>=end){ // 원소가 1개인 경우 그대로 두기
        return ; 
    }
    int key=start; // 키는 첫번째 원소
    int i=start+1; 
    int j=end;
    int temp; 

    while(i<=j){ // 엇갈릴때 까지 반복
        while(i<=end && data[i]<=data[key]){ // 키 값보다 큰 값을 만날때 까지
            i++; 
        }
        while(j>start && data[j]>=data[key] ){ // 키 값보다 작은 값을 만날때 까지
            j--; 
        }
        if(i>j){ // 엇갈린 경우 키 값과 교체 
            temp=data[j];
            data[j]=data[key];
            data[key]=temp; 
        }
        else{ // 엇갈리지 않았다면 i와 j를 교체
        temp=data[i];
        data[i]=data[j];
        data[j]=temp; 
        }
       
    }
    quickSort(data,start,j-1);
    quickSort(data,j+1,end);
}

void show(){
    for(int i=0;i<number;i++){
        printf("%d ",data[i]); 
    }
}

int main() {
    quickSort(data,0,number-1); 
    show(); 
    return 0;
}

병합 정렬 

#include <iostream>
using namespace std;

const int number = 8;
int sorted[number];

void merge(int arr[], int m, int middle, int n) {
    int i = m;
    int j = middle + 1;
    int k = m;

    while (i <= middle && j <= n) {
        if (arr[i] <= arr[j]) {
            sorted[k] = arr[i];
            i++;
        } else {
            sorted[k] = arr[j];
            j++;
        }
        k++;
    }

    while (i <= middle) {
        sorted[k] = arr[i];
        i++;
        k++;
    }
    while (j <= n) {
        sorted[k] = arr[j];
        j++;
        k++;
    }


    for (int t = m; t <= n; t++) {
        arr[t] = sorted[t];
    }
}


void mergeSort(int arr[], int m, int n) {
    if (m < n) {
        int middle = (m + n) / 2;
        mergeSort(arr, m, middle);
        mergeSort(arr, middle + 1, n);
        merge(arr, m, middle, n);
    }
}

int main() {
    int array[number] = {2,4,3,6,5,1,7,8};
    mergeSort(array, 0, number - 1);

    for (int i = 0; i < number; i++) {
        cout << array[i] << " ";
    }

    return 0;
}

 

 

'알고리즘 > 개념정리' 카테고리의 다른 글

유니온 파인드  (0) 2024.11.23
그래프를 구현하는 방법  (6) 2024.11.20
소수를 구하는 방법  (4) 2024.11.19

 

uuid 필드를 string -> UUID로 변경하는 과정에서 emailTokenRepository의 메서드 중 파라미터 값을 수정하지 않아서 테이블을 만드는 과정에서 타입이 불일치 돼서 발생한 문제 

 

저 오류의 경우 데이터베이스와 코드의 필드가 맞지 않는 문제 일 수 있음 

 

로그를 잘 읽어보면 문제가 보임 

 

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'emailService' defined in file [C:\Dongurami_jh\USW-Circle-Link-Server\build\classes\java\main\com\USWCicrcleLink\server\email\service\EmailService.class]: Unsatisfied dependency expressed through constructor parameter 2: Error creating bean with name 'emailTokenRepository' defined in com.USWCicrcleLink.server.email.repository.EmailTokenRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract java.util.Optional com.USWCicrcleLink.server.email.repository.EmailTokenRepository.findByEmailTokenIdAndCertificationTimeAfterAndIsEmailTokenExpired(java.lang.String,java.time.LocalDateTime,boolean); Reason: Failed to create query for method public abstract java.util.Optional com.USWCicrcleLink.server.email.repository.EmailTokenRepository.findByEmailTokenIdAndCertificationTimeAfterAndIsEmailTokenExpired(java.lang.String,java.time.LocalDateTime,boolean); Cannot compare left expression of type 'java.util.UUID' with right expression of type 'java.lang.String'

 

@GenericGenerator 

 

 

문제점 

 'org.hibernate.annotations.GenericGenerator' is deprecated since version 6.5 

 

 

 

해결 방법

https://stackoverflow.com/questions/76723290/using-the-new-type-for-uuidgenerator-instead-of-strategy

 

Using the new type() for UUIDGenerator instead of strategy?

According to the documentation, the following usage is deprecated: @GenericGenerator( name = "UUID", strategy = "org.hibernate.id.UUIDGenerator" ) One

stackoverflow.com

 

 

정리할 것 

  • uuid를 생성하는 방법에는 무엇이 있을까
  • @UuidGenerator란 무엇인가

 

https://www.baeldung.com/java-hibernate-uuid-primary-key

 

 

 

 

 

실습 과제 

 

 

#define NOTE_C4 262
#define NOTE_D4 294
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_G4 392
#define NOTE_A4 440
#define NOTE_B4 494
#define NOTE_C5 523

#define PIN_PIEZO 10
#define PIN_SWITCH 8 // 버튼 8번 핀에 연결하기 
#define T 200 // 16분 음표 길이
#define DB_WAIT 20 // 디바운싱을 위해 대기 시간 

void setup() {
  pinMode(PIN_PIEZO, OUTPUT);
  pinMode(PIN_SWITCH, INPUT_PULLUP);
  Serial.begin(9600);
  printMsg("\rWelcome to John's JukeBox"); // 환영 메세지 출력
}

int Notes[] = { NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4, NOTE_G4, NOTE_A4, NOTE_B4, NOTE_C5 };
String NoteStr[] = { "Do", "Re", "Mi", "Fa", "Sol", "La", "Si", "Do5" };

void play_note(int n, int d) {
  Serial.print(NoteStr[n] + " ");
  int duration = T * d; // 지속시간 
  tone(PIN_PIEZO, Notes[n], duration - T * 0.1);
  delay(duration);
}

int airplane_note[] = {2,1,0,1,2,2,2,1,1,1,2,2,2,2,1,0,1,2,2,2,1,1,2,1,0}; 
int airplane_duration[] = {3,1,2,2,2,2,4,2,2,4,2,2,4,3,1,2,2,2,2,4,2,2,2,2,4}; 
int airplane_N = 25;

// 비행기 노래 출력 함수 
void play_airplane() {
  
  for (int i = 0; i < airplane_N; i++) {
    play_note(airplane_note[i], airplane_duration[i]);
  }
}

// 메세지 출력 함수 
void printMsg(char *str){
  Serial.print("\r     ");
  Serial.print(str);
}
// blink 함수 
void blink(){
for(int i=0;i<3;i++){
         digitalWrite(13,HIGH);
         delay(1000);
         digitalWrite(13,LOW);
         delay(1000); 
      }
}
int state=0; //상태 변수 
void loop() {
  switch(state){
    case 0: // 기능 1 안내
      printMsg("\rPlay music?");
      state=1; break;

    case 1: // 기능 1 실행 
      while(digitalRead(8)==HIGH); // 버튼이 눌리지 않은 경우 아무것도 실행하지 않는다 
      printMsg("\rPlay Music....");
      play_airplane(); // 노래 출력 
      state=2; break; 

    case 2: // 기능 2 안내
     printMsg("\rBlink the Light?");
     state=3; break; 

    case 3:
      while(digitalRead(8)==HIGH) ; 
      printMsg("\rBlink LED....");
      blink(); 
      state=0; break; 

    default: break; 
  }
  
}

 

'임베디드 시스템' 카테고리의 다른 글

5장 인터럽트와 millis  (0) 2024.06.16
3. 아두이노 기본 클래스  (0) 2024.06.04
2. 아두이노 소개  (2) 2024.06.04

실습과제 

 

 

 

 

 

#define NOTE_C4 262
#define NOTE_D4 294
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_G4 392
#define NOTE_A4 440
#define NOTE_B4 494
#define NOTE_C5 523

#define PIN_PIEZO 10
#define PIN_SWITCH 2 // Button connected to pin 2, 인터럽트 핀 번호 0번 (Int0)
#define PIN_LED 13 // LED connected to pin 13

#define T 200 // 16th note length
#define DB_WAIT 20 // Debouncing wait time

volatile int sw_pressed = 0; // Interrupt flag

void setup() {
  pinMode(PIN_PIEZO, OUTPUT);
  pinMode(PIN_SWITCH, INPUT_PULLUP);
  pinMode(PIN_LED, OUTPUT);
  
  attachInterrupt(0, sw_isr, FALLING); // 버튼이 눌린 순간 인터럽트 함수 실행 
  Serial.begin(9600);
  printMsg("Welcome to John's JukeBox");
}

// Interrupt service routine
void sw_isr() {
  sw_pressed = 1;
}

int Notes[] = { NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4, NOTE_G4, NOTE_A4, NOTE_B4, NOTE_C5 };
String NoteStr[] = { "Do", "Re", "Mi", "Fa", "Sol", "La", "Si", "Do5" };

void play_note(int n, int d) {
  if (sw_pressed) return; // 버튼이 눌리면 노래 종료  
  Serial.print(NoteStr[n] + " ");
  int duration = T * d; // 노트 지속 시간
  tone(PIN_PIEZO, Notes[n], duration - T * 0.1);
  delay(duration);
}

int airplane_note[] = {2, 1, 0, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 1, 0, 1, 2, 2, 2, 1, 1, 2, 1, 0}; 
int airplane_duration[] = {3, 1, 2, 2, 2, 2, 4, 2, 2, 4, 2, 2, 4, 3, 1, 2, 2, 2, 2, 4, 2, 2, 2, 2, 4}; 
int airplane_N = 25;

// 비행기 노래 출력 
void play_airplane() {
  for (int i = 0; i < airplane_N; i++) {
    if (sw_pressed) { // 노래 실행 도중 버튼이 눌리면
      sw_pressed = 0; // 리셋
      break; // 노래 종료 
    }
    play_note(airplane_note[i], airplane_duration[i]);
  }
  noTone(PIN_PIEZO); // 재생 중지
}

// 메세지 출력 
void printMsg(const char *str) {
  Serial.print("\r     ");
  Serial.print(str);
}

// led 함수 
void blink() {
  for (int i = 0; i < 10; i++) {
    if (sw_pressed) {
      sw_pressed = 0; // 리셋
      break;
    }
    digitalWrite(PIN_LED, HIGH);
    delay(1000);
    digitalWrite(PIN_LED, LOW);
    delay(1000);
  }
}

int state = 0; // State variable
void loop() {
  switch (state) {
    case 0: // Guide for function 1
      printMsg("Play music?");
      state = 1; 
      break;

    case 1: // Execute function 1
      if(sw_pressed){ // 첫번째 버튼 눌렀을때 
      printMsg("Playing Music....");
      sw_pressed = 0; // 첫번째 버튼에 대한 인터럽트 변수 리셋!! 
      play_airplane(); // 노래 재생
      Serial.print(String("sw_pressed= ")+sw_pressed);
      state = 2; 
      }
     
      break; 

    case 2: // Guide for function 2
      printMsg("Blink the Light?");
      state = 3; 
      break; 

    case 3: // Execute function 2
     if(sw_pressed){
      printMsg("Blinking LED....");
      sw_pressed = 0; // 리셋
      Serial.print(String("sw_pressed= ")+sw_pressed);
      blink();
      state = 0; 
     }
     break; 

    default: 
      break; 
  }
}

 

 

Q 이미 각각의  paly_airplane 과 blink 안에 sw_pressed를 처리하는 부분이 있음에도 불구하고 case문 안에서 sw_pressed를 다시 리셋해주는 이유가 무엇인가 ?

 

Q case문 안에 있는 sw_pressed가 있을때와 없을때의 차이를 생각해보시오 

'임베디드 시스템' 카테고리의 다른 글

4장 디지털 입 출력  (2) 2024.06.16
3. 아두이노 기본 클래스  (0) 2024.06.04
2. 아두이노 소개  (2) 2024.06.04

+ Recent posts