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

환경: springboot3.2

 

기존과 같이 new PageImpl()을 이용하여 Page<T> 객체를 내리는데 아래와 같은 에러(warn)가 발생한다.

Serializing PageImpl instances as-is is not supported, meaning that there is no guarantee about the stability of the resulting JSON structure!
	For a stable JSON structure, please use Spring Data's PagedModel (globally via @EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO))

 

에러 발생 이유

  • Spring Boot 3.x
  • 내부적으로 사용하는:
    • Spring Data Commons 3.x (2023.0.x 이상)
    • Spring Framework 6.x

이 조합에서 PageImpl을 그대로 @RestController에서 반환하면 Jackson 직렬화 구조가 불안정하다는 경고가 발생한다.

 

Spring 팀은 PageImpl<T>의 직렬화가 다음 문제를 가진다고 판단했다:

  • Jackson 직렬화 시 필드 순서나 구조가 변경될 수 있음
  • 필드 이름이 내부 구현에 의존
  • API 스펙을 안정적으로 유지하기 어려움

그래서 Spring Data 팀이 공식적으로 PagedModel<T> 또는 DTO 변환을 권장하게 됨.

 

해결 방법

1. hate oas 를 사용하는 경우

PagedModel<T> 사용

return pagedResourcesAssembler.toModel(page);
 
: 결과가 PagedModel<EntityModel<T>> 형태가 되어 안정적인 JSON 구조를 보장
 

2. hate oas 사용 안하는 경우

 
1) 설정 추가로 해결
@Configuration
@EnableSpringDataWebSupport(pageSerializationMode = PageSerializationMode.VIA_DTO)
public class WebConfig {
}

2) 직접 커스텀 DTO를 만들어서 사용

public class PageResponse<T> {
    private List<T> content;
    private int page;
    private int size;
    private long totalElements;

    public PageResponse(List<T> content, int page, int size, long totalElements) {
        this.content = content;
        this.page = page;
        this.size = size;
        this.totalElements = totalElements;
    }

    // getters, setters
}
728x90
반응형
반응형

 

1. String Literal

  • 자바에서 "hello", "abc", "쿠폰"처럼 쌍따옴표로 직접 입력한 문자열
  • JVM의 String Constant Pool(문자열 상수 풀)에 저장됨
  • 같은 literal을 사용하면 동일한 인스턴스 재사용
  • 메모리 절약 + 빠름
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true

 

2. String 생성자 사용 (new)

 

  • 항상 heap 메모리새 인스턴스를 생성
  • 상수 풀에 있는 "hello"는 그대로 존재하지만, 생성자는 그것을 복사한 새로운 객체를 만듦
String s1 = "hello";
String s3 = new String("hello");
System.out.println(s1 == s3); // false (주소 다름)

 


 

  • 특별한 이유가 없다면 항상 "hello" 같은 literal 방식 사용
  • new String("...")은 피하는 것이 좋음. 필요할 땐 .intern()을 써서 상수 풀로 옮길 수 있음

 

728x90
반응형
반응형
UUID는 Universally Unique Identifier의 줄임말로, 전 세계적으로 고유한 ID를 의미

 

UUID란?

  • 128비트(16바이트) 크기의 고유 식별자
  • 일반적인 문자열 표현: "faca5a65-3500-4855-be9f-455c6544f856" (총 36자)
  • 보통 중복되지 않는 고유한 값을 자동으로 생성할 때 사용
  • 32자리 16진수
  • 하이픈 -으로 5개 구간으로 구분: 8-4-4-4-12
UUID uuid = UUID.randomUUID();

왜 UUID를 쓰나?

  • 고유 식별
    • 여러 시스템, 여러 서버에서 생성해도 충돌 위험이 거의 없음
  • 데이터 병합 안정성
    • 분산 시스템에서 병합해도 ID 충돌 없음
  • 보안성(노출 방지)
    • 숫자 증가 ID보다 추측 어렵고 노출에 강함

