본문 바로가기
스프링과 JPA 기반 웹 어플리케이션 개발/7부 알림

73. 스터디 개설 알림

by Backchus 2020. 5. 11.

스터디를 만들때가 아니라 공개할 때 알림

  • 알림 받을 사람: 스터디 주제와 지역에 매칭이 되는 Account
  • 알림 제목: 스터디 이름
  • 알림 메시지: 스터디 짧은 소개

QueryDSL설정

 

QueryDSL 설치

<dependency>
  <groupId>com.querydsl</groupId>
  <artifactId>querydsl-jpa</artifactId>
</dependency>
            <plugin>
                <groupId>com.mysema.maven</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <version>1.1.3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>target/generated-sources/java</outputDirectory>
                            <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                        </configuration>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>com.querydsl</groupId>
                        <artifactId>querydsl-apt</artifactId>
                        <version>${querydsl.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
  • 이후에 반드시 메이븐 컴파일 빌드 (mvc compile)를 해야 Q클래스 생성해준다.
  • 애노테이션 프로세서

 

스프링 데이터 JPA와 QueryDSL 연동

  • QuerydslPredicateExecutor 인터페이스 추가
  • Predicate 사용하기
public interface AccountRepository extends JpaRepository<Account, Long>, QuerydslPredicateExecutor<Account> {
}
public class AccountPredicates {
	public static Predicate findByTagsAndZones(Set<Tag> tags, Set<Zone> zones){
    	return QAccount.account.zones.any().in(zones).and(QAccount.account.tags.any().in(tags));
    }
}

스터디를 생성할때 스터디 생성알림을 보내지 않고 스터디 생성후 모집중일때 알림을 보내려면 

package me.weekbelt.studyolle.modules.study;

@RequiredArgsConstructor
@Transactional
@Service
public class StudyService {

    // .......
    
    private final ApplicationEventPublisher eventPublisher;

    public Study createNewStudy(Study study, Account account) {
        Study newStudy = studyRepository.save(study);
        newStudy.addManager(account);
//        eventPublisher.publishEvent(new StudyCreatedEvent(newStudy)); 삭제
        return newStudy;
    }

}

createNewStudy메소드에서 이벤트를 발생시키면 안되고

package me.weekbelt.studyolle.modules.study;


@RequiredArgsConstructor
@Transactional
@Service
public class StudyService {

    // .........

    public void publish(Study study) {
        study.publish();
        this.eventPublisher.publishEvent(new StudyCreatedEvent(study));
    }

    // ..........
}

스터디가 공개되는 publish메소드가 실행될때 이벤트를 발생 시켜야 한다.

 

스터디와 관련된 Tag와 Zone 정보를 가져오기위한 로직을 추가한다.

package me.weekbelt.studyolle.modules.study.event;

@RequiredArgsConstructor
@Slf4j
@Async
@Transactional
@Component
public class StudyEventListener {

    private final StudyRepository studyRepository;

    @EventListener
    public void handleStudyCreatedEvent(StudyCreatedEvent studyCreatedEvent) {
        Study study = studyRepository.findStudyWithTagsAndZonesById(studyCreatedEvent.getStudy().getId());
    }
}

 

StudyRepository에 Tag와Zone을 함께 조회하는 메소드 작성

package me.weekbelt.studyolle.modules.study;

@Transactional(readOnly = true)
public interface StudyRepository extends JpaRepository<Study, Long> {
    
    // ........
    
    @EntityGraph(value = "Study.withTagsAndZones", type = EntityGraph.EntityGraphType.FETCH)
    Study findStudyWithTagsAndZonesById(Long id);
}

 

StudyEntity에 엔티티 그래프 추가

package me.weekbelt.studyolle.modules.study;

@NamedEntityGraph(name = "Study.withTagsAndZones", attributeNodes = {
        @NamedAttributeNode("tags"),
        @NamedAttributeNode("zones")
})

// ........

public class Study {

    // ............
}

스터디의 Tag, Zone과 스터디 참여 계정의 Tag, Zone과 관련된 사람들에게 알림을 보내기위한 로직 생성

AccountRepository에 QuerydslPredicateExecutor를 상속

package me.weekbelt.studyolle.modules.account;

@Transactional(readOnly = true)
public interface AccountRepository extends JpaRepository<Account, Long>, 
QuerydslPredicateExecutor<Account> {  // 추가

    // ..........
}

 

Predicate를 AccountRepository에 제공하기위한 클래스 추가

스터디의 zone과 tag중에 account의 zone과 tag가 하나라도 겹치면 알림이 갈 수 있도록 하기위한 로직

package me.weekbelt.studyolle.modules.account;

public class AccountPredicates {

    public static Predicate findByTagsAndZones(Set<Tag> tags, Set<Zone> zones) {
        return account.zones.any().in(zones)
                .and(account.tags.any().in(tags));
    }
}

 

다시 StudyEventListener로 돌아와서 누구에게 알림을 발송해야하는지 조건과 그 조건 충족시 처리를 하는 로직을 작성한다.

package me.weekbelt.studyolle.modules.study.event;


@RequiredArgsConstructor
@Slf4j
@Async
@Transactional
@Component
public class StudyEventListener {

    private final StudyRepository studyRepository;
    private final AccountRepository accountRepository;
    private final EmailService emailService;
    private final TemplateEngine templateEngine;
    private final AppProperties appProperties;
    private final NotificationRepository notificationRepository;

    @EventListener
    public void handleStudyCreatedEvent(StudyCreatedEvent studyCreatedEvent) {
        // Tags와 Zones를 참조할수 있는 Study를 가져왔다.
        Study study = studyRepository.findStudyWithTagsAndZonesById(studyCreatedEvent.getStudy().getId());
        Iterable<Account> accounts = accountRepository.findAll(AccountPredicates.findByTagsAndZones(study.getTags(), study.getZones()));
        accounts.forEach(account -> {
            if (account.isStudyCreatedByEmail()){
                sendStudyCreatedEmail(study, account);
            }

            if (account.isStudyCreatedByWeb()) {
                saveStudyCreatedNotification(study, account);
            }
        });
    }

    private void sendStudyCreatedEmail(Study study, Account account) {
        Context context = new Context();
        context.setVariable("nickname", account.getNickname());
        context.setVariable("link", "/study/" + study.getEncodedPath());
        context.setVariable("linkname", study.getTitle());
        context.setVariable("message", "새로운 스터디가 생겼습니다.");
        context.setVariable("host", appProperties.getHost());
        String message = templateEngine.process("mail/simple-link", context);

        EmailMessage emailMessage = EmailMessage.builder()
                .subject("스터디올래, '" + study.getTitle() + "' 스터디가 생겼습니다.")
                .to(account.getEmail())
                .message(message)
                .build();

        emailService.sendEmail(emailMessage);
    }

    private void saveStudyCreatedNotification(Study study, Account account) {
        Notification notification = new Notification();
        notification.setTitle(study.getTitle());
        notification.setLink("/study/" + study.getEncodedPath());
        notification.setChecked(false);
        notification.setCreatedLocalDateTime(LocalDateTime.now());
        notification.setMessage(study.getShortDescription());
        notification.setAccount(account);
        notification.setNotificationType(NotificationType.STUDY_CREATED);
        notificationRepository.save(notification);
    }
}

 

 

참고: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-JPA-%EC%9B%B9%EC%95%B1#

 

스프링과 JPA 기반 웹 애플리케이션 개발 - 인프런

이 강좌에서 여러분은 실제로 운영 중인 서비스를 스프링, JPA 그리고 타임리프를 비롯한 여러 자바 기반의 여러 오픈 소스 기술을 사용하여 웹 애플리케이션을 개발하는 과정을 학습할 수 있습�

www.inflearn.com