minlog
article thumbnail

 

JAVA  Apache 이메일 인증 구현

 

대개 웹 사이트들을 보면 회원이 아이디를 잊었을 시 이메일을 통해 회원임을 확인한다.

  • 이메일 '인증번호'를 전송하여, 웹사이트에 입력한 번호와 일치하는지 확인
  • 이메일에서 링크를 클릭하여 인증

방법으로는 위의 두가지가 있는데, 이번 개인 프로젝트에서는 첫번째 방법 '인증번호' 를 통한 회원 확인 로직을 구현해보려고 한다.

 

프로젝트 명 : Travel Road
⭐ 개발환경
💻 BackEnd
- JAVA11, SpringBoot , JPA(Spring Data JPA),Spring Security,OAuth 2.0
- Build Tool : Gradle (Jar)
💻 FrontEnd
html, css, Javascript, thymeleaf, Bootstrap

 

 


 

📁 이메일 계정 세팅

메일을 전달할 클라이언트쪽의 이메일 계정은 보안이나 IMAP 설정이 필요하다. 현제 프로젝트에서는 Google로 설정하였다.   대부분 Google 또는 Naver 플랫폼을 사용하는데, 각 플랫폼의 설정 방법을 기록해보았다.

01) Google

구글 메일을 사용하여 인증 메일을 보낼 경우 설정 방법.

  • 구글 계정 > 보안 >  2단계 인증 '사용'으로 변경
  • 앱 비밀번호 생성 
    • 메일 , Windows 컴퓨터로 설정
    • 비밀번호는 프로젝트에서 비밀번호로 사용해야하기 때문에 따로 저장 ⭐⭐

 

  • 구글 이메일 설정 
    • 전달 및 POP/IMAP에서 'IMAP를 사용'으로 변경해준다.

 

 

02) Naver

네이버 메일을 사용하여 인증 메일을 보낼 경우 설정 방법.

  • 네이버 계정 > 보안 >  2단계 인증 '사용'으로 변경
  • 앱 비밀번호 생성

  • 네이버 메일 설정 
    • POP3/SMTP설정, IMAP/SMTP 설정을 사용으로 변경한다.

 


 

📁 프로젝트 세팅

 

01) 모듈 의존성 추가

  • 아파치에서 제공하는 'commons-email' 모듈을 설치한다.
  • SpringBoot를 이용해 Email을 전송할 수 있도록 'spring-boot-starter-mail' 모듈을 설치한다.

 

📑build.gradle

// https://mvnrepository.com/artifact/org.apache.commons/commons-email
implementation group: 'org.apache.commons', name: 'commons-email', version: '1.5'
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-mail
implementation 'org.springframework.boot:spring-boot-starter-mail:2.7.2'

 

02) 설정 파일 

프로젝트에 이메일 관련 설정은 properties 또는 config 파일로 할수 있다.

해당 파일은 중요한 정보가 적혀있으므로 외부에 노출을 조심해야한다. ( .gitignore 에서 파일명을 추가해주면, 해당 파일은 GitHub에 올라가지 않는다. ) properties 파일로 설정하는 방법을 사용해보았다. 

 

application.properties

스프링부트가 애플리케이션을 구동할 때 자동으로 로딩하는 파일
key - value 형식으로 값을 정의하면 애플리케이션에서 참조하여 사용할 수 있다.

 

이메일 관련 코드만 추가된 파일을 새로 만들경우, 애플리케이션 구동 시 스프링부트가 로딩 할수 있도록  application.properties  파일도 수정이 필요하다.

 

  • 공통내용
    • usrname : 클라이언트 이메일 ( ex. 아이디@gmail.com / 아이디@naver.com ) 
    • password : 앱비밀번호
  • Google
    • host : smtp.gmail.com
    • port : 587
  • naver
    • host : smtp.naver.com
    • port : 465

 

📑application-mail.properties

