[AndroidStudio,SpringBoot] KnockKnock 개발일지 - 0117 (프로젝트 배포, mariaDB rds연결)

오늘의 목표

  1. ✔배포 스크립트 작성 및 배포 완료하기
  2. ✔mariaDB 프로젝트 연결해보기 (rds 연결)
  3. 안드로이드와 springboot 프로젝트 연결하기
  4. 안드로이드에서 REST API로 CRUD 간단하게 출력해보기

오늘의 이슈

  1. 외부 인스턴스에서 test 깨지는 오류
  2. no main manifest attribute in 에러
  3. Error Executing DDL "alter table user drop foriegn key"
  4. error: package does not exist
  5. AWS RDS 인코딩 UTF8 설정하기

참고 링크

  1. 안드로이드 앱 만들기 #29 로그인&회원가입 youtube 영상
  2. [Android] 안드로이드 로그인 페이지 구현하기 #1 - Activity 전환하기
  3. 5) 스프링부트로 웹 서비스 출시하기 - 5. EC2에 배포하기
  4. [AWS] RDS에서 스프링부트 프로젝트 실행
  5. AWS RDS 인코딩 UTF8 설정하기

1. 안드로이드 스튜디오 기본 설정과 개념

build.gradle (Module)

  • implementation 'com.android.volley:volley:1.1.1' : 서버 통신과 관련된 라이브러리 (전송과 다운로드)

AndriodManifest.xml

  • <uses-permission android:name="android.permission.INTERNET"/> : 인터넷 권한 설정

main Activity 설정 (가장 먼저 실행되는 화면 설정)

 <activity android:name=".LoginActivity" android:exported="true">
       📌 <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
       📌 </intent-filter>
        </activity>

다음 부분을 main Activity 로 만들 Activity 사이에 넣어준다.

Intent 화면간 이동과 데이터 전달


intent 란?

intent는 앱 컴포넌트가 무엇을 할 것인지를 담는 메시지 객체이다. 메시지는 의사소통을 하기 위해 보내고 받는 것이다. 메시지를 사용하는 가장 큰 목적은 다른 엑티비티, 서비스, 브로드캐스트 리시버, 컨텐트 프로바이더 등을 실행하는 것이다. 인텐트는 그들 사이에 데이터를 주고 받기 위한 목적이다.


화면전환

화면 == 하나의 엑티비티
즉 화면 간의 이동하는 과정은 각각의 액티비티를 필요에 따라 띄우거나 닫는 과정과 같다.

package org.techtown.knockknock;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class LoginActivity extends AppCompatActivity implements View.OnClickListener{
    Button btn_login;
    Button btn_register2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        btn_login = (Button) findViewById(R.id.btn_login);
        btn_register2 = (Button)findViewById(R.id.btn_register2);

        btn_login.setOnClickListener(this);
        btn_register2.setOnClickListener(this);

    }

    @Override
  📌  public void onClick(View view) {
        switch(view.getId()){
            case R.id.btn_login:
                Intent intent = new Intent(getApplicationContext(), MainActivity.class);
                startActivity(intent);
                finish();
                break;
            case R.id.btn_register2:
                Intent intent2 = new Intent(getApplicationContext(), RegisterActivity.class);
                startActivity(intent2);
                finish();
                break;
        }
    }
}

화면 전환 코드는 다음과 같이 Intent 객체를 새로 생성해서 안에 현재 ApplicationContext와 전환된 화면 Activity 클래스를 넣으면 된다. intent의 시작은 startActivity(intent) 혹은 startActivityforResult(intent)로 한다.
Intent가 끝나고 현재 activity로 돌아오게 하기 위해 finish() 메서드를 넣어준다.

화면 간 데이터 전달

2.💥아직 해결 못한 compileQuerydsl 오류

나는 아직도 이 때의 오류를 잡지 못해서 헤매는 중이다.
그러나 오늘은 약간의 변화가 생겨서 이걸 기록하면 좋을 것 같다. 🎈😇

나를 괴롭히던 compileQuerydsl 오류는 결국 인프런에서도 도움을 못받은 채 나의 모든 개발 의욕을 앗아갔다.....
그래도 뭐라도 해야지 하는 심정으로 프로젝트 이것 저것 뒤적거리고 있던 찰나에


