Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: DB 마이그레이션 #386 #389

Merged
merged 8 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions backend/src/main/java/com/staccato/config/db/DataSourceConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.staccato.config.db;

import static com.staccato.config.db.ReaderDataSourceConfig.READER;
import static com.staccato.config.db.ReaderDataSourceConfig.READER_DATA_SOURCE;
import static com.staccato.config.db.WriterDataSourceConfig.WRITER;
import static com.staccato.config.db.WriterDataSourceConfig.WRITER_DATA_SOURCE;

import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import jakarta.persistence.EntityManagerFactory;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
public class DataSourceConfig {
@DependsOn({WRITER_DATA_SOURCE, READER_DATA_SOURCE})
@Bean
public DataSource routingDataSource(
@Qualifier(WRITER_DATA_SOURCE) DataSource writer,
@Qualifier(READER_DATA_SOURCE) DataSource reader
) {
DynamicRoutingDataSource routingDataSource = new DynamicRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(WRITER, writer);
dataSourceMap.put(READER, reader);
routingDataSource.setTargetDataSources(dataSourceMap);
routingDataSource.setDefaultTargetDataSource(writer);
return routingDataSource;
}

@Primary
@DependsOn({"routingDataSource"})
@Bean
public DataSource dataSource(DataSource routingDataSource) {
return new LazyConnectionDataSourceProxy(routingDataSource);
}

@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(entityManagerFactory);
return jpaTransactionManager;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.staccato.config.db;

import static org.springframework.transaction.support.TransactionSynchronizationManager.isCurrentTransactionReadOnly;

import static com.staccato.config.db.ReaderDataSourceConfig.READER;
import static com.staccato.config.db.WriterDataSourceConfig.WRITER;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
if (isCurrentTransactionReadOnly()) {
return READER;
}
return WRITER;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.staccato.config.db;

import javax.sql.DataSource;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.zaxxer.hikari.HikariDataSource;

@Configuration
public class ReaderDataSourceConfig {
protected static final String READER_DATA_SOURCE = "readerDataSource";
protected static final String READER = "reader";

@ConfigurationProperties(prefix = "spring.datasource.reader")
@Bean(name = READER_DATA_SOURCE)
public DataSource readerDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.staccato.config.db;

import javax.sql.DataSource;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.zaxxer.hikari.HikariDataSource;

@Configuration
public class WriterDataSourceConfig {
protected static final String WRITER_DATA_SOURCE = "writerDataSource";
protected static final String WRITER = "writer";

@ConfigurationProperties(prefix = "spring.datasource.writer")
@Bean(name = WRITER_DATA_SOURCE)
public DataSource writerDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
}
15 changes: 11 additions & 4 deletions backend/src/main/resources/application-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,23 @@ spring:
init:
mode: always
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: ${SPRING_DATASOURCE_URL}
username: ${SPRING_DATASOURCE_USERNAME}
password: ${SPRING_DATASOURCE_PASSWORD}
writer:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbcUrl: ${SPRING_WRITER_DATASOURCE_URL}
username: ${SPRING_DATASOURCE_USERNAME}
password: ${SPRING_DATASOURCE_PASSWORD}
reader:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbcUrl: ${SPRING_READER_DATASOURCE_URL}
username: ${SPRING_DATASOURCE_USERNAME}
password: ${SPRING_DATASOURCE_PASSWORD}
jpa:
database: MYSQL
show-sql: true
properties:
hibernate:
format_sql: true
create_empty_composites.enabled: true
hibernate:
ddl-auto: validate
database-platform: org.hibernate.dialect.MySQL8Dialect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

Expand Down Expand Up @@ -94,31 +93,6 @@ static Stream<Arguments> invalidMomentRequestProvider() {
);
}

@DisplayName("스타카토 생성 시 사진 5장까지는 첨부 가능하다.")
@Test
void createMoment() throws Exception {
// given
MomentRequest momentRequest = new MomentRequest("placeName", "address", BigDecimal.ONE, BigDecimal.ONE, LocalDateTime.now(), 1L,
List.of("https://example.com/images/namsan_tower1.jpg",
"https://example.com/images/namsan_tower2.jpg",
"https://example.com/images/namsan_tower3.jpg",
"https://example.com/images/namsan_tower4.jpg",
"https://example.com/images/namsan_tower5.jpg"));
String momentRequestJson = objectMapper.writeValueAsString(momentRequest);
MomentIdResponse momentIdResponse = new MomentIdResponse(1L);
when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create());
when(momentService.createMoment(any(MomentRequest.class), any(Member.class))).thenReturn(new MomentIdResponse(1L));

// when & then
mockMvc.perform(post("/moments")
.header(HttpHeaders.AUTHORIZATION, "token")
.contentType(MediaType.APPLICATION_JSON)
.content(momentRequestJson))
.andExpect(status().isCreated())
.andExpect(header().string(HttpHeaders.LOCATION, "/moments/1"))
.andExpect(content().json(objectMapper.writeValueAsString(momentIdResponse)));
}