왜 이걸 키로 쓰지? 장단점은?

 

디비로 조회

## string to uuid
select unhex('faca5a6535004855be9f455c6544f856') from dual;

select * from poh where uuid = unhex('faca5a6535004855be9f455c6544f856') ;
728x90
반응형
반응형

클러스터 인덱스

  • 테이블의 레코드 자체가 인덱스 순서에 맞춰 저장됨
  • 한 테이블에 하나만 존재 가능 (왜냐하면 테이블 자체의 정렬 순서를 정하기 때문)
  • 기본 키(Primary Key)가 자동으로 클러스터 인덱스로 지정되는 경우가 많음 (DBMS마다 다름; Mysql: O, postgreSql: X)
  • 범위 검색이 매우 빠름 (BETWEEN, >=, <= 등)
  • 클러스터 인덱스 기준으로 정렬되므로 자주 업데이트되거나 정렬 순서가 자주 바뀌는 컬럼은 비추천
  • INSERT/UPDATE 성능이 약간 저하될 수 있음 (정렬 유지 때문에)

유투브 쉬운코드

논 클러스터 인덱스

실제 데이터와 분리된 별도의 인덱스 구조(별도 공간) 책의 목차처럼 인덱스만 따로 있고, 실제 데이터를 찾아가야 함

비교

 

외래키

  • 다른 테이블의 PRIMARY KEY(기본 키)를 참조해서 두 테이블 간의 관계를 연결해주는 키
  • 외래 키는 무결성(일관성), 데이터 신뢰성을 지키는 역할
    • 제약이 없으면 잘못된 값이 들어갈 수 있음
  • 외래 키(Foreign Key)가 있다고 해서 자동으로 정렬되지는 않음
  • 외래 키는 정렬 기능이 아니라 제약 조건(Constraint)

외래키와 인덱스

  • 외래키는 위와 같은 이유로 보통 JOIN하거나 WHERE 조건에서 자주 사용되기 때문에 인덱스가 없으면 느려질 수 있음
  • 특히 참조 대상이 삭제되거나 변경될 때도 외래 키 무결성 검사 위해 관련 데이터 검색이 필요한데, 인덱스 없으면 전체 테이블을 스캔

MySQL에서는 FOREIGN KEY를 설정할 때, 참조 대상이 인덱스가 아니면 오류 발생

CREATE TABLE orders (
    id INT PRIMARY KEY,
    member_id INT,
    FOREIGN KEY (member_id) REFERENCES members(id)
);
  • 이 경우 orders.member_id에 자동으로 인덱스 생성됨
  • 이유: MySQL은 성능 및 무결성 검증을 위해 인덱스를 자동 생성 그래서 EXPLAIN으로 보면 member_id에 인덱스가 잡혀 있음

 

 

 

 

728x90
반응형

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

[mysql] string to uuid  (0) 2025.05.09
[mysql] 복합 인덱스와 explain  (0) 2025.02.03
[mysql] order by null  (0) 2024.12.19
[파티셔닝] 하는법, 쓰는법  (0) 2024.11.25
비관락/낙관락 쓰기락/읽기락 베타락/공유락  (1) 2024.11.09
반응형

환경: jdk6, bouncy castle 설치됨, https 통신 필요

https통신 시도 시 아래와 같은 에러 발생

원인

  • JDK 6 환경에서 TLS 1.2 통신을 시도
    • 기본적으로 JDK 6은 TLS 1.2를 지원하지 않음
    • 하지만 Bouncy Castle을 설치하여 강제로 TLS 1.2를 활성화함
  • TLS Handshake 실패
    • 서버가 제공한 인증서를 검증할 때, JDK 6의 TrustStore (신뢰할 수 있는 루트 인증서 저장소)에 해당 루트 인증서가 없었음
    • 즉, 신뢰할 수 있는 CA(인증 기관)의 인증서가 누락되어 있어 통신 실패