## Email Settings
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=이메일주소
spring.mail.password=앱비밀번호

spring.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
spring.mail.smtp.socketFactory.port=587
spring.mail.smtp.socketFactory.fallback=false

spring.mail.properties.mail.smtp.ssl.enable=true
spring.mail.properties.mail.smtp.ssl.trust=smtp.gmail.com
spring.mail.properties.mail.smtp.starttls.enable=true

spring.mail.properties.mail.transport.protocol=smtp
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.timeout=5000
spring.mail.properties.mail.debug=true

 

📑application.properties

spring.profiles.include=oauth,mail

 

 

 


 

📁 이메일 전송 로직 

사이트의 회원 아이디 찾기에서 이메일 인증을 사용하는 로직을 만들어보았다.

 

1) HTML - 회원 아이디 찾기 화면 

 

📑userId.html

더보기
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layout/basic}">
<th:block layout:fragment="content">
  <div class="container-fluid mt-5">
    <div class="content_box">
      <h2 class="tit text-center mb-4">회원 정보 찾기</h2>
      <!-- start : tab -->
      <ul class="nav nav-tabs">
        	...
      </ul>
      <!-- end : tab -->
      <!-- start : tab content-->
      <div class="tab-content" id="myTabContent">
        <div class="tab-pane fade show active" id="home-tab-pane" role="tabpanel" aria-labelledby="home-tab" tabindex="0">
          <div class="loginForm">
            <!--th:action="@{/userIdCheck}"-->
            <form id="idForm" th:action="@{/member/userIdCheck}" method="post">
              <table>
                <colgroup>
                  <col style="width:100px">
                  <col style="width:calc(100% - 100px)">
                </colgroup>
                <tbody>
                <tr>
                  <th><label for="name">이름</label></th>
                  <td>
                    <input th:name="name" name="name" id="name" class="form-control" type="text" placeholder="이름 입력하세요.">
                  </td>
                </tr>
                <tr  class="form-group email-form">
                  <th><label for="userEmail1">이메일</label></th>
                  <td>
                    <div class="input-group">
                      <input  type="hidden" name="userEmail" id="userEmail" value="">
                      <input type="text" class="form-control" name="userEmail1" id="userEmail1" placeholder="이메일" >
                      <select class="form-select select" name="userEmail2" id="userEmail2" >
                        <option>@naver.com</option>
                        <option>@daum.net</option>
                        <option>@gmail.com</option>
                        <option>@hanmail.com</option>
                        <option>@yahoo.co.kr</option>
                      </select>
                      <div class="input-group-addon">
                        <button type="button" class="btn btn-primary" id="mail-Check-Btn" >본인인증</button>
                      </div>
                    </div>
                    <div class="mail-check-box mt-3">
                      <input class="form-control mail-check-input" placeholder="인증번호 8자리를 입력해주세요!" disabled="disabled" maxlength="8">
                      <p id="timerUp">
                        <span id="timer" class="point-color"></span><i class='bi bi-clock point-color'></i>
                      </p>
                    </div>
                    <p class="info mt-2 point-color" style="font-size:12px;">* 회원가입 시 입력한 이메일을 입력해주세요</p>
                    <span id="mail-check-warn"></span>
                  </td>
                </tr>
                </tbody>
              </table>
              <a id="userCheckBtn"  class="btn btn-primary btn-lg w-100 mt-3 disabled" type="button">확인</a>
            </form>
          </div>
        </div>
      </div>
      <!--end :  tab content-->
      <div class=" pt-4 pb-4 mt-2 text-center border-top">
      		...
      </div>
    </div>
  </div>
	 ...
