728x90
반응형
728x90
반응형
반응형

환경: java11

 

요구사항: size 5인 array에 데이터를 넣고 없으면 빈 값으로 입력

String motionAvatar = "1022,1,,,"
int motionAvatarCount = 5;
String[] motionAvatarDetails = Stream
                                .concat(this.motionAvatar.split(","), Stream.generate(() -> ""))
                                .limit(motionAvatarCount)
                                .toArray(String[]::new);
                                
    //["1022", "1", "", "", ""]
  1. Stream.generate(() -> "") creates an infinite stream of empty strings.
  2. Stream.concat(...) concatenates the split array with the stream of empty strings.
  3. limit(motionAvatarCount) limits the concatenated stream to motionAvatarCount elements.
  4. toArray(String[]::new) converts the limited stream into an array of strings.

for loop 안돌고 세련된 방법으로 해보고 싶었는데 stream.generate 는 처음 봤다..

728x90
반응형
반응형

2024.02.04 - [서버 세팅 & tool/kafka] - [windows] kakfa connect; mariadb 설치

사용하게 된 배경: 기존의 서버가 h2 디비를 사용하여 1 인스턴스 - 1 디비를 사용하고 있었는데, 이렇게 되면 여러 인스턴스를 띄울 때 디비가 분리되어 있어서 디비 싱크가 안 맞는 문제가 발생. 그래서 그 h2 디비를 사용하지 않고 kafka source/sink connect를 이용하여 kafka에 연결된 maria 디비(단일 디비)로 사용하려는 것.

 

connect source -> connect sink 연동하기 (db to db)

kafka source connect 추가; 마리아디비 사용

connect source는 8083 포트 사용

echo '{
"name" : "my-source-connect",
"config" : {
	"connector.class" : "io.confluent.connect.jdbc.JdbcSourceConnector",
	"connection.url":"jdbc:mariadb://localhost:3306/mydb",
	"connection.user":"root",
	"connection.password":"1234",
	"mode": "incrementing",   //id 자동 증가
	"incrementing.column.name" : "id",
	"table.whitelist":"users",  // 변경사항 감지할 테이블
	"topic.prefix" : "my_topic_", //저장할 곳의 prefix
	"tasks.max" : "1"
	}
}

' | curl -X POST -d @- http://localhost:8083/connectors --header "content-Type:application/json"

데이터 전달 후 

상세를 확인해 봤는데.. 에러가 났었음

kafka connect 쪽 로그를 보니 같은 테이블 명이 여러 개라고..

 다른 데이터베이스에 있더라도 같은 이름의 테이블이면 안되는 듯..

테이블명을 바꾸거나 아래처럼 수정

다시 상태 확인 시 정상

 

이제 users 테이블에 데이터를 넣어주면 kafka에 해당 topic이 생긴 것을 확인할 수 있다.

consumer 도 확인해 보면 아래처럼 하나 추가된 것 확인

이 상태에서 insert 한번 더 해주면 consumer에 바로 다음 데이터가 추가되는 것을 볼 수 있다.

이 json을 포매터로 확인해 보면 다음과 같다.

fields에 필드에 대한 설명이 있고 payload에 실제 데이터가 들어있다. 나중에 topic을 이용하여 디비에 데이터를 저장하려고 하면 아래 포맷처럼 전달하여야 한다.

kafka sink connect 추가

echo '{
"name":"my-sink-connect",
"config":{
	"connector.class":"io.confluent.connect.jdbc.JdbcSinkConnector",
	"connection.url":"jdbc:mariadb://localhost:3306/mydb",
	"connection.user":"root",
	"connection.password":"1234",
	"auto.create":"true",  //디비와 연결하는데, 토픽과 같은 테이블 쓰겠다는 의미
	"auto.evolve":"true",
	"delete.enabled":"false",
	"tasks.max":"1",
	"topics":"my_topic_users"  //여기랑 연결
	}
}

'| curl -X POST -d @- http://localhost:8083/connectors --header "content-Type:application/json"

sink connect 생성 확인

이 상태에서 디비에 my_topic_users라는 sink table이 잘 생성되었는지 확인

