(103)

[Kotlin] Collections: 시퀀스(Sequences)는 무엇이고 언제 사용해야할까

안녕하세요. 저는 2년차 백엔드 개발자입니다.현재 JS(TS)로 개발하고 있고, Java는 코테용 언어로 사용하고 있습니다.코틀린을 사용하기 위해 현재 제가 알고 있는 지식 대비 필요 부분들만 학습하여 정리하는 Kotlin 시리즈입니다.피드백은 언제나 감사합니다!!     코틀린 기본 문법을 공부하고 있는 와중에, 컬렉션의 Lazy Evaluation을 지원하는 Sequence라는 것이 있다는 것을 알게 되었다. 기본 문법 포스팅에 내용을 남겼다가, 분리해야할 것 같아서 포스팅을 따로 작성했다.  SequenceKotlin 1.0에 추가된 Sequence 인터페이스는 Kotlin의 의 내장 라이브러리로, Collections의 다른 유형을 제공하기 위해 등장했다. 기본적으로 Sequence는 Iterat..

[Kotlin] 기본 문법 - Basic types, Collections, 조건문, 반복문

안녕하세요. 저는 2년차 백엔드 개발자입니다.현재 JS(TS)로 개발하고 있고, Java는 코테용 언어로 사용하고 있습니다.코틀린을 사용하기 위해 현재 제가 알고 있는 지식 대비 필요 부분들만 학습하여 정리하는 Kotlin 시리즈입니다.피드백은 언제나 감사합니다!! Kotlin 등장 배경내가 사용하고 있는 Nest는 nodejs의 구조적인 한계를 해결하기 위해 등장했으며 모듈 기반 아키텍처와 의존성 주입과 같은 개념을 적극 도입함으로써 nodejs의 자유도가 높은 구조를 개선하고 엔터프라이즈급 애플리케이션에서도 일관된 개발 경험을 제공하기 위해 만들어진 프레임워크이다.   코틀린의 공식 문서에는, 간결하고 안전하며 Java와 100% 호환이 되고 (JVM 위에서 동작)  NullSafety하다는 특징을 ..

Spring Security WebSecurityConfigurerAdapter / cors, csrf Deprecated 해결하기

서론최근 토이 프로젝트 진행중에 간단하게 회원기능을 구현하고자 Security를 사용하는데 이전 사용했던 WebSecurityConfigurerAdapter을 상속받아 구현하던 Config설정에 바뀐 부분이 있고 또한 시큐리티를 통한 권한 확인이나 로그인, 로그아웃 등 기타 작업등은 하지 않을 계획이라 csrf(), cors() disable을 설정하는 과정에서도 Deprecated된 것을 확인하여 기록하고자 함 아래는 각각 WebSecurityConfigurerAdapter와, Spring Security 6.1.2버전에서의 Deprecated API를 정리해둔 공식 docs https://spring.io/blog/2022/02/21/spring-security-without-the-websecuri..

QueryDSL 사용하기(SpringBoot 3.0) - QueryDSL 사용하기(4)

서론 드디어 아래의 쿼리를 QueryDSL로 뽑아내서 View시켜볼 수 있게 되었다. select C.CHAMPION_ID, C.PRICE, ROUND((C.PRICE-CP.PRICE)/CP.PRICE*100) as PERCENT from Champion C, (select CC.CHAMPION_ID, CC.PRICE from ChampionPriceLog CC where (CC.CHAMPION_ID, CC.CREATE_DATE) in(select CCC.CHAMPION_ID, MAX(CCC.CREATE_DATE) AS CREATE_DATE from ChampionPriceLog CCC group by CCC.CHAMPION_ID) order by CC.CHAMPION_ID) as CP where C.C..

QueryDSL JPA Test (Spring Boot 3.0) - QueryDSL 사용하기(3)

