728x90
반응형
728x90
반응형
반응형
  1. 인증서를 발급받고 FMN에서 통합인증서와 KEY 파일을 다운로드 받는다.
  2. 리눅스 서버로 파일을 업로드한다.
  3. nginx의 conf 파일에 해당 인증서 파일 경로를 변경한다. 혹은 인증서파일 자체를 교체한다.
    1. 경로 변경 시, nginx -t 명령어로 문법에 오류가 있는지 확인한다.
    2. passphrase 없는 인증서를 만들어서 적용; 이 때 서버 권한 확인
    3. sudo openssl rsa -in /nginx/conf/key.pem -out /nginx/conf/key_nopass.pem -passin pass:'passphrase'
  4. nginx -s reload 명령어로 변경한 내용을 적용한다.
  5. 브라우저에서 인증서 정보가 변경되었는지 (발급일, 만료일) 확인한다.

 

reload 하기 전 까지는 파일 이동/변경해도 잡히지 않음

728x90
반응형
반응형

 

@Getter
@Setter
public class Message {

    private Long    receiverSno;

    private String  receiverHid;

    private String  receiverNickname;

    private Long    senderSno;

    private String  senderNickname;

    public Message (User sender, User receiver) {
        this.setSenderSno(sender.getMemberNo());
        this.setSenderNickname(sender.getNickname());
        this.setReceiverSno(receiver.getMemberNo());
        this.setReceiverNickname(receiver.getNickname());    
    }
}

위와 같이 한 클래스 내의 변수에 접근하기 위해 위처럼 getter/setter를 써야 할지 아니면 직접 접근해야 할지 고민되었다.

기능상 하는 일이 같으니 상관없지 않을까 싶은 생각이 들었고, 혹시 다른 의견이 있나 싶었다.

대부분 private 변수인 경우 같은 클래스 내에서는 직접 접근을 선호하였다.

그 이유로는

  • 인스턴스가 내부 상태를 자체적으로 숨길 필요 없음. getter/setter는 내부 상세 구현을 가리는 외부 노출용(캡슐화)
  • getter/setter가 순수하게 그 일만 하는게 아니라 다른 일(validation 등)을 할 수도 있기에(override 할 경우) 예상과는 다르게 작동할 수 있음
  • 함수가 서로 의존하게되고 그 양이 많아지면 나중에 코드 수정이 복잡해짐
  • 가독성

 

혹시 성능상 차이가 있을까 싶어 더 찾아보았는데, 성능은 거의 똑같은 것으로 확인되었다.

 


참고

https://copyprogramming.com/howto/is-getter-method-call-to-access-variable-better-than-direct-variable-access-within-a-class

https://stackoverflow.com/questions/39767559/should-data-members-be-accessed-directly-or-using-getter-method

https://stackoverflow.com/questions/23931546/java-getter-and-setter-faster-than-direct-access

728x90
반응형
반응형

mysql8 이상으로 디비를 업그래이드를 하고 서버를 띄우면 아래 에러가 나면서 뜨지 않을 때가 있다..

Caused by: com.mysql.cj.exceptions.UnableToConnectException: Public Key Retrieval is not allowed

 

 

기존에 아마 useSSL = false 로 사용하고 있었을 건데, connection 맺을 때 아래 설정값이 추가로 필요하다.

allowPublicKeyRetrieval=true

이 값을 connection 정보에 추가하면 된다. 아래처럼..

jdbc:mysql://730a7c5e-9fb5-4~~~:13306/dbname?useUnicode=true&useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=UTF8&serverTimezone=Asia/Seoul

 

728x90
반응형
반응형

Map.getOrDefault(key, defaultValue)

key에 해당하는 값이 map에 있으면 그 값을 내리고 없으면 설정한 defaultValue를 내린다.

Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);

int valueA = map.getOrDefault("A", 0); // returns 1
int valueC = map.getOrDefault("C", 0); // returns 0