이후에 users에 데이터를 입력하면 자동으로 my_topic_users에도 똑같이 쌓이는 것 확인

consumer 로그에도 해당 로그 잘 쌓임

디비에 직접 데이터를 추가하는 것 말고도 producer에 위 포맷으로 데이터를 전송하면 동일한 효과를 낼 수 있다.

이후에 users 테이블을 확인하면 해당 데이터는 없고, my_topic_users 테이블 확인하면 있는 것을 확인.

users(source) -> my_topic_users(sink) 테이블의 방향으로 연결되어 있는데, 이번에는 producer를 통해 직접 my_topic_users에 넣어줬으니 users 테이블에는 없는 것.

728x90
반응형

'서버 세팅 & tool > kafka' 카테고리의 다른 글

[windows] kakfa connect; mariadb 설치  (0) 2024.02.04
[windows] kafka 실행  (0) 2024.02.04
반응형

환경: windows11, java17, springboot2.7.6

 

kakfa connect를 통해 데이터를 import/export 가능

코드 없이 configuration으로 데이터 이동 가능

Standalone mode, distribution mode 지원

  • restfult API 지원
  • stream, batch 형태로 데이터 전송 가능
  • 커스텀 connector를 통한 다양한 플러그인 제공(파일, mysql, 등)

 

테스트를 위한 mariaDB 다운로드(in windows)

참고로 마리아디비는 mysql과 코어를 같이 사용한다.

디비 생성을 위해 cmd로 접근

소스에 아래 추가

<dependency>
    <groupId>org.mariadb.jdbc</groupId>
    <artifactId>mariadb-java-client</artifactId>
    <version>3.1.4</version>
</dependency>

소스 띄우고 h2 콘솔로 아래 입력

cool


MariaDB 설치

MacOS

$ brew install mariadb

시작, 종료, 상태확인

$ mysql.server start, mysql.server stop, mysql.server status

접속

$ mysql –uroot

데이터베이스 생성

mysql> create database mydb;

Access denied 발생 시

$ sudo mysql –u root

mysql> use mysql;

mysql> select user, host, plugin FROM mysql.user;

mysql> set password for 'root'@'localhost'=password('test1357’);

mysql> flush privileges;

Windows)

다운로드

mariadb-10.5.8-winx64.zip 파일 다운로드

데이터베이스 초기화

.\bin\mariadb-install-db.exe 

    --datadir=C:\Work\mariadb-10.5.8-winx64\data 

    --service=mariaDB 

    --port=3306 

    --password=test1357


kafka connect 다운로드

http://packages.confluent.io/archive

들어가서 confluent community 7.5.3.zip 다운로드, 압축 해제

 

jdbc connector 다운로드

https://www.confluent.io/hub/confluentinc/kafka-connect-jdbc

 

kafka connect 실행

에러 발생 시 아래처럼 실행 파일 수정

마리아 디비 jar 파일을 kafka connect 폴더 아래로 이동

다시 실행. 그래도 에러

해당경로에 프로퍼티 파일이 없어서 나는 에러로 bat파일을 열어 경로 수정한다.

그리고 다시 실행하면 성공.

성공 후 토픽 리스트를 다시 조회하면, kafka connect 가 필요한 토픽이 추가된 것을 확인할 수 있다.

 

728x90
반응형

'서버 세팅 & tool > kafka' 카테고리의 다른 글

[windows] kakfa connect 연동  (0) 2024.02.04
[windows] kafka 실행  (0) 2024.02.04
반응형

환경: 윈도우11, kafka2.13-3.6.1

 

카프카는 공홈에서 받아주고, 압축을 풀어준다.

카프카/주키퍼 실행 시 아래와 같은 에러 발생한다.

확인해보니 kafka-server-start.bat실행 시 같은 폴더 안에 있는 kafka-run-class.bat를 참조하여 같이 실행시키는데 여기서 너무 많은 classpath를 호출하여 문제가 생기는 것. 윈도우 cmd는 명령줄에 8192글자까지 제한된다고 함..

 