셋팅 def queryDslVersion = '5.0.0' dependencies { //QueryDsl // 필수 implementation "com.querydsl:querydsl-jpa:${queryDslVersion}:jakarta" implementation "com.querydsl:querydsl-core:${queryDslVersion}" // QueryDsl 쿼리 타입 생성 (QClass 생성 시 @Entity 탐색) annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}:jakarta" // java.lang.NoClassDefFoundError:javax/persistence/Entity 에러 방지 annotationProc..

QueryDSL이란? - QueryDSL 사용하기(1)

서론 토이 프로젝트 진행 중 아래와 같은 쿼리 사용이 필요했고, QueryDSL을 한 번 사용해보기로 했음. select C.CHAMPION_ID, C.PRICE, ROUND((C.PRICE-CP.PRICE)/CP.PRICE*100) as PERCENT from Champion C, (select CC.CHAMPION_ID, CC.PRICE from ChampionPriceLog CC where (CC.CHAMPION_ID, CC.CREATE_DATE) in(select CCC.CHAMPION_ID, MAX(CCC.CREATE_DATE) AS CREATE_DATE from ChampionPriceLog CCC group by CCC.CHAMPION_ID) order by CC.CHAMPION_ID) as..

JPQL이란? - QueryDSL 사용하기(2)

서론 이전 글에 나왔던 키워드 중 JPQL에 대해 조금이라도 알아보기 위해 정리하는 글 JPQL(Java Persitence Query Language)이란? 엔티티 객체를 대상으로 하는 객체지향 쿼리로 SQL을 추상화한 객체지향 쿼리이며, 작성된 JPQL은 SQL로 변환된다. 기존 JPA의 메서드 호출만으로는 섬세한 쿼리 작성이 어렵다는 문제를 해결하기 위해 JPQL이 나타나게 되었으며 SQL을 추상화했기 때문에 특정 데이터베이스 SQL에 의존하지 않는다는 장점이 있다. SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN을 지원한다. 예시 Member 객체를 대상으로 이름에 "bazzi"가 포함된 모든 회원을 검색하는 쿼리 String jpql = "select m From ..

[Java] BufferedReader BufferedWriter

서론 요새 프로그래머스가 잠잠해져서 LeetCode 외에도 HackerRank라는 플랫폼에서도 코테문제를 풀고있는데, 얼른 플랫폼에 적응해서 적당한 난이도의 문제를 풀고 포스팅 하고 싶다 HackerRank - Online Coding Tests and Technical Interviews HackerRank is the market-leading technical assessment and remote interview solution for hiring developers. Start hiring at the pace of innovation! www.hackerrank.com 서론이 이상하게 흘러갔는데 여튼, 백준처럼 직접 입력받아 출력해야하는 포맷의 HackerRank인 만큼, 입출력에 대한 공부..

SynchronizedList / CopyOnWriteArrayList

[Java] 자바에서의 스레드 안전(Thread Safe)과 모니터(monitor)연관 게시물 https://mag1c.tistory.com/364 스레드 안전 - Thread Safe 연관 게시물 https://mag1c.tistory.com/365 [Java] 자바에서의 스레드 안전(Thread Safe)과 모니터(monitor) 자바에서의 Thread-Safe 1. Lock synchronized 아래 mag1c.tistory.com 스레드 안전 - Thread Safe연관 게시물 https://mag1c.tistory.com/365 [Java] 자바에서의 스레드 안전(Thread Safe)과 모니터(monitor) 자바에서의 Thread-Safe 1. Lock synchronized 아래 코드는 ..

[Kotlin] Collections: 시퀀스(Sequences)는 무엇이고 언제 사용해야할까

Tech/Java,Kotlin,Spring 2025. 2. 13. 13:40
728x90
728x90

 

안녕하세요. 저는 2년차 백엔드 개발자입니다.

현재 JS(TS)로 개발하고 있고, Java는 코테용 언어로 사용하고 있습니다.

코틀린을 사용하기 위해 현재 제가 알고 있는 지식 대비 필요 부분들만 학습하여 정리하는 Kotlin 시리즈입니다.

피드백은 언제나 감사합니다!!

 


 

 

 

 

코틀린 기본 문법을 공부하고 있는 와중에, 컬렉션의 Lazy Evaluation을 지원하는 Sequence라는 것이 있다는 것을 알게 되었다. 기본 문법 포스팅에 내용을 남겼다가, 분리해야할 것 같아서 포스팅을 따로 작성했다.

 

 

Sequence

Kotlin 1.0에 추가된 Sequence 인터페이스는 Kotlin의 의 내장 라이브러리로, Collections의 다른 유형을 제공하기 위해 등장했다. 기본적으로 Sequence는 Iterator을 통해 값을 반환하는 기능을 수행한다. 쉽게 말해 Collections의 처리 기능 중 하나라고 생각하면 되겠다.

(kotlin-stdlib/kotlin.sequences/Sequence)

 

package kotlin.sequences

/**
 * A sequence that returns values through its iterator. The values are evaluated lazily, and the sequence
 * is potentially infinite.
 *
 * Sequences can be iterated multiple times, however some sequence implementations might constrain themselves
 * to be iterated only once. That is mentioned specifically in their documentation (e.g. [generateSequence] overload).
 * The latter sequences throw an exception on an attempt to iterate them the second time.
 *
 * Sequence operations, like [Sequence.map], [Sequence.filter] etc, generally preserve that property of a sequence, and
 * again it's documented for an operation if it doesn't.
 *
 * @param T the type of elements in the sequence.
 */
public interface Sequence<out T> {
    /**
     * Returns an [Iterator] that returns the values from the sequence.
     *
     * Throws an exception if the sequence is constrained to be iterated once and `iterator` is invoked the second time.
     */
    public operator fun iterator(): Iterator<T>
}

 

 

Sequence는 컬렉션과 달리 요소들을 포함하지 않고 반복하면서 생성한다. 순회하며 일련의 연산을 하는 것이 Iterable과 동일한 기능을 제공하지만 여러 연산에서 컬렉션 처리에 대해 다른 방식을 사용한다.

 

Collections의 인터페이스인 Iterable은 여러 연산을 처리할 때, 각 처리 단계를 순차적으로 완료하고, 하나의 연산이 종료될 때 마다 중간 결과를 임시 컬렉션에 저장한다. 다음 단계는, 초기에 선언했던 컬렉션이 아닌 생성한 임시 컬렉션을 통해 진행된다.

val numbers = listOf(1, 2, 3, 4, 5)

val result = numbers.map { 
    println("Mapping $it")
    it * 2
}.filter { 
    println("Filtering $it")
    it > 5
}.find { true }
print("Result $result")

 

 

위 연산에서 가장 처음 생성됐던 numbers 컬렉션에서, map을 수행하면 map의 결과 컬렉션이 생성되고, filter또한 마찬가지이다.

 

반대로 Sequence는  함수를 수행할 때 지연 실행을 지원하는데 공식문서에서는 이를 evaluated lazily라고 표현하고 있다. 즉 Collections을 통한 다양한 연산을 수행할 때 Lazy Evaluation이 가능하게 해주는 인터페이스가 Sequence이다.

 

 

Sequence 생성하기

Sequence는컬렉션과 동일하게 생성하거나, 컬렉션에서 변환할 수 있다.

val sequenceExam = sequenceOf('1', '2', '3')

val numbers = listOf(1, 2, 3, 4, 5)
val numbersSequence = numbers.asSequence()

 

 

컬렉션이나 sequenceOf 외에도 함수를 사용하여 동적으로 생성할 수도 있다. 이 때 기본적으로 무한대의 시퀀스를 생성하기 때문에 반드시 특정 종료 조건을 명시해야한다.

val oddNumbers = generateSequence(1) { it + 2 } // 1, 3, 5, ........
println(oddNumbers.take(5).toList()) //take(5)를 통해 5개만 가져오게끔 설정
// [1, 3, 5, 7, 9]



// ⚠️ 무한 루프
val oddNumbers = generateSequence(1) { it + 2 } // 1, 3, 5, ........
println(oddNumbers.count())



// 종료 조건을 명시
val oddNumbersLessThan10 = generateSequence(1) { if (it < 8) it + 2 else null }

 

 

마지막으로, sequence 블록({ })을 활용하여 시퀀스를 하나 또는 여러개를 동시에 생성할 수도 있다.

val oddNumbers = sequence {
    yield(1) // 개별 요소 반환
    yieldAll(listOf(3, 5)) // 리스트의 모든 요소 반환
    yieldAll(generateSequence(7) { it + 2 }) // 무한 시퀀스 반환
}
println(oddNumbers.take(5).toList()) 
// 출력: [1, 3, 5, 7, 9]

 

위 코드에서 yield(1)은 단일 값을, yieldAll(listOf(3, 5))는 리스트의 모든 값을 반한다. 또한 yieldAll(generateSequence(7) { it + 2 })를 통해 무한 시퀀스를 마지막에 추가할 수도 있다.

 

 

 

Iterable vs Sequence

위에서, 기본 컬렉션의 연산 처리 방식은, 하나의 함수가 끝나면 임시 컬렉션을 생성한다고 했다. 반대로 Sequence의 경우 지연 연산을 통해 데이터를 임시 컬렉션에 저장하지 않고 처리하는 방식이라고 설명했다.

 

말로 와닿지 않아서, 직접 코드로 작성해보았다. 아래는 간단한 Iterable VS Sequence 예제이다.

val numbers = listOf(1, 2, 3, 4, 5)

// 즉시 실행
val result = numbers.map { 
    println("Mapping $it")
    it * 2
}.filter { 
    println("Filtering $it")
    it > 5
}.find { true }
print("Result $result")


// 지연 실행
val result = numbers.asSequence().map { 
    println("Mapping $it")
    it * 2
}.filter { 
    println("Filtering $it")
    it > 5
}.find { true }
print("Result $result")
// 즉시 실행
Mapping 1
Mapping 2
Mapping 3
Mapping 4
Mapping 5
Filtering 2
Filtering 4
Filtering 6
Filtering 8
Filtering 10
Result 6


// 지연 실행
Mapping 1
Filtering 2
Mapping 2
Filtering 4
Mapping 3
Filtering 6
6

 

 

즉시 실행 방식은 numbers 컬렉션에 대해 하나의 메서드가 끝난 후 다른 메서드를 실행하는 방식이다. 위 예제에서는 map의 연산이 끝난 후 filter을, filter 연산이 끝난 후 find가 수행된다. 이 과정에서 numbers 배열에 연산의 결과가 덮어씌워지는 것이 아니라, 임시 컬렉션이 생성되면서 연산 결과를 가지고있게 된다.

 

 

 

하지만 sequence를 사용하면 임시 컬렉션이 생성되지 않고, 최종 연산이 호출되어 실행될 때 까지 연기된다.

(Java의 Stream 객체의 동작과 같다고 하는데 이 부분은 잘 모르겠다.)

 

중간 결과를 따로 저장할 임시 컬렉션이 생성되지 않고, 원소에 연산을 차례대로 적용하다가, 결과가 얻어진 후의 연산은 수행하지 않는다. 그래서 위 결과에서 filtering 6 이후의 연산은 수행되지 않는 것이다.

 

 

 

 

그렇다면 언제 사용해야할까? 사실 잘 와닿지가 않아서 극단적인 상황을 가정해봤다.

class IteratorVsSequenceTest {

    @Test
    fun `즉시 실행과 지연 실행의 성능 차이`() {
        try {
            val numbers = (1..10_000_000).toList()

            val iteratorMemory = measureMemoryUsage {
                val iteratorTime = measureTimeMillis { iteratorFunction(numbers) }
                println("Iterator 실행 시간: ${iteratorTime}ms")
            }

            val sequenceMemory = measureMemoryUsage {
                val sequenceTime = measureTimeMillis { sequenceFunction(numbers) }
                println("Sequence 실행 시간: ${sequenceTime}ms")
            }

            println("Iterator 메모리 사용량: ${iteratorMemory} KB")
            println("Sequence 메모리 사용량: ${sequenceMemory} KB")

        } catch (e: Exception) {
            println("테스트 실패: ${e.message}")
        }
    }

    private fun iteratorFunction(lists: List<Int>): Int? {
        return lists
            .filter { it % 2 == 0 }
            .filter { it % 3 == 0 }
            .map { it * it }
            .map { it / 2 }
            .filter { it > 1_000_000 }
            .firstOrNull { it % 123456 == 0 }
    }

    private fun sequenceFunction(lists: List<Int>): Int? {
        return lists.asSequence()
            .filter { it % 2 == 0 }
            .filter { it % 3 == 0 }
            .map { it * it }
            .map { it / 2 }
            .filter { it > 1_000_000 }
            .firstOrNull { it % 123456 == 0 }
    }

    private fun measureMemoryUsage(block: () -> Unit): Long {
        val runtime = Runtime.getRuntime()
        System.gc()
        val before = runtime.totalMemory() - runtime.freeMemory()
        block()
        val after = runtime.totalMemory() - runtime.freeMemory()
        return (after - before) / 1024
    }
}

 

천 만개의 numbers List를 사용해서, 시퀀스함수에서는 최대한 빨리 조건을 만족해서 빠져나올 수 있도록 구성해봤다. 레퍼런스들을 찾아보고 메모리 사용량을 볼 수 있는 코드도 추가해보았다. 과연 결과는 어떻게 나왔을까?

 

Iterator 실행 시간: 249ms
Sequence 실행 시간: 6ms
Iterator 메모리 사용량: 184529 KB
Sequence 메모리 사용량: 512 KB

 

 

실행 시간 측면에서는 다소 차이가 있었지만 메모리 사용량에서 차이가 엄청나게 많았다. 위에서 설명했듯이 함수 하나하나에 임시 컬렉션을 사용하기 때문에 메모리 사용량에서 엄청난 차이가 있다. 위처럼 극단적인 상황은 없겠지만, 유추해볼 때 파일을 포함한 대용량 데이터를 핸들링할 때 사용해야할 것으로 보인다.

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[Kotlin] 기본 문법 - Basic types, Collections, 조건문, 반복문

Tech/Java,Kotlin,Spring 2025. 2. 12. 17:29
728x90
728x90

 

안녕하세요. 저는 2년차 백엔드 개발자입니다.

현재 JS(TS)로 개발하고 있고, Java는 코테용 언어로 사용하고 있습니다.

코틀린을 사용하기 위해 현재 제가 알고 있는 지식 대비 필요 부분들만 학습하여 정리하는 Kotlin 시리즈입니다.

피드백은 언제나 감사합니다!!


 

Kotlin 등장 배경

내가 사용하고 있는 Nest는 nodejs의 구조적인 한계를 해결하기 위해 등장했으며 모듈 기반 아키텍처와 의존성 주입과 같은 개념을 적극 도입함으로써 nodejs의 자유도가 높은 구조를 개선하고 엔터프라이즈급 애플리케이션에서도 일관된 개발 경험을 제공하기 위해 만들어진 프레임워크이다.

 

 

 

코틀린의 공식 문서에는, 간결하고 안전하며 Java와 100% 호환이 되고 (JVM 위에서 동작)  NullSafety하다는 특징을 Introduce에 서술해두었다. 이 밖에도 코틀린이 등장한 배경이라던가, 해결하고자 하는 문제가 무엇이었는지 찾아보고 싶어 찾아봤고, 기존의 자바에서 어떤 문제가 있었는지는 직접 자바로 서버 구축을 하고 유지 보수를 해보지 않아서, 정리된 글들을 가져왔다.

 

출처: f-lab

 

 

 

나는 이 수많은 이유들 중에 안드로이드 앱을 여러 개 출시할 계획으로 코틀린 공부를 시작한다.

현재 사용중인 RN / Node(Nest)를 통해 개발을 하면 훨씬 생산성이 향상되겠지만, 익숙함을 잠시 내려놓고 개인적인 개발을 할 때는 새로운 환경에서 다시 시작해보려한다.

 


Take Kotlin Tour

공식 문서의 기본 가이드에 간단히 나와있는 언어의 핵심 개념 페이지들을 하나씩 읽어보며 공부한 내용들을 정리해보았다. 내용이 너무 길어져서, Functions (+ Lamda), Classes, Null Safety는 다음 포스팅에 정리해서 총 2회분의 포스팅을 통해 문법을 정리하려고 한다.

 

 

 

 

 

 

 

Variable

JS의 let, const처럼 mutable하고 immutable한 키워드인 var, val을 통해 변수 선언을 할 수 있다.

// Immutable variable ( == const )
val test1 = 10
test1 = 5 // ❌ Val cannot be reassigned


// Mutable variable ( == let )
var test2 = "test" // 타입 추론 (test2: String)
test2  = 1         // ❌ Type Error (정수 리터럴이 String과 호환되지 않음)
tet2 = "TEST2"     // ✅


// 객체의 멤버 변수에서 `val`은 readonly처럼 동작
data class User(var name: String, val age: Int)
val user = User("mag1c", 30)
user.name = "mag2c" // ✅ 변경 가능
user.age = 20      // ❌ 변경 불가능


val list = mutableListOf(1, 2, 3)
list.add(4)                    // ✅ 가능 (리스트 내부 값 변경 가능)
list = mutableListOf(5, 6, 7)  // ❌ 오류 (val 자체는 변경 불가능)

 


Basic Types

TS처럼 자료형을 콜론(:)을 이용해 명시할 수도, 생략할 수도 있다. 마찬가지로 TS처럼 타입 추론을 자동으로 할 수 있기 때문에 굳이 명시하지 않아도 된다. 신기한 부분은, Unsigned Integers를 지원한다는 점이었다.

  Basic Types 예시
Integers Byte, Short, Int, Long val year: Int = 2020
Unsigned integers UByte, UShort, UInt, ULong val score: UInt = 100u
Floating-point numbers Float, Double val currentTemp: Float = 24.5f, val price: Double = 19.99
Booleans Boolean val isEnabled: Boolean = true
Characters Char val separator: Char = ','
Strings String val message: String = "Hello, world!"
Any Any val anything: Any = "HELLO"

 

Any 타입은 모든 타입의 부모 타입(super type)이다. 기본적으로 모든 값이 될 수 있다. TS의 any와 유사하지만 코틀린에서의 Any 타입은 nullable하지 않다. 옵셔널 연산자인 ?을 통해 nullable을 명시할 수 있다.

//TS
let value: any;  // undefined
value = 42;      // ✅
value = null;    // ✅
//Kotlin
var value: Any = "HELLO"
value = 42      // ✅
value = null    // ❌

var value: Any? = "HELLO"
value = 42      // ✅
value = null    // ✅

 

 

Type Check

is를 통한 타입 체크도 가능한데, TS의 typeof와 유사하게 동작한다. 다만 TS의 typeof는 object 타입이 어떤 객체인지까지는 명시해주지 않지만, is를 사용하게되면 instanceof 처럼 동작한다는 점이다.

// is를 통한 타입 체크가 가능
val string1 = "string1"
val isString = if (string1 is String) true else false
print(isString)

 

또한 TS에 있는 as를 통한 명시적 형변환도 코틀린에서 가능하다.

// casting
val obj: Any = "Hello"
val str: String = obj as String  // ✅ 명시적 형 변환
println(str.length)  // 5

// null-safe한 casting
val obj: Any? = null
val str: String? = obj as? String  // ✅ 안전한 형 변환 (null 반환 가능)

String Templates

JS의 Template literals과 유사하지만, 코틀린의 문법이 조금 더 편리하다고 느꼈다. JS에서는 항상 중괄호로 감싸주어야했지만, 코틀린에서는 달러 기호($) 하나면 끝이다.

// JS
let name = "mag1c"
let message = "HELLO"

function sendMessage(name, message) {
	console.log(`[${name}] ${message}`);
}
val name = "mag1c"
val message = "HELLO"

fun sendMessage(name: String, message: String) {
	println("[$name] $message")
}

 

Collections

기본적으로 List, Set, Map을 지원한다. 아래는 코틀린의 기본적인 Collections 구조이다.

 

 

 

기본적으로 JS의 Iterable과 같은 개념으로 보인다. forEach, iterator, hasNext등을 사용할 수 있고, 이런 내장 메서드를 통해 요소를 순회할 수 있다. 반대로 MutableIterable은 Iterable을 상속하지만, mutable한 요소를 구현하여 쓰기 작업을 가능하게 한다.

// Iterable<T>
val list: Iterable<Int> = listOf(1, 2, 3)
for (item in list) {
    println(item)
}



// MutableIterable<T>
val list = mutableListOf(1, 2, 3)
val iterator = list.iterator()
while (iterator.hasNext()) {
    if (iterator.next() == 2) {
        iterator.remove() // ✅ 요소 제거 가능
    }
}
println(list) // [1, 3]



// Collection<T>
val collection: Collection<Int> = listOf(1, 2, 3)
println(collection.size)        // ✅ 크기 확인 가능
println(collection.contains(2)) // ✅ 특정 요소 포함 여부 확인 가능
collection.add(4)               // ❌ (Collection<T>에는 add() 없음)



// MutableCollection<T>
val mutableCollection: MutableCollection<Int> = mutableListOf(1, 2, 3)
mutableCollection.add(4)    // ✅ 가능
mutableCollection.remove(1) // ✅ 가능

 

 

Map은 Iterator의 특성인 순회를 전제로 하지 않기 떄문에 독립적인 Collections을 구현한 것으로 보인다. 여튼 콜렉션 구현을 위한 인터페이스는 동일하다.

// List
val fruits = listOf("apple", "banana", "orange") // immutable
val myDoggies = mutableListOf("guzzi", "coco")   // mutable

// Set
val fruits = setOf("apple", "banana", "orange")  // immutable
val myDoggies = mutableSetOf("guzzi", "coco")    // mutable

// Map
val fruits = mapOf("apple" to 100, "banana" to 200, "orange" to 300)  // immutable
val myDoggies = mutableMapOf("guzzi" to 1, "coco" to 2)               // mutable

 

기본적으로 in 연산자도 지원하는 것으로 보인다.

println("apple" in fruits) //true

 

 

 

고차 함수(?)

JS의 map(), filter() 등의 Array.prototype에 있는 고차 함수들을 List, Set에서도 사용이 가능했다.

// map
val numbers = listOf(1, 2, 3)
val squared = numbers.map { it * it }
println(squared) // [1, 4, 9]



// filter
val numbers = listOf(1, 2, 3, 4, 5)
val evens = numbers.filter { it % 2 == 0 }
println(evens) // [2, 4]


// forEach
val numbers = listOf(1, 2, 3)
numbers.forEach { println(it) } // 1 2 3



// reduce
val numbers = listOf(1, 2, 3, 4)
val sum = numbers.reduce { acc, num -> acc + num }
println(sum) // 10



// flatMap
val arr = listOf(listOf(1, 2), listOf(3, 4))
val flattened = arr.flatMap { it }
println(flattened) // [1, 2, 3, 4]

 


Control Flow

조건문

IF문이나 WHEN문을 통해 조건문을 작성할 수 있으며, 공식 문서에 따르면 둘 중 하나를 선택하여 사용해야한다면 WHEN이 가독성, 유지보수, 확장성 측면에서 낫다고 설명하고 있다.

 

 

val trafficLightState = "Red"
var action: String

// if
if (trafficLightState === "Green") {
    action = "GO"
}
if (trafficLightState === "Yellow") {
    action = "SLOW DOWN"
}
if (trafficLightState === "Red") {
    action = "STOP"
}

// when
val action2 = when (trafficLightState){
    "Green" -> "GO"
    "Yellow" -> "SLOW DOWN"
    "Red" -> "STOP"
    else -> "?"
}

 

 

 

 

반복문

// Range 표현
1..4            // 1, 2, 3, 4
1..<4           // 1, 2, 3
4 downTo 1      // 4, 3, 2, 1
4..1            // ❌
1..5 step 2     // 1, 3, 5
'a'..'d'        // a, b, c, d
'z' downTo 's'  // z, y, x, ..., s
// for
for (number in 1..5) {}

val array = IntArray(10) { 0 }
for (elem in array) {}  // array 내부 요소를 하나씩 선택해서 순회

// while
while(true) { return }

// do while
var num = 0
do {
    println("TEST")
    num++
} while (num < 3)

 

 

 

 

 

 

 

 

References

https://kotlinlang.org/docs/kotlin-tour-welcome.html

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

Spring Security WebSecurityConfigurerAdapter / cors, csrf Deprecated 해결하기

Tech/Java,Kotlin,Spring 2023. 8. 20. 13:06
728x90
728x90

서론

최근 토이 프로젝트 진행중에 간단하게 회원기능을 구현하고자 Security를 사용하는데
이전 사용했던 WebSecurityConfigurerAdapter을 상속받아 구현하던 Config설정에 바뀐 부분이 있고
또한 시큐리티를 통한 권한 확인이나 로그인, 로그아웃 등 기타 작업등은 하지 않을 계획이라
csrf(), cors() disable을 설정하는 과정에서도 Deprecated된 것을 확인하여 기록하고자 함
 
아래는 각각 WebSecurityConfigurerAdapter와, Spring Security 6.1.2버전에서의
Deprecated API를 정리해둔 공식 docs
 
https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter

Spring Security without the WebSecurityConfigurerAdapter

In Spring Security 5.7.0-M2 we deprecated the WebSecurityConfigurerAdapter, as we encourage users to move towards a component-based security configuration. To assist with the transition to this new style of configuration, we have compiled a list of common

spring.io

 
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/annotation/web/builders/HttpSecurity.html#csrf() 

HttpSecurity (spring-security-docs 6.1.2 API)

securityContext Deprecated, for removal: This API element is subject to removal in a future version. Returns: the SecurityContextConfigurer for further customizations Throws: Exception

docs.spring.io

 
 

본론

구글링을 통한 blog나 stackoverflow도 좋지만
아래처럼 공식문서를 통해 간단하게 해결할 수 있는 부분이다.

 
 
딱히 무언가 권한이나 기타 설정들은 하지 않을 것이기 때문에 아래처럼 Config파일을 구성할 수 있었다.
 

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
        return http
                .csrf((csrf) -> csrf.disable())
                .cors((cors) -> cors.disable())
                .build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

QueryDSL 사용하기(SpringBoot 3.0) - QueryDSL 사용하기(4)

Tech/Java,Kotlin,Spring 2023. 8. 4. 05:34
728x90
728x90

서론

드디어 아래의 쿼리를 QueryDSL로 뽑아내서 View시켜볼 수 있게 되었다.

select C.CHAMPION_ID, C.PRICE, ROUND((C.PRICE-CP.PRICE)/CP.PRICE*100) as PERCENT
  from Champion C, (select CC.CHAMPION_ID, CC.PRICE
					  from ChampionPriceLog CC
					 where (CC.CHAMPION_ID, CC.CREATE_DATE) in(select CCC.CHAMPION_ID, MAX(CCC.CREATE_DATE) AS CREATE_DATE
																 from ChampionPriceLog CCC
																group by CCC.CHAMPION_ID)
					order by CC.CHAMPION_ID) as CP
 where C.CHAMPION_ID = CP.CHAMPION_ID
 order by C.CHAMPION_ID;

 

 

 

 

테스트

포스팅을 하게 만들었던 위의 쿼리를 사용하기 위해 QueryDSL을 사용해보기로 했다.

QChampion C = QChampion.champion;
QChampionPriceLog CPL = QChampionPriceLog.championPriceLog;

우선 필요한 QClass의 인스턴스를 생성한 뒤

 

List<Tuple> tupleList =
        (List<Tuple>) jpaQueryFactory.select(C.name, CPL.price, C.price)
                .from(CPL)
                .join(C)
                .on(CPL.champion_id.eq(C.id))
                .where(
                        Expressions.list(CPL.champion_id, CPL.create_date)
                                .in(
                                        jpaQueryFactory.select(
                                                        CPL.champion_id, CPL.create_date.max().as("create_date")
                                                )
                                                .from(CPL)
                                                .groupBy(CPL.champion_id)
                                )

                )
                .orderBy(CPL.champion_id.asc())
                .fetch();

조인을 실행한 결과를 Tuple형태로 반환시켰다.

Tuple은 QueryDSL에서 제공하는 컨테이너 클래스로, 쿼리 결과의 각 튜플을 나타낸다.

 

이를 원하는 형태로 View시키기 위해 아래와 같은 DTO객체를 사용했고, DTO에 원하는 정보를 tuple에서 뽑아서 List형태로 반환시켜 줄 계획이기 때문에 아래처럼 DTO와 반환 코드를 작성했다.

 

package com.example.lolchampionsinvestment.domain.champion;

import lombok.Data;

@Data
public class ChampionPriceDto {

    private String name;

    private int price;

    private int percent;

    public ChampionPriceDto(String name, int price, int percent) {
        this.name = name;
        this.price = price;
        this.percent = percent;
    }
}
List<ChampionPriceDto> championPriceDtoList = tupleList.stream()
        .map(tuple -> {
            String name = tuple.get(C.name);
            int price = tuple.get(C.price);
            int cplPrice = tuple.get(CPL.price);
            int percent = Math.round((price - cplPrice) * 100 / price);
            return new ChampionPriceDto(name, price, percent);
        })
        .collect(Collectors.toList());

 

 

select C.CHAMPION_ID, C.PRICE, ROUND((C.PRICE-CP.PRICE)/CP.PRICE*100) as PERCENT
  from Champion C, (select CC.CHAMPION_ID, CC.PRICE
					  from ChampionPriceLog CC
					 where (CC.CHAMPION_ID, CC.CREATE_DATE) in(select CCC.CHAMPION_ID, MAX(CCC.CREATE_DATE) AS CREATE_DATE
																 from ChampionPriceLog CCC
																group by CCC.CHAMPION_ID)
					order by CC.CHAMPION_ID) as CP
 where C.CHAMPION_ID = CP.CHAMPION_ID;

위의 쿼리를 QueryDSL로 수행하기 위한 테스트 코드 전체는 아래와 같다.

 

@SpringBootTest
@Transactional
public class ChampionPriceQueryRepositoryTest {

    @PersistenceContext
    EntityManager em;
    JPAQueryFactory jpaQueryFactory;

    @Autowired
    ChamiponPriceQueryRepository championPriceQueryRepository;
    @Autowired
    ChampionDataParsingService championDataParsingService;

    @BeforeEach
    void init() {
        jpaQueryFactory = new JPAQueryFactory(em);
    }

    @DisplayName("챔피언 별 가장 최근 가격을 가져온다.")
    @Test
    void getChampionsLatestPrice(){
        // given
        List<ChampionPriceDto> championPriceDtoList = championPriceQueryRepository.findAllLatestChampions();

        // when // then
        assertThat(championPriceDtoList.size()).isEqualTo(164);

        assertThat(championPriceDtoList)
                .extracting("name", "price", "percent")
                .containsAnyOf(
                        tuple("아트록스", 7000, -36),
                        tuple("아리", 2000, 0),
                        tuple("카이사", 8000, 0)
                );
    }

}

 

테스트를 위해, 아트록스 데이터는 11000원을 입력해두었다.

원하는 값인 현재 금액 : 7000, 금일 등락 퍼센트인 -36%가 잘 반환된 모습이다.

 

 

 

 

적용

테스트코드를 적용시킨 Repository를 만들고

 

아래의 서비스 객체를 만든 뒤

@Service
@RequiredArgsConstructor
@Transactional
public class ChampionService {

    private final ChamiponPriceQueryRepository championPriceQueryRepository;

    public List<List<ChampionPriceDto>> getAllLatestChampions() {
        List<ChampionPriceDto> list = championPriceQueryRepository.findAllLatestChampions();
        int listSize = list.size();

        List<List<ChampionPriceDto>> groupingList = new ArrayList<>();
        for(int i = 0; i < listSize; i+=8) {
            int endIdx = Math.min(i + 8, listSize);
            groupingList.add(list.subList(i, endIdx));
        }

        return groupingList;
    }
}

 

Controller를 통해 View를 시켜 보았다.

@RestController
public class MainViewController {

    @Autowired
    ChampionService championService;

    @GetMapping("/")
    public ModelAndView mainView() {
        ModelAndView mv = new ModelAndView();
        List<List<ChampionPriceDto>> groupingList = championService.getAllLatestChampions();

        mv.addObject("groupingList", groupingList);
        mv.setViewName("main/main.html");
        return mv;
    }

}

 

8개씩 그룹핑 한 이유는, View를 그렇게 하기 위함이었는데

일단 원하는 대로 잘 들어오긴 했고, 모든 데이터가 다 들어오기 때문에, 메인 화면에 보여주는 것이다 보니 등락폭이 큰 녀석들만 추려서 View시키던, 페이징을 시키는 방향으로 보수작업을 할 것 같다.

 

 

 

 

관련 포스팅

QueryDSL이란? - QueryDSL 사용하기(1)

JPQL이란? - QueryDSL 사용하기(2)

QueryDSL JPA Test (Spring Boot 3.0) - QueryDSL 사용하기(3)

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

QueryDSL JPA Test (Spring Boot 3.0) - QueryDSL 사용하기(3)

Tech/Java,Kotlin,Spring 2023. 8. 3. 07:52
728x90
728x90

셋팅

def queryDslVersion = '5.0.0'

dependencies {
	//QueryDsl
    // 필수
	implementation "com.querydsl:querydsl-jpa:${queryDslVersion}:jakarta"
	implementation "com.querydsl:querydsl-core:${queryDslVersion}"
    
    // QueryDsl 쿼리 타입 생성 (QClass 생성 시 @Entity 탐색)
	annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}:jakarta"
    
    // java.lang.NoClassDefFoundError:javax/persistence/Entity 에러 방지
	annotationProcessor "jakarta.persistence:jakarta.persistence-api"
	annotationProcessor "jakarta.annotation:jakarta.annotation-api"
}

