Skip to content

Commit

Permalink
- feat : hibernate-reactive 적용
Browse files Browse the repository at this point in the history
  • Loading branch information
wlsdnjs829 committed Jul 16, 2022
1 parent 30a9736 commit 11edfa9
Show file tree
Hide file tree
Showing 11 changed files with 404 additions and 3 deletions.
13 changes: 11 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,20 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-webflux'

implementation 'io.vertx:vertx-pg-client:4.3.0'
implementation 'org.hibernate.reactive:hibernate-reactive-core:1.1.6.Final'
implementation 'org.hibernate:hibernate-jpamodelgen'
implementation 'javax.validation:validation-api'
implementation 'com.ongres.scram:client:2.1'

implementation 'org.springdoc:springdoc-openapi-ui:1.6.9'
implementation 'org.springdoc:springdoc-openapi-webflux-ui:1.6.9'

compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.example.hibernatereactive;
package com.example.hibernate.reactive;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.example.hibernate.reactive.config;

import io.swagger.v3.core.converter.ModelConverters;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.SpecVersion;
import io.swagger.v3.oas.models.info.Info;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.GroupedOpenApi;
import org.springdoc.core.providers.ObjectMapperProvider;
import org.springdoc.webflux.core.converters.WebFluxSupportConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* Open API
*/
@Configuration
@RequiredArgsConstructor
public class OpenApiConfig {

private final ObjectMapperProvider objectMapperProvider;

private static final String WILD_CARD = "/**";
private static final String API_NAME = "Hibernate-reactive";
private static final String API_VERSION = "0.0.1";
private static final String API_DESCRIPTION = "Hibernate-reactive API 명세서";

@Bean
public GroupedOpenApi api() {
return GroupedOpenApi.builder()
.group(API_NAME)
.pathsToMatch(WILD_CARD)
.build();
}

@Bean
public OpenAPI springShopOpenAPI() {
ModelConverters.getInstance().addConverter(new WebFluxSupportConverter(objectMapperProvider));

return new OpenAPI()
.specVersion(SpecVersion.V31)
.info(new Info().title(API_NAME)
.description(API_DESCRIPTION)
.version(API_VERSION));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.example.hibernate.reactive.config;

import org.hibernate.reactive.stage.Stage;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.Persistence;

/**
* 세션 팩토리 설정
*/
@Configuration
public class SessionFactoryConfig {

private static final String PERSISTENCE_UNIT_NAME = "hibernate-reactive-persistence";

/**
* 세션 팩토리 반환
* <p>
* 영속성 단계에서 Stage & Mutiny 존재
* <p>
* Mutiny Uni 사용한 비동기 인터페이스
* https://smallrye.io/smallrye-mutiny/1.6.0/#reactive-converters-built-in
* <p>
* Stage CompletionStage 사용한 비동기 인터페이스
*/
@Bean
public Stage.SessionFactory sessionFactory() {
return Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME)
.unwrap(Stage.SessionFactory.class);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.example.hibernate.reactive.domain.customer;

import com.example.hibernate.reactive.enums.SexType;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

/**
* 테스트용 테이블
*/
@Table
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Customer {

@Id
@GeneratedValue
private Integer id;

@NotNull
@Size(max = 100)
@Column(unique = true, length = 100)
private String name;

@Column(length = 30)
@Enumerated(value = EnumType.STRING)
private SexType sex;

@Column
@Min(value = 18)
@Max(value = 70)
private Integer age;

public Customer(String name, SexType sex, Integer age) {
this.name = name;
this.sex = sex;
this.age = age;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.example.hibernate.reactive.domain.customer;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
@RequiredArgsConstructor
@Tag(name = "고객 Controller")
@RequestMapping(value = "/customer")
public class CustomerController {

private final CustomerService customerService;

@Operation(summary = "고객 조회")
@GetMapping(value = "/{name}")
public Mono<CustomerDto> getCustomer(@PathVariable String name) throws InterruptedException {
return customerService.getCustomer(name);
}

@PostMapping
@Operation(summary = "고객 등록")
public Mono<Void> save(@RequestBody CustomerDto customerDto) {
return customerService.save(customerDto);
}

@PutMapping
@Operation(summary = "고객 업데이트")
public Mono<Integer> update(@RequestBody CustomerDto customerDto) {
return customerService.update(customerDto);
}

@Operation(summary = "고객 삭제")
@DeleteMapping(value = "/{name}")
public Mono<Integer> delete(@PathVariable String name) {
return customerService.delete(name);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.hibernate.reactive.domain.customer;

import com.example.hibernate.reactive.enums.SexType;
import io.swagger.v3.oas.annotations.media.Schema;

import javax.validation.constraints.NotNull;

@Schema(description = "고객 객체")
public record CustomerDto(
@Schema(description = "이름", example = "이진원") @NotNull String name,
@Schema(description = "성별") @NotNull SexType sex,
@Schema(description = "나이", example = "28") @NotNull Integer age) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.example.hibernate.reactive.domain.customer;

import lombok.RequiredArgsConstructor;
import org.hibernate.reactive.stage.Stage;
import org.springframework.stereotype.Repository;

import java.util.concurrent.CompletableFuture;

/**
* 고객 Repository
*/
@Repository
@RequiredArgsConstructor
public class CustomerRepository {

private final Stage.SessionFactory sessionFactory;

private static final String NAME = "name";
private static final String SEX = "sex";
private static final String AGE = "age";

/**
* 고객 저장
*
* @param customer 고객
* @return CompletableFuture<Void>
*/
public CompletableFuture<Void> save(Customer customer) {
return sessionFactory.withTransaction(
(session, transaction) -> session.persist(customer)
).toCompletableFuture();
}

/**
* 고객 조회
*
* @param name 이름
* @return CompletableFuture<Customer>
*/
public CompletableFuture<Customer> get(String name) {
return sessionFactory.withSession(
session -> session.createQuery("from Customer where name=:name order by name", Customer.class)
.setParameter(NAME, name)
.getSingleResult()
).toCompletableFuture();
}

/**
* 고객 업데이트
*
* @param customer 고객
* @return CompletableFuture<Integer>
*/
public CompletableFuture<Integer> update(Customer customer) {
return sessionFactory.withTransaction(
session -> session
.createQuery("update Customer set name=:name, sex=:sex, age=:age where name=:name")
.setParameter(NAME, customer.getName())
.setParameter(SEX, customer.getSex())
.setParameter(AGE, customer.getAge())
.executeUpdate()
).toCompletableFuture();
}

/**
* 고객 삭제
*
* @param name 이름
* @return CompletableFuture<Integer>
*/
public CompletableFuture<Integer> delete(String name) {
return sessionFactory.withTransaction(
session -> session.createQuery("delete Customer where name=:name")
.setParameter(NAME, name)
.executeUpdate()
).toCompletableFuture();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.example.hibernate.reactive.domain.customer;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

@Slf4j
@Service
@AllArgsConstructor
public class CustomerService {

private final CustomerRepository customerRepository;

private static final String DELETE_CUSTOMER_FORMAT = "{} 고객 정보 : {} 삭제 완료";
private static final String POST_CUSTOMER_INFO = "고객 이름 : {} 저장 완료";
private static final String UPDATE_CUSTOMER_INFO = "{} 고객 업데이트 완료";

/**
* 고객 조회
*
* @param name 이름
*/
public Mono<CustomerDto> getCustomer(String name) {
var customerCf = customerRepository.get(name);
return Mono.fromFuture(customerCf)
.cast(Customer.class)
.map(customer -> new CustomerDto(customer.getName(), customer.getSex(), customer.getAge()));
}

/**
* 고객 저장
*
* @param customerDto 고객 객체
*/
public Mono<Void> save(CustomerDto customerDto) {
var customer = new Customer(customerDto.name(), customerDto.sex(), customerDto.age());
var cfInsert = customerRepository.save(customer);
return Mono.fromFuture(cfInsert)
.cast(Void.class)
.doOnNext(v -> log.info(POST_CUSTOMER_INFO, customer.getName()));
}

/**
* 고객 업데이트
*
* @param customerDto 고객 객체
*/
public Mono<Integer> update(CustomerDto customerDto) {
var customer = new Customer(customerDto.name(), customerDto.sex(), customerDto.age());
var updateCf = customerRepository.update(customer);
return Mono.fromFuture(updateCf)
.cast(Integer.class)
.doOnNext(info -> log.info(UPDATE_CUSTOMER_INFO, info));
}

/**
* 고객 삭제
*
* @param name 이름
*/
public Mono<Integer> delete(String name) {
var deleteCf = customerRepository.delete(name);
return Mono.fromFuture(deleteCf)
.cast(Integer.class)
.doOnNext(info -> log.info(DELETE_CUSTOMER_FORMAT, info, name));
}

}
10 changes: 10 additions & 0 deletions src/main/java/com/example/hibernate/reactive/enums/SexType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example.hibernate.reactive.enums;

/**
* 성별 타입
*/
public enum SexType {

MALE,
FEMALE,
}
Loading

0 comments on commit 11edfa9

Please sign in to comment.