여기서 주의해야할 점은 map 값의 유무와 상관없이 default value를 생성한다는 것이다.

그래서 값이 있어도 의미없는 객체가 생성되어 메모리를 차지할 수 있다.

따라서 혹시 기본 객체를 반환하려고 한다면(new AAOBJECT()), 매번 생성하는 것보다 하나 생성해놓고 쓰는게 효율적이다.

 

Map.computeIfAbsent(key, mappingFunction)

key에 해당하는 값이 map에 있으면 그 값을 내리고

없거나 null이면 mappingFunction을 실행하여 값을 계산한다.

계산 결과를 다시 map에 저장(put)한다.

getOrDefault 함수의 대안으로 사용하는 경우가 있는데, 불필요한 값이 map에 저장될 수 있어 조심해야 한다.

또한 사용 시 UnsupportedOpertionException 이 발생할 수 있는데, 이는 map이 unmodifiable map 이기 때문이다. hashmap으로 생성된 맵은 괜찮은데 혹시 없을 때 기본 값으로 Collections.emptyMap() 을 선언했다면 해당 맵은 unmodifiable이라 해당 에러가 발생할 수 있다.

Map<String, Integer> map = new HashMap<>();
        
// If "A" is not present, compute a new value using the mapping function
int valueA = map.computeIfAbsent("A", k -> 42);

// If "B" is not present, compute a new value using the mapping function
int valueB = map.computeIfAbsent("B", k -> 100);

System.out.println("Value for A: " + valueA); // Output: Value for A: 42
System.out.println("Value for B: " + valueB); // Output: Value for B: 100
Map<String, List<String>> map = new HashMap<>();
map.computeIfAbsent("A", k -> new ArrayList<>()).add("Apple");
map.computeIfAbsent("B", k -> new ArrayList<>()).add("Banana");

// After the operations, the map will contain {"A"=["Apple"], "B"=["Banana"]}
String[] words = {"Hello", "world", "Java", "programming"};

Map<Integer, StringBuilder> sentenceMap = new HashMap<>();

for (String word : words) {
	sentenceMap.computeIfAbsent(word.length(), k -> new StringBuilder()).append(word).append(" ");
}

sentenceMap.forEach((length, sentence) -> System.out.println("Length " + length + ": " + sentence.toString().trim()));

//Length 4: Java
//Length 5: Hello world
//Length 11: programming
public class FibonacciExample {
    private static Map<Integer, Long> fibCache = new HashMap<>();

    public static void main(String[] args) {
        int n = 10;
        long fibonacci = computeFibonacci(n);
        System.out.println("Fibonacci(" + n + ") = " + fibonacci);
    }

    private static long computeFibonacci(int n) {
        return fibCache.computeIfAbsent(n, k -> (k <= 1) ? k : computeFibonacci(k - 1) + computeFibonacci(k - 2));
    }
}

 

putIfAbsent와 차이점 요약

  1. 값 생성 방식:
    • putIfAbsent: 이미 준비된 값을 Map에 넣기만 함. 값이 항상 미리 준비되어 있어야 함.
    • computeIfAbsent: 값이 필요할 때만 mappingFunction을 호출해 값을 계산하고 삽입함. 값 생성 비용이 클 때 유용함.
  2. 값의 조건적 생성:
    • putIfAbsent: 키가 존재하지 않으면 값을 그대로 추가.
    • computeIfAbsent: 키가 존재하지 않으면 주어진 함수로 값을 계산해 추가.
  3. 사용 용도:
    • putIfAbsent: 단순히 키가 없을 때 특정 값을 추가하고 싶을 때 사용.
    • computeIfAbsent: 값 생성이 비용이 크거나, 키에 따라 동적으로 값을 계산해야 할 때 사용.