해결방법은 보통 아래와 같이 두가지 방법이 있는 것 같은데,  실행 파일을 수정해 봤는데도 에러가 났음..

  1. kafka 폴더를 C드라이브 바로 밑으로 옮긴후에 실행 + 폴더명 변경
  2. 실행파일 수정

https://stackoverflow.com/questions/48834927/the-input-line-is-too-long-when-starting-kafka/50168530#50168530

 

The input line is too long when starting kafka

I'm trying to run Kafka message queue on Windows. I'm usin this tutorial - https://dzone.com/articles/running-apache-kafka-on-windows-os When i try to run it with comand - .\bin\windows\kafka-ser...

stackoverflow.com

 

그래서 우선 폴더를 C > Program files 아래에 두고, 파일 이름까지 바꿨는데도 또 에러 발생.

진짜 개복치

그래서 C > windows 아래로 옮겼더니 된다.. 진짜 말이 되나

주키퍼 포트: 2181

카프카 포트: 9092

 

 

카프카가 제공하는 샘플 데이터 producer/consumer

토픽에 데이터를 쌓아두면 그걸 구독하는 컨수머가 가져가는 방식

9092 카프카 서버에 토픽 생성; 멀티 클러스터링 시 파티션 값 사용 

windows의 경우 아래 명령어의 .sh 대신 .bat로 바꿔서 실행해야 함!

메시지 생산

$KAFKA_HOME/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic quickstart-events

메시지 소비

$KAFKA_HOME/bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic quickstart-events --from-beginning

 

실제 실행 시..

728x90
반응형

'서버 세팅 & tool > kafka' 카테고리의 다른 글

[windows] kakfa connect 연동  (0) 2024.02.04
[windows] kakfa connect; mariadb 설치  (0) 2024.02.04
반응형

환경: springboot2.7.6, java 17, springcloud 2021.0.8

이전글:

2024.01.29 - [개발/spring] - [cloud] 파일을 동적으로 관리하는 config server

2024.01.29 - [개발/spring] - [cloud] spring cloud bus

spring cloud config service에서 파일로 관리되는 설정파일을 공용으로 쓰도록 설정되어 있고(native방식)

해당 설정파일에는 디비 연결 정보가 들어있어 password와 같은 민감정보가 있다.

파일로 관리되기 때문에 파일이 노출되면 민감정보가 바로 드러나게 되는데, 이를 암호화해서 관리해보자.

 

비대칭키 사용

1. config 서버에 암복호화 추가

config server에 bootstrap.yml을 만들고 암호화에 사용할 대칭키를 입력한다.

encrypt:
  key: abcdefghijklmnopqrstuvwxyz0123456789

암호화 확인

복호화 확인

 

2. 공용으로 관리하는 설정 파일(bootstrap.yml에 명시된 name)에 아래와 같이 내용 추가

password는 plain으로 sa인데 위의 암호화 api로 암호화 한 값을 넣었

spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:test
    username: sa
    password: '{cipher}07407f328f8c2ba016949743f42e0a9da120328d9e0f44f38bad351b9c2b7419'

 

참고로 설정 서버에 직접 파일을 확인하면 아래와 같이 복호화된 값이 나온다. 이 값이 cloud bus 로 연결된 사용처들에게 전달되는 값이다.

참고로 프로퍼티에 암호화된 값이 잘못되었을 경우 위의 password 부분에 n/a라고 표시된다.

 

대칭키 사용

RSA 키쌍 생성

keytool -genkeypair -alias apiEncryptionKey -keyalg RSA -dname "CN=Kenneth Lee, OU=API Development, O=joneconsulting.co.kr, L=Seoul, C=KR"  -keypass "1q2w3e4r" -keystore apiEncryptionKey.jks -storepass "1q2w3e4r"

keytool -genkeypair -alias apiEncryptionKey -keyalg RSA -dname "CN=Kenneth Lee, OU=API Development, O=joneconsulting.co.kr, L=Seoul, C=KR"  -keypass "1q2w3e4r" -keystore apiEncryptionKey.jks -storepass "1q2w3e4r"