?????????????????????????????????????????????????????????????????????????
띠용..
기존의 compileQuerydsl 오류는 build 돼서 패스되고 이제 그 이후의 test 오류가 발생했다.
오류 너머 오류는 이제 너무 익숙해서 그려려니 하는데 대체 compileQuerydsl 오류는 어떻게 해결된거지?

내가 쓴 방법은 다음과 같다.
1.

compileQuerydsl { // (7)
    options.annotationProcessorPath = configurations.querydsl
}
// 📌compileQuerydsl.doFirst { if(file(querydslDir).exists() ) delete(file(querydslDir)) }

아래 한 줄의 코드를 주석처리한다. (코드 내용은 querydsl 폴더가 있으면 그 폴더를 지우라는 의미)
2. ./gradlew build로 빌드한다. -> 이때는 compileQuerydsl 문제가 터지지 않는다.
3. 다시 build 하려고 하면 기존의 queryDsl이 이미 생성되었기 때문에 또 다른 오류가 발생해버린다. 그래서 build 하기 전에 항상 ./gradlew clean을 하고 다시 빌드한다.

왜 위의 주석 단 코드때문에 프로젝트 빌드가 외부에서 안되는지는 정말 모르겠다 ㅠ (로컬에서는 잘만 되기 때문에,,,)
그렇다고 매번 이렇게 clean하고 다시 build하는게 맞지는 않지 않을까? 어떻게 해야할지 좀 더 고민하고 찾아봐야겠다..
완전히 해결된 것은 아니지만 대충 문제가 어디인지 알게 되어서 기쁘다.. 뭐라도 진행된 기분
며칠간 정말 붙잡고 있어도 아무것도 이루어진것이 없어서 우울했는데 😭

embedded H2로 데이터베이스 변환

querydsl 문제를 얼레벌레 해결하고 나니 또다른 오류에 봉착했다.
바로 TEST가 깨지는 오류였다.
오류메세지는 다음과 같았다.

KnockKnockApplicationTests > contextLoads() FAILED
    java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:132
        Caused by: org.springframework.beans.factory.BeanCreationException at AbstractAutowireCapableBeanFactory.java:1804
            Caused by: org.hibernate.service.spi.ServiceException at AbstractServiceRegistryImpl.java:275
                Caused by: org.hibernate.HibernateException at DialectFactoryImpl.java:100

이 문제는 내가 테스트와 연결되어있는 DB를 embedded(내장) H2가 아니라 로컬에서 접근하는 로컬 H2를 사용하고 있었기 때문이였다.

몰랐지만 새로 안 사실이였는데
H2는 로컬 h2 데이터베이스와 Springboot에서 제공해주는 프로젝트 내장 h2로 나뉜다
내 프로젝트가 로컬 h2와 연결되어 있으니 당연히 외부에서 접근해서 테스트를 돌릴 때는 로컬 데이터베이스에 접근할 수 없어서 에러가 났던 것이다.
따라서 설정을 embedded h2로 다음과 같이 변경해주었다.


  1. runtimeOnly 'com.h2database:h2:1.4.197' 를 gradle.build 의 dependency에 추가한다.
  2. main\application.yml 파일 수정
spring:
  h2:
    console:
      enabled: true
  datasource:
    url: jdbc:h2:mem:testdb
#    username: sa
#    password:
#    driver-class-name: org.h2.Driver
  jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        format_sql: true
        show_sql: true

server:
  port: 8081

logging:
  level:
    org.hibernate.sql: debug
  1. test\application.yml 파일 수정
spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/knockknock
    username: sa
    password:
    driver-class-name: org.h2.Driver
  jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        format_sql: true
        show_sql: true
  profiles:
    active: dev

logging:
  level:
    org.hibernate.sql: debug

드디어..!! 드디어 build가 successful하게 이루어졌다 ㅠㅠㅠㅠㅠㅠ😭😭😭

ubuntu 에서 jdk11 설치하기

sudo amazon-linux-extras install java-openjdk11

졸업 프로젝트하다가 이거 못찾아서 또 한참을 헤맸네 ..ㅎㅎㅎㅎ

3. 배포시 no main manifest attribute in 에러

프로젝트 빌드가 잘 되는 것도 확인했겠다 이제 jojoldu님의 블로그 남은 단계를 밟아보려고 배포 스크립트를 작성하고 배포하려는 찰나에..!

또 다시 어김없이 오류가 나타났다.
nohup.out 파일에서 로그를 확인해보려고 생성된 nohup.out파일을 열람하였더니 다음과 같은 메세지가 떴다.