성능 및 유용성 측면

  • **putIfAbsent**는 값이 미리 존재하는 경우에 적합하고, 이미 계산된 값을 단순히 Map에 추가할 때 사용됩니다.
  • **computeIfAbsent**는 값의 계산이 복잡하거나 키 기반으로 계산해야 할 경우 적합하며, 이 경우 성능 최적화를 위해 값 계산이 필요한 시점에서만 수행할 수 있어 유리합니다.

 

추가!

concurrentHashMap의 경우 computeIfAbsent는 thread safe 하다

값이 없을 때만 값을 추가하는 연산을 원자적으로 처리해야, 이를 위해 ConcurrentHashMap의 computeIfAbsent() 메서드를 사용하여 해당 연산을 하나의 원자적 작업으로 처리

String value = cache.get("gameSetting");
if (value == null) {
    cache.put("gameSetting", "newValue");
}

cache가 concurrentHashMap이어도 위 코드는 원자성이 부족하여 멀티스레드 환경에 race condition에 놓이게 되어 위험하다.

728x90
반응형
반응형

클라우드는 하드웨어가 어디에 위치해있고 어떻게 운영하는지에 따라 구분됨

  • public
    • 일반 사용자들; 인터넷만 있으면 바로 사용 가능
  • private
    • 보안; 거버넌스
    • 하나의 기업/조직의 선택된 사용자만 독점적으로 사용
    • 초기 하드웨어 구매 필요
    • private cloud 도입한 조직이 리소스 및 보안을 완전히 제어; 하드웨어 유지 관리 및 업데이트
    • 비용이 들긴 함
  • hybrid
    • private + public
    • 상황에 따라 유연하게
    • 네트워크 중요
  • multi 
    • 두 개 이상의 외부 클라우드를 활용
    • aws + azure 
    • 다른 리전보다 다른 클라우드 서비스 프로바이더(csp)를 선택, 특화된 서비스를 위해 혼용하기도

 

클라우드는 인프라 리소스를 제공받는 범위에 따라 아래처럼 구분됨

  • IAAS
    • 인프라, 서버, 저장소, 네트워킹 제공
  • PAAS
    • os, middle ware 까지 제공; 디비(RDS)
  • SAAS
    • 전체 서비스/상품 제공; 두레이
  • DAAS
    • desktop as a service
    • 시간/장소/기기에 상관없이 언제 어디서나 개인의 업무환경에 접속할 수 있는 환경을 제공

 

책임공유모델

  • 서비스 제공자(CSP)와 사용자가 함께 보안과 운영에 있어 각자 책임을 짐
728x90
반응형
반응형

 

junit5 mockito로 테스트 코드 작성 시 

사용하지 않는 given 절이 있다면 아래와 같은 에러를 만나며 테스트 실패 처리된다.

org.mockito.exceptions.misusing.UnnecessaryStubbingException:  Unnecessary stubbings detected.

사용하지 않는 given 절을 지우면 되긴 하는데..

공통으로 사용하고 싶어서 만든 가정절이라면(ex. clock mocking) 그래서 굳이..? 싶다면 아래와 같은 어노테이션을 클래스 단에 추가하면 된다.

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)  //추가
728x90
반응형
반응형

Spring Batch 소개

  • Accenture 와 Pivotal이 협업하여 개발한 배치 프레임워크.
  • 정보를 가공하여 통계를 생성 시
  • 매우 큰 데이터를 주기적으로 처리해야 할 때
    • 일반적으로 전체 로그 데이터를 수집하고 통합하는 과정이 필요함.
    • e.g : DAU, WAU, MAU, WoW, QoQ, YoY
      • DAU, WAU, MAU : access log 데이터 추출 + unique 유저 계산
  • 내부/외부 시스템으로부터 생산된 다양한 형태의 데이터를 통합해야 할 때
    • 두개 이상의 데이터를 원하는 형태로 가공한 뒤, 조합하여 원하는 데이터를 생산.
    • e.g. CTR(클릭률 : Click Through Rate), CPC (클릭당 비용 : Cost Per Click), CVR(전환율 : Conversion Rate)
      • CTR == 클릭수 / 노출수 == access log 데이터 추출
      • CPC == 광고 집행비용 / 클릭수 == 비용 테이블과 access log 데이터 추출
      • CVR == 전환 수 / 클릭수 == 전환 계산에 필요한 로그 또는 테이블 + access log 데이터.
  • 배치 프로그래밍?
    • 배치 프로그램은 여러개의 작업으로 구성
    • 작업은 어러개의 단계로 구성
    • c.f. 젠킨스 파이프 라이닝, 데이터 파이프 라인, Airflow DAG Pipeline
      • A -> B -> C
      • 추상화; 플로우 강제; 등의 framework
    • 같은 단계들의 반복.

 

