728x90
반응형
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
반응형
반응형

 

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
반응형
반응형

 

메이븐 pom.xml에서 자꾸 annotationProcessorPaths 에러가 난다.

다른 예시에서는 된다고 하는데 왜 나만 안 되는 것인지 의아했는데..

해결은 의외뢰 간단했다. 메시지가 그저 불편하게 나왔을 뿐.

<version>3.8.1</version>

해당 plugin 에 버전이 있는지 확인해 보고, 없을 경우 넣도록 하자!!!!!

728x90
반응형

'개발 > java' 카테고리의 다른 글

[map] getOrDefault vs computeIfAbsent  (0) 2023.12.20
[pom] http로 repository 연결  (0) 2023.10.23
[java] orElse vs orElseGet  (0) 2023.10.11
디자인 패턴  (0) 2023.07.11
[이슈해결] NPE at HSSFSheet.autoSizeColumn  (0) 2023.06.28
반응형

환경: springboot2.5+, mysql

 

일반적으로 테이블은 아래와 같이 camel case 사용한다.

그래서 spring에 아래의 설정을 적용하고 엔티티에 camel case로 콜롬을 받는다.

(springboot3 기준)

spring.jpa.properties.hibernate.physical_naming_strategy=org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy
spring.jpa.properties.hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy

(springboot2 기준)

spring.jpa.properties.hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
spring.jpa.properties.hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy

(entity)

@Id
private BigInteger idSeq;
private String configType;
private String configName;
private LocalDateTime startDate;
private LocalDateTime endDate;
private String data;
private Short active;
private String regAdmin;
private String modAdmin;
private LocalDateTime regDate;
private LocalDateTime modDate;

 

 

근데 진짜 간혹 가다 아래와 같이.. camel case가 든 테이블이 섞여있는 경우가 있다.

이 entity의 column은 어떻게 정의해야 할까?

그래서 제일 먼저 생각나는 @Column을 이용해 재정의를 해본다.

@Column(name = "startTimeMillis")
private BigInteger startTimeMillis;
@Column(name = "expiryTimeMillis")
private BigInteger expiryTimeMillis;
@Column(name = "autoRenewing")
private String autoRenewing;

하지만 physical 설정은 맨 마지막에 적용되기 때문에..

startTimeMillis로 가져오라고 명명한 콜롬 역시 start_time_millis로 디비를 조회하여 에러가 난다.

그럼 어떻게 하지? 이 몇 개의 테이블 때문에 위 hibernate strategy를 포기하자니,, 모든 콜롬명을 재정의할 자신이 없다..

(PhysicalNamingStrategyStandardImpl 로 사용해야 할 것 같은데 이러면 기존 자바 속성값을 다 @Column(name= 언더스코어네임)으로 지정해야 하는 불편함이 생긴다..)

 

해결

쿼리를 짤 때 대소문자 구분이 없다는 사실이 떠올랐다.

즉, rewardCount, rewardcount, REWARDCount 건 쿼리는 잘 실행된다.

디비의 camel case 또한 사람의 눈에는 camel case 지만 컴퓨터한테는 별 의미 없는 구분인 것이다.

그래서 아래와 같이 @Column을 명명하고 사람의 눈에는 보기 좋아야 하니까 변수명에는 camel case로 구분하였다.

@Column(name = "starttimemillis")
private BigInteger startTimeMillis;
@Column(name = "expirytimemillis")
private BigInteger expiryTimeMillis;
@Column(name = "autorenewing")
private String autoRenewing;

별문제 없이 쿼리가 실행되는 것 확인하였다.

728x90
반응형

+ Recent posts