해결

  • 루트 인증서를 설치하고 JDK 6 KeyStore에 추가
    • keytool 명령어를 사용하여 루트 인증서를 JDK 6의 TrustStore에 등록 
    • cacerts는 JDK의 기본 TrustStore ($JAVA_HOME/jre/lib/security/cacerts)
keytool -import -trustcacerts -keystore cacerts -storepass changeit -file rootCA.crt -alias myrootca
  • 다시 TLS 1.2 통신을 시도하여 성공
    • 루트 인증서가 추가되었으므로 서버 인증서 검증이 정상적으로 완료됨
    • 최종적으로 TLS 1.2 통신이 정상적으로 수행됨

 

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

환경: java17, spring boot 3.1.5, spring batch 5.0.3, mysql5.7

 

이슈:

아래 에러가 간헐적으로 발생하며 배치 실패. 재실행 시 정상 처리

Caused by: com.mysql.cj.jdbc.MysqlXAException: XAER_DUPID: The XID already exists
	at com.mysql.cj.jdbc.MysqlXAConnection.mapXAExceptionFromSQLException(MysqlXAConnection.java:344)
	at com.mysql.cj.jdbc.MysqlXAConnection.dispatchCommand(MysqlXAConnection.java:329)
	at com.mysql.cj.jdbc.MysqlXAConnection.start(MysqlXAConnection.java:290)
	at com.atomikos.datasource.xa.XAResourceTransaction.resume(XAResourceTransaction.java:217)
	... 81 common frames omitted
Caused by: java.sql.SQLException: XAER_DUPID: The XID already exists
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:130)
	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
	at com.mysql.cj.jdbc.StatementImpl.executeInternal(StatementImpl.java:763)
	at com.mysql.cj.jdbc.StatementImpl.execute(StatementImpl.java:648)
	at com.mysql.cj.jdbc.MysqlXAConnection.dispatchCommand(MysqlXAConnection.java:323)

디비에서 xa recover; 로 검색 시 남아있는 트랜젝션 없는 것 확인

 

해결:

XID 중복이라 우선 XID가 어떻게 생성되는지 확인

XID: <gtrid>:<bqual>
  • gtrid (Global Transaction ID): 분산 트랜잭션을 식별하는 고유한 값
  • bqual (Branch Qualifier): 트랜잭션 내에서 개별 브랜치를 구분하는 값

16진수 → ASCII 디코딩
제공된 XID는 16진수(Hex)로 인코딩 되어 있음. 이를 ASCII 문자로 변환해야 함

XID: 172.18.0.3.tm174046320874700011:172.18.0.3.tm11
XID: <IP>:tm<TIMESTAMP>:<BRANCH_ID>

gtrid = 172.18.0.3.tm174046320874700011
bqual = 172.18.0.3.tm11

  • 172.18.0.3 → 트랜잭션을 실행한 클라이언트 서버의 IP(여기서는 배치 서버)
  • tm174046320874700011 → 글로벌 트랜잭션 ID (gtrid)
    • 174046320874700011  타임스탬프 기반의 유니크 ID
  • tm11 → 브랜치 ID (bqual)

 

XID에 배치 서버 ip와 timestamp가 들어가므로 해당 서버에서 그 시간에 동시에 돌았던 'jta transaction'과 연관이 있었을 것으로 생각..

그걸 확인하기 위해 xid에 프로젝트 명이나 job 이름을 넣어보기로 했다.

상수 값도 줄 수 있고 아래처럼 spel을 사용하여 동적으로 줄 수도 있다.

spring.jta.atomikos.properties.transaction-manager-unique-name=${TRANSACTION_MANAGER_NAME:defaultTxManager}

 

  • 설정 대상: Atomikos 전체 트랜잭션 관리자
  • 역할: 트랜잭션 관리자(Transaction Manager)의 고유한 이름 지정
  • 사용 위치: 전역적으로 하나만 설정 (애플리케이션 전체에서 단 하나)
  • 중복되면 안 됨: 동일 네트워크 내 여러 인스턴스에서 서로 다른 값 필요

 