Why Spring Batch? - 기술적 목표

  • 스프링 배치 프레임워크의 기능을 사용하여 비지니스 로직에 집중한다.
  • 즉시 사용할 수 있는 (Out-of-box) 실행 인터페이스를 제공
  • 인프라 계층(DB Reader, Queue reader ..)과 구분되어 있음.

Why Spring Batch?

  • 스프링 배치(Spring Batch)는 스케줄링 프레임워크가 아님.
  • 스케줄링은 다른 도움을 받아서 해야한다..
    • 스케줄링 프레임워크 : Quartz, Tivoli, Control-M, and others
    • 스프링 프레임워크 : @Scheduled
    • 리눅스의 Crontab
    • Jenkins, Rundeck etc.
    • 스프링 배치 프레임워크 애플리케이션은 스케줄러와 함께 작동하도록 설계됨.
  • 스프링 프레임워크가 제공하는 기능들
    • Transaction management
    • Chunk based processing
    • Start/Stop/Restart
    • Retry/Skip
    • Job 처리 통계
    • Web based administration interface (Spring Cloud Data Flow)
      • 따로 모듈이 있다.
728x90
반응형
반응형

대충 아래와 같이 수정

plugins {
    id 'org.springframework.boot' version '3.1.2'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
    id 'java'
    id "org.sonarqube" version "3.0"
    id 'jacoco'
}

ext {
    querydslVersion = '5.0.0'
    set('springCloudVersion', "2021.0.5")
}