private key 확인

 keytool -list -keystore apiEncryptionKey.jks -v

인증서 가져오기

 keytool -export -alias apiEncryptionKey -keystore apiEncryptionKey.jks -rfc -file trustServer.cer

공개키 가져오기

keytool -import -alias trustServer -file trustServer.cer -keystore publicKey.jks

확인

테스트: 위와 동일한 방법

위와 동일하게 파일에는 암호화 값이 있고 사용시에는 자동으로 복호화 해준다.

728x90
반응형
반응형

환경: windows11 프로, 64비트

 

윈도우에서 rabbitmq 설치 시 erlang을 필수로 설치해야한다.

딱히 호환되는 버전 지정 없이, 최신버전들로 설치해도 문제없다.

 

powershell 관리자 권한 실행

  • ctrl + r 
  • powershell 입력
  • ctrl + shift + enter

명령어

관리자 권한 필요: rabbitmq-service stop, rabbitmq-service start

 

특이사항

윈도우에서 설치하고 GUI(화면)로 관리하려면 rabbitmq_management라는 플러그인을 enable 시켜야하고, 그 후에는 위 명령어로 서비스 재시작을 해야한다.

또한 해당 GUI를 확인하기 위해 인터넷 창에서 http://127.0.0.1:15672/#/를 실행할 경우, 실행되지 않는데,,

아래처럼 윈도우 방화벽을 열어야 한다. 세상에..

https://velog.io/@yeseong31/RabbitMQ-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%8B%A4%ED%96%89

 

velog

 

velog.io

 

rabbitmq는 시스템 기동 시 같이 실행되기 때문에 서비스 화면에서 떠있는지 확인하면 된다.

728x90
반응형
반응형

환경: springboot2.7.6, java 17, springcloud 2021.0.8

 

사용 이유:

분산 시스템의 노드(서비스들)를 경량 메시지 브로커(rabbit mq)와 연결하여 상태 및 구성에 대한 변경 사항을 연결된 노드에게 전달(broadcast)한다.

즉 중간에 메세지를 전달하는 미들웨어(AMQP; 메시지 지향 프로토)를 둠으로써 안정적으로 변경사항을 적용하도록 함

 

사용 법:

우선 기존에 spring cloud config 서버가 있어야 하고 거기서 설정파일을 가져오게끔 bootstrap도 추가되어 있어야 한다.

아래 글 참고.

2024.01.29 - [개발/spring] - [cloud] 파일을 동적으로 관리하는 config server

버스로 연결하려는 모든 프로젝트에 아래와 같이 디펜덴시 추가한다. 수정 시 확산을 위해 actuator도 필요하다.

<!-- 설정파일 외부  -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

<!-- actuator -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator </artifactId>
</dependency>
<!-- spring cloud bus -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp </artifactId>
</dependency>
management:
  endpoints:
    web:
      exposure:
        include: busrefresh

actuator 설정에 busrefresh 추가

연결된 서비스 중 아무데서나 위 api를 호출하면 204 성공이 떨어지고 spring cloud config 서버를 통해 받고 있던 데이터들이 전 서비스에 걸쳐 모두 한 번에 refresh 되는 것을 볼 수 있다. (이전에는 각각 /actuator/refresh를 호출했어야 했음)

728x90
반응형
반응형

환경: springboot2.7.6, java 17, springcloud 2021.0.8

 

application.properties/yml과 같은 설정파일을 수정하면 서버를 재시작해야 한다는 부담이 있다.

spring cloud에서 제공하는 config server를 이용하면 설정 파일을 수정해도 재시작하지 않고, 동적으로 값을 읽어 올 수 있다.

 

공용 프로퍼티 설정 서비스 만들기(이하 config service라고 명명)

1. 프로퍼티 파일을 외부로 빼준다. 보통 공통 값(디비, API url, 공통으로 사용하는 값 등)을 뺀 파일을 외부에 생성한다.

여기서는 documents아래에 임의의 폴더를 생성하여 만들었다.

참고로 윈도우로 작업하였다.

 