// QueryDsl 빌드 옵션 (선택)
// QueryDsl 디렉토리 경로
def querydslDir = "$buildDir/generated/querydsl"

// 경로 추가 >> QueryDsl 소스 코드 컴파일 시 빌드
sourceSets {
	main.java.srcDirs += [ querydslDir ]
}

// 컴파일 설정(AnnotationProcessor가 생성하는 소스코드를 해당 경로로 설정)
tasks.withType(JavaCompile) {
	options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir)
}

// clean실행 시 마지막 작업으로 디렉토리(QClass) 삭제 >> 충돌 방지
clean.doLast {
	file(querydslDir).deleteDir()
}

 

 

 

위와 같은 셋팅을 사용했고, 주의할 점은 아래에 있다.

implementation "com.querydsl:querydsl-jpa:${queryDslVersion}:jakarta"

//implementation 'com.querydsl:querydsl-jpa'

아래처럼, 버전과 jakarta를 명시해주지 않으면 Config파일 설정 시 에러가 발생한다.

 

 

또한 Q 파일을 찾지 못해 java.lang.NoClassDefFoundException 관련 에러가 발생한다면 아래 코드를 추가하자.

annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'

 

또한 아래의 Config 클래스를 생성하여 JPAQueryFactory를 Bean으로 등록해주었다.

