07. 회원 가입 ①, 가입 확인 메일 전송에서 연결한 DB 테이블과, Member
엔티티, application.properties
는 똑같이 이용한다.
💡 mapper.xml을 이용하는 방법이다.
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> 문 안에서, 가장 앞에 "" 안의 글자가 있으면 자동으로 지워 준다.
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);
}
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)한다고 한다.
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
를 만든 것이다.
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가 발생하지 않도록 도와주며 여러 메소드를 제공한다.
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();
}
}
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>