</th:block>
</html>

 

  • 이메일 전달 기능 
    • Ajax : 형식으로 Form 저장
    • Get : 방식으로 데이터를 전달하여  /mailCheck?key=value  뒤에 값을 붙힐수 있다 
    • Success : 데이터 전달 성공시 data( 인증코드 값을 가져온다)

 

 <script>
      const formSubmit = $('#userCheckBtn'); //확인 버튼
      const emailValue = $('#userEmail'); // 이메일
      let checkInput = $('.mail-check-input') // 인증번호 입력하는곳
      let msg = "";

     // 인증코드 유효시간 카운트다운 및 화면 출력
      let timer; //카운트 다운
      let isRunning = false; //인증 코드 유효 


      // 타이머 함수 실행
      function sendAuthNum(){
          // 남은 시간(초)
          let leftSec = 60,
              display = document.querySelector('#timer');
          // 이미 타이머가 작동중이면 중지
          if (isRunning){
              clearInterval(timer);
          }
          startTimer(leftSec, display);
      }

      function startTimer(count, display) {
          let minutes, seconds;
          timer = setInterval(function () {
              minutes = parseInt(count / 60, 10);
              seconds = parseInt(count % 60, 10);
              minutes = minutes < 10 ? "0" + minutes : minutes;
              seconds = seconds < 10 ? "0" + seconds : seconds;
              display.textContent = minutes + ":" + seconds;
              // 타이머 종료
              if (--count < 0) {
                  clearInterval(timer);
                  display.textContent = "";
                  code = null;
                  isRunning = false;
              }
          }, 1000);
          isRunning = true;
      }

      function emailSendNot(email){
          console.log(email);

          msg  += "<div class='text-center'>";
          msg  += "<b class='border box-gray p-2 mb-3 mt-3' style='display:inline-block'>"+email+"</b>";
          msg  += "<p>이미 인증번호가 위 이메일로 전송되었습니다.</p>";
          msg  += "</div>";

          $('#modalJoin .modal-title').html("회원인증");
          $('#modalJoin .modal-message').html(msg);
          $('#modalJoin').modal("show");
      }

      function emailSend(email){
          $.ajax({
              type : 'get',
              url : '/mailCheck?email='+email,
              success : function (data) {
                  code = data;
                  console.log("data : " +  code);
				// 모달 메시지 작성
                  msg  += "<div class='text-center'>";
                  msg  += "<i class='bi bi-clock point-color ' style='font-size:30px;'></i><br />";
                  msg  += "<b class='border box-gray p-2 mb-3 mt-3' style='display:inline-block'>"+email+"</b>";
                  msg  += "<p class='point-color info'>1분 이내로 입력해주세요.</p>";
                  msg  += "<p>인증번호가 위 이메일로 전송되었습니다.</p>";
                  msg  += "</div>";

                  //인증번호 메일 전달 모달
                  $('#modalJoin .modal-title').html("회원인증");
                  $('#modalJoin .modal-message').html(msg);
                  $('#modalJoin').modal("show");
                  checkInput.attr('disabled',false);
                  formSubmit.removeClass('disabled');
                  sendAuthNum(); // 타이머 함수 실행
              }
          }); // end ajax
      }
      
      $(function(){
      	$('#mail-Check-Btn').click(function() {
              const email = $('#userEmail1').val() + $('#userEmail2').val(); // 이메일 주소값 얻어오기!
              if (isRunning){
                  msg = "";
                  emailSendNot(email);
              }else{
                  msg = "";
                  emailValue.val(email);
                  emailSend(email);
              }
          }); // end send eamil

          // 인증번호 비교 및 아이디 찾기 폼 전송
          formSubmit.click(function () {
             let checkInputVal = checkInput.val(); // 인증번호 값
              const $resultMsg = $('#mail-check-warn');
              //console.log("checkInputVal : "+checkInputVal);

              if(checkInputVal === code){
                  $("#idForm").submit();
              }else{
                  $resultMsg.html('인증번호가 불일치 합니다. 다시 확인해주세요!.');
                  $resultMsg.css('color','red');
              }
          });
      });
  </script>

 

  • Modal
    • 메일 전송 성공시 팝업 노출
    • 아이디 찾기 '확인'버튼 클릭 시, 인증번호가 불일치할 경우 팝업 노출
    • 아이디 찾기 '확인'버튼 클릭 시, 일치하는 회원이(아이디, 이메일) 없을 경우 팝업 노출
