DAO의 작성과 적용
1. DAO(Data Access Object)란?
데이터(data)에 접근(access)하기 위한 객체(object)
Database에 저장된 데이터를 읽기, 쓰기, 삭제, 변경을 수행(CRUD)
DB테이블당 하나의 DAO를 작성
2. 계층(layer)의 분리
중복 제거를 위해 공통부분인 selectUser를 분리해야 함
UserDao를 영속 계층(Persistance Layer, Data Access Layer)이라고 하고
Controller들을 Presentation Layer(Data를 보여주는 계층) 이라 함.
- 분리
1) 관심사
2) 변하는것과 변하지 않는 것
3) 중복
UserDao를 이용해 중복코드를 제거하고 관심사를 분리함.
그리고 변경에 유리해진다.
package com.fastcampus.ch3;
public interface UserDao {
int deleteUser(String id);
User selectUser(String id);
// 사용자 정보를 user_info테이블에 저장하는 메서드
int insertUser(User user);
int updateUser(User user);
void deleteAll() throws Exception;
}
package com.fastcampus.ch3;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
//@Component - @Controller, @Repository, @Service, @ControllerAdvice
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
DataSource ds;
final int FAIL = 0;
@Override
public int deleteUser(String id) {
int rowCnt = FAIL; // insert, delete, update
Connection conn = null;
PreparedStatement pstmt = null;
String sql = "delete from user_info where id= ? ";
try {
conn = ds.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, id);
// int rowCnt = pstmt.executeUpdate(); // insert, delete, update
// return rowCnt;
return pstmt.executeUpdate(); // insert, delete, update
} catch (SQLException e) {
e.printStackTrace();
return FAIL;
} finally {
// close()를 호출하다가 예외가 발생할 수 있으므로, try-catch로 감싸야함.
// try { if(pstmt!=null) pstmt.close(); } catch (SQLException e) { e.printStackTrace();}
// try { if(conn!=null) conn.close(); } catch (SQLException e) { e.printStackTrace();}
close(pstmt, conn); // private void close(AutoCloseable... acs) {
}
}
@Override
public User selectUser(String id) {
User user = null;
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
String sql = "select * from user_info where id= ? ";
try {
conn = ds.getConnection();
pstmt = conn.prepareStatement(sql); // SQL Injection공격, 성능향상
pstmt.setString(1, id);
rs = pstmt.executeQuery(); // select
if (rs.next()) {
user = new User();
user.setId(rs.getString(1));
user.setPwd(rs.getString(2));
user.setName(rs.getString(3));
user.setEmail(rs.getString(4));
user.setBirth(new Date(rs.getDate(5).getTime()));
user.setSns(rs.getString(6));
user.setReg_date(new Date(rs.getTimestamp(7).getTime()));
}
} catch (SQLException e) {
return null;
} finally {
// close()를 호출하다가 예외가 발생할 수 있으므로, try-catch로 감싸야함.
// close()의 호출순서는 생성된 순서의 역순
// try { if(rs!=null) rs.close(); } catch (SQLException e) { e.printStackTrace();}
// try { if(pstmt!=null) pstmt.close(); } catch (SQLException e) { e.printStackTrace();}
// try { if(conn!=null) conn.close(); } catch (SQLException e) { e.printStackTrace();}
close(rs, pstmt, conn); // private void close(AutoCloseable... acs) {
}
return user;
}
// 사용자 정보를 user_info테이블에 저장하는 메서드
@Override
public int insertUser(User user) {
int rowCnt = FAIL;
Connection conn = null;
PreparedStatement pstmt = null;
// insert into user_info (id, pwd, name, email, birth, sns, reg_date)
// values ('asdf22', '1234', 'smith', 'aaa@aaa.com', '2022-01-01', 'facebook', now());
String sql = "insert into user_info values (?, ?, ?, ?,?,?, now()) ";
try {
conn = ds.getConnection();
pstmt = conn.prepareStatement(sql); // SQL Injection공격, 성능향상
pstmt.setString(1, user.getId());
pstmt.setString(2, user.getPwd());
pstmt.setString(3, user.getName());
pstmt.setString(4, user.getEmail());
pstmt.setDate(5, new java.sql.Date(user.getBirth().getTime()));
pstmt.setString(6, user.getSns());
return pstmt.executeUpdate(); // insert, delete, update;
} catch (SQLException e) {
e.printStackTrace();
return FAIL;
} finally {
close(pstmt, conn); // private void close(AutoCloseable... acs) {
}
}
// 매개변수로 받은 사용자 정보로 user_info테이블을 update하는 메서드
@Override
public int updateUser(User user) {
int rowCnt = FAIL; // insert, delete, update
// Connection conn = null;
// PreparedStatement pstmt = null;
String sql = "update user_info " +
"set pwd = ?, name=?, email=?, birth =?, sns=?, reg_date=? " +
"where id = ? ";
// try-with-resources - since jdk7
try (
Connection conn = ds.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql); // SQL Injection공격, 성능향상
){
pstmt.setString(1, user.getPwd());
pstmt.setString(2, user.getName());
pstmt.setString(3, user.getEmail());
pstmt.setDate(4, new java.sql.Date(user.getBirth().getTime()));
pstmt.setString(5, user.getSns());
pstmt.setTimestamp(6, new java.sql.Timestamp(user.getReg_date().getTime()));
pstmt.setString(7, user.getId());
rowCnt = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
return FAIL;
}
return rowCnt;
}
public void deleteAll() throws Exception {
Connection conn = ds.getConnection();
String sql = "delete from user_info ";
PreparedStatement pstmt = conn.prepareStatement(sql); // SQL Injection공격, 성능향상
pstmt.executeUpdate(); // insert, delete, update
}
private void close(AutoCloseable... acs) {
for(AutoCloseable ac :acs)
try { if(ac!=null) ac.close(); } catch(Exception e) { e.printStackTrace(); }
}
}
(인터페이스 추출을 통해 UserDao를 인터페이스로 추출하고,
UserDao를 구현하는 클래스인 UserDaoImpl 클래스를 만들었음)
UserDao를 구현한 UserDaoImpl은 @Component 대신 @Repository 를 사용하여 빈을 생성한다.
@Repository에는 메타 애너테이션으로 @Component가 포함되어 있기 때문에
Component-scan 태그에 의해 scan이 되어 Bean이 생성된다.
DB와의 연결을 close() 메소드를 통해 끊어줘야 하고, try-catch문이 필요함.
try-catch문이 들어간 메소드를 만들어 사용해주었음.
또는 Autocloseable 인터페이스를 구현한 클래스의 객체는 try with resource 를 사용할 수 있음.
DAO를 로그인과 회원가입 페이지에 적용을 해본다.
package com.fastcampus.ch3;
import java.net.URLEncoder;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/login")
public class LoginController {
@Autowired
UserDao userDao;
@GetMapping("/login")
public String loginForm() {
return "loginForm";
}
@GetMapping("/logout")
public String logout(HttpSession session) {
// 1. 세션을 종료
session.invalidate();
// 2. 홈으로 이동
return "redirect:/";
}
@PostMapping("/login")
public String login(String id, String pwd, String toURL, boolean rememberId,
HttpServletRequest request, HttpServletResponse response) throws Exception {
// 1. id와 pwd를 확인
if(!loginCheck(id, pwd)) {
// 2-1 일치하지 않으면, loginForm으로 이동
String msg = URLEncoder.encode("id 또는 pwd가 일치하지 않습니다.", "utf-8");
return "redirect:/login/login?msg="+msg;
}
// 2-2. id와 pwd가 일치하면,
// 세션 객체를 얻어오기
HttpSession session = request.getSession();
// 세션 객체에 id를 저장
session.setAttribute("id", id);
if(rememberId) {
// 1. 쿠키를 생성
Cookie cookie = new Cookie("id", id); // ctrl+shift+o 자동 import
// 2. 응답에 저장
response.addCookie(cookie);
} else {
// 1. 쿠키를 삭제
Cookie cookie = new Cookie("id", id); // ctrl+shift+o 자동 import
cookie.setMaxAge(0); // 쿠키를 삭제
// 2. 응답에 저장
response.addCookie(cookie);
}
// 3. 홈으로 이동
toURL = toURL==null || toURL.equals("") ? "/" : toURL;
return "redirect:"+toURL;
}
private boolean loginCheck(String id, String pwd) {
User user = userDao.selectUser(id);
if(user==null) return false;
return user.getPwd().equals(pwd);
// return "asdf".equals(id) && "1234".equals(pwd);
}
}
먼저 컨트롤러에 @Autowired를 이용하여 UserDao 객체를 연결한다.
그리고 loginCheck 메소드에 id와 pwd를 체크하는 코드를 작성하였다.
id와 일치하는 정보를 바탕으로 User 객체를 만들고, 비밀번호 일치여부를 체크한다.
package com.fastcampus.ch3;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.beans.propertyeditors.StringArrayPropertyEditor;
import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
@Controller // ctrl+shift+o 자동 import
@RequestMapping("/register")
public class RegisterController {
@Autowired
UserDao userDao;
final int FAIL = 0;
@InitBinder
public void toDate(WebDataBinder binder) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
binder.registerCustomEditor(Date.class, new CustomDateEditor(df, false));
binder.setValidator(new UserValidator()); // UserValidator를 WebDataBinder의 로컬 validator로 등록
// List<Validator> validatorList = binder.getValidators();
// System.out.println("validatorList="+validatorList);
}
@GetMapping("/add")
public String register() {
return "registerForm"; // WEB-INF/views/registerForm.jsp
}
@PostMapping("/add")
public String save(@Valid User user, BindingResult result, Model m) throws Exception {
System.out.println("result="+result);
System.out.println("user="+user);
// User객체를 검증한 결과 에러가 있으면, registerForm을 이용해서 에러를 보여줘야 함.
if(!result.hasErrors()) {
// 2. DB에 신규회원 정보를 저장
int rowCnt = userDao.insertUser(user);
if(rowCnt!=FAIL) {
return "registerInfo";
}
}
return "registerForm";
}
private boolean isValid(User user) {
return true;
}
}
먼저 UserDao 객체를 연결하고,
UserValidator를 이용한 검증 결과에 에러가 없으면 DB에 user정보를 저장한다.
출처 : 스프링의 정석 : 남궁성과 끝까지 간다
'Spring & SpringBoot > Spring' 카테고리의 다른 글
220506 Spring - Chapter 3. Spring DI와 AOP (Part. 6) (0) | 2022.05.07 |
---|---|
220505 Spring - Chapter 3. Spring DI와 AOP (Part. 5) (0) | 2022.05.06 |
220503 Spring - Chapter 3. Spring DI와 AOP (Part. 3) (0) | 2022.05.03 |
220502 Spring - Chapter 3. Spring DI와 AOP (Part. 2) (0) | 2022.05.02 |
220430 Spring - Chapter 3. Spring DI와 AOP (0) | 2022.04.30 |