no main manifest attribute, in /home/ec2-user/app/git/KnockKnock-0.0.1-SNAPSHOT-plain.jar
no main manifest attribute, in /home/ec2-user/app/git/KnockKnock-0.0.1-SNAPSHOT-plain.jar
no main manifest attribute, in /home/ec2-user/app/git/KnockKnock-0.0.1-SNAPSHOT-plain.jar
no main manifest attribute, in /home/ec2-user/app/git/KnockKnock-0.0.1-SNAPSHOT-plain.jar

no main manifest attribute in 에러는 spring 애플리케이션을 빌드한 결과물로 나온 jar파일에서 처음 호출할 Main 메소드를 찾지 못했다는 에러다.

plain.jar 파일생성 옵션 끄기

찾아보니 spring boot 2.5.0 이상 버전의 gradle로 빌드할 때 jar 파일이

  1. 앱이름.jar -> bootJar Task로 생성된것

  2. 앱이름-plain.jar -> build Task로 생성된것

이렇게 두 개 생성된다고 한다.
첫번째 jar는 해당 프로젝트에 필요한 모든 의존성이 같이 추가된것으로 MANIFEST.MF까지 모두 정상적인 형태로 나온다.
하지만 plain.jar는 의존성을 제외하고 딱 프로젝트에 있는 자원들만 jar로 만든것으로 spring 관련 의존성이 빠져 MANIFEST.MF에 Main메소드의 위치가 나오지 않는다.

에러 메세지를 보니 plain.jar 파일을 java -jar로 실행해서 에러가 난 것 같다
따라서 springboot의 공식 사이트에 나와있는 plain.jar 파일을 생성하지 않는 명령어를 build.gradle에 추가했다.

jar {
    enabled = false
}

그런데 여전히 plain.jar 파일이 생성되는 것이다 😭😭 대체 왜이럴까

bootJar{
    enabled(true)
    archiveClassifier.set("")
}

구글링해서 다음과 같은 내용을 추가하니까 해결되었다. 후,, 대체 공식 사이트에서 제시한 방법도 안통하면 어쩌자는 건지 😣😣🔥 어쨌든 해결되어서 다행이다.
결론은 위에 jar옵션과 bootJar 옵션을 둘 다 주어야 한다는 것!!

📚(참고) 배포스크립트와 이후 과정

대부분의 과정은 jojoldu님을 참고하였고 따라서 생략되는 부분도 많을 것이다.

#!/bin/bash

REPOSITORY=/home/ec2-user/app/git // git에서 가져온 프로젝트 레파지토리 위치 

cd $REPOSITORY/KnockKnock/ // 프로젝트 레파지토리 명 

echo "> Git Pull"

git pull

echo "> 프로젝트 Build 시작"

./gradlew build

echo "> Build 파일 복사"