우선 해당 시간에 돈 배치 중 jta transaction 을 사용하는 배치는 이 배치 밖에 없을 것 같고

그래서 중복이 나도 한 프로젝트 안에서, 특히 이 job 안에서 발생했을 것 같긴 한데

우선 그걸 확증하기 위해 위와 같은 설정을 추가해 본다.

그래도 나면 그땐 진짜 우리끼리의 싸움

+ 그때는 XID에 UUID를 심을 수 있는 빈을 위 설정에 연결하는 게 좋을 것 같다.


추가로 

spring.jta.atomikos.properties.transaction-manager-unique-name=${TRANSACTION_MANAGER_NAME:defaultTxManager}

이 설정 값과 소스의 아래 부분이 동일한 설정이라고 생각했는데.. 그것은 아니었다..

AtomikosDataSourceBean dataSource = new AtomikosDataSourceBean();
dataSource.setUniqueResourceName(resourceNameCreator.createResourceName(BATCH_DATASOURCE_NAME));

 

  • 설정 대상: 특정 DataSource(XA 리소스)
  • 역할: 각 XA 리소스를 식별하는 고유한 이름(Unique Name) 지정
  • 사용 위치: 각각의 AtomikosDataSourceBean 객체에 대해 개별적으로 설정
  • 중복되면 안 됨: 각 DataSource마다 고유해야 함 (여러 개의 XA 리소스를 사용할 경우 필수)

 

 

AtomikosDataSourceBean은 데이터 소스에 해당하는 고유 이름을 주는 것이고 

transaction-manager-unique-name는 atomikos transaction manager의 고유한 이름을 지정하는 것이었다..

 

우선 아래와 같이 세팅하고 추이를 지켜보기로 했다.

728x90
반응형
반응형

타임셰어링(Time Sharing)과 컨텍스트 스위칭(Context Switching)은 밀접한 관계가 있지만 다른 개념이라 하여 정리해본다.

 

타임셰어링(Time Sharing)

(OS가 관리하는) CPU 시간을 여러 프로세스 또는 스레드에 분배하는 방식

  • 목적: 여러 작업을 동시에 실행하는 것처럼 보이게 함 (멀티태스킹)
  • 방법: 일정한 시간 간격(타임 슬라이스, Time Slice)마다 CPU를 다른 프로세스/스레드에 할당
    • 모든 프로세스가 공평하게 CPU 시간을 나누어 사용(선점 없음)
  • 결과: 사용자는 여러 작업이 동시에 실행되는 것처럼 느끼지만, 실제로는 CPU가 빠르게 번갈아가며 실행하는 것

 

컨텍스트 스위칭(Context Switching)

CPU가 실행 중인 프로세스 또는 스레드를 변경할 때, 현재 상태(Context)를 저장하고 새로운 프로세스 또는 스레드의 상태를 복원하는 과정

  • 목적: 여러 프로세스를 실행하기 위해 이전 실행 상태를 저장하고 새로운 작업을 로드
  • 필요한 이유: OS가 프로세스나 스레드를 교체할 때 이전 작업을 나중에 다시 실행할 수 있도록 하기 위해
  • 오버헤드 발생: 컨텍스트를 저장하고 복원하는 작업은 추가적인 CPU 자원을 소모

컨텍스트 스위칭 과정

  1. 현재 실행 중인 프로세스의 레지스터, 메모리 상태(Context) 저장
  2. 새로운 프로세스의 레지스터, 메모리 상태 복원
  3. CPU가 새로운 프로세스를 실행

즉, 타임셰어링을 하려면 필연적으로 컨텍스트 스위칭이 발생함!


타임셰어링은 CPU 시간을 나누어 여러 프로세스가 실행되도록 하는 방식
타임셰어링을 수행하려면 컨텍스트 스위칭이 발생해야 함
하지만 컨텍스트 스위칭은 타임셰어링이 아닐 수도 있음

  • 예) 우선순위 기반 선점형(Preemptive) 스케줄링에서도 컨텍스트 스위칭 발생
    • 높은 우선순위의 프로세스가 CPU를 빼앗아 사용할 수 있음(선점 가능)

