Skip to content

Commit

Permalink
선착순 이벤트 퀴즈 제출 최종 점검 (#129)
Browse files Browse the repository at this point in the history
* feat: 선착순 퀴즈 제출때의 커넥션 할당을 위한 새로운 DataSource 설정 구현

* feat: 선착순 퀴즈 제출때의 커넥션 할당을 위한 Transaction Manager 구현

* feat: 선착순 퀴즈 제출을 위한 DataSource와 Transactional 실제 Service에 적용

* refactor: 선착순 퀴즈 제출을 위한 DataSource ConnectionTimeOut 최소값으로 수정

* refactor: HikariConfig 설정 수정

[- url, username, password 설정 방식 수정]

* refactor: 기본 DataSource가 빈으로 등록 되도록 구현

* feat: OrderResultSaveService가 다른 DataSource를 주입받는지 확인하는 테스트 구현

* feat: DataSource 분리 구현

[- Order 도메인 중에서 퀴즈제출과 관련된 OrderResult,OrderApplyCount 분리.
- 다른 테이블과 결합도가 적음]

* refactor: 퀴즈 정답 제출시 OrderResult용 HikariDataSource를 쓰도록 수정

* refactor: DataSource관련 설정 파일이 테스트 환경에서 등록되지 않도록 수정

* refactor: 통합 테스트가 언제나 local.yml환경에 의지하도록 수정

* refactor: 별개의 트랜잭션 매니저가 필요한 메소드 주석으로 수정

[- 추후 구현]

* refactor: 선착순 검사 통합 테스트 주석으로 수정

[- 추후 구현]

* refactor: 선착순 이벤트 스케쥴링 주기 30초 -> 1초로 수정

[=]

* refactor: 선착순 이벤트 퀴즈 제출시 커넥션을 얻지 못 했을 때의 에러 명시와 , 반환값 수정

[- 선착순 이벤트 퀴즈 제출 메소드에서 커넥션을 얻지 못할때의 에러(CannotCreateTransactionException)를 명확하게 표기하도록 수정
- 커넥션을 얻었지만 선착순 인원이 꽉 찼을 시에도 바로 값을 반환하여 커넥션을 다시 얻으려하는 시도가 없도록 수정]

* refactor: 선착순 이벤트 퀴즈 제출시 커넥션을 얻으려고 하는 횟수 수정

[- 250ms 5번 1.25초 -> 250ms 120번 -> 30초(디폴트값)]

* refactor: 선착순 퀴즈 마감시 바로 데이터소스를 닫아버리도록 수정

* refactor: 커넥션 분리 테스트 이름 수정

* refactor: 선착순 꽉 참 테스트가 숫자 확인이 아닌, Null Pointer Exception(Datasource)을 검사하도록 수정

* refactor: DataSource가 닫혔을시 바로 값을 반환하도록 수정

* refactor: 선착순이 꽉차도 DataSource가 닫히지 않도록 수정

---------

Co-authored-by: 조도연 <[email protected]>
  • Loading branch information
starwook and ysndy authored Aug 25, 2024
1 parent 179c99d commit 09b2321
Show file tree
Hide file tree
Showing 32 changed files with 539 additions and 151 deletions.
4 changes: 3 additions & 1 deletion common/src/main/java/com/watermelon/server/BaseEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {

@Column(updatable = false)
@Column(updatable = false,name ="created_at")
@CreatedDate
private LocalDateTime createdAt;


@Column(name="updated_at")
@LastModifiedDate
private LocalDateTime updatedAt;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableCaching
Expand Down
6 changes: 4 additions & 2 deletions order/src/main/java/com/watermelon/server/Scheduler.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.watermelon.server;

import com.watermelon.server.order.service.CurrentOrderEventManageService;
import com.watermelon.server.orderResult.service.CurrentOrderEventManageService;
import com.watermelon.server.order.service.OrderEventSchedulingService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -12,6 +12,8 @@
@Component
@RequiredArgsConstructor
public class Scheduler {


private final OrderEventSchedulingService orderEventSchedulingService;
private final CurrentOrderEventManageService currentOrderEventManageService;
@Scheduled(fixedRate = 1000)
Expand All @@ -23,7 +25,7 @@ public void checkOrderEvent(){
log.info("changed current order event id is {}", newCurrentEventId);
}
}
@Scheduled(fixedRate = 30000)
@Scheduled(fixedRate = 300000)
public void checkCurrentOrderEvent(){
Long currentEventId = currentOrderEventManageService.getCurrentOrderEventId();
log.info("current order event id is {}", currentEventId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.watermelon.server.common.config;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactory",
transactionManagerRef = "transactionManager",
basePackages = {"com.watermelon.server.order.repository"}
)
@Configuration
@Profile("!local")
public class DatasourceConfig {

@Value("${spring.datasource1.url}")
private String url;

@Value("${spring.datasource1.username}")
private String username;

@Value("${spring.datasource1.password}")
private String password;

@Value("${spring.datasource1.hikari.maximum-pool-size}")
private int maximumPoolSize;

@Value("${spring.datasource1.hikari.leak-detection-threshold}")
private long leakDetectionThreshold;

@Bean
@Primary
public DataSource dataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(url);
hikariConfig.setUsername(username);
hikariConfig.setPassword(password);
hikariConfig.setMaximumPoolSize(maximumPoolSize);
hikariConfig.setLeakDetectionThreshold(leakDetectionThreshold);
hikariConfig.setPoolName("orderPool");
return new HikariDataSource(hikariConfig);
}

@Bean
@Primary
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder
) {
Map<String,Object> properties = new HashMap<>();
properties.put("dialect", "org.hibernate.dialect.MySQL8InnoDBDialect");
properties.put("hibernate.show_sql", true);
properties.put("hibernate.format_sql", true);
// properties.put("hibernate.ddl-auto", "update");
properties.put("open_in_view", "false");
properties.put("hibernate.hbm2ddl.auto", "update");
properties.put("hibernate.physical_naming_strategy" , "org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy");
return builder
.dataSource(dataSource())
.packages("com.watermelon.server.order")
.properties(properties)
.build();
}

@Bean
@Primary
public JpaTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory.getObject());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//package com.watermelon.server.common.config;
//
//
//import com.zaxxer.hikari.HikariDataSource;
//import jakarta.persistence.EntityManagerFactory;
//import org.springframework.beans.factory.annotation.Value;
//import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
//import org.springframework.context.annotation.Bean;
//import org.springframework.context.annotation.Configuration;
//import org.springframework.context.annotation.Primary;
//import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
//import org.springframework.orm.jpa.JpaTransactionManager;
//import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
//
//@Configuration
//@EnableJpaRepositories(
// entityManagerFactoryRef = "orderResultEntityManager",
// transactionManagerRef = "orderResultTransactionManager",
// basePackages = {"com.watermelon.server.order.repository"}
//)
//public class HikariConfig {
// @Value("${spring.datasource.url}")
// private String url;
//
// @Value("${spring.datasource.username}")
// private String username;
//
// @Value("${spring.datasource.password}")
// private String password;
//
// private int maximumPoolSize =10;
//
// @Bean
// @Primary
// public HikariDataSource orderDataSource() {
// com.zaxxer.hikari.HikariConfig hikariConfig = new com.zaxxer.hikari.HikariConfig();
// hikariConfig.setJdbcUrl(url);
// hikariConfig.setUsername(username);
// hikariConfig.setPassword(password);
// hikariConfig.setMaximumPoolSize(maximumPoolSize);
// return new HikariDataSource(hikariConfig);
// }
// @Bean(name = "orderResultDatasource")
// public HikariDataSource orderResultDataSource() {
// com.zaxxer.hikari.HikariConfig hikariConfig = new com.zaxxer.hikari.HikariConfig();
// hikariConfig.setJdbcUrl(url);
// hikariConfig.setUsername(username);
// hikariConfig.setPassword(password);
// hikariConfig.setMaximumPoolSize(maximumPoolSize);
// hikariConfig.setConnectionTimeout(250L);
// //orderEventQuiz만을 위한 설정 0.1초마다 확인
// return new HikariDataSource(hikariConfig);
// }
//
// @Bean
// @Primary
// public LocalContainerEntityManagerFactoryBean orderEntityManager(EntityManagerFactoryBuilder builder) {
// return builder
// .dataSource(orderDataSource())
// .packages("com/watermelon/server/order")
// .persistenceUnit("order")
// .build();
//// LocalContainerEntityManagerFactoryBean orderManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
//// orderManagerFactoryBean.setDataSource(orderDataSource());
//// orderManagerFactoryBean.setPackagesToScan("com/watermelon/server/order");
//// orderManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
//// orderManagerFactoryBean.setPersistenceUnitName("order");
//// return orderManagerFactoryBean;
// }
//
// @Bean(name ="orderResultEntityManager")
// public LocalContainerEntityManagerFactoryBean orderResultEntityManager(EntityManagerFactoryBuilder builder) {
// return builder
// .dataSource(orderResultDataSource())
// .packages("com/watermelon/server/order")
// .persistenceUnit("orderResult")
// .build();
// }
//
// @Bean
// @Primary
// public JpaTransactionManager orderTransactionManager(
// EntityManagerFactory entityManagerFactory
// ) {
// return new JpaTransactionManager(entityManagerFactory);
// }
//
// @Bean(name = "orderResultTransactionManager")
// public JpaTransactionManager orderResultTransactionManager(
// EntityManagerFactory entityManagerFactory
// ) {
// return new JpaTransactionManager(entityManagerFactory);
// }
//}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//package com.watermelon.server.common.config;
//
//
//import com.zaxxer.hikari.HikariConfig;
//import com.zaxxer.hikari.HikariDataSource;
//import jakarta.persistence.EntityManagerFactory;
//import org.springframework.beans.factory.annotation.Value;
//import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
//import org.springframework.context.annotation.Bean;
//import org.springframework.context.annotation.Configuration;
//import org.springframework.context.annotation.Primary;
//import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
//import org.springframework.orm.jpa.JpaTransactionManager;
//import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
//import org.springframework.transaction.annotation.EnableTransactionManagement;
//
//@EnableTransactionManagement
//@EnableJpaRepositories(
// entityManagerFactoryRef = "orderEntityManager",
// transactionManagerRef = "orderTransactionManager",
// basePackages = {"com.watermelon.server.order.repository"}
//)
//@Configuration
//public class OriginHikariConfig {
// @Value("${spring.datasource1.url}")
// private String url;
//
// @Value("${spring.datasource1.username}")
// private String username;
//
// @Value("${spring.datasource1.password}")
// private String password;
//
// private int maximumPoolSize =10;
//
// @Bean
// @Primary
// public HikariDataSource orderDataSource() {
// HikariConfig hikariConfig = new HikariConfig();
// hikariConfig.setJdbcUrl(url);
// hikariConfig.setUsername(username);
// hikariConfig.setPassword(password);
// hikariConfig.setMaximumPoolSize(maximumPoolSize);
// return new HikariDataSource(hikariConfig);
// }
//
// @Bean
// @Primary
// public LocalContainerEntityManagerFactoryBean orderEntityManager(EntityManagerFactoryBuilder builder) {
// return builder
// .dataSource(orderDataSource())
// .packages("com/watermelon/server/order")
// .persistenceUnit("order")
// .build();
// }
// @Bean
// @Primary
// public JpaTransactionManager orderTransactionManager(
// EntityManagerFactory entityManagerFactory
// ) {
// return new JpaTransactionManager(entityManagerFactory);
// }
//}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import com.watermelon.server.order.exception.WinnerAlreadyParticipateException;
import com.watermelon.server.order.exception.WrongPhoneNumberFormatException;
import com.watermelon.server.order.exception.WrongOrderEventFormatException;
import com.watermelon.server.order.result.service.OrderResultCommandService;
import com.watermelon.server.orderResult.service.OrderResultCommandService;
import com.watermelon.server.order.service.OrderEventCommandService;
import com.watermelon.server.order.service.OrderEventQueryService;
import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.watermelon.server.order.domain;

import com.fasterxml.jackson.databind.ser.Serializers;
import com.watermelon.server.BaseEntity;
import com.watermelon.server.order.dto.request.RequestOrderEventDto;
import com.watermelon.server.order.result.domain.OrderApplyCount;
import jakarta.persistence.*;
import lombok.*;
import org.slf4j.Logger;
Expand All @@ -15,6 +15,7 @@

@Entity
@Getter
@Table(name = "order_event")
@RequiredArgsConstructor
public class OrderEvent extends BaseEntity {

Expand All @@ -33,8 +34,10 @@ public class OrderEvent extends BaseEntity {
private LocalDateTime startDate;
private LocalDateTime endDate;
private int winnerCount;

private int currentWinnerCount;
@Setter
// @Enumerated(EnumType.STRING)
private OrderEventStatus orderEventStatus;

public OrderEvent(Quiz quiz, LocalDateTime startDate, LocalDateTime endDate) {
Expand All @@ -54,8 +57,6 @@ public OrderEvent(Quiz quiz, LocalDateTime startDate, LocalDateTime endDate) {

}



public static OrderEvent makeOrderEventWithOutImage(RequestOrderEventDto requestOrderEventDto){
Quiz quiz = Quiz.makeQuiz(requestOrderEventDto.getQuiz());
OrderEventReward reward = OrderEventReward.makeReward(requestOrderEventDto.getReward());
Expand Down Expand Up @@ -117,9 +118,6 @@ public static OrderEvent makeOrderEventWithInputIdForDocumentation(RequestOrderE
}





@Transactional
public void changeOrderEventStatusByTime(LocalDateTime now){
if(orderEventStatus.equals(OrderEventStatus.END)) return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@


import com.watermelon.server.order.dto.request.RequestOrderRewardDto;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor
@Table(name = "order_event_reward")
public class OrderEventReward {

@Id @GeneratedValue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
@NoArgsConstructor
@Entity
@Getter
@Table(name = "order_event_winner")
public class OrderEventWinner extends BaseEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Expand Down Expand Up @@ -39,6 +40,4 @@ public static OrderEventWinner makeWinner(OrderEvent orderEvent
.applyTicket(applyTicket)
.build();
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
@Getter
@Entity
@NoArgsConstructor
@Table(name = "quiz")
public class Quiz {
@Id @GeneratedValue
private Long id;
Expand Down
Loading

0 comments on commit 09b2321

Please sign in to comment.