...
sourceCompatibility = '17'
dependencies{ ...

 	implementation "com.querydsl:querydsl-jpa:${querydslVersion}:jakarta"
	annotationProcessor "com.querydsl:querydsl-apt:${querydslVersion}:jakarta"
	annotationProcessor "jakarta.persistence:jakarta.persistence-api"
	annotationProcessor "jakarta.annotation:jakarta.annotation-api"

	runtimeOnly 'com.mysql:mysql-connector-j'
    
    ...

 

1. 빌드 그래들 에러

PermittedSubclasses requires ASM9

gradle 7.2 -> 7.6로 업그래이드

 

2. javax.* -> jakarta.*

intellij를 이용해서 전체 변환

https://www.jetbrains.com/guide/java/tutorials/migrating-javax-jakarta/use-migration-tool/#:~:text=From%20the%20menu%2C%20navigate%20to,a%20preview%20of%20the%20refactorings.

 

Using IntelliJ IDEA's migration tool

Migrating to Java EE with IntelliJ IDEA's refactoring tool

www.jetbrains.com

 

2-1. 하지만 에러가 남..

DataSource는 javax로 둬야하는 듯..?

import javax.sql.DataSource;

 

3. mysql meta builder

MetadataBuilderContributor 가 deprecated 되어 사용불가 

FunctionContributor 를 사용해야하는 듯..

근데 아직 잘 모르겠음

 

4. webclient onStatus 함수 변경

boolean -> predicate<httpStatusCode> 로 변경됨

 

5. 시작하면 아래와 같은 에러가 나는데...

java.lang.ClassNotFoundException: javax.servlet.http.HttpServletRequest

아마도 springfox 스웨거 때문인 것 같다...

 

6. 스웨거 다 옮기면.. ehcache도 수정해야할 듯..? javax 사용하는데 문제없는지 확인 필요

728x90
반응형
반응형

build.gradle

plugins {
    id 'org.springframework.boot' version '2.7.6'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
    id 'java'
    id "org.sonarqube" version "3.0"
    id 'jacoco'
}

ext {
    querydslVersion = '5.0.0'
    set('springCloudVersion', "2021.0.5")
}

수정하고 빌드하고 프로그램 시작하면 아래와 같은 에러가 날 텐데..

org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; 
nested exception is java.lang.NullPointerException

application.properties에 아래 추가

spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER

테스트 돌릴 때 아래와 같은 에러가 나면.. 테스트 application.properties에도 잊지 말고 추가

Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang
.NullPointerException: Cannot invoke "org.springframework.web.servlet.mvc.condition
.PatternsRequestCondition.getPatterns()" because "this.condition" is null

원인

path maching 기본 전략이 2.6부터 ant matching -> path pattern으로 변경됨

AntPathMatcher는 간단하고 기존의 코드와의 호환성을 제공하는 반면, PathPattern은 성능과 유연성 측면에서 더 우수합니다. 스프링 부트 2.6.0부터 기본 매칭 전략으로 PathPattern이 도입되었으며, 보다 복잡한 URL 구조를 효율적으로 처리할 수 있도록 돕습니다.

 


 

h2 test 에러

springboot2.5.6; h2 1.4.200 

h2로 레파지토리 테스트를 하고 있다. 아래 entity가 있으면 테스트 실행 시 아래 쿼리가 자동으로 실행되면서 테이블이 생성된다.

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private BigInteger seq;
 create table `item_withdraw_log` (
       `seq` decimal(19,2) not null auto_increment,
       ...

이렇게 잘 사용하고 있었는데.. 버전업을 하고 테스트가 실패한다.

 

springboot2.7.6; h2 2.1.214

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private BigInteger seq;
Caused by: org.h2.jdbc.JdbcSQLFeatureNotSupportedException: Feature not supported: "DECIMAL(19, 2)"; SQL statement:

같은 create 쿼리를 실행하는데 decimal관련 에러가 난다.

 

mysql bigint는.. 꼭..?! 자바의 long으로 해야 하는 듯..

아니면 아래 어노테이션 추가...

@Column(precision = 100, scale = 0)
//100은 좀 심하고 디비의 최대 자릿수 정도로 하면 될 듯

허나.. 테스트를 위한 어노테이션은 혼동스러울 것 같아 BigInteger -> Long을 변환하는 게 나을 것 같다.

https://www.h2database.com/html/migration-to-v2.html

 

Migration to 2.0

Contents Introduction Upgrading File Format Data types Identity columns and sequences INFORMATION_SCHEMA General Introduction Between version 1.4.200 and version 2.0.202 there have been considerable changes, such that a simple update is not possible. It wo

www.h2database.com


jpa native query 에러

2.5.6 버전에서 사용하고 있던.. 잘 돌아가던 쿼리가 있다.

  @Query(value = "SELECT * FROM hd_user_leaderboard_data_log AS huldl " + "WHERE huldl.gid = :gid AND huldl.base_date > :baseDate "
      + "AND huldl.leaderboard_id IN :leaderboardIds " + "AND huldl.extra_data ->> '$.ArenaType' = :arenaType ORDER BY huldl.log_date DESC ",
         nativeQuery = true)
Page<UserLeaderboardDataLog> findAllByGidAndBaseDateAfterAndArenaTypeAndLeaderboardIdNotIn(String gid,
    LocalDateTime baseDate,
    String arenaType,
    Set<Integer> leaderboardIds,
    Pageable pageable);

 

컴파일 시 문제는 없었는데 서버 시작 후 api를 호출하니 아래와 같은 에러가 발생..

Caused by: org.hibernate.exception.SQLGrammarException: could not extract ResultSet
..
Caused by: java.sql.SQLSyntaxErrorException: Unknown column 'huldl' in 'field list'
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:120)
    ..

심지어 하이버네이트 실행 쿼리가 지나가고, 그걸 긁어서 db client 툴에 붙여 넣고 실행하면 잘 나온다.

사용하던 쿼리기도 하고 쿼리 자체에는 문제가 없다는 사실 확인..

그러면 왜 나는 거지..

해결 1.

에러 내용이 huldl 관련 에러라.. 우선 alias를 지워본다..

@Query(value = "SELECT * FROM leaderboard_data_log " + "WHERE gid = :gid AND base_date > :baseDate "
    + "AND leaderboard_id IN :leaderboardIds " + "AND extra_data ->> '$.ArenaType' = :arenaType ORDER BY log_date DESC ", nativeQuery = true)

성공..

해결 2.

다른 글을 보니 alias에 backtick(`)를 사용하면 된다고 해서.. 해봤는데 잘 된다..

@Query(value = "SELECT * FROM hd_user_leaderboard_data_log AS `huldl` " + "WHERE `huldl`.gid = :gid AND `huldl`.base_date > :baseDate "
    + "AND `huldl`.leaderboard_id IN :leaderboardIds " + "AND `huldl`.extra_data ->> '$.ArenaType' = :arenaType ORDER BY `huldl`.log_date DESC ",
       nativeQuery = true)

https://stackoverflow.com/questions/74055494/native-query-does-not-recognized-in-query-in-spring-data-jpa

 

Native query does not recognized in @Query in spring data jpa

When I define a query method in ContactRepository and write a native mysql query in @Query, it gives the following error. What is the reason? Can't I write an alias for the table? @Repository public

stackoverflow.com

해결 3.

alias를 asd로 바꿔보니 잘된다..ㅋㅋ 역시 huldl이 뭔가 있음

@Query(value = "SELECT * FROM hd_user_leaderboard_data_log AS asd " + "WHERE asd.gid = :gid AND asd.base_date > :baseDate "
    + "AND asd.leaderboard_id IN :leaderboardIds " + "AND asd.extra_data ->> '$.ArenaType' = :arenaType ORDER BY asd.log_date DESC ",
       nativeQuery = true)

 

아무래도 버전 2.7.6으로 바꾸면서 alias로 둔 huldl이 잘못 변환되거나 예약어 거나 그런 것 같다.

 

*) 참고로 native query = false 인 기본 jpa에서 콜롬/테이블 명에 나도 모르게 예약어를 사용하여 에러가 날 수도 있는데, 이를 방지하기 위해서 아래 설정을 추가하면 jpa에서 쿼리 만들어서 실행할 때 해당 이름에 알아서 backtick을 붙여서 날려준다.

spring.jpa.properties.hibernate.globally_quoted_identifiers=true

 


참고

https://chartio.com/learn/sql-tips/single-double-quote-and-backticks-in-mysql-queries/

 

Single Quote, Double Quote, and Backticks in MySQL Queries

Using Backticks, Double Quotes, and Single Quotes when querying a MySQL database can be boiled down to two basic points: Quotes (Single and Double) are used …

chartio.com

 

728x90
반응형
반응형

maven3부터는 repository와의 통신을 https만 지원한다.

하지만 사내 repository는 http로만 통신이 되어야 하고 https일 경우 에러가 나는 상황이라 난감하였다.

이것 때문에 maven2를 사용했었는데 intellij 최신버전에서는 maven2를 사용할 수 없다.

 

intellij에 별도의 메이븐을 설정한다.

번들이 아닌 별도의 메이븐을 사용해야만 문제해결이 쉽다.

해당 경로에 가서 settings.xml을 열고 아래 부분을 주석처리 한다.

해결!

728x90
반응형

+ Recent posts