필요 시 Repository에서 JPAQueryFactory를 DI하여 사용할 수 있다.

@Configuration
@RequiredArgsConstructor
public class QuerydslConfig {
 
    private final EntityManager em;
    @Bean
    public JPAQueryFactory jpaQueryFactory(EntityManager em){
        return new JPAQueryFactory(em);
    }
}

 

 

모든 설정을 마쳤다면 Gradle - Build - Clean 후 compileJava를 수행하거나. (Gradle 빌드 도구 활용)

 

직접 터미널에 아래 명령어를 입력해도 된다.

./gradlew clean

./gradlew compileJava

 

 

 

테스트 해보기

package com.example.lolchampionsinvestment;

import com.example.lolchampionsinvestment.config.QuerydslConfig;
import com.example.lolchampionsinvestment.domain.champion.Champion;
import com.example.lolchampionsinvestment.domain.champion.ChampionRepository;
import com.example.lolchampionsinvestment.domain.champion.QChampion;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;

import java.time.LocalDateTime;

import static org.assertj.core.api.Assertions.assertThat;

@DataJpaTest
@ActiveProfiles("test")
@Import(QuerydslConfig.class)
@Transactional
public class QuerydslTest {

    @PersistenceContext
    EntityManager em;

    JPAQueryFactory jpaQueryFactory;