@DisplayName("올바르지 않은 날짜 형식으로 스타카토 생성을 요청하면 예외가 발생한다.")
@Test
void failCreateMomentWithInvalidVisitedAt() throws Exception {
Expand All @@ -145,30 +119,6 @@ void failCreateMomentWithInvalidVisitedAt() throws Exception {
.andExpect(jsonPath("$.message").value("요청 본문을 읽을 수 없습니다. 올바른 형식으로 데이터를 제공해주세요."));
}

@DisplayName("사진이 5장을 초과하면 스타카토 생성에 실패한다.")
@Test
void failCreateMomentByImageCount() throws Exception {
// given
MomentRequest momentRequest = new MomentRequest("placeName", "address", BigDecimal.ONE, BigDecimal.ONE, LocalDateTime.now(), 1L,
List.of("https://example.com/images/namsan_tower1.jpg",
"https://example.com/images/namsan_tower2.jpg",
"https://example.com/images/namsan_tower3.jpg",
"https://example.com/images/namsan_tower4.jpg",
"https://example.com/images/namsan_tower5.jpg",
"https://example.com/images/namsan_tower6.jpg"));
String momentRequestJson = objectMapper.writeValueAsString(momentRequest);
ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "사진은 5장까지만 추가할 수 있어요.");
when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create());

// when & then
mockMvc.perform(post("/moments")
.header(HttpHeaders.AUTHORIZATION, "token")
.contentType(MediaType.APPLICATION_JSON)
.content(momentRequestJson))
.andExpect(status().isBadRequest())
.andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse)));
}

@DisplayName("사용자가 잘못된 요청 형식으로 정보를 입력하면, 스타카토를 생성할 수 없다.")
@ParameterizedTest
@MethodSource("invalidMomentRequestProvider")
Expand Down Expand Up @@ -248,31 +198,6 @@ void updateMomentById() throws Exception {
.andExpect(status().isOk());
}

@DisplayName("추가하려는 사진이 5장이 넘는다면 스타카토 수정에 실패한다.")
@Test
void failUpdateMomentByImagesSize() throws Exception {
// given
long momentId = 1L;
ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "사진은 5장까지만 추가할 수 있어요.");
MomentUpdateRequest updateRequest = new MomentUpdateRequest("placeName",
List.of("https://example.com/images/namsan_tower1.jpg",
"https://example.com/images/namsan_tower2.jpg",
"https://example.com/images/namsan_tower3.jpg",
"https://example.com/images/namsan_tower4.jpg",
"https://example.com/images/namsan_tower5.jpg",
"https://example.com/images/namsan_tower6.jpg"));
String updateRequestJson = objectMapper.writeValueAsString(updateRequest);
when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create());

// when & then
mockMvc.perform(put("/moments/{momentId}", momentId)
.header(HttpHeaders.AUTHORIZATION, "token")
.contentType(MediaType.APPLICATION_JSON)
.content(updateRequestJson))
.andExpect(status().isBadRequest())
.andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse)));
}

@DisplayName("적합하지 않은 경로변수의 경우 스타카토 수정에 실패한다.")
@Test
void failUpdateMomentById() throws Exception {
Expand Down
Loading