2. 이 파일을 추적할 서버를 새로 만든다. 아래와 같이 디펜덴시를 추가하고 어노테이션을 단다.

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>
@EnableConfigServer
@SpringBootApplication

그리고 이제 추적할 파일에 대한 정보를 프로퍼티 파일에 작성한다.

server:
  port: 8888

spring:
  application:
    name: config-service
  cloud:
    config:
      server:
        git:
          uri: file:///Users/JIHYUN BANG/Documents/source/inflearn/git-local-repo
     	  default-label: main

파일을 바로 볼거라 file:// 로 시작하는데 깃으로 관리한다면 아래 처럼 해당 주소를 기입하면 될 것 같다.

        git:
          uri: https://github.com/haileyjhbang/inflearn-config.git
         # username: bbb //private repository 일 경우 필요
         # password: aaa //private repository 일 경우 필요
     #  uri: file:///Users/JIHYUN BANG/Documents/source/inflearn/git-local-repo

여기서 주의해야하는 것은 파일 경로를 알기 위해 아래와 같이 명령어를 입력할 경우 (윈도우의 경우) 아래와 같이 C드라이브 아래부터 나오는데, /c 를 제외하고 /Users 부터 입력해야 한다는 것.

$ pwd
/c/Users/JIHYUN BANG/Documents/source/inflearn/git-local-repo

 

2-1. 파일 시스템으로 프로퍼티들을 관리할 경우 profiles.active: native

spring:
  application:
    name: config-service
  profiles:
    active: native
  cloud:
    config:
      server:
        native:
          search-locations: file:///${user.home}/Documents/source/inflearn/native-repo

 

3. 서버를 시작하고 아래 주소로 확인해 보면 ecommerce.yml의 내용이 화면에 찍힌다.

http://localhost:8888/ecommerce/default

 

4. 허나 로그를 보면 아래와 같은 에러가 지나가는데

org.springframework.cloud.config.server.environment.NoSuchLabelException: No such label: main
	at org.springframework.cloud.config.server.environment.JGitEnvironmentRepository.refresh(JGitEnvironmentRepository.java:307) ~[spring-cloud-config-server-3.1.8.jar:3.1.8]
	at org.springframework.cloud.config.server.environment.JGitEnvironmentRepository.getLocations(JGitEnvironmentRepository.java:256) ~[spring-cloud-config-server-3.1.8.jar:3.1.8]
	at org.springframework.cloud.config.server.environment.MultipleJGitEnvironmentRepository.getLocations(MultipleJGitEnvironmentRepository.java:139) ~[spring-cloud-config-server-3.1.8.jar:3.1.8]
...

Caused by: org.eclipse.jgit.api.errors.RefNotFoundException: Ref main cannot be resolved
	at org.eclipse.jgit.api.CheckoutCommand.call(CheckoutCommand.java:223) ~[org.eclipse.jgit-5.13.1.202206130422-r.jar:5.13.1.202206130422-r]
	at org.springframework.cloud.config.server.environment.JGitEnvironmentRepository.checkout(JGitEnvironmentRepository.java:461) ~[spring-cloud-config-server-3.1.8.jar:3.1.8]
	at org.springframework.cloud.config.server.environment.JGitEnvironmentRepository.refresh(JGitEnvironmentRepository.java:300) ~[spring-cloud-config-server-3.1.8.jar:3.1.8]
	... 69 common frames omitted

2024-01-29 14:01:10.993  INFO 20748 --- [nio-8888-exec-1] .c.s.e.MultipleJGitEnvironmentRepository : Will try to refresh master label instead.
2024-01-29 14:01:11.218  WARN 20748 --- [nio-8888-exec-1] .c.s.e.MultipleJGitEnvironmentRepository : Could not merge remote for master remote: null

내용을 살펴보면 git-local-repo\ecommerce.yml 가 깃으로 관리되는데 main 브랜치가 없다는 것이다.

다시 보니 기본 브랜치가 master로 되어 있었고

설정파일을 아래처럼 수정하면 에러가 사라진다.

spring.cloud.config.server.git.default-label: master

 