    @BeforeEach
    void init() {
        jpaQueryFactory = new JPAQueryFactory(em);

        Champion aatrox = createChampion("Aatrox", 1000, "아트록스", LocalDateTime.now());
        Champion teemo = createChampion("Teemo", 2000, "티모", LocalDateTime.now());
        Champion ahri = createChampion("Ahri", 3000, "아리", LocalDateTime.now());
        em.persist(aatrox);
        em.persist(teemo);
        em.persist(ahri);
    }

    @DisplayName("Querydsl test >> 올바르게 insert되었는지 확인")
    @Test
    void selectBeforeInsertDataWithQuerydsl(){
        //given
        QChampion champion = new QChampion("champion");

        //when
        Champion result1 = jpaQueryFactory.select(champion)
                .from(champion)
                .where(champion.name.eq("Aatrox"))
                .fetchOne();
        Champion result2 = jpaQueryFactory.select(champion)
                .from(champion)
                .where(champion.name.eq("Teemo"))
                .fetchOne();
        Champion result3 = jpaQueryFactory.select(champion)
                .from(champion)
                .where(champion.name.eq("Ahri"))
                .fetchOne();

        //then
        assertThat(result1.getName()).isEqualTo("Aatrox");
        assertThat(result2.getPrice()).isEqualTo(2000);
        assertThat(result3.getDescription()).isEqualTo("아리");
    }

