Skip to content

Commit

Permalink
Merge pull request #575 from woowacourse-teams/refactor/539-batch
Browse files Browse the repository at this point in the history
batch insert main merge
  • Loading branch information
hum02 authored Oct 12, 2023
2 parents 1743047 + d96d6aa commit 2d5d780
Show file tree
Hide file tree
Showing 17 changed files with 354 additions and 86 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.project.yozmcafe.domain.cafe;
package com.project.yozmcafe.config;

import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.boot.model.FunctionContributor;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.project.yozmcafe.domain.cafe;

import com.project.yozmcafe.domain.member.Member;
import jakarta.transaction.Transactional;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class UnViewedCafeRepository {

private static final int INSERT_BATCH_SIZE = 110;

private final JdbcTemplate jdbcTemplate;

public UnViewedCafeRepository(final JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

@Transactional
public void saveUnViewedCafes(final List<Cafe> cafes, final Member member) {
for (int i = 0; i < cafes.size(); i += INSERT_BATCH_SIZE) {
List<Cafe> subItems = cafes.subList(i, Math.min(i + INSERT_BATCH_SIZE, cafes.size()));
batchInsert(subItems, member.getId());
}
}

private void batchInsert(final List<Cafe> cafes, final String memberId) {
final String sql = "INSERT INTO un_viewed_cafe (cafe_id, member_id) VALUES (?, ?)";

final List<Object[]> batchArguments = cafes.stream()
.map(cafe -> new Object[]{cafe.getId(), memberId})
.toList();

jdbcTemplate.batchUpdate(sql, batchArguments);
}
}
36 changes: 23 additions & 13 deletions server/src/main/java/com/project/yozmcafe/domain/member/Member.java
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
package com.project.yozmcafe.domain.member;

import static com.project.yozmcafe.exception.ErrorCode.NOT_EXISTED_LIKED_CAFE;
import static jakarta.persistence.CascadeType.MERGE;
import static jakarta.persistence.CascadeType.PERSIST;
import static java.lang.Math.min;
import static java.util.Collections.emptyList;
import static java.util.Objects.isNull;

import java.util.ArrayList;
import java.util.List;

import org.springframework.data.domain.Persistable;

import com.project.yozmcafe.domain.BaseEntity;
import com.project.yozmcafe.domain.cafe.Cafe;
import com.project.yozmcafe.domain.cafe.LikedCafe;
import com.project.yozmcafe.domain.cafe.UnViewedCafe;
import com.project.yozmcafe.exception.BadRequestException;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OrderBy;
import org.springframework.data.domain.Persistable;

import java.util.ArrayList;
import java.util.List;

import static com.project.yozmcafe.exception.ErrorCode.NOT_EXISTED_LIKED_CAFE;
import static jakarta.persistence.CascadeType.MERGE;
import static jakarta.persistence.CascadeType.PERSIST;
import static java.lang.Math.min;
import static java.util.Collections.emptyList;
import static java.util.Objects.isNull;

@Entity
public class Member extends BaseEntity implements Persistable<String> {
Expand Down Expand Up @@ -59,6 +57,16 @@ public void addUnViewedCafes(final List<Cafe> cafes) {
unViewedCafes.addAll(allUnViewedCafes);
}

public List<Cafe> filterAlreadyExist(List<Cafe> cafes) {
final List<Cafe> alreadyExist = unViewedCafes.stream()
.map(UnViewedCafe::getCafe)
.toList();

return cafes.stream()
.filter(cafe -> !alreadyExist.contains(cafe))
.toList();
}

public boolean isUnViewedCafesSizeUnder(final int sizeExclusive) {
return unViewedCafes.size() < sizeExclusive;
}
Expand Down Expand Up @@ -143,4 +151,6 @@ public List<UnViewedCafe> getUnViewedCafes() {
public List<LikedCafe> getLikedCafes() {
return likedCafes;
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.project.yozmcafe.service;

import com.project.yozmcafe.domain.member.Member;
import org.springframework.context.ApplicationEvent;

public class CafeRefillEvent extends ApplicationEvent {

private final Member member;

public CafeRefillEvent(final Object source, final Member member) {
super(source);
this.member = member;
}

public Member getMember() {
return member;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.project.yozmcafe.service;

import com.project.yozmcafe.domain.member.Member;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

@Component
public class CafeRefillEventPublisher {

private final ApplicationEventPublisher eventPublisher;

public CafeRefillEventPublisher(final ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}

public void publishCafeRefillEvent(final Member member) {
eventPublisher.publishEvent(new CafeRefillEvent(this, member));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import static com.project.yozmcafe.exception.ErrorCode.NOT_EXISTED_CAFE;
import static com.project.yozmcafe.exception.ErrorCode.NOT_EXISTED_MEMBER;
import static com.project.yozmcafe.service.UnViewedCafeService.DEFAULT_UNVIEWED_CAFE_SIZE_CONDITION;
import static io.micrometer.common.util.StringUtils.isBlank;

@Service
Expand All @@ -27,13 +28,13 @@ public class CafeService {

private final CafeRepository cafeRepository;
private final MemberRepository memberRepository;
private final UnViewedCafeService unViewedCafeService;
private final CafeRefillEventPublisher eventPublisher;

public CafeService(final CafeRepository cafeRepository, final MemberRepository memberRepository,
final UnViewedCafeService unViewedCafeService) {
final CafeRefillEventPublisher eventPublisher) {
this.cafeRepository = cafeRepository;
this.memberRepository = memberRepository;
this.unViewedCafeService = unViewedCafeService;
this.eventPublisher = eventPublisher;
}

public List<CafeResponse> getCafesForUnLoginMember(final Pageable pageable) {
Expand Down Expand Up @@ -61,8 +62,12 @@ public List<CafeRankResponse> getCafesOrderByLikeCount(final Pageable pageable)
@Transactional
public List<CafeResponse> getCafesForLoginMember(final String memberId, final int size) {
final Member member = getMemberByIdOrThrow(memberId);

if (member.isUnViewedCafesSizeUnder(DEFAULT_UNVIEWED_CAFE_SIZE_CONDITION)) {
eventPublisher.publishCafeRefillEvent(member);
}

final List<UnViewedCafe> cafes = member.getNextUnViewedCafes(size);
unViewedCafeService.refillWhenUnViewedCafesSizeUnderTwenty(member);

return cafes.stream()
.map(UnViewedCafe::getCafe)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.project.yozmcafe.service;

import com.project.yozmcafe.domain.member.Member;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;

@Component
public class UnViewedCafeRefillListener {

private static final Logger LOGGER = LoggerFactory.getLogger(UnViewedCafeRefillListener.class);

private final UnViewedCafeService unViewedCafeService;

public UnViewedCafeRefillListener(final UnViewedCafeService unViewedCafeService) {
this.unViewedCafeService = unViewedCafeService;
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void listen(final CafeRefillEvent event) {
final Member member = event.getMember();
try {
unViewedCafeService.refillUnViewedCafes(member);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.project.yozmcafe.domain.CafeShuffleStrategy;
import com.project.yozmcafe.domain.cafe.Cafe;
import com.project.yozmcafe.domain.cafe.CafeRepository;
import com.project.yozmcafe.domain.cafe.UnViewedCafeRepository;
import com.project.yozmcafe.domain.member.Member;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -13,22 +14,23 @@
@Transactional(readOnly = true)
public class UnViewedCafeService {

private static final int DEFAULT_SIZE_CONDITION = 20;
public static final int DEFAULT_UNVIEWED_CAFE_SIZE_CONDITION = 20;

private final CafeRepository cafeRepository;
private final UnViewedCafeRepository unViewedCafeRepository;
private final CafeShuffleStrategy shuffleStrategy;

public UnViewedCafeService(final CafeRepository cafeRepository,
public UnViewedCafeService(final CafeRepository cafeRepository, final UnViewedCafeRepository unViewedCafeRepository,
final CafeShuffleStrategy shuffleStrategy) {
this.cafeRepository = cafeRepository;
this.unViewedCafeRepository = unViewedCafeRepository;
this.shuffleStrategy = shuffleStrategy;
}

@Transactional
public void refillWhenUnViewedCafesSizeUnderTwenty(final Member member) {
if (member.isUnViewedCafesSizeUnder(DEFAULT_SIZE_CONDITION)) {
final List<Cafe> shuffledCafes = shuffleStrategy.shuffle(cafeRepository.findAll());
member.addUnViewedCafes(shuffledCafes);
}
public void refillUnViewedCafes(final Member member) {
final List<Cafe> shuffledCafes = shuffleStrategy.shuffle(cafeRepository.findAll());
final List<Cafe> unViewedCafes = member.filterAlreadyExist(shuffledCafes);
unViewedCafeRepository.saveUnViewedCafes(unViewedCafes, member);
}
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
package com.project.yozmcafe.service.auth;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.project.yozmcafe.controller.auth.OAuthProvider;
import com.project.yozmcafe.controller.dto.AuthorizationUrlDto;
import com.project.yozmcafe.controller.dto.TokenResponse;
import com.project.yozmcafe.domain.member.Member;
import com.project.yozmcafe.domain.member.MemberInfo;
import com.project.yozmcafe.domain.member.MemberRepository;
import com.project.yozmcafe.service.UnViewedCafeService;
import com.project.yozmcafe.service.CafeRefillEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;

import static com.project.yozmcafe.service.UnViewedCafeService.DEFAULT_UNVIEWED_CAFE_SIZE_CONDITION;

@Service
@Transactional(readOnly = true)
public class AuthService {

private final UnViewedCafeService unViewedCafeService;
private final MemberRepository memberRepository;
private final JwtTokenProvider jwtTokenProvider;
private final CafeRefillEventPublisher eventPublisher;

public AuthService(final JwtTokenProvider jwtTokenProvider, final MemberRepository memberRepository,
final UnViewedCafeService unViewedCafeService) {
final CafeRefillEventPublisher eventPublisher) {
this.jwtTokenProvider = jwtTokenProvider;
this.memberRepository = memberRepository;
this.unViewedCafeService = unViewedCafeService;
this.eventPublisher = eventPublisher;
}

@Transactional
Expand All @@ -36,7 +36,10 @@ public TokenResponse createAccessToken(final String code, final OAuthProvider pr

final Member member = memberRepository.findById(memberInfo.openId())
.orElseGet(() -> memberRepository.save(memberInfo.toMember()));
unViewedCafeService.refillWhenUnViewedCafesSizeUnderTwenty(member);

if (member.isUnViewedCafesSizeUnder(DEFAULT_UNVIEWED_CAFE_SIZE_CONDITION)) {
eventPublisher.publishCafeRefillEvent(member);
}

return new TokenResponse(jwtTokenProvider.createAccessFrom(member.getId()));
}
Expand All @@ -53,6 +56,6 @@ public TokenResponse refreshAccessToken(final String access, final String refres
public List<AuthorizationUrlDto> getAuthorizationUrls() {
return Arrays.stream(OAuthProvider.values())
.map(provider -> new AuthorizationUrlDto(provider.getProviderName(), provider.getAuthorizationUrl()))
.collect(Collectors.toList());
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
com.project.yozmcafe.domain.cafe.CustomFunctionContributor
com.project.yozmcafe.config.CustomFunctionContributor
2 changes: 1 addition & 1 deletion server/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
spring:
datasource:
url: jdbc:mysql://localhost:20000/yozm-cafe?useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=UTF-8&serverTimezone=UTC
url: jdbc:mysql://localhost:20000/yozm-cafe?useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=UTF-8&serverTimezone=UTC&rewriteBatchedStatements=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,3 @@ void findByNoFilters() {
.containsOnly(cafe1.getName(), cafe2.getName(), cafe3.getName(), cafe4.getName());
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.project.yozmcafe.domain.cafe;

import com.project.yozmcafe.BaseTest;
import com.project.yozmcafe.domain.member.Member;
import com.project.yozmcafe.domain.member.MemberRepository;
import com.project.yozmcafe.fixture.Fixture;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

class UnViewedCafeRepositoryTest extends BaseTest {

@Autowired
private UnViewedCafeRepository unViewedCafeRepository;

@Autowired
private CafeRepository cafeRepository;

@Autowired
private MemberRepository memberRepository;

@Test
@DisplayName("unViewedCafe를 batch insert한다.")
void batchInsertUnViewedCafes() {
// given
final List<Cafe> cafes = new ArrayList<>();
for (int i = 0; i < 200; i++) {
cafes.add(Fixture.getCafe("name", "address", 0));
}
final List<Cafe> savedCafes = cafeRepository.saveAll(cafes);
final Member member = memberRepository.save(new Member("id", "name", "image"));

//when
unViewedCafeRepository.saveUnViewedCafes(savedCafes, member);

// then
final Member refilledMember = memberRepository.findWithUnViewedCafesById(member.getId()).get();
assertThat(refilledMember.getUnViewedCafes()).hasSize(200);
}
}
Loading

0 comments on commit 2d5d780

Please sign in to comment.