| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
Tags
- CLIP
- 데이터 시각화
- 알고리즘
- python기초
- 랭그래프
- 소프트웨어 개발
- CNN
- 데이터엔지니어
- TTS
- 객체지향
- RNN
- 머신러닝
- RDBMS
- SQL
- 자연어처리
- UMAP
- 생성형 인공지능
- python 기초
- Python
- LangGraph
- 에이전트
- ASR
- 트랜스포머
- 딥러닝
- dementional reduction
- Transformer
- 기초
- 캐글
- 힙정렬
- 정보처리기사
Archives
- Today
- Total
수달이네 기술 블로그
3. MVC패턴을 이용한 로그인 회원가입 구 본문
회원구현부는 최대한 기능을 상세히 나누어 구현한다.
1. DB설계 & Entity 만들기
디비버 상에서 (오라클로 구현)
CREATE TABLE USERS(
user_id NUMBER,
login_id varchar2(50) NOT NULL,
password varchar2(200) NOT NULL,
gender varchar2(10),
email varchar2(100),
address varchar2(200),
address_detail varchar2(200),
zipcode varchar2(10),
CONSTRAINT pk_users PRIMARY KEY (user_id)
);
CREATE SEQUENCE seq_users;
- 로그인 시 필요할 데이터 베이스를 설계 후 데이터베이스에 만든다.
- 해당 결과물을 기반으로 JDBC에 구현예정
2. dto/UserDTO.java기반으로 구현
package com.example.finalApp.dto;
import lombok.Data;
@Data
public class UserDTO {
// CREATE TABLE USERS(
// user_id NUMBER,
// login_id varchar2(50) NOT NULL,
// password varchar2(200) NOT NULL,
// gender varchar2(10),
// email varchar2(100),
// address varchar2(200),
// address_detail varchar2(200),
// zipcode varchar2(10),
// CONSTRAINT pk_users PRIMARY KEY (user_id)
// );
private Long userId; //Long 레퍼클래스타입, 객체타입(Null값 저장 가능, DB의 NUMBER, BIGINT컬럼과 매핑시 주로 사용)
private String loginId;
private String password;
private String gender;
private String email;
private String address;
private String addressDetail;
private String zipcode;
}
- 롬복: @Data:를 통해 Setter, Getter, ToString등을 한번에 구현
3.repository구현
repository에선 유저의 값을 삽입하고, 가져오는 함수들을 만든다.
인터페이스
package com.example.finalApp.repository;
import java.util.Optional;
import com.example.finalApp.dto.UserDTO;
public interface UserRepository {
//Repository가 가져야할 기능(메소드 목록)만 정의
//구현은 jdbcUserRepository.java에서 구현
//회원가입(새로운 사용자 저장: 반환값이 필요하지 않으므로 void)
void insertUser(UserDTO user);
//DB삽입 성공> 정상실행
//DB삽입 실패 > 예외
//로그인 시 유저번호 조회(PK)
Optional<Long> findUserNumber(String loginId, String password);
//로그인 할 때 로그인 성공 > 해당 사용자의 user_id(PK)를 반환
//로그인 실패 > 아무런 값도 반환 하지 않음.
//Optional<Long>을 쓰는 이유
//로그인 성공 user_id가 존재하므로 Optional.of(값)이 반환
//로그인 실패 일치하는 사용자가 없으므로 Optional.empty()반환
}
- 회원가입과 로그인 시 유저번호를 조회하는 기능을 만든다.
- Optional<Long>을 반환하는 이유는 유저번호가 있을 수도 있고, 없을수도 있기 때문에 안정적으로 반환하기 위해서 이다.
구현부
package com.example.finalApp.repository;
import java.util.Optional;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import com.example.finalApp.dto.UserDTO;
import lombok.RequiredArgsConstructor;
@Repository // Spring이 이 클래스를 DAO(데이터 접근 레이어)로 관리
@RequiredArgsConstructor // final 필드인 JdbcTmplate을 생성자 주입(DI)
public class jdbcUserRepository implements UserRepository {
private final JdbcTemplate jdbc;
// alt + shift + s + v : 오버라이딩 (재정의)
@Override
public void insertUser(UserDTO user) {
// 시퀀스 번호 미리 가져오기
Long userId = jdbc.queryForObject("SELECT SEQ_USER.NEXTVAL FROM DUAL", Long.class);
user.setUserId(userId);
// Insert 실행
String sql = "INSERT INTO users (USER_ID, LOGIN_ID, PASSWORD, GENDER, EMAIL, ADDRESS, ADDRESS_DETAIL, ZIPCODE) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
jdbc.update(sql, user.getUserId(), user.getLoginId(), user.getPassword(), user.getGender(), user.getEmail(),
user.getAddress(), user.getAddressDetail(), user.getZipcode());
}
@Override
public Optional<Long> findUserNumber(String loginId, String password) {
String sql = "SELECT USER_ID FORM USERS WHERE LOGIN_ID = ? AND PASSWORD=?";
try {
Long userId = jdbc.queryForObject(sql, Long.class, loginId, password);
// queryForObject : 결과가 정확히 1행일 때 그 행의 첫 컬럼을 Long으로 반환
return Optional.ofNullable(userId);
// 행이 있으면 Optional.ofNullable(userId) Optional로 감싸서 반환
// USER_ID NUMBER NOT NULL이면 of로만 써도 가능함
} catch (EmptyResultDataAccessException e) {
// TODO Auto-generated catch block
return Optional.empty(); //일치하는 계정이 없음
// 행이 없으면 queryForObject가 EmptyReusltDataAccessException를 던지므로 Optional.empty()를
// 반환 => 일치하는 사용자가 없음을 의미
}
}
}
- jdbc를 이용하여 데이터베이스에서 값을 가져오는 쿼리문을 짠다.
- insertUser에선 insert문을 이용하여 삽입,
- findUserNumbner에선 select문을 이용하여 데이터를 찾는다.
4.UserService
비즈니스 로직, 즉 비밀번호 확인, 아이디 검사등을 담당
인터페이스
package com.example.finalApp.service;
import java.util.Optional;
import com.example.finalApp.dto.UserDTO;
public interface UserService {
//서비스 계층: 비즈니스 로직 처리
//회원가입 비즈니스 로직
//비밀번호 확인, 중복 아이디 검사, 필수값 겁증, Repository를 호출하여 DB에 저장
void join(UserDTO user, String confirmPassword);
//로그인 비즈니스 로직
//입력한 로그인 id가 존재하는지 확인, 비밀번호가 일치하는지 확인, 성공하면 사용자 번호 반환, 실패시 Optional,empty반환
Optional<Long> login(String loginId, String password);
}
구현부
package com.example.finalApp.service;
import java.util.Optional;
import org.springframework.stereotype.Service;
import com.example.finalApp.dto.UserDTO;
import com.example.finalApp.repository.UserRepository;
import lombok.RequiredArgsConstructor;
@Service // 스프링이 서비스 빈으로 등록(트랜잭션, 비즈니스 로직 위치)
@RequiredArgsConstructor // final 필드(userRepository)를 매개변수로 받는 생성자 자동 생성 -> 생성자 주입(DI)
public class UserServiceImpl implements UserService {
private final UserRepository userRepository; // 실제 DB접근을 수행하는 영속성 계층
// private final : 반드시 필요하고, 변경되면 안되는 의존성이라는 의미
// final이라 생성자 에서만 초기화가 가능 -> 필드 주입이 아닌 생성자 주입을 강제하는 패턴
// NPE(NullPointerException)을 방지, 테스트 코드 작성시 의존성이 명확, 순환 참조를 발견 쉬움
@Override
public void join(UserDTO user, String confirmPassword) {
// 비밀번호 확인 체크
if (!user.getPassword().equals(confirmPassword)) {
throw new IllegalArgumentException("비밀번호와 비밀번호 확인이 일치하지 않습니다");
}
// DB 저장 호출
userRepository.insertUser(user);
}
@Override
public Optional<Long> login(String loginId, String password) {
//레포지토리에게 조회 위임
return userRepository.findUserNumber(loginId, password);
}
}
- service 중 join문에선 회원가입과 관련하여 확인하므로 비밀번호와 비밀번호가 일치하는지 확인하는내용을 반환
- login에선 레포지토리에게 조회를 위임하여 같은 아이디가 있으면 조회, 없으면 Optional.empty()를 반환
service까지 구현했으나,
user를 찾지 못함.(404: 사용자 잘못)
5. controller구현