    private static Champion createChampion(String name, int price, String description, LocalDateTime createDateTime) {
        return Champion.builder()
                .name(name)
                .price(price)
                .description(description)
                .createDateTime(createDateTime)
                .build();
    }


}

 

테스트는 정상 수행되었다.

 

Insert 세 건에 대한 조회를 진행하여 모두 일치하는 것을 확인할 수 있다.

 

 

 

 

 

 

 

 

다양한 쿼리 키워드나, 연산에 대한 사용법은 정리가 잘 된 포스팅을 발견했다. 결국 API를 호출해서 API의 포맷대로 잘 사용하면 되는 것 같다.

 

 

Spring-Data-JPA [8] Querydsl 사용

저번 포스팅에선 Querydsl 설정을 했습니다. 이제는 직접 사용해보겠습니다. 1. JPAQueryFactory 등록 JpaRepository를 custom 했다는 컨벤션으로 ~~ RepositoryCustom을 만들어 해당 Repository를 상속하고, ~~ Impl로

dingdingmin-back-end-developer.tistory.com

 

 

 

 

마무리

다음 포스팅에선, 드디어 실제 토이 프로젝트에 적용될 예시 쿼리를 QueryDSL로 직접 사용해서 View까지 해보도록 하겠다.

 

 

 

 

 

 

 

참조

https://velog.io/@youngerjesus/%EC%9A%B0%EC%95%84%ED%95%9C-%ED%98%95%EC%A0%9C%EB%93%A4%EC%9D%98-Querydsl-%ED%99%9C%EC%9A%A9%EB%B2%95

https://tecoble.techcourse.co.kr/post/2021-08-08-basic-querydsl/

https://binco.tistory.com/entry/QueryDSL-%EC%A0%81%EC%9A%A9-%EC%98%88%EC%A0%9C%EC%8B%9C%EB%A6%AC%EC%A6%88

https://velog.io/@soyeon207/QueryDSL-Spring-Boot-%EC%97%90%EC%84%9C-QueryDSL-JPA-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0

 

 

 

 

 

 

관련 포스팅

QueryDSL이란? - QueryDSL 사용하기(1)

JPQL이란? - QueryDSL 사용하기(2)

 

 

 

 

 

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

QueryDSL이란? - QueryDSL 사용하기(1)

Tech/Java,Kotlin,Spring 2023. 8. 2. 07:28
728x90
728x90

서론

토이 프로젝트 진행 중 아래와 같은 쿼리 사용이 필요했고, QueryDSL을 한 번 사용해보기로 했음.

select C.CHAMPION_ID, C.PRICE, ROUND((C.PRICE-CP.PRICE)/CP.PRICE*100) as PERCENT
  from Champion C, (select CC.CHAMPION_ID, CC.PRICE
					  from ChampionPriceLog CC
					 where (CC.CHAMPION_ID, CC.CREATE_DATE) in(select CCC.CHAMPION_ID, MAX(CCC.CREATE_DATE) AS CREATE_DATE
																 from ChampionPriceLog CCC
																group by CCC.CHAMPION_ID)
					order by CC.CHAMPION_ID) as CP
 where C.CHAMPION_ID = CP.CHAMPION_ID;

 

 

 

QueryDSL

간략하게 QueryDSL에 대한 조사를 했다.

 

 

Querydsl - Unified Queries for Java

Unified Queries for Java. Querydsl is compact, safe and easy to learn. <!-- Querydsl Unified Queries for Java Querydsl provides a unified querying layer for multiple backends in Java. Compared to the alternatives Querydsl is more compact, safer and easier

querydsl.com

 

QueryDSL이란 하이버네이트 쿼리 언어의 쿼리를 생성 및 관리해주는 오픈소스 프레임워크이며

위의 공식 문서에서 알 수 있듯이 JPA뿐 아니라 여러 언어를 지원한다.

 

QueryDSL은 정적 타입을 이용하여 SQL과 같은 쿼리를 생성할 수 있게 해준다.

 

 

 

QueryDSL JPA

진행중인 토이 프로젝트가 스프링부트를 사용하기 때문에, QueryDSL JPA에 대해 알아볼 필요가 있다.

 

QueryDSL JPA는 SQL, JPQL을 코드로 작성할 수 있도록 해주는 빌더 API이며, Entity클래스와 매핑되는 QClass라는 객체를 사용해 쿼리를 실행한다.

 

 

JPQL 이란?

공부를 위해 포스팅해놓은 링크가 있으니 참조하자.

 

 

QClass 란?

위에서 언급했든 QueryDSL은 컴파일 단계에서 Entity를 매핑하여 QClass를 생성한다.

 

JPAAnnotationProcessor 클래스(docs 링크)가 컴파일 시점에 작동하여 @Entity 애너테이션을 찾아 분석하여 Entity와 형태가 같은 Static Class로 QClass를 생성하며, QueryDSL은 쿼리 작성 시 이 QClass를 기반으로 쿼리를 실행하게 된다.

 

 

 

QueryDSL JPA 사용 이유

그렇다면 QueryDSL을 왜 사용할까?

 