<!-- Modal -->
<script type="text/javascript" th:if="${errTit != null}">
    $(document).ready(function (){
        $('#modalJoin').modal("show");
    })
</script>
<div class="modal fade" id="modalJoin" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h1 class="modal-title fs-5" id="staticBackdropLabel" th:text="${errTit}">메시지</h1>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        <div class="modal-message" th:text="${errCont}">
         <p>일치하는 회원이 없습니다.</p>
        </div>
      </div>
    </div>
  </div>
</div>
<!-- End: Modal -->

 

 

2)  Controller 

앞에서 Ajax 로 전달한 데이터를 활용하여 서비스 로직에 값을 전달하고 결과를 받아 리턴해준다.

  • sendEmail( email, "userCheck" );
    • email : 전달될 이메일
    • "userCheck" : 회원 이메일 체크 인증 번호

 

📑 MailController.java

package com.example.travel.controller;

import com.example.travel.service.user.MailSendService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.data.repository.query.Param;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@Log4j2
@RestController("/")
@RequiredArgsConstructor
public class MailController {

    private final MailSendService mailSendService;
    //이메일 인증
    @GetMapping("/mailCheck")
    @ResponseBody
    public String mailCheck(@Param(value = "email") String email) throws Exception {
        System.out.println("이메일 인증 요청이 들어옴!");
        System.out.println("이메일 인증 이메일 : " + email);

        String userCheck = mailSendService.sendEmail(email, "userCheck");// 인증 번호
        log.info("이메일 인증 번호 : " + userCheck);
        return userCheck;
    }


}

 

 

3) Service 

  • @Value("${spring.mail.username}") :  '📑 application-mail.properties key의 vaule 값을 가져온다. 
  • createPassword : 랜덤 인증번호
  • sendEmail : HtmlEmail을 사용하여 이메일 전달 기능
  • msgEmailPw : 회원 이메일 체크 메일 내용

 

📑 MailSendService.java

package com.example.travel.service.user;

import lombok.RequiredArgsConstructor;
import org.apache.commons.mail.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Random;


@Service
@RequiredArgsConstructor
public class MailSendService{

    // application-mail.properties 값 가져오기 // 보안
    @Value("${spring.mail.host}")
    String host;

    @Value("${spring.mail.port}")
    int port;
    @Value("${spring.mail.username}")
    String username;

    @Value("${spring.mail.password}")
    String password;


    String ePw; // 랜덤 인증번호

    // 전달할 이메일 내용
    String msgTit = "[Travel Road]";
    String msgTxt;




    public String createPassword() {
        StringBuffer key = new StringBuffer();
        Random rnd = new Random();

        for (int i = 0; i < 8; i++) { // 인증코드 8자리
            int index = rnd.nextInt(3); // 0~2 까지 랜덤

            switch (index) {
                case 0:
                    key.append((char) ((int) (rnd.nextInt(26)) + 97));
                    //  a~z  (ex. 1+97=98 => (char)98 = 'b')
                    break;
                case 1:
                    key.append((char) ((int) (rnd.nextInt(26)) + 65));
                    //  A~Z
                    break;
                case 2:
                    key.append((rnd.nextInt(10)));
                    // 0~9
                    break;
            }
        }
        return ePw=key.toString();
    }