요청 url과 다르다.
그 이유는 controller를 구현하지 않았기 때문이다.
controller를 구현하면,
package com.example.finalApp.controller;
import java.util.Optional;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.example.finalApp.dto.UserDTO;
import com.example.finalApp.service.UserService;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
@Controller
@RequiredArgsConstructor
@RequestMapping("/user")
public class UserController {
private final UserService userService;
// 요청 URL : GET /user/join => joinForm()메소드 실행
@GetMapping("/join")
public String joinForm(Model model) {
if (!model.containsAttribute("user")) {
// Model에 user가 없으면 새 UserDTO(빈 객체) 넣기
model.addAttribute("user", new UserDTO());
}
return "user/join"; // templates/user/join.html
}
// 회원가입 처리
// 요청 URL : POST /user/join => join() 메소드 실행
@PostMapping("/join")
public String join(@ModelAttribute("user") UserDTO user, @RequestParam("confirm-password") String confirmPassword,
RedirectAttributes ra) {
try {
userService.join(user, confirmPassword);
ra.addFlashAttribute("msg", "회원가입이 완료되었습니다. 로그인해주세요.");
return "redirect:/user/login";
} catch (Exception e) {
ra.addFlashAttribute("error", "회원가입중 오류가 발생했습니다.");
return "redirect:/user/join";
}
}
// 요청 URL : GET /user/login => loginForm() 메소드 실행
@GetMapping("/login")
public String loginForm() {
return "user/login";
}
// 요청 URL : POST /user/login => login() 메소드 실행
@PostMapping("/login")
public String login(@RequestParam("loginId") String loginId, @RequestParam("password") String password,
HttpSession session, RedirectAttributes ra) {
Optional<Long> userId = userService.login(loginId, password);
if (userId.isPresent()) {
session.setAttribute("userId", userId.get());
session.setAttribute("loginId", userId);
return "redirect:/board/list"; // 로그인 성공 시 게시판으로 이동
} else {
ra.addFlashAttribute("error", "아이디 또는 비밀번호가 틀렸습니다.");
return "redirect:/user/login"; // 로그인 실패 시 다시 로그인 페이지로 이동
}
}
// 요청 URL : GET /user/logout => logout() 메소드 실행
@GetMapping("/logout")
public String logout(HttpSession session) {
session.invalidate();
return "redirect:/user/login";
}
}
- controller에선 각 path의 기능을 완성한다.
- /join에선 회원가입을 Post로 정보를 주고받는다(Post로 만들어 Get처럼 데이터가 앞에서 주고받아지는 것을 방지)
- /login 도 마찬가지로 Post로
- /logout에선 Get으로 매핑한다.
이후엔 게시판 구현도 해볼 것이다.
'웹 > Spring' 카테고리의 다른 글
| 2. Spring으로 MVC패턴 구축하기1 (0) | 2025.12.09 |
|---|---|
| 1. SpringBoot 기초 (0) | 2025.12.08 |
| 0. SpringBoot (1) | 2025.12.04 |