Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b6f3dd0
update show_schedule_register.md to include 'use' object with section…
YeaChan05 Oct 19, 2025
1ae45b4
add seat usage request to ShowScheduleRegisterRequest with validation…
YeaChan05 Oct 19, 2025
d9e81e9
add note on inventory seat creation upon successful schedule registra…
YeaChan05 Oct 19, 2025
4f54517
add section existence check in hall validation and repository
YeaChan05 Oct 19, 2025
b473199
add section existence check in hall validation and repository
YeaChan05 Oct 19, 2025
fae03a9
add custom Hibernate naming strategy for table and column naming conv…
YeaChan05 Oct 21, 2025
ab5f738
add seat row and number fields to Seat entity with a factory method f…
YeaChan05 Oct 21, 2025
b5f31b9
add section creation with seats during hall registration and validate…
YeaChan05 Oct 21, 2025
1ba33e7
add test for handling duplicate seat IDs in show schedule registration
YeaChan05 Oct 22, 2025
dd9e4fb
add validation for grade IDs in show schedule registration
YeaChan05 Oct 22, 2025
ab14023
add validation for grade IDs in show schedule registration
YeaChan05 Oct 22, 2025
76560b3
add grade seat mapping in show schedule registration
YeaChan05 Oct 22, 2025
050fa3f
add validation to ensure grade IDs are unique in show schedule regist…
YeaChan05 Oct 22, 2025
bbc7a74
add validation to return BAD_REQUEST for invalid seat IDs in gradeAss…
YeaChan05 Oct 22, 2025
a210ca1
add validation to return BAD_REQUEST for duplicate seat IDs in gradeA…
YeaChan05 Oct 24, 2025
3a6a821
update section and seat registration methods for improved clarity and…
YeaChan05 Oct 24, 2025
d95643b
refactor show schedule registration tests to use generateShowSchedule…
YeaChan05 Oct 24, 2025
7342a6c
add validation to ensure all seat IDs match section seats in show sch…
YeaChan05 Oct 24, 2025
3cba248
add validation to return BAD_REQUEST for duplicate seat IDs in gradeA…
YeaChan05 Nov 6, 2025
3f73279
return schedule Id for response
YeaChan05 Nov 6, 2025
3612387
mark schedule registration as complete when inventory seats are created
YeaChan05 Nov 6, 2025
1da0148
remove empty seat ID checks in section ID validation methods
YeaChan05 Nov 6, 2025
c7d8572
add test to validate successful response when excludeSeatIds have no …
YeaChan05 Nov 6, 2025
a9d6e8b
simplify toSnakeCase method for better readability and performance
YeaChan05 Nov 6, 2025
b078037
add role hierarchy configuration for security authorization
YeaChan05 Nov 6, 2025
a300456
add JdbcBatchUtils for batch processing with customizable parameter b…
YeaChan05 Nov 7, 2025
2750ef7
add methods to extract and clear seat states in Inventory and integra…
YeaChan05 Nov 7, 2025
fb1e176
refactor TestFixture to integrate JdbcBatchUtils for batch processing…
YeaChan05 Nov 7, 2025
4fc7024
add methods to extract seat rows and clear seats in Hall, implement b…
YeaChan05 Nov 7, 2025
953bcd3
refactor InventoryCommandRepository to use JdbcBatchUtils for batch i…
YeaChan05 Nov 7, 2025
d150de7
add StringFormatterUtils for converting strings to snake_case and ref…
YeaChan05 Nov 7, 2025
bf02544
add EntityInsertBuilder for dynamic SQL generation and refactor batch…
YeaChan05 Nov 7, 2025
9ce0358
refactor TestFixture to use EntityInsertBuilder for section and shows…
YeaChan05 Nov 7, 2025
fa156fc
refactor EntityInsertBuilder to improve binding logic and add unit te…
YeaChan05 Nov 7, 2025
4b8db9d
refactor Grade, Hall, Inventory, Show, and ShowFixture to streamline …
YeaChan05 Nov 8, 2025
6f934cf
add generated source directory and update build.gradle for Querydsl-E…
YeaChan05 Nov 8, 2025
4e967c3
refactor repositories to replace EntityInsertBuilder with direct JDBC…
YeaChan05 Nov 11, 2025
b9541a7
implement seat registration feature with inventory mapping for perfor…
YeaChan05 Nov 11, 2025
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
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package org.mandarin.booking.adapter.security;

import org.mandarin.booking.adapter.AuthorizationRequestMatcherConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import org.springframework.stereotype.Component;
Expand All @@ -23,4 +26,12 @@ public void authorizeRequests(
.requestMatchers(HttpMethod.POST, "/api/hall").hasAuthority("ROLE_ADMIN")
.anyRequest().authenticated();
}

