[Spring Boot] 08. 회원 가입 ②

shr·2022년 2월 17일
0

Spring

목록 보기
8/23
post-thumbnail

회원 가입 ②


07. 회원 가입 ①, 가입 확인 메일 전송에서 연결한 DB 테이블과, Member 엔티티, application.properties는 똑같이 이용한다.

💡 mapper.xml을 이용하는 방법이다.

  1. src/main/resources - mapper - mapper.xml 생성

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.example.demo.dao.MemberDao">
        <select id="existsById" resultType="boolean">
            select count(*) from member where username=#{username} and rownum=1
        </select>
    
        <select id="findById" resultType="member">
            select * from member where username=#{username} and rownum=1
        </select>
    
        <select id="findByEmail" resultType="member">
            select * from member where email=#{email} and rownum=1
        </select>
    
        <insert id="save">
            insert into member(username, password, irum, email, birthday, levels) 
            values (#{username}, #{password}, #{irum}, #{email}, #{birthday}, #{levels})
        </insert>
    
        <update id="update">
            update member
            <trim suffixOverrides="," prefix="set">
                <if test="password!=null">password=#{password},</if>
                <if test="email!=null">email=#{email},</if>
                <if test="enabled!=null">enabled=#{enabled},</if>
                <if test="authority!=null">authority=#{authority},</if>
                <if test="checkcode!=null">checkcode=#{checkcode},</if>
                <if test="count!=null">count=#{count},</if>
                <if test="levels!=null">levels=#{levels},</if>
            </trim>
            where username=#{username}
        </update>
    
        <delete id="deleteById">
            delete from member where username=#{username}
        </delete>
    </mapper>

    📝 <trim>

    suffix=""
    <trim> 문 안에서, "" 안의 내용을 가장 뒤에 붙여 준다.

    suffixOverrides=""
    <trim> 문 안에서, 가장 뒤에 "" 안의 글자가 있으면 자동으로 지워 준다.

    prefix=""
    <trim> 문 안에서, "" 안의 내용을 가장 앞에 붙여 준다.

    prefixOverrides=""
    <trim> 문 안에서, 가장 앞에 "" 안의 글자가 있으면 자동으로 지워 준다.


  1. src/main/java - com.example.demo.dao - MemberDao 인터페이스 생성

    package com.example.demo.dao;
    
    import java.util.Optional;
    
    import org.apache.ibatis.annotations.*;
    
    import com.example.demo.entity.*;
    
    @Mapper
    public interface MemberDao {
        public Integer save(Member member);
    
        // mybatis 3.5부터 Optional 리턴을 지원한다.
        public Optional<Member> findById(String username);
    
        // 이메일로 아이디 찾기 1. select * from - 범용성이 좋다.
        public Optional<Member> findByEmail(String email);
    
        // 이메일로 아이디 찾기 2. select username from - 최적화가 되어 있다.
        // public String findUsernameByEmail(String email);
    
        public Integer update(Member member);
    
        public Integer deleteById(String username);
    
        public Boolean existsById(String username);
    }

  1. src/main/java - com.example.demo.controller - MemberController 클래스 생성

    package com.example.demo.controller;
    
    import java.time.LocalDate;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.WebDataBinder;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.InitBinder;
    import org.springframework.web.bind.annotation.PostMapping;
    
    import com.example.demo.controller.editor.DatePropertyEditor;
    import com.example.demo.entity.Member;
    import com.example.demo.service.MemberService;
    
    import lombok.RequiredArgsConstructor;
    
    @RequiredArgsConstructor
    @Controller
    public class MemberController {
        private final MemberService service;
        
        @InitBinder
        public void init(WebDataBinder wdb) {
            // 사용법 - wdb.registerCustomEditor(출력 클래스, 에디터 이름);
    
            // Q. LocalDate로 변환할 에디터를 등록한다면?
            // A. wdb.registerCustomEditor(LocalDate.class, new DatePropertyEditor()); ▶ 모든 LocalDate에 대해 동작한다.
            // Q. 특정 필드(birthday)에만 동작하게 하려면?		
            wdb.registerCustomEditor(LocalDate.class, "birthday", new DatePropertyEditor());
        }
    
        @GetMapping("/member/join")
        public void join() {
        }
    
        @PostMapping("/member/join")
        public String join(Member member) {
            service.join(member);
            return "redirect:/member/login";
        }
    }

    📝 사용자 입력을 가지고 커맨드 객체를 생성하는 작업을 스프링에서는 바인딩(binding)한다고 한다.


  1. src/main/java - com.example.demo.controller.editor - DatePropertyEditor 클래스 생성

    package com.example.demo.controller.editor;
    
    import java.beans.*;
    import java.time.LocalDate;
    
    public class DatePropertyEditor extends PropertyEditorSupport {
        @Override
        public void setAsText(String text) throws IllegalArgumentException {
            // text 파라미터 : 사용자가 입력한 문자열, 지금 같은 경우 "2020-11-20"
            String str[] = text.split("-");		// js : ["2020", "11", "20"]
            Integer year = Integer.parseInt(str[0]);
            Integer month = Integer.parseInt(str[1]);
            Integer day = Integer.parseInt(str[2]);
    
            LocalDate date = LocalDate.of(year, month, day);
    
            setValue(date);
    
            // 이제 controller 가서 등록하면 된다.
        }
    }

    스프링은 문자열을 String(문자열), Integer(정수), Double(실수)로 변환할 수 있는 PropertyEditor를 제공한다. 하지만 문자열을 날짜 또는 enum으로 변환하는 PropertyEdition은 제공하지 않는다. 문자열을 날짜 또는 enum으로 변환하고 싶다면 우리가 만들어서 등록해야 한다. 따라서 "2020-01-10"을 LocalDate로 변환하는 DatePropertyEditor를 만든 것이다.


  1. src/main/java - com.example.demo.service - MemberService 클래스 생성

    package com.example.demo.service;
    
    import java.util.Optional;
    
    import javax.mail.MessagingException;
    import javax.mail.internet.*;
    
    import org.springframework.mail.javamail.*;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.stereotype.Service;
    
    import com.example.demo.dao.MemberDao;
    import com.example.demo.entity.Member;
    
    import lombok.RequiredArgsConstructor;
    
    @RequiredArgsConstructor
    @Service
    public class MemberService {
        private final JavaMailSender javaMailSender;
        private final MemberDao memberDao;
        private final PasswordEncoder passwordEncoder;
    
        // 메일 보내는 메소드
        public void sendMail(String from, String to, String title, String content) throws MessagingException {
            MimeMessage message = javaMailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message, false, "utf-8");
            helper.setFrom(from);
            helper.setTo(to);
            helper.setSubject(title);
            helper.setText(content, true);
            javaMailSender.send(message);
        }
    
        // 아이디 중복 확인
        public Boolean idAvailableCheck(String username) {
            // username이 있으면 true -> 해당 아이디는 쓸 수 없다. 사용 가능한가? false이니까 결과 값은 !로 뒤집어 준다.
            return !memberDao.existsById(username);
        }
    
        // 아이디 찾기
        public String findId(String email) {
            // return memberDao.findByEmail(email).getUsername(); ▶ 사용자가 없는 경우에 NPE 발생 위험이 높다.
    NoSuchElementException
            Optional<Member> result = memberDao.findByEmail(email);
            if (result.isPresent() == true)
                return result.get().getUsername();
            return "아이디를 찾지 못했습니다. T_T";
        }
    
        // 회원 가입
        public void join(Member member) {
            member.setPassword(passwordEncoder.encode(member.getPassword()));
            System.out.println(member);
        }
    }

    Optional

    DAO 작성자는 DB를 직접 보게 되므로 null 발생 여부를 미리 짐작할 수 있기 때문에 이를 Service 작성자에게 전달해 주어야 하는데, 이때 사용하는 것이 Optional이다. Optional은 null이 올 수 있는 값을 감싸는 Wrapper 클래스로, NPE가 발생하지 않도록 도와주며 여러 메소드를 제공한다.


  1. src/main/java - com.example.demo - SecurityConfig 클래스 생성

    package com.example.demo;
    
    import org.springframework.beans.factory.annotation.*;
    import org.springframework.context.annotation.*;
    import org.springframework.security.config.annotation.method.configuration.*;
    import org.springframework.security.config.annotation.web.builders.*;
    import org.springframework.security.config.annotation.web.configuration.*;
    import org.springframework.security.crypto.password.*;
    
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable();		// csrf 끄기
            http.authorizeHttpRequests().antMatchers("/**").permitAll();
        }
    }

  1. src/main/resource - log4jdbc.log4j2.properties, logback-spring.xml 추가

    log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
    log4jdbc.dump.sql.maxlinelength=0
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern> %d{HH:mm:ss.SSS} %highlight(%-5level) %magenta(%-4relative) --- [ %thread{10} ] %cyan(%logger{40}) : %msg%n </pattern>
            </encoder>
        </appender>
        <!--  내가 만든 클래스에 대한 로깅 설정 -->
        <logger name="com.icia" level="info" />
    
        <!--  3rd party 로깅 설정 -->
        <logger name="org.springframework" level="info" />
        <logger name="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" level="trace" />
    
        <!--  log4jdbc 로깅 설정 -->
        <logger name="jdbc.connection" level="warn"/>
        <logger name="jdbc.resultsettable" level="info"/>
        <logger name="jdbc.audit" level="warn"/>
        <logger name="jdbc.sqltiming" level="warn"/>
        <logger name="jdbc.resultset" level="warn"/>
        <root level="info">
            <appender-ref ref="console" />
        </root>
    </configuration>
profile
못하다 보면 잘하게 되는 거야 ・ᴗ・̥̥̥

0개의 댓글