    //메일보내기
    public String sendEmail(String userEmail, String value) throws Exception {
        try {
            createPassword(); // 인증번호 생성

            HtmlEmail email = new HtmlEmail();
            email.setCharset("euc-kr"); // 한글 인코딩
            email.setHostName(host); //SMTP서버 설정
            email.setSmtpPort(port);
            email.setSSL(true);
            email.setAuthentication(username, password); //메일인증
            
            try {
                email.addTo(userEmail,userEmail); // 수신자
            } catch (EmailException e) {
                e.printStackTrace();
            }

            try {
                email.setFrom(username, "Travel Road"); // 발신자
            } catch (EmailException e) {
                e.printStackTrace();
            }
            
            // Create the email message
            email.addTo(userEmail, userEmail);  //수신자
            email.setFrom(username, "Travel Road"); // 발신자


            // 메일 제목 & 내용 설정
            if(value.equals("userCheck")){
                //아이디 체크 인증번호가 있을 시 인증 메일 내용 전달
                msgEmailPw();
            }

            email.setSubject(msgTit); // 메일 제목
            email.setHtmlMsg(msgTxt); // 메일 내용

            // set the alternative message
            //email.setTextMsg("Your email client does not support HTML messages");

            email.send();
        } catch (EmailException e) {
            e.printStackTrace();
        }



        return ePw;
   }


    // 메일 제목 & 내용 설정
    public void msgEmailPw(){
        msgTit = "[Travel Road] 이메일 인증번호 전달";
        //content
        StringBuffer strBuf = new StringBuffer();
        strBuf.append("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"ko\">");
        strBuf.append("<head>");
        strBuf.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">");
        strBuf.append("<meta http-equiv=\"Content-Script-Type\" content=\"text/javascript\">");
        strBuf.append("<meta http-equiv=\"Content-Style-Type\" content=\"text/css\">");
        strBuf.append("<title>[Travel Road]이메일인증 번호</title>");
        strBuf.append("</head>");
        strBuf.append("<body style=\"margin:0;font-size:1em\">");
        strBuf.append("<div>");
        strBuf.append("<h1 style='font-size:1.3em;margin-bottom:10px;'>안녕하세요 'Travel Road' 입니다.</h1>");
        strBuf.append("<p>회원님의 아이디를 찾으시려면 <span style='font-weight:bold;color:red'>'인증번호'</span>란에 아래 번호를 넣어주세요.</p>");
        strBuf.append("<p style='margin:10px 0;border: 1px solid #ced4da;background:#eee;padding:20px;font-size:1.2em'>");
        strBuf.append("[ 인증번호 : <span style='font-weight:bold;color:red'>" + ePw + "</span> ]");
        strBuf.append("</p>");
        strBuf.append("<p>감사합니다.</p>");
        strBuf.append("</div>");
        strBuf.append("</body>");
        strBuf.append("</html>");
        msgTxt = strBuf.toString();



        // embed the image and get the content id
        //URL url = new URL("http://www.apache.org/images/asf_logo_wide.gif");
        //String cid = email.embed(url, "Apache logo");
        // set the html message
        ///images/home/bul_citizen_icon01.png

    }

}

 

 


 

 

📁 아이디 찾기 로직 

앞의  '📁 이메일 전송 로직 ' 에서 전달 받은 인증번호와 회원이 입력한 인증번호를 비교하여,

값이 일치할 경우 아이디 찾기 폼을 'submit' 한다.

전달된 정보가 일치 하지 않을 경우, 해당 페이지로 다시 리다이렉트 되며 모달 팝업을 띄워준다.

일치할 경우, 레파지토리를 통해 검색된 아이디를 사용자 화면에 뿌려준다.

 

1) Controller

📑 UserController.java

package com.example.travel.controller;

import com.example.travel.domain.UserTravel;
import com.example.travel.dto.user.UserDTO;
import com.example.travel.service.user.MailSendService;
import com.example.travel.service.user.UserService;
import lombok.extern.log4j.Log4j2;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;


@Log4j2
@Controller
@RequestMapping("/")
public class UserController {
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    ...
    