@Bean
static RoleHierarchy roleHierarchy() {
return RoleHierarchyImpl.withDefaultRolePrefix()
.role("ADMIN").implies("DISTRIBUTOR")
.role("DISTRIBUTOR").implies("USER")
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.mandarin.booking.app;

import static java.lang.Math.max;
import static java.lang.Math.min;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class JdbcBatchUtils {
private final JdbcTemplate jdbcTemplate;

public <T> void batchUpdate(String sql,
List<T> items,
SqlParameterBinder<T> binder,
int batchSize) {
if (items.isEmpty()) {
return;
}
int size = items.size();
int chunk = max(1, batchSize);
for (int start = 0; start < size; start += chunk) {// chunk size만큼 slice
int end = min(size, start + chunk);
List<T> sub = items.subList(start, end);

jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
binder.bind(ps, sub.get(i));
}

@Override
public int getBatchSize() {
return sub.size();
}
});
}
}

@FunctionalInterface
public interface SqlParameterBinder<T> {
void bind(PreparedStatement ps, T item) throws SQLException;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.mandarin.booking.app;


import static org.mandarin.booking.StringFormatterUtils.toSnakeCase;

import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.jspecify.annotations.Nullable;

public class TableAwarePhysicalNamingStrategy extends PhysicalNamingStrategyStandardImpl {

private static final ThreadLocal<@Nullable String> CURRENT_TABLE = new ThreadLocal<>();

@Override
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) {
var table = toSnakeCase(name.getText());
CURRENT_TABLE.set(table);
return Identifier.toIdentifier(table, name.isQuoted());
}

@Override
public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment jdbcEnvironment) {
var logical = name.getText();
var physical = toSnakeCase(logical);
return Identifier.toIdentifier(physical, name.isQuoted());
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,42 @@
package org.mandarin.booking.app.hall;

import jakarta.validation.constraints.NotEmpty;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.mandarin.booking.app.JdbcBatchUtils;
import org.mandarin.booking.domain.hall.Hall;
import org.mandarin.booking.domain.hall.Hall.SeatInsertRow;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository
@Transactional
@RequiredArgsConstructor
class HallCommandRepository {
private final HallRepository jpaRepository;
private final JdbcBatchUtils jdbcBatchUtils;

Hall insert(Hall hall) {
return jpaRepository.save(hall);
var seatRows = hall.extractSeatRows();
hall.clearSeats();// jpa가 아닌 jdbc로 일괄 삽입을 위해 clear

var saved = jpaRepository.save(hall);

batchInsertSeats(seatRows);

return saved;
}

private void batchInsertSeats(@NotEmpty List<SeatInsertRow> rows) {
jdbcBatchUtils.batchUpdate(
"INSERT INTO seat (section_id, seat_row, seat_number) VALUES (?, ?, ?)",
rows,
(ps, row) -> {
ps.setLong(1, row.section().getId());
ps.setString(2, row.rowNumber());
ps.setString(3, row.seatNumber());
},
1000
);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package org.mandarin.booking.app.hall;

import static org.mandarin.booking.domain.hall.QSeat.seat;
import static org.mandarin.booking.domain.hall.QSection.section;

import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.HashSet;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.mandarin.booking.domain.hall.Hall;
import org.mandarin.booking.domain.hall.HallException;
Expand All @@ -11,6 +17,7 @@
@RequiredArgsConstructor
class HallQueryRepository {
private final HallRepository repository;
private final JPAQueryFactory jpaQueryFactory;

boolean existsById(Long hallId) {
return repository.existsById(hallId);
Expand All @@ -24,4 +31,28 @@ Hall findById(Long hallId) {
return repository.findById(hallId)
.orElseThrow(() -> new HallException("NOT_FOUND", "해당 공연장을 찾을 수 없습니다."));
}

boolean existsByHallIdAndSectionId(Long hallId, Long sectionId) {
return findById(hallId).hasSectionOf(sectionId);
}

boolean containsSeatIdsBySectionId(Long sectionId, List<Long> seatIds) {
var fetched = jpaQueryFactory
.select(seat.id)
.from(section)
.join(section.seats, seat)
.where(section.id.eq(sectionId))
.fetch();
return new HashSet<>(fetched).containsAll(seatIds);
}

boolean equalsSeatIdsBySectionId(Long sectionId, List<Long> seatIds) {
var fetched = jpaQueryFactory
.select(seat.id)
.from(section)
.join(section.seats, seat)
.where(section.id.eq(sectionId))
.fetch();
return new HashSet<>(seatIds).equals(new HashSet<>(fetched));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.mandarin.booking.app.hall;

import java.util.List;
import lombok.RequiredArgsConstructor;
import org.mandarin.booking.domain.hall.Hall;
import org.mandarin.booking.domain.hall.HallException;
Expand Down Expand Up @@ -32,11 +33,32 @@ public void checkHallExistByHallName(String hallName) {
}
}

@Override
public void checkHallExistBySectionId(Long hallId, Long sectionId) {
if (!queryRepository.existsByHallIdAndSectionId(hallId, sectionId)) {
throw new HallException("NOT_FOUND", "해당 공연장에 섹션이 존재하지 않습니다.");
}
}

@Override
public void checkHallInvalidSeatIds(Long sectionId, List<Long> seatIds) {
if (!queryRepository.containsSeatIdsBySectionId(sectionId, seatIds)) {
throw new HallException("BAD_REQUEST", "해당 섹션에 해당하지 않는 좌석이 있습니다.");
}
}

@Override
public void checkSectionContainsAllOf(Long sectionId, List<Long> seatIds) {
if (!queryRepository.equalsSeatIdsBySectionId(sectionId, seatIds)) {
throw new HallException("BAD_REQUEST", "해당 섹션 좌석과 총 좌석이 상이합니다.");
}
}

@Override
public HallRegisterResponse register(String userId, HallRegisterRequest request) {
checkHallExistByHallName(request.hallName());

var hall = Hall.create(request.hallName(), userId);
var hall = Hall.create(request.hallName(), request.sections(), userId);

var saved = commandRepository.insert(hall);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
package org.mandarin.booking.app.hall;

import java.util.List;

public interface HallValidator {
void checkHallExistByHallId(Long hallId);

void checkHallExistByHallName(String hallName);

void checkHallExistBySectionId(Long hallId, Long sectionId);

void checkHallInvalidSeatIds(Long sectionId, List<Long> seatIds);

void checkSectionContainsAllOf(Long sectionId, List<Long> seatIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.mandarin.booking.app.show;

import jakarta.validation.constraints.NotEmpty;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.mandarin.booking.app.JdbcBatchUtils;
import org.mandarin.booking.domain.show.Inventory;
import org.mandarin.booking.domain.show.SeatState.SeatStateRow;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository
@Transactional
@RequiredArgsConstructor
class InventoryCommandRepository {
private final InventoryRepository repository;
private final JdbcBatchUtils jdbcBatchUtils;

void insert(Inventory inventory) {
List<SeatStateRow> rows = inventory.extractSeatStateRows();
inventory.clearSeatStates();// jpa가 아닌 jdbc로 일괄 삽입하기 위해 clear

repository.save(inventory);

batchInsert(inventory.getId(), rows);
}

void batchInsert(Long inventoryId, @NotEmpty List<SeatStateRow> rows) {
jdbcBatchUtils.batchUpdate(
"INSERT INTO seat_state (inventory_id, seat_id, grade_id) VALUES (?, ?, ?)",
rows,
(ps, row) -> {
ps.setLong(1, inventoryId);
ps.setLong(2, row.seatId());
ps.setLong(3, row.gradeId());
},
1000
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.mandarin.booking.app.show;

import org.mandarin.booking.domain.show.Inventory;
import org.springframework.data.repository.Repository;

interface InventoryRepository extends Repository<Inventory, Long> {
void save(Inventory inventory);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.mandarin.booking.app.show;

import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.mandarin.booking.domain.show.Inventory;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
class InventoryService implements InventoryWriter {
private final InventoryCommandRepository commandRepository;

@Override
public void createInventory(Long scheduleId, Map<Long, List<Long>> seatAssociations) {
Inventory inventory = Inventory.create(scheduleId, seatAssociations);
commandRepository.insert(inventory);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.mandarin.booking.app.show;

import java.util.List;
import java.util.Map;

public interface InventoryWriter {
void createInventory(Long scheduleId, Map<Long, List<Long>> seatAssociations);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.mandarin.booking.app.show;

import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import org.mandarin.booking.domain.show.Show;
import org.springframework.stereotype.Repository;
Expand All @@ -9,10 +10,13 @@
@Transactional
@RequiredArgsConstructor
class ShowCommandRepository {
private final ShowRepository jpaRepository;
private final EntityManager entityManager;

Show insert(Show show) {
return jpaRepository.save(show);
entityManager.persist(show);
entityManager.flush();
entityManager.refresh(show);
return show;
}
}

Loading