diff --git a/Dockerfile b/Dockerfile index 2bce382..1b0d34d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM repo.ncsoft.net/paige-docker-dev-local/fp:openjdk-11-base +FROM openjdk:17-alpine VOLUME /tmp ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} app.jar diff --git a/src/main/java/com/inspire12/practice/api/config/security/SecurityConfig.java b/src/main/java/com/inspire12/practice/api/config/security/SecurityConfig.java index db9a934..d50582f 100644 --- a/src/main/java/com/inspire12/practice/api/config/security/SecurityConfig.java +++ b/src/main/java/com/inspire12/practice/api/config/security/SecurityConfig.java @@ -35,6 +35,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .antMatchers("/", "/css/**", "/images/**", "/js/**", "/h2-console/**", "/actuator/**") .permitAll() .antMatchers("/api/v1/**").permitAll() + .antMatchers("/lab/**").permitAll() .anyRequest().authenticated() // .and() // .exceptionHandling() diff --git a/src/main/java/com/inspire12/practice/api/config/web/AsyncConfig.java b/src/main/java/com/inspire12/practice/api/config/web/AsyncConfig.java new file mode 100644 index 0000000..b9f6da9 --- /dev/null +++ b/src/main/java/com/inspire12/practice/api/config/web/AsyncConfig.java @@ -0,0 +1,32 @@ +package com.inspire12.practice.api.config.web; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor; +import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +import java.util.concurrent.Executors; + +@EnableAsync +@Configuration +public class AsyncConfig implements AsyncConfigurer { + + @Bean + protected WebMvcConfigurer webMvcConfigurer() { + return new WebMvcConfigurerAdapter() { + @Override + public void configureAsyncSupport(AsyncSupportConfigurer configurer) { + configurer.setTaskExecutor(getTaskExecutor()); + } + }; + } + + @Bean + protected ConcurrentTaskExecutor getTaskExecutor() { + return new ConcurrentTaskExecutor(Executors.newFixedThreadPool(5)); + } +} diff --git a/src/main/java/com/inspire12/practice/api/presentation/controller/sample/LabController.java b/src/main/java/com/inspire12/practice/api/presentation/controller/sample/LabController.java index 05dc280..5f9440a 100644 --- a/src/main/java/com/inspire12/practice/api/presentation/controller/sample/LabController.java +++ b/src/main/java/com/inspire12/practice/api/presentation/controller/sample/LabController.java @@ -8,4 +8,15 @@ @RestController public class LabController { + @GetMapping + public String hi() throws InterruptedException { + int i = 100; + while (i > 0) { +// Thread t = new Thread(, () -> System.out.println("hi")); + +// t.start(); + i--; + } + return "hi"; + } } diff --git a/src/main/java/com/inspire12/practice/api/presentation/controller/sample/WebFluxController.java b/src/main/java/com/inspire12/practice/api/presentation/controller/sample/WebFluxController.java index 46c19e7..60c9a96 100644 --- a/src/main/java/com/inspire12/practice/api/presentation/controller/sample/WebFluxController.java +++ b/src/main/java/com/inspire12/practice/api/presentation/controller/sample/WebFluxController.java @@ -1,17 +1,22 @@ package com.inspire12.practice.api.presentation.controller.sample; import com.inspire12.practice.api.domain.posts.PostsResponseDto; +import io.micrometer.core.annotation.Timed; import lombok.RequiredArgsConstructor; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Flux; +import reactor.core.publisher.FluxSink; import reactor.core.publisher.Mono; +import java.util.List; + @RequiredArgsConstructor @RestController -@RequestMapping("/flux") +@RequestMapping("/api/v1/flux") public class WebFluxController { private final ServerProperties serverProperties; @@ -22,8 +27,22 @@ private Mono getPostResponse() { WebClient client = WebClient.create("http://localhost:" + port); return client.get().uri("/api/v1/posts/" + "1") - .retrieve() - .bodyToMono(PostsResponseDto.class); + .retrieve() + .bodyToMono(PostsResponseDto.class); + } + + @Timed + @GetMapping("/api") + public Flux getTest() { + List a = List.of("asdfasdfasdfasdf", "asdfasdfasdf"); + + return Flux.create((FluxSink sink) -> { + sink.onRequest(request -> { // request는 Subscriber가 요청한 데이터 개수 + for (int i = 1; i <= request; i++) { + sink.next(a.get(i)); // Flux.generate()의 경우와 달리 한 번에 한 개 이상의 next() 신호 발생 가능 + } + }); + }); } } diff --git a/src/main/java/com/inspire12/practice/api/presentation/controller/sample/ticker/TestRepository.java b/src/main/java/com/inspire12/practice/api/presentation/controller/sample/ticker/TestRepository.java new file mode 100644 index 0000000..fec5e02 --- /dev/null +++ b/src/main/java/com/inspire12/practice/api/presentation/controller/sample/ticker/TestRepository.java @@ -0,0 +1,98 @@ +package com.inspire12.practice.api.presentation.controller.sample.ticker; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StopWatch; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +@Slf4j +@RequiredArgsConstructor +@Repository +public class TestRepository { + + private final JdbcTemplate jdbcTemplate; + + public void bulkUpsert(Class clz, List dataList) { + if (dataList.isEmpty()) { + return; + } + String query = createQuery(clz); + int fieldCount = dataList.get(0).getClass().getDeclaredFields().length; + + StopWatch timer = new StopWatch(); + timer.start(); + jdbcTemplate.batchUpdate(query, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) { + T data = dataList.get(i); + AtomicInteger index = new AtomicInteger(1); + ReflectionUtils.doWithFields(clz, field -> { + field.setAccessible(true); + try { + ps.setObject(index.get(), ReflectionUtils.getField(field, data)); + ps.setObject(index.get() + fieldCount, ReflectionUtils.getField(field, data)); + } catch (SQLException e) { + throw new RuntimeException(e); + } + index.getAndIncrement(); + }); + } + + @Override + public int getBatchSize() { + return dataList.size(); + } + }); + timer.stop(); + log.info("batchInsert -> Total time in seconds: " + timer.getTotalTimeSeconds()); + + } + + private String createQuery(Class clz) { + StringBuilder fieldListStr = new StringBuilder(" ("); + AtomicInteger fieldCount = new AtomicInteger(); + StringBuilder updateQuery = new StringBuilder(); + + ReflectionUtils.doWithFields(clz, field -> { +// String columnName = field.getDeclaredAnnotation(Column.class) != null ? field.getDeclaredAnnotation(Column.class).name() : field.getName(); + fieldListStr.append(field.getName()).append(","); + updateQuery.append(field.getName()).append("=?").append(","); + fieldCount.getAndIncrement(); + }); + if (!fieldListStr.isEmpty()) { + fieldListStr.deleteCharAt(fieldListStr.length() - 1); + } + fieldListStr.append(")"); + fieldListStr.append(" "); + + if (!updateQuery.isEmpty()) { + updateQuery.deleteCharAt(updateQuery.length() - 1); + } + + StringBuilder values = new StringBuilder("("); + for (int i = 0; i < fieldCount.get(); i++) { + values.append("?,"); + } + if (values.length() > 0) { + values.deleteCharAt(values.length() - 1); + } + values.append(")"); + + + String query = """ + INSERT INTO %s %s values %s ON DUPLICATE KEY UPDATE %s + """.formatted(clz.getSimpleName(), fieldListStr, values, updateQuery); + + System.out.println(query); + + return query; + } +} diff --git a/src/main/java/com/inspire12/practice/api/presentation/controller/sample/ticker/TickerTrade.java b/src/main/java/com/inspire12/practice/api/presentation/controller/sample/ticker/TickerTrade.java new file mode 100644 index 0000000..26f72c0 --- /dev/null +++ b/src/main/java/com/inspire12/practice/api/presentation/controller/sample/ticker/TickerTrade.java @@ -0,0 +1,61 @@ +package com.inspire12.practice.api.presentation.controller.sample.ticker; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.*; +import org.hibernate.annotations.Comment; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.*; +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EntityListeners(AuditingEntityListener.class) +@JsonIgnoreProperties(ignoreUnknown = true) +@Table +@IdClass(TickerTradeId.class) +public class TickerTrade { + + @Id + @Comment(value = "거래일자") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") + private LocalDate tradeDate; + @Id + @Comment(value = "종목코드") + private String stockCode; + @Comment(value = "종목 이름") + private String stockName; + @Comment(value = "상장된 시장(KOSPI/KOSDAQ)") + private String stockMkt; + @Comment(value = "시가 총액") + private Long mktCap; + @Comment(value = "종가( 해당 거래일 마지막 주식 가격)") + private Long close; + @Comment(value = "종가등락액 (직전 거래일 대비 등락액)") + private Long netChg; + @Comment(value = "종가등락율 (직전 거래일 대비 등락 비율)") + private Double pctChg; + + @Comment(value = "딜리버리 날짜") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime deliveryTime; + + @CreatedDate + @Column(updatable = false) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createdDate; + + @LastModifiedDate + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updatedDate; + + +} diff --git a/src/main/java/com/inspire12/practice/api/presentation/controller/sample/ticker/TickerTradeId.java b/src/main/java/com/inspire12/practice/api/presentation/controller/sample/ticker/TickerTradeId.java new file mode 100644 index 0000000..40f5405 --- /dev/null +++ b/src/main/java/com/inspire12/practice/api/presentation/controller/sample/ticker/TickerTradeId.java @@ -0,0 +1,17 @@ +package com.inspire12.practice.api.presentation.controller.sample.ticker; + +import lombok.*; + +import java.io.Serializable; +import java.time.LocalDate; + +@Setter +@Getter +@EqualsAndHashCode +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TickerTradeId implements Serializable { + private LocalDate tradeDate; + private String stockCode; +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index aa4d0ca..61d241a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -47,10 +47,12 @@ spring: public-url: http://localhost:8000 logging: + config: classpath:logback/logback-${spring.profiles.active:local}.xml level: com.inspire12.practice.api: info root: info + springdoc: swagger-ui: path: index.html diff --git a/src/main/resources/logback/logback-access.xml b/src/main/resources/logback/logback-access.xml new file mode 100644 index 0000000..d40be40 --- /dev/null +++ b/src/main/resources/logback/logback-access.xml @@ -0,0 +1,19 @@ + + + + + + + + ${LOG_DIR}/access.log + + ${LOG_DIR}/access.log.%d{yyyyMMdd} + 1 + + + %h %l %u [%date] "%r" %s %b %D "%i{Referer}" "%i{User-Agent}" + + + + + \ No newline at end of file diff --git a/src/main/resources/logback/logback-dev.xml b/src/main/resources/logback/logback-dev.xml new file mode 100644 index 0000000..e5400dc --- /dev/null +++ b/src/main/resources/logback/logback-dev.xml @@ -0,0 +1,37 @@ + + + + + + + + + + %highlight([%-5level]) %cyan(%d{yyyy-MM-dd HH:mm:ss.SSS}) : %green([%logger{0}:%line]) - %msg%n + + + + + + + ${LOG_DIR}/application.log + + + [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} : [%logger{0}:%line] - %msg%n + + + + ${LOG_DIR}/application.log.%d{yyyy-MM-dd} + 10 + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/logback/logback-inspire12.xml b/src/main/resources/logback/logback-inspire12.xml new file mode 100644 index 0000000..a673eae --- /dev/null +++ b/src/main/resources/logback/logback-inspire12.xml @@ -0,0 +1,18 @@ + + + + + + + + %highlight([%-5level]) %cyan(%d{yyyy-MM-dd HH:mm:ss.SSS}) : %green([%logger{0}:%line]) - %msg%n + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/logback/logback-live.xml b/src/main/resources/logback/logback-live.xml new file mode 100644 index 0000000..e5400dc --- /dev/null +++ b/src/main/resources/logback/logback-live.xml @@ -0,0 +1,37 @@ + + + + + + + + + + %highlight([%-5level]) %cyan(%d{yyyy-MM-dd HH:mm:ss.SSS}) : %green([%logger{0}:%line]) - %msg%n + + + + + + + ${LOG_DIR}/application.log + + + [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} : [%logger{0}:%line] - %msg%n + + + + ${LOG_DIR}/application.log.%d{yyyy-MM-dd} + 10 + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/logback/logback-local.xml b/src/main/resources/logback/logback-local.xml new file mode 100644 index 0000000..a673eae --- /dev/null +++ b/src/main/resources/logback/logback-local.xml @@ -0,0 +1,18 @@ + + + + + + + + %highlight([%-5level]) %cyan(%d{yyyy-MM-dd HH:mm:ss.SSS}) : %green([%logger{0}:%line]) - %msg%n + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback/logback-spring.xml similarity index 100% rename from src/main/resources/logback-spring.xml rename to src/main/resources/logback/logback-spring.xml diff --git a/src/test/java/com/inspire12/practice/api/presentation/controller/sample/ticker/TestRepositoryTest.java b/src/test/java/com/inspire12/practice/api/presentation/controller/sample/ticker/TestRepositoryTest.java new file mode 100644 index 0000000..da2a67c --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/presentation/controller/sample/ticker/TestRepositoryTest.java @@ -0,0 +1,53 @@ +package com.inspire12.practice.api.presentation.controller.sample.ticker; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +@ActiveProfiles("test") +@ExtendWith(SpringExtension.class) +class TestRepositoryTest { + + + TestRepository testRepository; + + @MockBean + JdbcTemplate jdbcTemplate; + + @BeforeEach + void setUp() { + testRepository = new TestRepository(jdbcTemplate); + } + + @AfterEach + void tearDown() { + } + + @Test + void bulkUpsert() { + testRepository.bulkUpsert(TickerTrade.class, + List.of(TickerTrade + .builder() + .close(1L) + .deliveryTime(LocalDateTime.now()) + .mktCap(1L) + .netChg(1L) + .pctChg(1d) + .stockCode("1") + .stockMkt("1") + .stockName("1") + .pctChg(1d) + .tradeDate(LocalDate.now()) + + .build())); + } +} \ No newline at end of file diff --git a/src/test/java/com/inspire12/practice/api/sample/lab/parellel/ForkingStreamConsumer.java b/src/test/java/com/inspire12/practice/api/sample/lab/parellel/ForkingStreamConsumer.java new file mode 100644 index 0000000..39e3645 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lab/parellel/ForkingStreamConsumer.java @@ -0,0 +1,27 @@ +package com.inspire12.practice.api.sample.lab.parellel; + +// + +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Future; +import java.util.function.Consumer; + +// +public class ForkingStreamConsumer implements Consumer, StreamTest.StreamForker.Results { + + public ForkingStreamConsumer(List> queues, Map> actions) { + + } + + @Override + public R get(Object key) { + return null; + } + + @Override + public void accept(T t) { + + } +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lab/parellel/StreamTest.java b/src/test/java/com/inspire12/practice/api/sample/lab/parellel/StreamTest.java new file mode 100644 index 0000000..2af3155 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lab/parellel/StreamTest.java @@ -0,0 +1,71 @@ +package com.inspire12.practice.api.sample.lab.parellel; + + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.function.Function; +import java.util.stream.Stream; + +public class StreamTest { + + + @Test + void test() { + List l = List.of("hihi", "hihi2"); + StreamForker stringStreamForker = new StreamForker<>(l.stream()); + + } + + public class StreamForker { + private final Stream stream; + private final Map, ?>> forks = new HashMap(); + + public StreamForker(Stream stream) { + this.stream = stream; + } + + public StreamForker fork(Object key, Function, ?> f) { + forks.put(key, f); + return this; + } + +// public Results getResults() { +// ForkingStreamConsumer consumer = build(); +// try { +// stream.sequential().forEach(consumer); +// } finally { +// consumer.finish(); +// } +// return +// } + + private ForkingStreamConsumer build() { + List> queues = new ArrayList<>(); + Map> actions = forks.entrySet() + .stream().reduce(new HashMap<>(), + (map, e) -> { + map.put(e.getKey(), getOperationResult(queues, e.getValue())); + return map; + }, (m1, m2) -> { + m1.putAll(m2); + return m1; + }); + return new ForkingStreamConsumer<>(queues, actions); + } + + private Future getOperationResult(List> queues, Function, ?> value) { + return new FutureTask<>(() -> "hi"); + } + + public interface Results { + R get(Object key); + } + } +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lab/reactive/Main.java b/src/test/java/com/inspire12/practice/api/sample/lab/reactive/Main.java new file mode 100644 index 0000000..60a49e1 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lab/reactive/Main.java @@ -0,0 +1,7 @@ +package com.inspire12.practice.api.sample.lab.reactive; + +public class Main { + public static void main(String[] args) { + + } +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lab/server/NettyMain.java b/src/test/java/com/inspire12/practice/api/sample/lab/server/NettyMain.java new file mode 100644 index 0000000..96439c2 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lab/server/NettyMain.java @@ -0,0 +1,9 @@ +package com.inspire12.practice.api.sample.lab.server; + +public class NettyMain { + + public static void main(String[] args) { + + } + +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lab/server/NettyServerHandler.java b/src/test/java/com/inspire12/practice/api/sample/lab/server/NettyServerHandler.java new file mode 100644 index 0000000..1f9f467 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lab/server/NettyServerHandler.java @@ -0,0 +1,33 @@ +package com.inspire12.practice.api.sample.lab.server; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.util.CharsetUtil; + +@Sharable +public class NettyServerHandler extends ChannelInboundHandlerAdapter { + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + // msg 가 들어올때마다 호출 + ByteBuf in = (ByteBuf) msg; + System.out.println("Server received : " + in.toString(CharsetUtil.UTF_8)); + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + // 마지막 메시지가 처리되었음을 핸들러에게 알림 + ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + // 예외가 발생한 경우 호출 + cause.printStackTrace(); + ctx.close(); + } +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lab/socket/ChatRoom.java b/src/test/java/com/inspire12/practice/api/sample/lab/socket/ChatRoom.java new file mode 100644 index 0000000..f8ca3ab --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lab/socket/ChatRoom.java @@ -0,0 +1,23 @@ +package com.inspire12.practice.api.sample.lab.socket; + +import lombok.Getter; +import org.springframework.web.socket.WebSocketSession; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +@Getter +public class ChatRoom { + private String id; + private String name; + private final Set sessions = new HashSet<>(); + + public static ChatRoom create(String name) { + ChatRoom created = new ChatRoom(); + created.id = UUID.randomUUID().toString(); + created.name = name; + return created; + } + +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lab/socket/ChatRoomRepository.java b/src/test/java/com/inspire12/practice/api/sample/lab/socket/ChatRoomRepository.java new file mode 100644 index 0000000..395e371 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lab/socket/ChatRoomRepository.java @@ -0,0 +1,32 @@ +package com.inspire12.practice.api.sample.lab.socket; + +import org.springframework.stereotype.Repository; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Repository +public class ChatRoomRepository { + private final Map chatRoomMap; + private final Collection chatRooms; + + public ChatRoomRepository() { + chatRoomMap = Collections.unmodifiableMap( + Stream.of(ChatRoom.create("1번방"), ChatRoom.create("2번방"), ChatRoom.create("3번방")) + .collect(Collectors.toMap(ChatRoom::getId, Function.identity()))); + chatRooms = Collections.unmodifiableCollection(chatRoomMap.values()); + } + + public ChatRoom getChatRoom(String id) { + return chatRoomMap.get(id); + } + + public Collection getChatRooms() { + return chatRoomMap.values(); + } +} + diff --git a/src/test/java/com/inspire12/practice/api/sample/lab/socket/SocketConfig.java b/src/test/java/com/inspire12/practice/api/sample/lab/socket/SocketConfig.java new file mode 100644 index 0000000..1b47777 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lab/socket/SocketConfig.java @@ -0,0 +1,18 @@ +package com.inspire12.practice.api.sample.lab.socket; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +@Configuration +@EnableWebSocket +public class SocketConfig implements WebSocketConfigurer { + + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) { +// webSocketHandlerRegistry.addHandler() +// .setAllowedOrigins("*").withSockJS(); + } +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lab/socket/SocketMain.java b/src/test/java/com/inspire12/practice/api/sample/lab/socket/SocketMain.java new file mode 100644 index 0000000..ed4df0b --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lab/socket/SocketMain.java @@ -0,0 +1,4 @@ +package com.inspire12.practice.api.sample.lab.socket; + +public class SocketMain { +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lab/toy/ToyMain.java b/src/test/java/com/inspire12/practice/api/sample/lab/toy/ToyMain.java new file mode 100644 index 0000000..cb3a8f9 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lab/toy/ToyMain.java @@ -0,0 +1,51 @@ +package com.inspire12.practice.api.sample.lab.toy; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.text.CharacterIterator; +import java.text.StringCharacterIterator; + +public class ToyMain { + public static final String ANSI_RESET = "\u001B[0m"; + public static final String ANSI_BLACK = "\u001B[30m"; + public static final String ANSI_RED = "\u001B[31m"; + public static final String ANSI_GREEN = "\u001B[32m"; + public static final String ANSI_YELLOW = "\u001B[33m"; + public static final String ANSI_BLUE = "\u001B[34m"; + public static final String ANSI_PURPLE = "\u001B[35m"; + public static final String ANSI_CYAN = "\u001B[36m"; + public static final String ANSI_WHITE = "\u001B[37m"; + + public static void main(String[] args) throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + String input; + while (!(input = br.readLine()).equals(" ")) { + System.out.println(checkStarMacro(input)); + } + } + + private static String checkStarMacro(String input) throws IOException { + + CharacterIterator it = new StringCharacterIterator(input); + StringBuilder result = new StringBuilder(); + while (it.current() != CharacterIterator.DONE) { + result.append(it.current()); + if (Character.isDigit(it.current())) { + if (it.next() != 'e') { + result.append(ANSI_RED).append(it.current()).append(ANSI_RESET); + } else { + result.append(it.current()); + } + if (it.next() != 'w') { + result.append(ANSI_RED).append(it.current()).append(ANSI_RESET); + } else { + result.append(it.current()); + } + } + + it.next(); + } + return result.toString(); + } +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lib/cache/CacheObject.java b/src/test/java/com/inspire12/practice/api/sample/lib/cache/CacheObject.java new file mode 100644 index 0000000..34f8f31 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lib/cache/CacheObject.java @@ -0,0 +1,4 @@ +package com.inspire12.practice.api.sample.lib.cache; + +public interface CacheObject { +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lib/cache/CacheService.java b/src/test/java/com/inspire12/practice/api/sample/lib/cache/CacheService.java new file mode 100644 index 0000000..97a2d20 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lib/cache/CacheService.java @@ -0,0 +1,59 @@ +package com.inspire12.practice.api.sample.lib.cache; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +public interface CacheService { + Map get(String cacheKey); + + Optional getOpt(String cacheKey, K key); + + boolean lock(String cacheKey, K key, long leaseTime, TimeUnit timeUnit); + + void unlock(String cacheKey, K key); + + void set(String cacheKey, K key, V value); + + void set(String cacheKey, K key, V value, long ttl, TimeUnit timeUnit); + + void merge(String cacheKey, K key, V value); + + void setAsync(String cacheKey, K key, V value); + + void setAsync(String cacheKey, K key, V value, long ttl, TimeUnit timeUnit); + + + void replace(String cacheKey, K key, V value); + + void putAsync(String cacheKey, K key, V value); + + void putAsync(String cacheKey, K key, V value, Consumer consumer); + + void remove(String cacheKey, K key); + + void evict(String cacheKey, K key); + + boolean containsKey(String cacheKey, K key); + + void putAsync(String cacheKey, K key, V value, long ttl, TimeUnit timeUnit, Consumer consumer); + + void clear(String cacheKey); + + Map getCachedMap(String cacheKey); + + Set getCachedMapKeySet(String cacheKey, Predicate predicate); + + V updateSynchronized(String cacheKey, K key, V value); + + V updateSynchronized(String cacheKey, K key, Function updateFunc, Supplier insertFunc); + + V updateSynchronizedWithCondition(String cacheKey, K key, Function updateFunc, Supplier insertFunc, Predicate predicate, Supplier failToReturn); + + void removeSynchronized(String cacheKey, K key, Function removeFunc); +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lib/cache/MemoryCacheServiceImpl.java b/src/test/java/com/inspire12/practice/api/sample/lib/cache/MemoryCacheServiceImpl.java new file mode 100644 index 0000000..96e9103 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lib/cache/MemoryCacheServiceImpl.java @@ -0,0 +1,134 @@ +package com.inspire12.practice.api.sample.lib.cache; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +//@Service +public class MemoryCacheServiceImpl implements CacheService { + + Map map = new ConcurrentHashMap<>(); + + @Override + public Map get(String cacheKey) { + return map; + } + + @Override + public Optional getOpt(String cacheKey, String key) { + return Optional.empty(); + } + + @Override + public boolean lock(String cacheKey, String key, long leaseTime, TimeUnit timeUnit) { + return false; + } + + @Override + public void unlock(String cacheKey, String key) { + + } + + @Override + public void set(String cacheKey, String key, CacheObject value) { + + } + + @Override + public void set(String cacheKey, String key, CacheObject value, long ttl, TimeUnit timeUnit) { + + } + + @Override + public void merge(String cacheKey, String key, CacheObject value) { + + } + + @Override + public void setAsync(String cacheKey, String key, CacheObject value) { + + } + + @Override + public void setAsync(String cacheKey, String key, CacheObject value, long ttl, TimeUnit timeUnit) { + + } + + @Override + public void replace(String cacheKey, String key, CacheObject value) { + + } + + @Override + public void putAsync(String cacheKey, String key, CacheObject value) { + + } + + @Override + public void putAsync(String cacheKey, String key, CacheObject value, Consumer consumer) { + + } + + @Override + public void remove(String cacheKey, String key) { + + } + + @Override + public void evict(String cacheKey, String key) { + + } + + @Override + public boolean containsKey(String cacheKey, String key) { + return false; + } + + @Override + public void putAsync(String cacheKey, String key, CacheObject value, long ttl, TimeUnit timeUnit, Consumer consumer) { + + } + + @Override + public void clear(String cacheKey) { + + } + + @Override + public Map getCachedMap(String cacheKey) { + return null; + } + + @Override + public Set getCachedMapKeySet(String cacheKey, Predicate predicate) { + return null; + } + + @Override + public CacheObject updateSynchronized(String cacheKey, String key, CacheObject value) { + return null; + } + + @Override + public CacheObject updateSynchronized(String cacheKey, String key, Function updateFunc, Supplier insertFunc) { + return null; + } + + @Override + public CacheObject updateSynchronizedWithCondition(String cacheKey, String key, Function updateFunc, Supplier insertFunc, Predicate predicate, Supplier failToReturn) { + return null; + } + + @Override + public void removeSynchronized(String cacheKey, String key, Function removeFunc) { + + } +// CacheManager cacheManager + +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lib/cache/SimpleCacheCustomizer.java b/src/test/java/com/inspire12/practice/api/sample/lib/cache/SimpleCacheCustomizer.java new file mode 100644 index 0000000..6628416 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lib/cache/SimpleCacheCustomizer.java @@ -0,0 +1,15 @@ +package com.inspire12.practice.api.sample.lib.cache; + +import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer; +import org.springframework.cache.concurrent.ConcurrentMapCacheManager; + +import java.util.Arrays; + +//@Component +public class SimpleCacheCustomizer implements CacheManagerCustomizer { + + @Override + public void customize(ConcurrentMapCacheManager cacheManager) { + cacheManager.setCacheNames(Arrays.asList("users", "transactions")); + } +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lib/ds/Edge.java b/src/test/java/com/inspire12/practice/api/sample/lib/ds/Edge.java new file mode 100644 index 0000000..310c58b --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lib/ds/Edge.java @@ -0,0 +1,14 @@ +package com.inspire12.practice.api.sample.lib.ds; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.RequiredArgsConstructor; + +@Data +@RequiredArgsConstructor +@AllArgsConstructor +public class Edge { + int from; + int to; + int weight; +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lib/ds/Graph.java b/src/test/java/com/inspire12/practice/api/sample/lib/ds/Graph.java new file mode 100644 index 0000000..f0598f6 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lib/ds/Graph.java @@ -0,0 +1,19 @@ +package com.inspire12.practice.api.sample.lib.ds; + +import java.util.ArrayList; +import java.util.List; + +public class Graph { + List> graph = new ArrayList<>(); + + public Graph(int nodeCount) { + for (int i = 0; i <= nodeCount; i++) { + graph.add(new ArrayList<>()); + } + } + + public void addEdge(int from, int to, int weight) { + Edge newEdge = new Edge(from, to, weight); + graph.get(from).add(newEdge); + } +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lib/ds/path/RunMinPath.java b/src/test/java/com/inspire12/practice/api/sample/lib/ds/path/RunMinPath.java new file mode 100644 index 0000000..7702928 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lib/ds/path/RunMinPath.java @@ -0,0 +1,10 @@ +package com.inspire12.practice.api.sample.lib.ds.path; + +public class RunMinPath { + /** + * @param args + */ + public static void main(String[] args) { + + } +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lib/ds/sort/InsertionSort.java b/src/test/java/com/inspire12/practice/api/sample/lib/ds/sort/InsertionSort.java new file mode 100644 index 0000000..e14bd70 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lib/ds/sort/InsertionSort.java @@ -0,0 +1,41 @@ +package com.inspire12.practice.api.sample.lib.ds.sort; + +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; + +public class InsertionSort { + public static List sort(List list) { + List sortedSubList = new ArrayList<>(); + if (list.isEmpty()) { + return list; + } + sortedSubList.add(list.get(0)); + for (int i = 1; i < list.size(); i++) { + int value = list.get(i); + sortedSubList.add(getIndex(sortedSubList, value), value); + } + + LocalDateTime localDateTime = LocalDateTime.now(); + ZonedDateTime zonedDateTime = ZonedDateTime.now(); + + return sortedSubList; + } + + public static int getIndex(List sortedSubList, int value) { + int left = 0; + int right = sortedSubList.size() - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (value < sortedSubList.get(mid)) { + right = mid - 1; + } else if (value > sortedSubList.get(mid)) { + left = mid + 1; + } else { + return mid; + } + } + return left; + } +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lib/ds/sort/MergeSort.java b/src/test/java/com/inspire12/practice/api/sample/lib/ds/sort/MergeSort.java new file mode 100644 index 0000000..c0ce7f3 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lib/ds/sort/MergeSort.java @@ -0,0 +1,56 @@ +package com.inspire12.practice.api.sample.lib.ds.sort; + +import java.util.ArrayList; +import java.util.List; + +public class MergeSort { + public static List sort(List list) { + + return splitForMerge(list); + } + + private static List splitForMerge(List subList) { + int mid = subList.size() / 2; + if (subList.size() <= 1) { + return subList; + } + + List left = splitForMerge(subList.subList(0, mid)); + List right = splitForMerge(subList.subList(mid, subList.size())); + + return merge(left, right); + } + + private static void print(Object object) { + System.out.println(object); + } + + private static List merge(List left, List right) { + List result = new ArrayList<>(); + + int leftIndex = 0; + int rightIndex = 0; + + while (leftIndex < left.size() && rightIndex < right.size()) { + int leftValue = left.get(leftIndex); + int rightValue = right.get(rightIndex); + + if (leftValue <= rightValue) { + result.add(leftValue); + leftIndex++; + } else { + result.add(rightValue); + rightIndex++; + } + } + while (leftIndex < left.size()) { + result.add(left.get(leftIndex)); + leftIndex++; + } + while (rightIndex < right.size()) { + result.add(right.get(rightIndex)); + rightIndex++; + } + return result; + } +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lib/ds/sort/QuickSort.java b/src/test/java/com/inspire12/practice/api/sample/lib/ds/sort/QuickSort.java new file mode 100644 index 0000000..0fbe696 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lib/ds/sort/QuickSort.java @@ -0,0 +1,40 @@ +package com.inspire12.practice.api.sample.lib.ds.sort; + +import java.util.ArrayList; +import java.util.List; + +public class QuickSort { + public static List sort(List list) { + return quickSort(list); + } + + private static List quickSort(List list) { + System.out.println(list); + if (list.size() <= 1) { + return list; + } + int partition = list.get(getPartitionIndex(list)); + List left = new ArrayList<>(); + List right = new ArrayList<>(); + List equals = new ArrayList<>(); + for (int value : list) { + if (value < partition) { + left.add(value); + } else if (value > partition) { + right.add(value); + } else { + equals.add(value); + } + } + + List result = quickSort(left); + result.addAll(equals); + result.addAll(quickSort(right)); + + return result; + } + + private static int getPartitionIndex(List list) { + return list.size() / 2; + } +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lib/ds/sort/RunSort.java b/src/test/java/com/inspire12/practice/api/sample/lib/ds/sort/RunSort.java new file mode 100644 index 0000000..eb0b12b --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lib/ds/sort/RunSort.java @@ -0,0 +1,14 @@ +package com.inspire12.practice.api.sample.lib.ds.sort; + +import java.util.Arrays; +import java.util.List; + +public class RunSort { + public static void main(String[] args) { + List list = Arrays.asList(3, 2, 1, 5, 12, 31); + System.out.println(MergeSort.sort(list)); + System.out.println(QuickSort.sort(list)); + list = Arrays.asList(1, 1, 1, 1, 12, 31); + System.out.println(InsertionSort.sort(list)); + } +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lib/ds/stringSearch/Kmp.java b/src/test/java/com/inspire12/practice/api/sample/lib/ds/stringSearch/Kmp.java new file mode 100644 index 0000000..0c9ceab --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lib/ds/stringSearch/Kmp.java @@ -0,0 +1,69 @@ +package com.inspire12.practice.api.sample.lib.ds.stringSearch; + +import java.util.ArrayList; +import java.util.List; + +public class Kmp { + + public static List run(String match, String fullText) { + match = "ABCDABE"; + fullText = "ABCDABCDABEE"; + List indexes = new ArrayList<>(); + List piTable = makePiTable(match); + + int piIndex = 0; + for (int i = 0; i < fullText.length(); i++) { + char c = fullText.charAt(i); + + while (piIndex > 0 && c != match.charAt(piIndex)) { + piIndex = piTable.get(piIndex - 1); + } + + if (c == match.charAt(piIndex)) { + if (match.length() - 1 <= piIndex) { + indexes.add(i - match.length() + 1); // 시작 지점을 저장 + piIndex = piTable.get(piIndex); // 겹친게 또 겹칠 수도 있어 + } else { + piIndex++; + } + } + } + return indexes; + } + + private static List makePiTable(String match) { + int matchIndex = 0; + List pi = new ArrayList<>(); + pi.add(0); + for (int subStrLength = 1; subStrLength < match.length(); subStrLength++) { + char c = match.charAt(matchIndex); + while (matchIndex > 0 && (c != match.charAt(subStrLength))) { + matchIndex = pi.get(matchIndex - 1); + } + if (match.charAt(subStrLength) == match.charAt(matchIndex)) { + matchIndex++; + pi.add(matchIndex); + } else { + pi.add(0); + } + } + return pi; + } +// List pi = new ArrayList<>(); +// int index = 0; +// for (int i = 1; i < match.length(); i++) { +// while(index > 0 && (pi.get(i) != pi.get(index))) { +// index = pi.get(index - 1); +// } +// if (pi.get(i).equals(pi.get(index))) { +// pi.set(i, index++); +// } +// } +// +// return pi; +// } + +// private static int calPi() { +// +// } +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lib/ds/stringSearch/RunStringSearch.java b/src/test/java/com/inspire12/practice/api/sample/lib/ds/stringSearch/RunStringSearch.java new file mode 100644 index 0000000..1a17d9c --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lib/ds/stringSearch/RunStringSearch.java @@ -0,0 +1,13 @@ +package com.inspire12.practice.api.sample.lib.ds.stringSearch; + +import java.util.List; + +public class RunStringSearch { + public static void main(String[] args) { + String fullText = "ABABABABBABABABABC"; + String match = "ABABABABC"; + + List index = Kmp.run(match, fullText); + System.out.println(index); + } +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lib/rank/Rank.java b/src/test/java/com/inspire12/practice/api/sample/lib/rank/Rank.java new file mode 100644 index 0000000..b7f72e5 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lib/rank/Rank.java @@ -0,0 +1,14 @@ +package com.inspire12.practice.api.sample.lib.rank; + +//import com.fasterxml.jackson.annotation.JsonUnwrapped; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class Rank { + int rank; + // @JsonUnwrapped + T content; +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lib/rank/RankCollector.java b/src/test/java/com/inspire12/practice/api/sample/lib/rank/RankCollector.java new file mode 100644 index 0000000..cee51d2 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lib/rank/RankCollector.java @@ -0,0 +1,65 @@ +package com.inspire12.practice.api.sample.lib.rank; + +import lombok.AllArgsConstructor; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@AllArgsConstructor +public class RankCollector implements Collector>, List>> { + + private Comparator comparator; + + + @Override + public Supplier>> supplier() { + return ArrayList::new; + } + + @Override + public BiConsumer>, T> accumulator() { + return (List> list, T current) -> { + if (list.isEmpty()) { + list.add(new Rank<>(1, current)); + } else { + Rank lastElement = list.get(list.size() - 1); + int rank = 0; + if (comparator.compare(lastElement.getContent(), current) == 0) { + rank = lastElement.getRank(); + } else if (comparator.compare(lastElement.getContent(), current) < 0) { + rank = list.size() + 1; + } else { + throw new RuntimeException(); // 정렬이 되어있어야함 + } + list.add(new Rank<>(rank, current)); + } + }; + } + + @Override + public BinaryOperator>> combiner() { + return (List> list1, List> list2) -> { + list1.addAll(list2); + return list1; + }; + } + + @Override + public Function>, List>> finisher() { + return Function.identity(); + } + + @Override + public Set characteristics() { + return Stream.of(Characteristics.IDENTITY_FINISH).collect(Collectors.toSet()); + } +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lib/rank/RankRunner.java b/src/test/java/com/inspire12/practice/api/sample/lib/rank/RankRunner.java new file mode 100644 index 0000000..e920a80 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lib/rank/RankRunner.java @@ -0,0 +1,42 @@ +package com.inspire12.practice.api.sample.lib.rank; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +public class RankRunner { + + @Test + void testRun() { + Comparator comparator = Comparator.comparingInt(Batter::getHr).reversed(); + List batters = Arrays.asList( + new Batter("이승엽", 5), + new Batter("김태균", 3), + new Batter("이대호", 6), + new Batter("나성범", 5) + ); + RankRunner rankRunner = new RankRunner(); + rankRunner.run(batters, comparator); + } + + public List> run(List l, Comparator comparator) { + List> ranks = l.stream() + .sorted(comparator) + .collect(new RankCollector<>(comparator)); + ranks.forEach(rank -> System.out.println(rank.getRank() + " " + rank.getContent())); + return ranks; + } + + @ToString + @Getter + @AllArgsConstructor + public static class Batter { + String name; + int hr; // 홈런 + } +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lib/transaction/TransactionService.java b/src/test/java/com/inspire12/practice/api/sample/lib/transaction/TransactionService.java new file mode 100644 index 0000000..48983d9 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lib/transaction/TransactionService.java @@ -0,0 +1,17 @@ +package com.inspire12.practice.api.sample.lib.transaction; + +import java.util.concurrent.Callable; + +public interface TransactionService { + void doInNewTransaction(Runnable runnable); + + T doInNewTransaction(Callable callable); + + T doInNewTransactionWithRetryingForUpdate(Callable callable); + + T doInNewTransactionWithRetryingForUpdate(Callable callable, long backoffMilliseconds, int retryingCount); + + T doInNewTransactionWithRetryingForInsert(Callable callable); + + T doInNewTransactionWithRetryingForInsert(Callable callable, long backoffMilliseconds, int retryingCount); +} diff --git a/src/test/java/com/inspire12/practice/api/sample/lib/transaction/TransactionServiceImpl.java b/src/test/java/com/inspire12/practice/api/sample/lib/transaction/TransactionServiceImpl.java new file mode 100644 index 0000000..4eea360 --- /dev/null +++ b/src/test/java/com/inspire12/practice/api/sample/lib/transaction/TransactionServiceImpl.java @@ -0,0 +1,139 @@ +package com.inspire12.practice.api.sample.lib.transaction; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.hibernate.StaleObjectStateException; +import org.springframework.dao.CannotAcquireLockException; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.orm.ObjectOptimisticLockingFailureException; +import org.springframework.transaction.IllegalTransactionStateException; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; + +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +@RequiredArgsConstructor +@Slf4j +public class TransactionServiceImpl implements TransactionService { + + private static final long DEFAULT_BACKOFF_MILLISECONDS = 500; + private static final int DEFAULT_RETRYING_COUNT = 3; + private String name; + private PlatformTransactionManager transactionManager; + + @Override + public void doInNewTransaction(Runnable runnable) { + DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); + definition.setName("doInNewTransaction" + name); + definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + TransactionStatus txStatus = transactionManager.getTransaction(definition); + try { + runnable.run(); + transactionManager.commit(txStatus); + } catch (Exception e) { + log.error("Error in do new transaction {}", name, e); + transactionManager.rollback(txStatus); + } + } + + @Override + public T doInNewTransaction(Callable callable) { + DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); + definition.setName("doInNewTransaction" + name); + definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + TransactionStatus txStatus = transactionManager.getTransaction(definition); + T result = null; + try { + result = callable.call(); + } catch (Exception e) { + log.error("Error in do new transaction {}", name, e); + transactionManager.rollback(txStatus); + } + transactionManager.commit(txStatus); + return result; + } + + @Override + public T doInNewTransactionWithRetryingForUpdate(Callable callable) { + return doInNewTransactionWithRetryingForUpdate(callable, DEFAULT_BACKOFF_MILLISECONDS, DEFAULT_RETRYING_COUNT); + } + + @Override + public T doInNewTransactionWithRetryingForUpdate(Callable callable, long backoffMilliseconds, int retryingCount) { + int tryingCount = 0; + while (tryingCount < retryingCount) { + DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); + definition.setName("doInNewTransactionWithRetryingForUpdate" + name); + definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + + TransactionStatus txStatus = transactionManager.getTransaction(definition); + try { + T result = callable.call(); + transactionManager.commit(txStatus); + return result; + } catch (ObjectOptimisticLockingFailureException | StaleObjectStateException | + CannotAcquireLockException le) { + tryingCount++; + log.debug("Retrying Update: {} {}", tryingCount, le.getMessage()); + try { + transactionManager.rollback(txStatus); + } catch (IllegalTransactionStateException itse) { + log.debug("ITransactionSE occurred {}", name); + } + try { + TimeUnit.MICROSECONDS.sleep(backoffMilliseconds); + } catch (InterruptedException e) { + log.debug("Transaction sleep error {}", name, e); + } + } catch (Exception e) { + log.error("Error in retrying update {}", name, e); + transactionManager.rollback(txStatus); + break; + } + } + throw new IllegalStateException("Retrying for update over " + name); + } + + @Override + public T doInNewTransactionWithRetryingForInsert(Callable callable) { + return doInNewTransactionWithRetryingForInsert(callable, DEFAULT_BACKOFF_MILLISECONDS, DEFAULT_RETRYING_COUNT); + } + + @Override + public T doInNewTransactionWithRetryingForInsert(Callable callable, long backoffMilliseconds, int retryingCount) { + int tryingCount = 0; + while (tryingCount < retryingCount) { + DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); + definition.setName("doInNewTransactionWithRetryingForUpdate" + name); + definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + + TransactionStatus txStatus = transactionManager.getTransaction(definition); + try { + T result = callable.call(); + transactionManager.commit(txStatus); + return result; + } catch (DataIntegrityViolationException le) { + tryingCount++; + log.debug("Retrying Update: {} {}", tryingCount, le.getMessage()); + try { + transactionManager.rollback(txStatus); + } catch (IllegalTransactionStateException itse) { + log.debug("ITransactionSE occurred " + name); + } + try { + TimeUnit.MICROSECONDS.sleep(backoffMilliseconds); + } catch (InterruptedException e) { + log.debug("Transaction sleep error " + name, e); + } + } catch (Exception e) { + log.error("Error in retrying update " + name, e); + transactionManager.rollback(txStatus); + break; + } + } + throw new IllegalStateException("Retrying for update over " + name); + } +}