JPQL은 결국 쿼리를 문자열로 입력하기 때문에, 오타가 발생하거나 관리하기가 어렵고, 컴파일 단계에서가 아닌 런타임 시 오류를 확인할 수 있다는 문제가 있다.

 

QueryDSL은 단순 문자열이 아닌 코드를 통해 작성하기 때문에, 코드 작성자의 실수(오타)를 예방할 수 있으며, 객체 지향적인 개발이 가능하다. 또한 컴파일 단계에서도 오류를 빠르게 발견할 수 있다는 장점이 있다.

 

 

아래의 예시는 JPQL과 QueryDSL의 같은 상황에 대한 사용 예시이다.

String jpql = "select * from Member m join Point p on p.member_id = m.id"
List<Member> result = em.createQuery(jpql, Member.class).getResultList();
return jpaQueryFactory
.from(member)
.join(member.point, point)
.fetch();

 

JPQL이 객체지향 쿼리라고는 하지만, 결국 String 쿼리를 객체로 변환시켜주는 과정이 필요하기 때문에, 문자열 입력 시 오타와 같은 상황이 충분히 발생 가능하며, 문자열이 잘못됐더라도 컴파일 단계에서는 확인이 불가능한 것을 알 수 있다.

 

QueryDSL은 쿼리를 문자열이 아닌, 코드화하여 전달하기 때문에 위에서 언급했던 장점들을 가져갈 수 있다.

 

 

 

 

 

 

 

 

참조

https://ittrue.tistory.com/292

https://velog.io/@soyeon207/QueryDSL-%EC%9D%B4%EB%9E%80

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

JPQL이란? - QueryDSL 사용하기(2)

Tech/Java,Kotlin,Spring 2023. 8. 1. 19:28
728x90
728x90

서론

이전 글에 나왔던 키워드 중 JPQL에 대해 조금이라도 알아보기 위해 정리하는 글

 

 

JPQL(Java Persitence Query Language)이란?

엔티티 객체를 대상으로 하는 객체지향 쿼리로 SQL을 추상화한 객체지향 쿼리이며, 작성된 JPQL은 SQL로 변환된다.

 

기존 JPA의 메서드 호출만으로는 섬세한 쿼리 작성이 어렵다는 문제를 해결하기 위해 JPQL이 나타나게 되었으며 SQL을 추상화했기 때문에 특정 데이터베이스 SQL에 의존하지 않는다는 장점이 있다.

 

SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN을 지원한다.

 

 

 

예시

Member 객체를 대상으로 이름에 "bazzi"가 포함된 모든 회원을 검색하는 쿼리

String jpql = "select m From Member m where m.name like ‘%bazzi%'";
List<Member> result = em.createQuery(jpql, Member.class).getResultList();

for (Member member : result) {
    System.out.println("Member = " + member);
}

 

 

SQL과의 차이점

1. 대소문자 구분

엔티티와 필드는 대소문자를 구분한다.

위의 예시에서는, Member와 name은 대소문자를 반드시 구분해주어야 한다.

select, from, as와 같은 키워드들은 대소문자를 구분하지 않아도 된다.

 

2. 엔티티 이름

Member은 Entity 이름으로, @Entity(name = "Menber")로 설정 가능하며, 생략 시 기본 값으로 클래스 이름을 사용한다.

 

3. 별칭(Alias)

JPQL에서 Alias는 필수로 명시해야 하며, AS 키워드는 생략 가능하다.

 

 

구현 방법

구현 방법에는 TypedQuery, Query가 있다.

 

TypedQuery는 변환 타입이 명확할 때, 아래처럼 타입 정보를 작성하여 리턴받을 수 있다.

TypedQuery<Member> query1 = em.createQuery("select m from Member m", Member.class);
TypedQuery<String> query2 = em.createQuery("select m.username from Member m", String.class);

기본적으로 엔티티 타입을 기입했고, query2와 같이 리턴 타입이 username과 같이 명확할 경우 타입 정보를 기입할 수 있다.

 

 

 

Query는 데이터 검색 결과의 타입을 명시하지 않는다.

 

Query query3 = em.createQuery("select m.username, m.age from Member m");

 

 

조회한 결과는 getResultList()혹은 getSingleResult()와 같은 API를 사용하여 List 혹은 단일 객체를 반환한다.

TypedQuery<Member> query1 = em.createQuery("select m from Member m", Member.class);
List<Member> resultList = query1.getResultList();

for (Member m : resultList) {
    System.out.println("m = " + m.getUsername());
}
TypedQuery<Member> query2 = em.createQuery("select m from Member m where m.id = 1L", Member.class);
Member singleResult = query2.getSingleResult();

System.out.println("singleResult = " + singleResult.getUsername());

 

 

 

파라미터 바인딩

이름 기준 파라미터

Member result = em.createQuery("select m from Member m where m.username = :username", Member.class)
                .setParameter("username", "member1")
                .getSingleResult();

System.out.println("result = " + result.getUsername());

이름을 기준으로 파라미터를 바인딩하며, 콜론(:)을 사용해 데이터가 추가될 곳을 지정하며

query.setParameter()을 호출하여 데이터를 동적으로 바인딩한다.

 

 

위치 기준 파라미터

Member result = em.createQuery("select m from Member m where m.username = ?1", Member.class)
                .setParameter(1, "member1")
                .getSingleResult();

System.out.println("result = " + result.getUsername());

위치 기준 파라미터를 사용하려면 ? 다음에 위치 값을 주면 된다.

 

위치가 변경될 가능성이 높기 때문에, 위치 기준 파라미터보다는 이름 기준 파라미터를 사용하는 것을 권장한다고 한다.

 

 

 

JPQL의 문제점

객체지향 쿼리이지만, 문자열을 통해 JPQL을 작성하는 것이기 때문에 오타가 발생하기 쉽고, 유지보수가 어렵다.

 

또한 문자열 자체의 검증이 컴파일 단계에서 불가능하기 때문에, 쿼리가 직접 실행되는 런타임에서 해당 오류를 발견할 수 있다. 물론 이는 테스트 코드에서 해소할 수 있는 부분이 있지만, 실제 운영 시에도 발생할 수 있다는 불안을 해소할 수 없다.

 

 

 

 

 

 

 

 

 

참조

https://dev-coco.tistory.com/141

https://ittrue.tistory.com/270

https://ittrue.tistory.com/292

https://velog.io/@soyeon207/QueryDSL-%EC%9D%B4%EB%9E%80

 

 

 

 

 

 

관련 포스팅

QueryDSL란? - QueryDSL 사용하기(1)

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[Java] BufferedReader BufferedWriter

Tech/Java,Kotlin,Spring 2023. 6. 6. 06:56
728x90
728x90

서론

요새 프로그래머스가 잠잠해져서 LeetCode 외에도 HackerRank라는 플랫폼에서도 코테문제를 풀고있는데, 얼른 플랫폼에 적응해서 적당한 난이도의 문제를 풀고 포스팅 하고 싶다

 

 

HackerRank - Online Coding Tests and Technical Interviews

HackerRank is the market-leading technical assessment and remote interview solution for hiring developers. Start hiring at the pace of innovation!

www.hackerrank.com

 

서론이 이상하게 흘러갔는데 여튼, 백준처럼 직접 입력받아 출력해야하는 포맷의 HackerRank인 만큼, 입출력에 대한 공부를 조금더 할 필요가 있었다.

 

 

 

BufferedReader / BufferedWriter

기존 입출력을 Scanner와 System.out을 활용하는 것에 익숙해져 있었고

요새는 코딩을 할 때, 파일 입출력 외에는 입출력을 사용하지 않아 거의 가물가물..

 

코테 문제를 풀 때, String값을 자주 바꿔줘야할 때 아래의 예시처럼, String에 직접 값을 추가하는 것 보다 StringBuffer의 append()를 활용하는 것이 성능 테스트 시 속도가 더 빠른 것을 경험한 적이 있다.

String str = "";
str += "a";
str += "pp";
str += "le";

