์ฌ์ฉ๋๋ ์ฝ๋๋ฅผ ์ ๋ฆฌํด๋ด ๋๋น
๊ทธ์ ์์ง ์๊ธฐ ์ํ ๊ธฐ๋ก์ฉ์ด๊ณ ...๐ + WebFlux์ MongoDB๋ ์กฐ๊ธ ์ ๋ฆฌ
์์๋ก ์ ๋ฐ์ดํธ ์์ .. ๐ค
Document (MongoDB)
- data class
- ๋ฐ์ดํฐ๋ฅผ ๋ด๋ ๋ชฉ์ ์ ํด๋์ค ์ ์ธ
- ๋ฐ์ดํฐ ์ ์ฅ, ๊ด๋ฆฌํ๋ ๋ฐ ํ์ํ ๊ธฐ๋ณธ์ ์ธ ๋ฉ์๋ ์๋ ์์ฑ
- equals(), hashCode(), toString(), componentN(), copy() ๋ฑ ์์ฑ ๐ ํ์์ ๋ฐ๋ผ ์ง์ ๊ตฌํ ๊ฐ๋ฅ
- ๋ฐ์ดํฐ ํด๋์ค์ ์ฃผ ์์ฑ์์ ์ ์ธ๋ ํ๋กํผํฐ ๐ val๋ก ์ ์ธ
data class User(val name: String, val age: Int)
fun main() {
val user1 = User("Alice", 30)
val user2 = User("Bob", 25)
println(user1.toString()) // ์ถ๋ ฅ: User(name=Alice, age=30)
println(user1.hashCode()) // ์ถ๋ ฅ: ํด์ ์ฝ๋ ๊ฐ
println(user1 == user2) // ์ถ๋ ฅ: false, ๊ฐ ํ๋๋ฅผ ๋น๊ตํ์ฌ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํฉ๋๋ค.
val olderUser = user1.copy(age = 35)
println(olderUser) // ์ถ๋ ฅ: User(name=Alice, age=35)
}
- companion object
- ํด๋์ค ์์ ์ ์๋๋ ํน๋ณํ ๊ฐ์ฒด
- ํด๋์ค์ ์ธ์คํด์ค ์์ด๋ ํด๋์ค์ ์ฐ๊ด๋ ์ ์ ๋ฉค๋ฒ๋ ๋ฉ์๋ ์ ์ธ ๊ฐ๋ฅ
- ์๋ฐ์ static์ ์ ์ฌํ ์ญํ
- ๊ฐ ํด๋์ค๋ ๋จ ํ๋์ companion object๋ง ๊ฐ์ง ์ ์์
- ๊ธฐ๋ณธ ์ด๋ฆ ์๋ต ๊ฐ๋ฅ ๐ companion
class User(val name: String, val age: Int) {
companion object {
fun create(name: String): User {
return User(name, 0)
}
}
}
fun main() {
val user = User.create("Alice")
println("User: ${user.name}, Age: ${user.age}") // "User: Alice, Age: 0" ์ถ๋ ฅ
}
๐ ์ธ์คํด์ค ์์ฑํ์ง ์๊ณ ๋ companion object ๋ฉค๋ฒ ์ ๊ทผ ๊ฐ๋ฅ
- operator fun invoke
- operator fun์ ์ฐ์ฐ์ ์ค๋ฒ๋ก๋ฉ ํ์ฉํ๋ ํน๋ณํ ํจ์ ์ ์ธ ๋ฐฉ์
- ์ฐ์ฐ์ ์ค๋ฒ๋ก๋ฉ ๐ ํด๋์ค๋ ๋ฐ์ดํฐ ํ์ ์ ๋ํด ํน์ ์ฐ์ฐ์์ ๋์์ ์ ์ํ๊ฑฐ๋ ์ฌ์ ์
- invoke ์ฐ์ฐ์ ๐ ๊ฐ์ฒด๋ฅผ ํจ์์ฒ๋ผ ํธ์ถ ๊ฐ๋ฅ
class Greeter(val greeting: String) {
operator fun invoke(name: String) {
println("$greeting, $name!")
}
}
fun main() {
val greeter = Greeter("Hello")
greeter("World") // ์ถ๋ ฅ: Hello, World!
}
greeter ๊ฐ์ฒด๋ฅผ ํจ์์ฒ๋ผ ํธ์ถ
greeter("World")์ greeter.invoke("World")๋ ๋์ผ
- with(๊ฐ์ฒด) ๐ with๊ด๋ จ ์ ๋ฆฌ
- ๊ฒฐ๊ณผ ๋ฐํ ์์ด ๋ด๋ถ์์ ์์ ๊ฐ์ฒด๋ฅผ ์ด์ฉํด ๋ค๋ฅธ ํจ์ ํธ์ถ
- ์ฃผ์ด์ง ๊ฐ์ฒด์ ๋ํด ๋ธ๋ก์ ์คํํ๊ณ ๋ธ๋ก ๋ด์์ ํด๋น ๊ฐ์ฒด์ ๋ฉค๋ฒ์ ์ง์ ์ ๊ทผ
@Document("cat")
data class CatDocument(
@Id
val catId: String? = null,
val color: String?,
val catType: List<String>?,
){
companion object{
operator fun invoke(cat: Cat) = with(cat){
CatDocument(
catId = catId, // catId = this.catId -> this ์๋ต
color = color,
catType = catType,
) // CatDocument ์์ฑ์ ํธ์ถํ์ฌ ์๋ก์ด CatDocument ๊ฐ์ฒด ์์ฑ
}
}
}
๐ Cat ๊ฐ์ฒด๋ฅผ CatDocument ๊ฐ์ฒด๋ก ๋ณํํ๋ ๊ธฐ๋ฅ
๐ cat์ with ๋ธ๋ก ์์์ Cat ๊ฐ์ฒด์ ํ๋์ ์ ๊ทผํด์ cat์ ๊ธฐ๋ฐ์ผ๋ก CatDocument ๊ฐ์ฒด ์์ฑ
๐ with ๋ธ๋ก ๋ด์ this๋ cat์ ๊ฐ๋ฆฌํด !
Repository (MongoDB)
- ReactiveMongoRepository
- ์คํ๋ง ๋ฐ์ดํฐ MongoDB์์ ์ ๊ณตํ๋ ์ธํฐํ์ด์ค
- ๋น๋๊ธฐ์ ์ผ๋ก MongoDB ์กฐ์ ๊ธฐ๋ฅ ์ ๊ณต ๐ Webflux
- CRUD ๋ฉ์๋ ์ ๊ณต
- @Document ์ด๋ ธํ ์ด์ ์ฌ์ฉํ์ฌ MongoDB์ ์ปฌ๋ ์ ์ ๋งคํ๋จ
- @EnableReactiveMongoRepository
- Spring Data MongoDB์ reactive repository๋ฅผ ํ์ฑํํ๋ ๋ฐ ์ฌ์ฉ
- basePackages : ๋ฆฌํฌ์งํ ๋ฆฌ ์ธํฐํ์ด์ค๋ฅผ ๊ฒ์ํ ํจํค์ง ์ง์
- reactiveMongoTemplateRef : ํน์ ReactiveMongoTemplate ๋น ์ฌ์ฉ ์ง์
@SpringBootApplication
@EnableReactiveMongoRepositories(
basePackages = ["com.example.repositories"],
reactiveMongoTemplateRef = "reactiveMongoTemplate"
)
- MongoDB์์ ๊ฐ์ฒด๋ฅผ ์ฟผ๋ฆฌํ์ฌ ํน์ ์กฐ๊ฑด์ ๋ง๋ ๊ฒฐ๊ณผ ํ์ด์ง ์ฒ๋ฆฌ
dbConfiguration.MongoTemplate().find(query, Document::class.java)
.skip(skipOffset)
.take(size)
๐น query๋ MongoDB์์ ๊ฒ์ํ ์กฐ๊ฑด ์ ์
๐น Document::class.java๋ ๊ฒ์ํ ๋ฌธ์์ ํด๋์ค ํ์ ์ง์ ๋ฐ ๋ฐํํ ๊ฐ์ฒด
๐น.skip(skipOffset) ๋ ๊ฒฐ๊ณผ ์งํฉ์์ ์ง์ ๋ ์์ ๋ฌธ์ ๊ฑด๋๋ฐ๋๋ก ์ง์
๐น.take(size) ๋ ๋ฐํํ ์ต๋ ๋ฌธ์ ์ = ํ ํ์ด์ง์ ํ์ํ ๋ฌธ์ ์
- Criteria
- ์ฟผ๋ฆฌ ์์ฑ ์ ์กฐ๊ฑด ์ ์ํ๋ ๋ฐ ์ฌ์ฉ๋๋ ๊ฐ์ฒด ๐ ํํฐ๋ง
- addCriteria() ๐ Query ๊ฐ์ฒด์ ์๋ก์ด ๊ฒ์ ์กฐ๊ฑด ์ถ๊ฐ ์ญํ
@Service
class PersonService @Autowired constructor(private val mongoTemplate: MongoTemplate) {
fun findByLastNameAndAge(lastName: String, age: Int): List<Person> {
val criteria = Criteria().andOperator(
Criteria("lastName").`is`(lastName),
Criteria("age").gte(age)
)
val query = Query(criteria)
return mongoTemplate.find(query, Person::class.java)
}
}
@Service
public class UserService {
@Autowired
private MongoTemplate mongoTemplate;
public List<User> findByAgeGreaterThan(int age) {
Query query = new Query();
query.addCriteria(Criteria.where("age").gt(age));
return mongoTemplate.find(query, User.class);
}
}
- query.with()
- ๋ค์ํ ์ต์ ์ ์ฉ ๐ ํ์ด์ง, ์ ๋ ฌ, ํน์ ์ต์ ๋ฑ ์ค์
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
Query query = new Query();
Query query2 = new Query();
query.with(Sort.by(Sort.Direction.ASC, "fieldName"));
query2.with(PageRequest.of(pageNumber, pageSize));
Service
- flatMap
- ๋น๋๊ธฐ์ ์ผ๋ก ๋ฐ์ดํฐ ์คํธ๋ฆผ ์ฒ๋ฆฌํ๊ณ ์ฐ๊ฒฐ
- let
- null์ด ์๋ ๊ฒฝ์ฐ ์ฌ์ฉ๋ ๋ก์ง ์์ฑํ๊ณ ์๋ก์ด ๊ฒฐ๊ณผ ๋ฐํ
- Mono.just()
- ๋น๋๊ธฐ์ ์ผ๋ก ๋จ์ผ ๊ฐ ์์ฑ
- doOnSuccess
- Mono ๋๋ Flux์์ ๋น๋๊ธฐ ์์ ์ด ์ฑ๊ณต์ ์ผ๋ก ์๋ฃ๋ ํ ๋ถ๊ฐ์ ์ธ ๋์ ์ํ
override fun update(taskId: String, task: Task): Mono<Task> {
log.info { "[update] taskId: $taskId, task: $task" }
return repository.findById(taskId)
.flatMap { document ->
repository.save(TaskDocument(taskId, document, task))
.flatMap { taskDocument ->
task.taskSchedules?.let {
taskScheduleService.updateByTaskId(taskId, it, Task(taskDocument))
.collectList()
.map { scheduleList -> Task(taskDocument, scheduleList) }
} ?: Mono.just(Task(taskDocument))
}
.doOnSuccess { log.info { "[update] taskId: $taskId" } }
}
}
๐ฅ ๋ณดํต Spring + JPA๋ฅผ ์ฌ์ฉํด์ UPDATE ๋ก์ง์ ๊ตฌํํ ๋ ์ํฐํฐ ๊ณ์ธต์์ ์์ ๊ด๋ จ ๋น์ฆ๋์ค ๋ก์ง์ ์ถ๊ฐํด์ ์ฒ๋ฆฌํ๊ธฐ ๋๋ฌธ์ repository.save()๋ฅผ ์ฌ์ฉํ ํ์๊ฐ ์์๋ค ๐ EntityManager๊ฐ ํธ๋์ญ์ ๋ด์์ ์ํฐํฐ์ ๋ณ๊ฒฝ์ฌํญ์ ์๋์ผ๋ก ์ถ์ ํ๊ธฐ ๋๋ฌธ(dirty Checking)
โจ ํ์ง๋ง ๋ฆฌ์กํฐ๋ธ ํ๋ก๊ทธ๋๋ฐ ๋ชจ๋ธ์์๋ ๋ชจ๋ ์์ ์ด non-blocking ๋ฐ ๋น๋๊ธฐ์ ์ผ๋ก ์ํ๋๋ฏ๋ก ์ํฐํฐ๋ฅผ ์์์ฑ ์ปจํ ์คํธ์์ ๊ด๋ฆฌํ์ง ์์ dirty checking์ด ์กด์ฌํ์ง ์๊ฒ ๋๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ์ํฐํฐ์ ๋ํ ๋ชจ๋ ์์ ์ฌํญ์ ๋ช ์์ ์ผ๋ก DB์ ์ ์ฅ๋๊ฒ๋ ํด์ผํ๋ฏ๋ก repository.save()๋ฅผ ํธ์ถํด ๋ณ๊ฒฝ์ฌํญ์ ์ ์ฅ !
- thenEmpty()
Reactor ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ ๊ณตํ๋ ๋ฉ์๋๋ก, ํน๋ณํ ๋ฐํํ ๊ฐ์ด ์์ ๋ ์ฌ์ฉ
๐ ๋จ์ํ ๋น๋๊ธฐ์ ์ธ ์์ ์ด ์๋ฃ๋์์์ ์ฒ๋ฆฌํ๋ ์ฉ๋
public final Mono<Void> thenEmpty(Publisher<Void> other) {
return this.then(fromDirect(other));
}
- .when()
Reactor ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ ๊ณตํ๋ ๋ฉ์๋๋ก, ์ฌ๋ฌ ๊ฐ์ Mono๋ฅผ ๋ณ๋ ฌ๋ก ์คํํ๊ณ ๋ชจ๋ Mono๊ฐ ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฐ ํ ๊ฒฐ๊ณผ๋ฅผ ์ฒ๋ฆฌํ ์ ์๋ ๊ธฐ๋ฅ ์ ๊ณต
Aggregate given publishers into a new Mono that will be fulfilled when all of the given sources have completed.
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
public class MonoWhenExample {
public static void main(String[] args) {
// ๋ ๊ฐ์ ๋น๋๊ธฐ ์์
์ ์
Mono<String> task1 = Mono.fromCallable(() -> {
Thread.sleep(1000); // 1์ด ๋์ ๋๊ธฐ
return "Task 1 completed";
}).subscribeOn(Schedulers.boundedElastic());
Mono<String> task2 = Mono.fromCallable(() -> {
Thread.sleep(2000); // 2์ด ๋์ ๋๊ธฐ
return "Task 2 completed";
}).subscribeOn(Schedulers.boundedElastic());
// Mono.when()์ ์ฌ์ฉํ์ฌ ๋ ์์
์ ๋ณ๋ ฌ๋ก ์คํํ๊ณ , ๋ชจ๋ ์์
์ด ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆผ
Mono.when(task1, task2)
.doOnTerminate(() -> System.out.println("All tasks completed"))
.block(); // ๋ธ๋กํน ํธ์ถ๋ก ๊ฒฐ๊ณผ ๋๊ธฐ (์์ ์ฉ, ์ค์ ์ฌ์ฉ ์ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌ)
System.out.println("Program finished");
}
}
โจ ์ค์ ์ฝ๋์์๋ .thenEmpty(Mono.`when`( ~~~) )
์ด๋ ๊ฒ when ๋ฉ์๋์ ๋ฐฑํฑ์ ์ถ๊ฐํ๋ค. ๊ทธ ์ด์ ๋ ์ฝํ๋ฆฐ์์ when์ด ํค์๋์ด๊ธฐ ๋๋ฌธ์ด๋ค.
์ถฉ๋์ ํผํ๊ณ ์ฝ๋์ ๊ฐ๋ ์ฑ์ ์ ์งํ๊ธฐ ์ํด ๋ฐฑํฑ์ ์ฌ์ฉํ๋ค !
- filterNot
์ปฌ๋ ์ ์ ์์๋ฅผ ์กฐ๊ฑด์ ๋ฐ๋ผ ํํฐ๋งํ์ฌ ํน์ ์กฐ๊ฑด์ ๋ถํฉํ์ง ์๋ ์์๋ค๋ก ์๋ก์ด ์ปฌ๋ ์ ๋ฐํ
์ฐธ๊ณ ์๋ฃ
https://mangkyu.tistory.com/358
https://www.geeksforgeeks.org/spring-webflux-reactive-crud-rest-api-example/
https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html
'Kotlin' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Kotlin] ์ฝํ๋ฆฐ ๋ฌธ๋ฒ ๊ณ ๊ธ 2 (0) | 2024.07.09 |
---|---|
[Kotlin] ์ฝํ๋ฆฐ ๋ฌธ๋ฒ ๊ณ ๊ธ 1 (0) | 2024.07.08 |
[Kotlin] ์ฝํ๋ฆฐ ๋ฌธ๋ฒ ๊ธฐ์ด 2 (1) | 2024.07.08 |
[Kotlin] ์ฝํ๋ฆฐ ๋ฌธ๋ฒ ๊ธฐ์ด 1 (1) | 2024.07.05 |