5. 신기한 점은 git uri에  file://로 설정할 경우 해당 파일 경로가 remote 깃과 연결되지 않았을 때는 파일 저장만으로도 바로 반영이 되었는데, 

remote 깃과 연결한 후에는 파일을 수정하여 저장하더라도 자동으로 git에서 파일을 가져와서 override 하기 때문에 파일이 수정되지 않는다. 그 후에는 깃에 커밋하고 푸시해야만 반영됨.

즉, 파일시스템으로 프로퍼티를 관리하기위한 제대로된 방법은 native를 사용하는 것이다.

file://인 경우 ecommerce.yml이 반영되려면 깃에 커밋을 하지 않고도 저장만 하면 바로 반영이 된다. 즉, 저장을 하고 http://localhost:8888/ecommerce/default 를 호출하면 서버 재시작 없이도 변경된 내용을 확인할 수 있음. 물론 이는 지금 깃이 아닌 파일을 바라보도록 했기 때문.. 깃으로 수정했다면 remote push까지 해야한다. <<-깃에 연결 전

 

 

공용 설정 가져오는 서버 설정 추가(사용처; 이하 user service라고 명명)

 

1. 사용처에 아래 디펜덴시 추가

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

2. 공용으로 관리하기로 한 정보는 설정파일에서 없애고 resources/ 아래에 bootstrap.yml 추가(이름 꼭 확인)

해당 파일은 프로젝트에서 별도로 가지고 있는 application.yml 보다 먼저 로딩된다.

로딩이 그렇다는 것이고 적용 우선 순위는 아래와 같다.

 application.yml   application-`name`.yml   application-`name`-<profile>.yml

즉, bootstrap.yml로 로딩된 ecommerce.yml 은 위 순서에서 application-ecommerce.yml으로 인식되어 두번째 우선순위를 갖는다.

# application.yml에서 일부를 띠어서 공용으로 관리하기로 했기 때문에
# application.yml 이전에 불러올 정보를 가져올 프로퍼티임
spring:
  cloud:
    config:
      uri: http://127.0.0.1:8888
      name: ecommerce   #파일명.yml 일 때 파일명 작성

3. 설정 후 서버 시작 시 아래 로그 지나감 확인

INFO 24156 --- [  restartedMain] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://127.0.0.1:8888
INFO 24156 --- [  restartedMain] c.c.c.ConfigServicePropertySourceLocator : Located environment: name=ecommerce, profiles=[default], label=null, version=4c8198ea3c95ce84f573a78f41b637a4da81ad49, state=null
INFO 24156 --- [  restartedMain] b.c.PropertySourceBootstrapConfiguration : Located property source: [BootstrapPropertySource {name='bootstrapProperties-configClient'}, BootstrapPropertySource {name='bootstrapProperties-file:///Users/JIHYUN BANG/Documents/source/inflearn/git-local-repo/file:/C:/Users/JIHYUN BANG/Documents/source/inflearn/git-local-repo/ecommerce.yml'}]

 

4. 혹시 실행 시 아래 에러를 만나면, (그래도 실행은 잘 되지만) 프로퍼티 수정이 필요하다.

2024-01-29 15:29:08.527 ERROR 20628 --- [  restartedMain] o.a.catalina.session.StandardManager     : Exception loading sessions from persistent storage

java.io.EOFException: null
	at java.base/java.io.ObjectInputStream$PeekInputStream.readFully(ObjectInputStream.java:2926) ~[na:na]
...
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) ~[spring-boot-2.7.6.jar:2.7.6]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) ~[spring-boot-2.7.6.jar:2.7.6]
	at com.example.userservice.UserServiceApplication.main(UserServiceApplication.java:18) ~[classes/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[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:568) ~[na:na]
	at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) ~[spring-boot-devtools-2.7.6.jar:2.7.6]

해당 에러는 Tomcat의 SESSIONS.ser 파일이 없을 때 나는 에러로 추정되며, 해당 파일은 Tomcat이 애플리케이션 세션 상태를 저장하는 데 사용된다. (참고로 현 프로젝트는 spring-boot-starter-web 안의 tomcat을 사용 중)