StringBuffer str = new StringBuffer();
str.append("a");
str.append("pp");
str.append("le");

 

입력된 데이터가 버퍼를 거쳐 전달되어 데이터 처리 효율성이 높다. 즉 속도가 훨씬 빠르다. 그래서 많은 양의 데이터를 처리할 때 유리하며, 코테 문제에서도 StringBuffer을 사용하는 것이 더 좋다. 라고 하는 것이다.

 

 

BufferedReader의 기본 사용법은 아래와 같다.

BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));

String str = bf.readLine();
int num = Integer.parseInt(bf.readLine());

 

입력 시에는 readLine()을 활용하며, 아래의 int 선언구와 같이 String이 아닌 다른 타입으로 사용 시 형변환이 필수적이다. 또한 예외처리가 필수적이며, IDE를 사용 시 readLine을 사용하다 보면 IOException에 대한 예외처리를 해달라고 컴파일 에러를 낸다. 말그대로 Input / Output에 대한 Exception이다.

 

 

IOException (Java Platform SE 8 )

Constructs an IOException with the specified detail message and cause. Note that the detail message associated with cause is not automatically incorporated into this exception's detail message.

docs.oracle.com

 

 

또한 위의 예시에서처럼, Line단위로 구분을 짓기 때문에, 가공을 위해서 후처리를 해주어야 한다.

StringTokenizer st = new StringTokenizer(str);
int num1 = Integer.parseInt(st.nextToken());
int num2 = Integer.parseInt(st.nextToken());

String str[] = str.split(" ");

StringTokenizer에 입력받은 문자열을 넣어, nextToken()을 활용하여 입력받은 줄 단위의 문자열에서 공백 단위로 구분지어 순서대로 호출할 수 있다. 또한 split을 사용하는 것도 방법이 될 수 있다.

 

그리고, 버퍼를 사용하여 데이터를 읽기 때문에 버퍼에 남아있는 데이터를 소비하고 해제하여야 하며 이를 위해 close 메서드를 마지막에 사용해 주는 것이 좋다.

bf.close();

 

 

 

다음으로 BufferedWriter을 사용하는 예제이다.

BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
String str = "apple";
bw.write(str+"\n");
bw.flush();
bw.close();

 

우선 flush()와 close()가 눈에 띄는데, 객체 생성 시 새로운 버퍼가 생성되기 때문에, flush()를 사용하여 버퍼에 남아있는 데이터를 강제로 출력하고, 버퍼를 비워주어야하며, close()를 사용하여 버퍼를 닫고 리소스를 해제하는 후처리 작업이 동행되어야 한다.

 

 

 

 

예제

서론에서 언급했던 HackerRank 예제이다

 

Simple Array Sum | HackerRank

Calculate the sum of integers in an array.

www.hackerrank.com

import java.io.*;
import java.math.*;
import java.security.*;
import java.text.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import java.util.regex.*;
import java.util.stream.*;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;

class Result {

    /*
     * Complete the 'simpleArraySum' function below.
     *
     * The function is expected to return an INTEGER.
     * The function accepts INTEGER_ARRAY ar as parameter.
     */

    public static int simpleArraySum(List<Integer> ar) {
    // Write your code here

    }

}

public class Solution {
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(System.getenv("OUTPUT_PATH")));

        int arCount = Integer.parseInt(bufferedReader.readLine().trim());

        List<Integer> ar = Stream.of(bufferedReader.readLine().replaceAll("\\s+$", "").split(" "))
            .map(Integer::parseInt)
            .collect(toList());

        int result = Result.simpleArraySum(ar);

        bufferedWriter.write(String.valueOf(result));
        bufferedWriter.newLine();

        bufferedReader.close();
        bufferedWriter.close();
    }
}

 

단순 배열안에 있는 모든 값을 더하는 문제였고 아래와 같이 해결함.

public static int simpleArraySum(List<Integer> ar) {
    int answer = 0;
    for(int i : ar){
        answer += i;
    }
    return answer;
}
728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

SynchronizedList / CopyOnWriteArrayList

Tech/Java,Kotlin,Spring 2023. 6. 2. 06:16
728x90
728x90

[Java] 자바에서의 스레드 안전(Thread Safe)과 모니터(monitor)

연관 게시물 https://mag1c.tistory.com/364 스레드 안전 - Thread Safe 연관 게시물 https://mag1c.tistory.com/365 [Java] 자바에서의 스레드 안전(Thread Safe)과 모니터(monitor) 자바에서의 Thread-Safe 1. Lock synchronized 아래

mag1c.tistory.com

스레드 안전 - Thread Safe

연관 게시물 https://mag1c.tistory.com/365 [Java] 자바에서의 스레드 안전(Thread Safe)과 모니터(monitor) 자바에서의 Thread-Safe 1. Lock synchronized 아래 코드는 Synchronized 키워드를 사용하여 스레드의 안전성을 보

mag1c.tistory.com

 


 
 
 
멀티 쓰레드 환경에서, List를 사용하기 위해 SynchronizedList나 CopyOnWriteArrayList를 사용할 수 있다.
 
 

SynchronizedList

java.utils.Collections의 List를 상속받아 구현되어있으며, synchronized 키워드를 통해 thread-safe를 구현시킨다.

// java.util.Collections

...

static class SynchronizedList<E> extends SynchronizedCollection<E> implements List<E> {
    private static final long serialVersionUID = -7754090372962971524L;

    final List<E> list;

    ...

    public E get(int index) {
        synchronized (mutex) {return list.get(index);}
    }
    public E set(int index, E element) {
        synchronized (mutex) {return list.set(index, element);}
    }
    public void add(int index, E element) {
        synchronized (mutex) {list.add(index, element);}
    }
    public E remove(int index) {
        synchronized (mutex) {return list.remove(index);}
    }

    public int indexOf(Object o) {
        synchronized (mutex) {return list.indexOf(o);}
    }
    public int lastIndexOf(Object o) {
        synchronized (mutex) {return list.lastIndexOf(o);}
    }

    ...
}

 
 
 

CopyOnWriteArrayList

set, add과정에서 lock과 함께 array를 copy후 write하는 방식으로 처리하기 때문에
get을 수행할 때 thread-safe하게 조회가 가능하다.

// java.util.concurrent.CopyOnWriteArrayList

public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;

    final transient Object lock = new Object();

    private transient volatile Object[] array;

    ...

    public E get(int index) {
        return elementAt(getArray(), index);
    }

    public E set(int index, E element) {
        synchronized (lock) {
            Object[] es = getArray();
            E oldValue = elementAt(es, index);

            if (oldValue != element) {
                es = es.clone();
                es[index] = element;
            }
            setArray(es);
            return oldValue;
        }
    }

    public boolean add(E e) {
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            es = Arrays.copyOf(es, len + 1);
            es[len] = e;
            setArray(es);
            return true;
        }
    }

    ...
}

SynchronizedList는 단순 synchronized 키워드를 사용한 동기화된 List이기 때문에 모든 읽기, 쓰기 동작에 lock이 걸린다. 때문에 읽기보다 쓰기 동작이 많고, 크기가 큰 리스트의 경우에 적합할 것이다.
 
CopyOnWriteArrayList는 set, add과정에서 lock과 함께 copy후 write하는 방식이기 때문에, 읽기 동작에서의 수행능력이 좋을 것이다. 또한 특유의 동작 방식때문에 쓰기 동작에서는 상당한 부하가 발생할 것이다.

 

 
 

SynchronizedList : read < write
CopyOnWriteArrayList : read > write

 
 
 
 
 
 
 
 
 

참조
https://taes-k.github.io/2021/12/26/synchronizedlist-copyonwritelist/
https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedList-java.util.List-
http://asuraiv.blogspot.com/2020/02/java-synchronizedlist-vs.html
https://jihyehwang09.github.io/2020/04/14/java-copyonwritearraylist/
 
 
 

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

방명록