    @PostMapping("member/userIdCheck")
    public String userIdCheck(@ModelAttribute("userTravel") UserDTO user ,Model model){
        String Result = userService.userGetName(user.getName(), user.getUserEmail());
        log.info("Result = {}",Result);

        if(Result == null){
            model.addAttribute("errTit","회원확인");
            model.addAttribute("errCont","일치하는 회원이 없습니다.");
            log.info("일치하는 회원이 없습니다.");
            return "member/userId";
        }
        log.info("=======================");
        user.setUserId(Result);
        return "member/userIdCheck";

    }
}

 

2) Service

📑 UserService.interface

package com.example.travel.service.user;

import com.example.travel.domain.UserTravel;
import com.example.travel.dto.user.UserDTO;

public interface UserService {
    public UserTravel userSave(UserDTO userSaveDTO); // user 저장
    public UserDTO userGetNo(Long no); // user 가져오기
    ...
    public String userGetName(String name,String email); // 아이디 찾기
    
    default UserTravel dtoToEntity(UserDTO dto){
        UserTravel result = UserTravel.builder()
                .userNo(dto.getUserNo())
                .userId(dto.getUserId())
                .userEmail(dto.getUserEmail())
                .password(dto.getPassword())
                .name(dto.getName())
                .userBirthday(dto.getUserBirthday())
                .userGender(dto.getUserGender())
                .userPhone(dto.getUserPhone())
                .userImg(dto.getUserImg())
                .address(dto.getAddress())
                .addressPostcode(dto.getAddressPostcode())
                .addressDetail(dto.getAddressDetail())
                .addressExtra(dto.getAddressExtra())
                .userAgree(dto.getUserAgree())
                .userSocial(dto.getUserSocial())
                .build();
        return result;
    }

    default UserDTO entityToDto(UserTravel userTravel){
        return UserDTO.builder()
                .userNo(userTravel.getUserNo())
                .userId(userTravel.getUserId())
                .userEmail(userTravel.getUserEmail())
                .password(userTravel.getPassword())
                .name(userTravel.getName())
                .userBirthday(userTravel.getUserBirthday())
                .userGender(userTravel.getUserGender())
                .userPhone(userTravel.getUserPhone())
                .userImg(userTravel.getUserImg())
                .address(userTravel.getAddress())
                .addressPostcode(userTravel.getAddressPostcode())
                .addressDetail(userTravel.getAddressDetail())
                .addressExtra(userTravel.getAddressExtra())
                .build();
    }

}

 

📑 UserServiceImpl.java

package com.example.travel.service.user;

import com.example.travel.domain.UserRole;
import com.example.travel.domain.UserTravel;
import com.example.travel.dto.user.UserDTO;
import com.example.travel.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Optional;

@RequiredArgsConstructor
@Service
@Log4j2
public class UserServiceImpl implements UserService {

    final UserRepository userRepository;
    final PasswordEncoder passwordEncoder;
  	...
    //아이디 찾기
    @Override
    public String userGetName(String name, String email) {
        Optional<UserTravel> result = userRepository.getUserByNameAndUserEmail(name, email);
        if (result.isPresent()){
            //값이 있을경우
            return result.get().getUserId();
        }else{
            // 값이 없을 경우
            return null;
        }

    }

    ...
}

 

3) Repository

📑 UserRepository.interfase

package com.example.travel.repository;

import com.example.travel.domain.UserTravel;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface UserRepository extends JpaRepository<UserTravel,Long> {
    UserTravel getUserByUserNo(Long no);
    UserTravel getUserByUserId(String id);

    ...

    //회원아이디 찾기
    @EntityGraph(attributePaths = {"roleSet"}, type = EntityGraph.EntityGraphType.LOAD)
    @Query(value = "select m from UserTravel m where m.name = :name and m.userEmail = :email" ,nativeQuery = false)
    Optional<UserTravel> getUserByNameAndUserEmail(@Param(value = "name") String name,@Param(value = "email")String email);
    ...
}

 

 

 

profile

minlog

@jimin-log

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!