세션 지속성(persistent session)은 웹 서버가 다운되거나 재시작될 때 사용자 세션 정보를 유지하는 기능인데 이를 통해 서버가 재시작된 후에도 사용자가 이전의 세션을 계속 사용할 수 있음.

세션 지속성을 '끈다'는 것은 이러한 세션 정보의 유지를 비활성화하는 것을 의미한다. 즉, 서버가 다운되거나 재시작되면 모든 사용자 세션 정보가 손실되는 것이다. 이 기능은 개발 과정에서 테스트나 디버깅을 쉽게 하기 위해 사용되곤 하는데, 서비스의 안정성이나 사용자 경험을 위해서는 일반적으로 세션 지속성이 활성화되어 있는 것이 좋다.

spring boot의 경우 내장 tomcat을 사용하기 때문에 직접적인 tomcat 설정 파일을 수정할 수 없음

따라서 에러를 없애기 위해서 application.yml에 아래 설정을 추가(기본적으로 true로 세팅됨)

server:
  servlet:
    session:
      persistent: false

 

5. 공용 설정파일인 ecommerce.yml 파일을 동적으로 가져오려면

위의 config service에서는 파일 커밋 시 바로 반영된 것을 확인할 수 있었으나, 사용처에서는 바로 확인이 안 된다는 것을 알 수 있다. 동적으로 가져오려면 아래와 같은 방법이 있다.

  1. 사용처를 재기동
  2. actuator refresh api 이용
  3. spring cloud bus 사용

1번 방법은.. 매우 비효율적

 

5-1. actuator refresh 사용

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator </artifactId>
</dependency>
management:
  endpoints:
    web:
      exposure:
        include: refresh, health, beans

재시작 후 ecommerce.yml을 수정하고 저장하면

config service에 반영된 것 확인되고 아래처럼 POST 요청하면 서버가 리로드 되면서 수정된 사항이 반영된다.

다만 actuator refrest를 사용할 경우 해당 프로퍼티를 바라보는 모든 서비스들에 대해 실행해줘야 하기 때문에.. 만약 관련 서비스들이 많다면 여간 번거로운 작업이 아닐 수 없다. 하여 보통은 spring cloud bus를 사용한다.

 

5-1-1. 여러 profile 사용

위와 같이 여러 환경 프로퍼티 파일이 있을 때 사용처에서 아래와 같이 설정하면 해당 환경으로 가져온다.

spring:
  cloud:
    config:
      uri: http://127.0.0.1:8888
      name: ecommerce
  profiles:
    active: prod

-> ecommerce-prod.yml

spring:
  cloud:
    config:
      uri: http://127.0.0.1:8888
      name: ecommerce
  profiles:
    active: dev

-> ecommerce-dev.yml

참고로 해당 값은 아래의 active profile에 dev로 적어도 같은 효과이다.

혹은 VM options에 아래와 같이 작성한다.

-Dspring.profiles.active=dev

 

환경별 프로퍼티 파일을 추가하고 config server를 다시 불러오면 아래와 같이 해당 환경의 프로퍼티 파일과 default 파일을 모두 불러오는 것을 볼 수 있다.

728x90
반응형
반응형

환경: java 17, springboot 2.7.6

 

아래 라이브러리를 사용하여 JWT토큰 파싱할 때 에러날 경우

소스:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
subject = Jwts.parser().setSigningKey(environment.getProperty("token.secret"))
        .parseClaimsJws(jwt).getBody()
        .getSubject();

에러:

java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter
	at io.jsonwebtoken.impl.Base64Codec.decode(Base64Codec.java:26) ~[jjwt-0.9.1.jar:0.9.1]
	at io.jsonwebtoken.impl.DefaultJwtParser.setSigningKey(DefaultJwtParser.java:151) ~[jjwt-0.9.1.jar:0.9.1]
	at com.example.gatewayservice.filter.AuthorizationFilter.isValidJwt(AuthorizationFilter.java:54) ~[classes/:na]

 

해결:

아래 디펜덴시를 추가하면 해결

    <dependency>
        <groupId>javax.xml.bind</groupId>
        <artifactId>jaxb-api</artifactId>
        <version>2.3.1</version>
    </dependency>

 

https://medium.com/@jiri.caga/java-lang-classnotfoundexception-javax-xml-bind-datatypeconverter-in-java-17-solved-499d0ea776d0

728x90
반응형
반응형

환경: springboot 2.7, java11

현 상황: 외부 api가 아래와 같은 json으로 전달될 때 서버에서 해당 값을 받아서 내려야 함

    "isBan": true,
    "isPaidUser": false,

 

1.  primitive + is~ + no jsonproperty

private boolean isPaidUser;

private boolean isBan;

////// getter/setter모양

 public boolean isPaidUser() {
      return isPaidUser;
    }

    public void setPaidUser(boolean paidUser) {
      isPaidUser = paidUser;
    }

    public boolean isBan() {
      return isBan;
    }

    public void setBan(boolean ban) {
      isBan = ban;
    }

결과

: 옳지 않은 값 반환

 

2. wrapper + no is + no jsonproperty

private Boolean isPaidUser;

private Boolean ban;

////// getter/setter모양

   public Boolean getPaidUser() {
      return isPaidUser;
    }

    public void setPaidUser(Boolean paidUser) {
      isPaidUser = paidUser;
    }

    public Boolean getBan() {
      return ban;
    }

    public void setBan(Boolean ban) {
      this.ban = ban;
    }

: null 반환

 

3. wrapper + is~ + no jsonproperty

  private Boolean isPaidUser;

  private Boolean isBan;
  
////// getter/setter모양

  public Boolean getPaidUser() {
    return isPaidUser;
  }

  public void setPaidUser(Boolean paidUser) {
    isPaidUser = paidUser;
  }

  public Boolean getBan() {
    return isBan;
  }

  public void setBan(Boolean ban) {
    isBan = ban;
  }
}

: wrapper class 사용 시 jsonproperty 없어도 알맞은 값 반환, 그 이유는 boolean의 경우 자동으로 is를 자동으로 삭제하여 판단하기 때문 getter/setter 생김새 참고

 

4. primitive + is ~ + with jsonproperty

@JsonProperty("isPaidUser")
private boolean isPaidUser;

@JsonProperty("isBan")
private boolean isBan;

////// getter/setter모양

//자동 생성
    public boolean isBan() {
      return isBan;
    }

    public void setBan(boolean ban) {
      isBan = ban;
    }
    
// jackson lib에서 사용하는 getter
    public boolean getIsBan(){
      return isBan;
    }

: JsonProperty를 사용하게 되면 설정한 IsBan의 함수를 찾아서 내리고

@Getter로 자동생성된 isBan에서 ban이 추가적으로 내려옴??? 왜 이놈만 두 번 내려올까..

=> 정확한 원리는 모르겠음

 

5. primitive + is~ + getter override

private boolean isPaidUser;
private boolean isBan;

////// getter/setter모양

    public boolean getIsBan() {
      return isBan;
    }

    public boolean getIsPaidUser() {
      return isPaidUser;
    }

: getter를 getIs~ 로 작성해주면 해결

 

6. primitive + no is + with jsonproperty

@JsonProperty("isPaidUser")
private boolean paidUser;

@JsonProperty("isBan")
private boolean ban;

: set할 때는 paidUser를 찾아 세팅하고 내릴 때는 자동 생성되는 getter사용

: 해결

 

정리

When using Jackson for JSON serialization and deserialization, it recognizes this convention and automatically maps it to the property name without the "is" prefix.

If you annotate a getter method with @JsonProperty, Jackson will use the annotated method as the getter for that property.

 

json의 isEnable 값을 기본적으로는 enable 값에 꽂음

enable -> getter 사용해서 내림..

아직 완벽하게 이해한게 아니라 좀 더 보완해야 한다..

 

결론

1. Wrapper class

  • is~ 로 필드명 주기

2. primitive type

  • is 없이 필드명 주고 JsonProperty 사용하기
  • is 로 필드명 주고 getIs~로 getter override
728x90
반응형

+ Recent posts