즉, 타임셰어링은 CPU 시간을 나누는 방식이고, 컨텍스트 스위칭은 그 과정에서 발생하는 기술적인 동작!

728x90
반응형
반응형

Call by Value vs Call by Reference

  • Call by Value: 인자로 전달된 변수의 만 복사하여 사용. 원본 변수에는 영향을 주지 않는다.
  • Call by Reference: 인자로 전달된 변수의 메모리 주소(참조값, reference)를 전달. 함수 내에서 값이 변경되면 원본 변수에도 영향을 미친다.

자바는 Call by Value

자바의 메서드 호출 방식은 항상 Call by Value다. 즉, 메서드가 인자를 받을 때 원본 변수의 값을 복사하여 전달한다.

기본 타입 (Primitive Type)

기본 타입(예: int, double, char 등)은 Call by Value로 동작하여 원본 변수에 영향을 주지 않는다. 

참조 타입 (Reference Type)

객체(Object)와 같은 참조 타입의 경우, 객체의 참조값(메모리 주소)이 값으로 전달된다. 따라서 참조하는 객체의 속성을 변경하면 원본 객체에도 영향을 준다. 하지만 참조 자체를 변경해도(새로운 객체를 할당하면) 원본에는 영향을 주지 않는다.

class Person {
    String name;
}

public class Example {
    public static void changePerson(Person p) {
        p = new Person(); // 새로운 객체를 할당
        p.name = "Charlie";
    }

    public static void main(String[] args) {
        Person person = new Person();
        person.name = "Bob";

        changePerson(person);
        System.out.println(person.name); // 여전히 Bob (새로운 객체는 원본에 영향을 주지 않음)
    }
}

 

changePerson 함수 안에서 p가 새로운 객체를 가리키도록 변경되었지만, 이것은 메서드 내부의 p 변수가 가리키는 참조가 바뀐 것일 뿐, 원래 person 변수에는 영향을 주지 않는다. 만약 메서드 내부에서 새로운 객체를 만들어 원본에도 반영하고 싶다면, 리턴 값을 활용하여 원본 변수를 직접 변경해야 한다.

public static Person changePerson(Person p) {
    return new Person("New Person");
}

public static void main(String[] args) {
    Person person = new Person("Original");
    person = changePerson(person); // 리턴 값을 원본 변수에 할당
    System.out.println(person.name); // "New Person"
}

객체의 참조값(메모리 주소)을 복사하여 전달하기 때문에, 객체 내부 값은 변경할 수 있지만, 객체 자체를 변경할 수는 없다.

 

즉, Java는 "Call by Value of Reference"


유투브 쉬운코드

728x90
반응형
반응형

환경: 자바8+

 

SimpleDateFormat은 멀티스레드 환경에서 안전하지 않다.

SimpleDateFormat은 내부적으로 Calendar 인스턴스를 공유하는데, 이 과정에서 공유 자원 변경이 발생하여 멀티스레드 환경에서 예상치 못한 결과를 초래할 수 있다. SimpleDateFormat은 멀티스레드 환경에서 사용할 경우 각 스레드마다 별도 인스턴스를 생성하거나 ThreadLocal을 이용해야 한다.

 

Java 8부터는 DateTimeFormatter가 제공되며, 이는 불변(immutable) 객체이므로 여러 스레드에서 동시에 안전하게 사용할 수 있다. 따라서 매번 새로 생성할 필요 없이, 재사용하는 것이 성능적으로도 더 유리하다.

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class DateTimeFormatterExample {
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        Runnable task = () -> {
            String formattedDate = LocalDateTime.now().format(FORMATTER);
            System.out.println(Thread.currentThread().getName() + " : " + formattedDate);
        };

        for (int i = 0; i < 5; i++) {
            new Thread(task).start();
        }
    }
}
728x90
반응형

+ Recent posts