cp ./build/libs/*.jar $REPOSITORY/

echo "> 현재 구동중인 애플리케이션 pid 확인"

CURRENT_PID=$(pgrep -f 📌KnockKnock) // 프로젝트 레파지토리 명 

echo "$CURRENT_PID"

if [ -z $CURRENT_PID ]; then
    echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다."
else
    echo "> kill -2 $CURRENT_PID"
    kill -9 $CURRENT_PID
    sleep 5
fi

echo "> 새 어플리케이션 배포"

JAR_NAME=$(ls $REPOSITORY/ |grep 📌'KnockKnock' | tail -n 1)

echo "> JAR Name: $JAR_NAME"

nohup java -jar $REPOSITORY/$JAR_NAME &
  • nohup은 build 후 java - jar 명령어가 실행되며 프로젝트가 구동을 시작할 때 찍히는 로그들을 기록해주는 명령어이다. 이 로그는 nohup.out 파일에 생성된다.
    쉽게 말해 로컬에서 프로젝트를 실행할 때 springboot 로그부터 뜨는 그 로그가 nohup.out 파일에 저장되면 정상적으로 프로젝트가 실행되고 있다고 이해하면 된다.

  • chmod 755 ./deploy.sh : ./deploy.sh 파일의 실행 권한 주기. 이후 배포는 ./deploy,sh 코드로 해결

  • ps -ef|grep {프로젝트(레파지토리)명} : 프로젝트로 실행중인 프로세스(jar 파일)을 확인하는 명령어

이 때 📌핀 꽂이둔 부분은 jar 파일의 이름에서 일부를 검색하는 코드이기 떄문에 jar 파일명을 잘 확인하고 해당하는 프로젝트 명으로 입력해주어야 한다!!!


4. MariaDB 와 SpringBoot 연결하기

  • dependencies 에 추가할 내용
 runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
    compile("org.mariadb.jdbc:mariadb-java-client")
  • application.yml 에 추가할 내용
spring:
  datasource:
    url: jdbc:mariadb://{rds엔드포인트}:3306/{데이터베이스 이름}
    username: 마스터 사용자 명 
    password: 마스터 사용자 비밀번호 
    driver-class-name: org.mariadb.jdbc.Driver

이렇게 연결하고 로컬에서 프로젝트를 돌려본 결과 오류가 발생했다 😇

org.hibernate.tool.schema.spi.commandacceptanceexception: error executing ddl "alter table user drop foriegn key"

이 오류를 찾아본 결과 지정된 table을 찾아서 drop할 foriegn key를 drop해야 하는데
문제는 alter table을 찾을 수 없었던 것이다.
이 문제가 발생하는 까닭은 바로 jpa.hibernate.ddl-auto: create 옵션에 있었다.

DDL의 5가지 옵션

  • none: 아무것도 없음
  • create-drop: SessionFactory 시작지점에 drop한 후 create를 실행하며 SessionFactory 종료시 다시 drop
  • update: 변경된 스키마 적용
  • create: SessionFactory 시작 지점에 drop한 후 create 실행
  • validate: 변경된 스키마가 존재하면 변경사항을 출력하고 app을 종료

나는 create 옵션을 두고 있었는데 프로젝트가 실행될때마다 create옵션으로 인해 모든 테이블이 drop되기 때문에 alter table에서 해당 table 찾을 수 없었던 것이다.
따라서 이 문제의 해결은 ddl-auto: update 로 옵션을 줘서 해결할 수 있었다

5. Package does not exist 오류

이 오류는 내가 rds 연결 후 만들어놓은 REST API 가 잘 동작하는지 확인하기 위해 초기 데이터를 넣어주려고 initDB 파일에 초기 데이터 내용을 작성한 뒤 프로젝트를 실행하니까 나는 오류였다.

다음과 같이 아주 멀쩡히 잘 있는 패키지 경로를 못찾겠다고 하니 아주 환장할 노릇이였다 😧

이 문제는 compileQuerydsl 문제와 같은,, 문제인데 결과적으로 Qclass generate된 부분을 clean하고 돌려주면 해결된다
그러나 또 다른 문제에 직면하고 만다,,

6. AWS RDS 인코딩 UTF8 설정하기

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'initDB': Invocation of init method failed; nested exception is org.springframework.dao.DataAccessResourceFailureException: could not extract ResultSet; nested exception is org.hibernate.exception.JDBCConnectionException: could not extract ResultSet
	at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:160) ~[spring-beans-5.3.12.jar:5.3.12]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:440) ~[spring-beans-5.3.12.jar:5.3.12]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1796) ~[spring-beans-5.3.12.jar:5.3.12]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:620) ~[spring-beans-5.3.12.jar:5.3.12]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.12.jar:5.3.12]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.12.jar:5.3.12]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.12.jar:5.3.12]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.12.jar:5.3.12]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.12.jar:5.3.12]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944) ~[spring-beans-5.3.12.jar:5.3.12]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.12.jar:5.3.12]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.12.jar:5.3.12]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.5.6.jar:2.5.6]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-2.5.6.jar:2.5.6]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) ~[spring-boot-2.5.6.jar:2.5.6]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:338) ~[spring-boot-2.5.6.jar:2.5.6]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[spring-boot-2.5.6.jar:2.5.6]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1332) ~[spring-boot-2.5.6.jar:2.5.6]
	at jpaproject.knockknock.KnockKnockApplication.main(KnockKnockApplication.java:16) ~[main/:na]
Caused by: org.springframework.dao.DataAccessResourceFailureException: could not extract ResultSet; nested exception is org.hibernate.exception.JDBCConnectionException: could not extract ResultSet
	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:255) ~[spring-orm-5.3.12.jar:5.3.12]
	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:233) ~[spring-orm-5.3.12.jar:5.3.12]
	at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:551) ~[spring-orm-5.3.12.jar:5.3.12]
	at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) ~[spring-tx-5.3.12.jar:5.3.12]
	at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242) ~[spring-tx-5.3.12.jar:5.3.12]
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152) ~[spring-tx-5.3.12.jar:5.3.12]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.12.jar:5.3.12]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.12.jar:5.3.12]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) ~[spring-aop-5.3.12.jar:5.3.12]
	at jpaproject.knockknock.repository.MemberRepository$$EnhancerBySpringCGLIB$$8d486979.findByNickName(<generated>) ~[main/:na]
	at jpaproject.knockknock.service.MemberService.validateSameNickNameExsist(MemberService.java:38) ~[main/:na]
	at jpaproject.knockknock.service.MemberService.signIn(MemberService.java:25) ~[main/:na]
	at jpaproject.knockknock.service.MemberService$$FastClassBySpringCGLIB$$c4a35944.invoke(<generated>) ~[main/:na]
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.12.jar:5.3.12]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783) ~[spring-aop-5.3.12.jar:5.3.12]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.12.jar:5.3.12]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.12.jar:5.3.12]
	at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-5.3.12.jar:5.3.12]
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) ~[spring-tx-5.3.12.jar:5.3.12]
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.12.jar:5.3.12]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.12.jar:5.3.12]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.12.jar:5.3.12]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) ~[spring-aop-5.3.12.jar:5.3.12]
	at jpaproject.knockknock.service.MemberService$$EnhancerBySpringCGLIB$$d024d447.signIn(<generated>) ~[main/:na]
	at jpaproject.knockknock.InitService.dbInit1(InitService.java:24) ~[main/:na]
	at jpaproject.knockknock.InitService$$FastClassBySpringCGLIB$$c547a807.invoke(<generated>) ~[main/:na]
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.12.jar:5.3.12]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783) ~[spring-aop-5.3.12.jar:5.3.12]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.12.jar:5.3.12]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.12.jar:5.3.12]
	at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-5.3.12.jar:5.3.12]
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) ~[spring-tx-5.3.12.jar:5.3.12]
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.12.jar:5.3.12]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.12.jar:5.3.12]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.12.jar:5.3.12]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) ~[spring-aop-5.3.12.jar:5.3.12]
	at jpaproject.knockknock.InitService$$EnhancerBySpringCGLIB$$abab9270.dbInit1(<generated>) ~[main/:na]
	at jpaproject.knockknock.InitDB.init(InitDB.java:17) ~[main/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
	at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:389) ~[spring-beans-5.3.12.jar:5.3.12]
	at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:333) ~[spring-beans-5.3.12.jar:5.3.12]
	at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:157) ~[spring-beans-5.3.12.jar:5.3.12]
	... 18 common frames omitted
Caused by: org.hibernate.exception.JDBCConnectionException: could not extract ResultSet
	at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:48) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:67) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.loader.Loader.getResultSet(Loader.java:2297) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2050) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2012) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.loader.Loader.doQuery(Loader.java:948) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:349) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.loader.Loader.doList(Loader.java:2843) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.loader.Loader.doList(Loader.java:2825) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2657) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.loader.Loader.list(Loader.java:2652) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:506) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.hql.internal.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:400) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.engine.query.spi.HQLQueryPlan.performList(HQLQueryPlan.java:219) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1414) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.query.internal.AbstractProducedQuery.doList(AbstractProducedQuery.java:1636) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1604) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at org.hibernate.query.internal.AbstractProducedQuery.getSingleResult(AbstractProducedQuery.java:1652) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	at jpaproject.knockknock.repository.MemberRepository.findByNickName(MemberRepository.java:34) ~[main/:na]
	at jpaproject.knockknock.repository.MemberRepository$$FastClassBySpringCGLIB$$cb64a86a.invoke(<generated>) ~[main/:na]
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.12.jar:5.3.12]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783) ~[spring-aop-5.3.12.jar:5.3.12]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.12.jar:5.3.12]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.12.jar:5.3.12]
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137) ~[spring-tx-5.3.12.jar:5.3.12]
	... 57 common frames omitted
Caused by: java.sql.SQLTransientConnectionException: (conn=1255) Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and (utf8mb4_general_ci,COERCIBLE) for operation '='
	at org.mariadb.jdbc.internal.util.exceptions.ExceptionFactory.createException(ExceptionFactory.java:79) ~[mariadb-java-client-2.7.4.jar:na]
	at org.mariadb.jdbc.internal.util.exceptions.ExceptionFactory.create(ExceptionFactory.java:158) ~[mariadb-java-client-2.7.4.jar:na]
	at org.mariadb.jdbc.MariaDbStatement.executeExceptionEpilogue(MariaDbStatement.java:266) ~[mariadb-java-client-2.7.4.jar:na]
	at org.mariadb.jdbc.ClientSidePreparedStatement.executeInternal(ClientSidePreparedStatement.java:229) ~[mariadb-java-client-2.7.4.jar:na]
	at org.mariadb.jdbc.ClientSidePreparedStatement.execute(ClientSidePreparedStatement.java:149) ~[mariadb-java-client-2.7.4.jar:na]
	at org.mariadb.jdbc.ClientSidePreparedStatement.executeQuery(ClientSidePreparedStatement.java:163) ~[mariadb-java-client-2.7.4.jar:na]
	at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52) ~[HikariCP-4.0.3.jar:na]
	at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java) ~[HikariCP-4.0.3.jar:na]
	at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:57) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
	... 80 common frames omitted
Caused by: org.mariadb.jdbc.internal.util.exceptions.MariaDbSqlException: Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and (utf8mb4_general_ci,COERCIBLE) for operation '='
	at org.mariadb.jdbc.internal.util.exceptions.MariaDbSqlException.of(MariaDbSqlException.java:34) ~[mariadb-java-client-2.7.4.jar:na]
	at org.mariadb.jdbc.internal.protocol.AbstractQueryProtocol.exceptionWithQuery(AbstractQueryProtocol.java:194) ~[mariadb-java-client-2.7.4.jar:na]
	at org.mariadb.jdbc.internal.protocol.AbstractQueryProtocol.exceptionWithQuery(AbstractQueryProtocol.java:177) ~[mariadb-java-client-2.7.4.jar:na]
	at org.mariadb.jdbc.internal.protocol.AbstractQueryProtocol.executeQuery(AbstractQueryProtocol.java:321) ~[mariadb-java-client-2.7.4.jar:na]
	at org.mariadb.jdbc.ClientSidePreparedStatement.executeInternal(ClientSidePreparedStatement.java:220) ~[mariadb-java-client-2.7.4.jar:na]
	... 85 common frames omitted
Caused by: java.sql.SQLException: Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and (utf8mb4_general_ci,COERCIBLE) for operation '='
	at org.mariadb.jdbc.internal.protocol.AbstractQueryProtocol.readErrorPacket(AbstractQueryProtocol.java:1694) ~[mariadb-java-client-2.7.4.jar:na]
	at org.mariadb.jdbc.internal.protocol.AbstractQueryProtocol.readPacket(AbstractQueryProtocol.java:1556) ~[mariadb-java-client-2.7.4.jar:na]
	at org.mariadb.jdbc.internal.protocol.AbstractQueryProtocol.getResult(AbstractQueryProtocol.java:1519) ~[mariadb-java-client-2.7.4.jar:na]
	at org.mariadb.jdbc.internal.protocol.AbstractQueryProtocol.executeQuery(AbstractQueryProtocol.java:318) ~[mariadb-java-client-2.7.4.jar:na]
	... 86 common frames omitted

전체 에러는 다음과 같다.

이 에러는 인코딩 문제가 제대로 정리되지 않아서 생기는 오류였다.
블로그에서 나온 대로 AWS에서 파라미터 그룹을 모두 utf8mb4, utf8mb4_general_ci 로 바꾸고 character_set_database, collation_connection을 수동으로 utf8mb4로 변경했더니 해결되었다

한 가지 헤맸던 점은 수동으로 변경해줄 때

ALTER DATABASE 데이터베이스명 CHARACTER SET = 'utf8mb4'
COLLATE = 'utf8mb4_general_ci';

저기 데이터베이스명을 RDS 데이터베이스명으로 생각했는데 그것 뿐만 아니라 Innodb까지 변경해줘야한다
또한 변경하자마자 바로 반영되는 것이 아니니 DB도 재접속해보고, 당연히 기존의 table은 drop한 뒤 새로 생성해주어야 한다


짠! 이리하여 REST API를 써서 CR이 되는 것을 확인했다.
오늘은 시간이 너무 늦어서 나머지 CRUD 기능은 다음에 더 해보고 내일은 안드로이드 연결까지 할 수 있었으면 좋겠다!

오늘의 나야 수고 많았다 🥰

좋은 웹페이지 즐겨찾기