41강 DB격리수준 READ COMMIT
트랜잭션: 일이 처리되기 위한 가장 작은 단위
42강 READ COMMIT의 정합성 문제
정합성문제 부정합 - 동일한 쿼리의 결과가 다르다. PHANTOM READ - 동일한 쿼리 마다 결과가 보였다 안보였다 하는 것.
43강 REPEACTABLE READ
MySQL → InnoDB 스토리지 엔진 → Repeactable read 이상 사용 → 부정합 발생 X
동영상 설명 잘못된 부분 - Repeactable Read는 update시의 정합성을 유지해주지만 insert된 유령데이터가 생기는 것을 막지는 못합니다. 즉 팬텀현상을 막을 수 는 없습니다. MySQL - Innodb 를 사용하면 트랜잭션 고립수준이 REPEATABLE READ입니다. 그래서 Select를 할 때도 트랜잭션을 타게 해주면 트랜잭션의 시작 시점 부터 트랜잭션이 끝나는 시점까지 Select했을 때의 결과가 동일함을 보장해주기 때문에 정합성이 유지됩니다. 하지만 정합성은 update일 때만 유지해주고 insert가 일어나면 유령 데이터가 생기는 현상인 팬텀현상아 생기게 됩니다.
44강 스프링의 전통적인 트랜잭션
JDBC커넥션과 트랜잭션의 시작이 Controller 진입직전!! JDBC커넥션과 트랜잭션의 종료가 Controller 종료시점!!
45강 스프링 JPA의 OSIV 전략
스프링 2.0이상 기본전략 : OSIV 전략 영속성 컨텍스트 세션 시작 : 컨트롤러 시작전 트랜잭션, JDBC 커넥션 시작 : 서비스 시작 후 트랜잭션, JDBC 커넥션 종료 : 서비스 종료 후 (이때 커밋) 영속성 컨텍스트 세션 종료 : 컨트롤러 종료 후 (프록시 객체를 통한 lazy로딩 가능)
스프링부트의 트랜잭션
- 세션의 시작은 서블릿이 시작되는 시점 부터~ (세션은 영속성 컨텍스트를 포함)
- 트랜잭션의 시작은 서비스 레이어부터, JDBC 커넥션도 이 시점부터.
- 트랜잭션의 종료는 서비스 계층에서 종료, JDBC 커넥션도 이 시점 부터 종료.
- 세션은 컨트롤러 영역까지 끌고 가기 때문에 영속성이 보장되어 select가 가능해지고 lazy-loading이 가능해진다.
46강 블로그 프로젝트 전통적인 방식의 로그인 방법
user.js 에 login함수 추가
let index = {
init: function(){
$("#btn-save").on("click", ()=>{ // function(){}, ()=>{} this를 바인딩하기 위해서!!
this.save();
});
$("#btn-login").on("click", ()=>{ // function(){}, ()=>{} this를 바인딩하기 위해서!!
this.login();
});
},
save:function(){
//alert('user의 save함수 호출됨');
let data = {
username: $("#username").val(),
password: $("#password").val(),
email: $("#email").val(),
};
//console.log(data);
// ajax호출시 default가 비동기 호출
// ajax 통신을 이용해서 3개의 데이터를 json으로 변경하여 insert 요청!!
// ajax가 통신을 성공하고 json을 리턴해주면 자동으로 자바 오브젝트로 변환해주네요.
$.ajax({
// 회원가입 수행 요청
type: "POST",
url: "/blog/api/user",
data: JSON.stringify(data), // http body데이터
contentType: "application/json; charset=utf-8", // body데이터가 어떤 타입인지(MIME)
dataType: "json" // 요청을 서버로해서 응답이 왔을때 기본적으로 모든 것이 문자열 (생긴게 json이라면) => javascript오브젝트로 변경
}).done(function(resp){
alert("회원가입이 완료되었습니다.");
console.log(resp);
location.href= "/blog";
}).fail(function(error) {
alert(JSON.stringify(error));
}); // ajax 통신을 이용해서 3개의 데이터를 json으로 변경하여 insert 요청!!
},
login:function(){
//alert('user의 save함수 호출됨');
let data = {
username: $("#username").val(),
password: $("#password").val(),
};
//console.log(data);
// ajax호출시 default가 비동기 호출
// ajax 통신을 이용해서 3개의 데이터를 json으로 변경하여 insert 요청!!
// ajax가 통신을 성공하고 json을 리턴해주면 자동으로 자바 오브젝트로 변환해주네요.
$.ajax({
// 회원가입 수행 요청
type: "POST",
url: "/blog/api/user/login",
data: JSON.stringify(data), // http body데이터
contentType: "application/json; charset=utf-8", // body데이터가 어떤 타입인지(MIME)
dataType: "json" // 요청을 서버로해서 응답이 왔을때 기본적으로 모든 것이 문자열 (생긴게 json이라면) => javascript오브젝트로 변경
}).done(function(resp){
alert("로그인이 완료되었습니다.");
location.href= "/blog";
}).fail(function(error) {
alert(JSON.stringify(error));
}); // ajax 통신을 이용해서 3개의 데이터를 json으로 변경하여 insert 요청!!
}
}
index.init();
package com.cos.blog.controller.api;
import com.cos.blog.dto.ResponseDto;
import com.cos.blog.model.RoleType;
import com.cos.blog.model.User;
import com.cos.blog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
@RestController
public class UserApiController {
@Autowired
private UserService userService;
@PostMapping("/api/user")
public ResponseDto<Integer> save(@RequestBody User user){ // username, password, email
System.out.println("UserApiController : save 호출됨");
// 실제로 DB에 insert를 하고 아래에서 return이 되면 되요.
user.setRole(RoleType.USER);
userService.회원가입(user);
return new ResponseDto<Integer>(HttpStatus.OK.value(),1); // 자바오브젝트를 JSON으로 변환해서 리턴 (Jackson)
}
@PostMapping("/api/user/login")
public ResponseDto<Integer> login(@RequestBody User user, HttpSession session) {
System.out.println("UserApiController : login 호출됨");
User principal = userService.로그인(user); // principal (접근주체)
if(principal != null) {
session.setAttribute("principal",principal);
}
return new ResponseDto<Integer>(HttpStatus.OK.value(),1); // 자바오브젝트를 JSON으로 변환해서 리턴 (Jackson)
}
}
package com.cos.blog.service;
import com.cos.blog.model.RoleType;
import com.cos.blog.model.User;
import com.cos.blog.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
// 스프링이 커포넌트 스캔을 통해서 Bean에 등록을 해줌. IoC를 해준다.
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public int 회원가입(User user) {
String rawPassword = user.getPassword(); // 1234 원문
//String encPassword = encoder.encode(rawPassword); // 해쉬
//user.setPassword(encPassword);
user.setRole(RoleType.USER);
try {
userRepository.save(user);
return 1;
} catch (Exception e) {
return -1;
}
}
@Transactional(readOnly = true) // Select할때 트랜잭션 시작, 서비스 종료시에 트랜잭션 종료 (정합성)
public User 로그인(User user) {
return userRepository.findByUsernameAndPassword(user.getUsername(),user.getPassword());
}
}
loginForm.jsp 수정
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ include file="../layout/header.jsp" %>
<div class="container">
<form>
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control" placeholder="Enter username" id="username">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" placeholder="Enter password" id="password">
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox"> Remember me
</label>
</div>
</form>
<button id="btn-save" class="btn btn-primary">로그인완료</button>
</div>
<script src="/blog/js/user.js"></script>
<%@include file="../layout/footer.jsp" %>
header.jsp 수정
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix = "c" uri = "http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Bootstrap Example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-expand-md bg-dark navbar-dark">
<a class="navbar-brand" href="/blog">Cos</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsibleNavbar">
<c:choose>
<c:when test="${empty sessionScope.pricipal}">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/blog/user/loginForm">로그인</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/blog/user/joinForm">회원가입</a>
</li>
</ul>
</c:when>
<c:otherwise>
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/blog/board/writeForm">글쓰기</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/blog/user/userForm">회원정보</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/blog/user/logout">로그아웃</a>
</li>
</ul>
</c:otherwise>
</c:choose>
</div>
</nav>
<br />
package com.cos.blog.repository;
import com.cos.blog.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
// DAO
// 자동으로 bean등록이 된다.
// @Repository 생략 가능하다.
public interface UserRepository extends JpaRepository<User,Integer> {
// JPA Naming 전략
// SELECT * FROM user WHERE username = ? AND password = ?;
User findByUsernameAndPassword(String username,String password);
// @Query(value = "SELECT * FROM user WHERE username = ? AND password = ?",nativeQuery = true)
// User login(String username, String password);
}
47강 시큐리티 시작전 요청 주소 변경
application.yml 의 context-path: / 으로 수정
그리고 /blog/~~~~ 했던거들 /~~~~ 로 수정
48강 스프링 시큐리티 체험해보기
주석처리
header.jsp 추가
49강 스프링 시큐리티 로그인 페이지 커스터마이징
인증이 안된 사용자들이 출입할 수 있는 경로를 /auth/** 허용 그냥 주소가 / 이면 index.jsp 허용 static이하에 있는 /js/**, /css/**, image/**
package com.cos.blog.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
// 빈 등록 : 스프링 컨테이너에서 객체를 관리할 수 있게 하는 것
@Configuration // 빈등록(IoC관리)
@EnableWebSecurity // 시큐리티 필터가 등록이 된다.
@EnableGlobalMethodSecurity(prePostEnabled = true) // 특정 주소로 접근을 하면 권한 및 인증을 미리 체크
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/auth/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/auth/loginForm");
}
}
50강 비밀번호 해쉬 후 회원가입하기
고정길이의 16진수값으로 변경 X
고정길이의 문자열로 변경 O
이런식으로 ↓
UserService.java
package com.cos.blog.service;
import com.cos.blog.model.RoleType;
import com.cos.blog.model.User;
import com.cos.blog.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
// 스프링이 커포넌트 스캔을 통해서 Bean에 등록을 해줌. IoC를 해준다.
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private BCryptPasswordEncoder encoder;
@Transactional
public void 회원가입(User user) {
String rawPassword = user.getPassword(); // 1234 원문
String encPassword = encoder.encode(rawPassword); // 해쉬
user.setPassword(encPassword);
user.setRole(RoleType.USER);
userRepository.save(user);
}
// @Transactional(readOnly = true) // Select할때 트랜잭션 시작, 서비스 종료시에 트랜잭션 종료 (정합성)
// public User 로그인(User user) {
// return userRepository.findByUsernameAndPassword(user.getUsername(),user.getPassword());
// }
}
SecurityConfig.java
package com.cos.blog.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
// 빈 등록 : 스프링 컨테이너에서 객체를 관리할 수 있게 하는 것
@Configuration // 빈등록(IoC관리)
@EnableWebSecurity // 시큐리티 필터가 등록이 된다.
@EnableGlobalMethodSecurity(prePostEnabled = true) // 특정 주소로 접근을 하면 권한 및 인증을 미리 체크
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean // IoC가 됨
public BCryptPasswordEncoder encodePWD() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // csrf 토큰 비활성화 (테스트시 걸어두는게 좋음)
.authorizeRequests()
.antMatchers("/","/auth/**","/js/**","/css/**","/image/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/auth/loginForm");
}
}
비밀번호가 해쉬로 잘 저장
51강 XSS와 CSRF
스프링 시큐리티를 사용하면 CSRF처럼 개발자가 의도하지 않은(예상하지 못한) 요청들을 원천차단해서 개발자가 원하는 방향으로만 프로그램을 사용할 수 있도록 제한할 수 있다고 느꼈습니다.
package com.cos.blog.config;
import com.cos.blog.config.auth.PrincipalDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
// 빈 등록 : 스프링 컨테이너에서 객체를 관리할 수 있게 하는 것
@Configuration // 빈등록(IoC관리)
@EnableWebSecurity // 시큐리티 필터가 등록이 된다.
@EnableGlobalMethodSecurity(prePostEnabled = true) // 특정 주소로 접근을 하면 권한 및 인증을 미리 체크
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PrincipalDetailService principalDetailService;
@Bean // IoC가 됨
public BCryptPasswordEncoder encodePWD() {
return new BCryptPasswordEncoder();
}
// 시큐리티가 대신 로그인해주는데 password를 가로채기를 하는데
// 해당 password가 뭘로 해쉬가 되어 회원가입이 되었는지 알아야
// 같은 해쉬로 암호화해서 DB에 있는 해쉬랑 비교할 수 있음.
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(principalDetailService).passwordEncoder(encodePWD());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // csrf 토큰 비활성화 (테스트시 걸어두는게 좋음)
.authorizeRequests()
.antMatchers("/","/auth/**","/js/**","/css/**","/image/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/auth/loginForm")
.loginProcessingUrl("/auth/loginProc")
.defaultSuccessUrl("/"); // 스프링 시큐리티가 해당 주소로 로그인을 가로채서 대신 로그인 해준다.
}
}
package com.cos.blog.config.auth;
import com.cos.blog.model.User;
import com.cos.blog.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service // Bean 등록
public class PrincipalDetailService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
// 스프링이 로그인 요청을 가로챌 때, username, password 변수 2개를 가로채는데
// password 부분 처리는 알아서 함.
// username이 DB에 있는지만 확인해주면 됨.
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User pricipal = userRepository.findByUsername(username)
.orElseThrow(()->{
return new UsernameNotFoundException("해당 사용자를 찾을 수 없습니다.: "+username);
});
return new PrincipalDetail(pricipal); // 시큐리티의 세션에 유저 정보가 저장이 됨. 아이디:user, 패스워드:콘솔창
}
}
UserRepository.java 수정
package com.cos.blog.repository;
import com.cos.blog.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.Optional;
// DAO
// 자동으로 bean등록이 된다.
// @Repository 생략 가능하다.
public interface UserRepository extends JpaRepository<User,Integer> {
// SELECT * FROM user WHERE username = 1?;
Optional<User> findByUsername(String username);
}
// JPA Naming 전략
// SELECT * FROM user WHERE username = ? AND password = ?;
// User findByUsernameAndPassword(String username,String password);
// @Query(value = "SELECT * FROM user WHERE username = ? AND password = ?",nativeQuery = true)
// User login(String username, String password);
PrincipalDetail.java
package com.cos.blog.config.auth;
import com.cos.blog.model.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayDeque;
import java.util.Collection;
// 스프링 시큐리티가 로그인 요청을 가로채서 로그인을 진행하고 완료가 되면 UserDetails 타입의 오브젝트를
// 스프링 시큐리티의 고유한 세션저장소에 저장을 해준다.
public class PrincipalDetail implements UserDetails {
private User user; // 콤포지션
public PrincipalDetail(User user) {
this.user = user;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
// 계정이 만료되지 않았는지 리턴한다. (true:만료안됨)
@Override
public boolean isAccountNonExpired() {
return true;
}
// 계정이 잠겨있지 않았는지 리턴한다. (true:잠기지 않음)
@Override
public boolean isAccountNonLocked() {
return true;
}
// 비밀번호가 만료되지 않았는지 리턴한다. (true:만료안됨)
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 계정이 활성화(사용가능)인지 리턴한다. (true:활성화)
@Override
public boolean isEnabled() {
return true;
}
// 계정이 갖고 있는 권한 목록을 리턴한다. (권한이 여러개 있을 수 있어서 루프를 돌아야 하는데 우리는 하나만)
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collectors = new ArrayDeque<>();
collectors.add(()->{return "ROLE_"+user.getRole();});
return collectors;
}
}
스프링 시큐리티를 사용하기 위해, 커스터마이징 툴로 WebSecuriyConfigurerAdapter를 제공하는데,
그 툴을 상속받아 SecurityConfig라는 설정파일을 만들고,
어디로 요청 들어오면 확인 할지, 해쉬는 뭘로할지, 유저 아이디는 어디서 찾을 지 등 설정을 해줍니다.
그래서 유저 객체는 PrincipalDetailService로 찾으시고, 해쉬는 비크립트로 푸시고.. 시큐리티 세션에는 UserDetails밖에 저장 못하니까 구현하시고,, 등등
디테일 한 설정방법을 설명해주시는 걸로 이해했습니다.
53강 글쓰기 완료
saveForm.java
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ include file="../layout/header.jsp"%>
<div class="container">
<form>
<div class="form-group">
<label for="title">Title</label>
<input type="text" class="form-control" placeholder="Enter title" id="title">
</div>
<div class="form-group">
<label for="content">Content:</label>
<textarea class="form-control summernote" rows="5" id="content"></textarea>
</div>
</form>
<button id="btn-save" class="btn btn-primary">글쓰기완료</button>
</div>
<script>
$('.summernote').summernote({
tabsize: 2,
height: 300
});
</script>
<script src="/js/board.js"></script>
<%@include file="../layout/footer.jsp"%>
BoardController.java
package com.cos.blog.controller;
import com.cos.blog.config.auth.PrincipalDetail;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class BoardController {
// @Autowired
// private PrincipalDetail principal;
// @AuthenticationPrincipal PrincipalDetail principal
@GetMapping({"","/"}) // 컨트롤로에서 세션을 어떻게 찾는지?
public String index() {
// WEB-INF/views/index.jsp
// System.out.println("로그인 사용자 아이디: "+principal.getUsername());
return "index";
}
// USER 권한이 필요
@GetMapping("/board/saveForm")
public String saveForm() {
return "board/saveForm";
}
}
BoardApiController.java
package com.cos.blog.controller.api;
import com.cos.blog.config.auth.PrincipalDetail;
import com.cos.blog.dto.ResponseDto;
import com.cos.blog.model.Board;
import com.cos.blog.model.User;
import com.cos.blog.service.BoardService;
import com.cos.blog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BoardApiController {
@Autowired
private BoardService boardService;
@PostMapping("/api/board")
public ResponseDto<Integer> save(@RequestBody Board board, @AuthenticationPrincipal PrincipalDetail principal){
boardService.글쓰기(board,principal.getUser());
return new ResponseDto<Integer>(HttpStatus.OK.value(),1); // 자바오브젝트를 JSON으로 변환해서 리턴 (Jackson)
}
}
BoardService.java
package com.cos.blog.service;
import com.cos.blog.model.Board;
import com.cos.blog.model.RoleType;
import com.cos.blog.model.User;
import com.cos.blog.repository.BoardRepository;
import com.cos.blog.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
// 스프링이 커포넌트 스캔을 통해서 Bean에 등록을 해줌. IoC를 해준다.
@Service
public class BoardService {
@Autowired
private BoardRepository boardRepository;
@Transactional
public void 글쓰기(Board board,User user) { // title, content
board.setContent("0");
board.setUser(user);
boardRepository.save(board);
}
}
BoardRepository.java
package com.cos.blog.repository;
import com.cos.blog.model.Board;
import com.cos.blog.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
// DAO
// 자동으로 bean등록이 된다.
// @Repository 생략 가능하다.
public interface BoardRepository extends JpaRepository<Board,Integer> {
}
54강 글목록보기
BoardController.java
package com.cos.blog.controller;
import com.cos.blog.config.auth.PrincipalDetail;
import com.cos.blog.service.BoardService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class BoardController {
@Autowired
private BoardService boardService;
// 컨트롤로에서 세션을 어떻게 찾는지?
// @AuthenticationPrincipal PrincipalDetail principal
@GetMapping({"","/"})
public String index(Model model) {
model.addAttribute("boards",boardService.글목록());
return "index"; // viewResolver 작동!
}
// USER 권한이 필요
@GetMapping("/board/saveForm")
public String saveForm() {
return "board/saveForm";
}
}
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ include file="layout/header.jsp"%>
<div class="container">
<c:forEach var="board" items="${boards}">
<div class="card m-2">
<div class="card-body">
<h4 class="card-title">${board.title}</h4>
<a href="#" class="btn btn-primary">상세보기</a>
</div>
</div>
</c:forEach>i
</div>
<%@include file="layout/footer.jsp"%>
BoardService.java
package com.cos.blog.service;
import com.cos.blog.model.Board;
import com.cos.blog.model.RoleType;
import com.cos.blog.model.User;
import com.cos.blog.repository.BoardRepository;
import com.cos.blog.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
// 스프링이 커포넌트 스캔을 통해서 Bean에 등록을 해줌. IoC를 해준다.
@Service
public class BoardService {
@Autowired
private BoardRepository boardRepository;
@Transactional
public void 글쓰기(Board board,User user) { // title, content
board.setContent("0");
board.setUser(user);
boardRepository.save(board);
}
public List<Board> 글목록(){
return boardRepository.findAll();
}
}
55강 글목록 페이징하기
BoardController.java
package com.cos.blog.controller;
import com.cos.blog.config.auth.PrincipalDetail;
import com.cos.blog.service.BoardService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class BoardController {
@Autowired
private BoardService boardService;
// 컨트롤로에서 세션을 어떻게 찾는지?
// @AuthenticationPrincipal PrincipalDetail principal
@GetMapping({"","/"})
public String index(Model model,@PageableDefault(size=3,sort="id",direction = Sort.Direction.DESC) Pageable pageable) {
model.addAttribute("boards",boardService.글목록(pageable));
return "index"; // viewResolver 작동!
}
// USER 권한이 필요
@GetMapping("/board/saveForm")
public String saveForm() {
return "board/saveForm";
}
}
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ include file="layout/header.jsp"%>
<div class="container">
<c:forEach var="board" items="${boards.content}">
<div class="card m-2">
<div class="card-body">
<h4 class="card-title">${board.title}</h4>
<a href="#" class="btn btn-primary">상세보기</a>
</div>
</div>
</c:forEach>i
<ul class="pagination justify-content-center">
<c:choose>
<c:when test="${boards.first}">
<li class="page-item disabled"><a class="page-link" href="?page=${boards.number-1}">Previous</a></li>
</c:when>
</c:choose>
<c:otherwise>
<li class="page-item"><a class="page-link" href="?page=${boards.number-1}">Previous</a></li>
</c:otherwise>
<c:choose>
<c:when test="${boards.last}">
<li class="page-item disabled"><a class="page-link" href="?page=${boards.number-1}">Previous</a></li>
</c:when>
</c:choose>
<c:otherwise>
<li class="page-item"><a class="page-link" href="?page=${boards.number-1}">Previous</a></li>
</c:otherwise>
</ul>
</div>
<%@include file="layout/footer.jsp"%>
56강 글 상세보기
BoardService.java
package com.cos.blog.service;
import com.cos.blog.model.Board;
import com.cos.blog.model.RoleType;
import com.cos.blog.model.User;
import com.cos.blog.repository.BoardRepository;
import com.cos.blog.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
// 스프링이 커포넌트 스캔을 통해서 Bean에 등록을 해줌. IoC를 해준다.
@Service
public class BoardService {
@Autowired
private BoardRepository boardRepository;
@Transactional
public void 글쓰기(Board board,User user) { // title, content
board.setContent("0");
board.setUser(user);
boardRepository.save(board);
}
public Page<Board> 글목록(Pageable pageable){
return boardRepository.findAll(pageable);
}
public Board 글상세보기(int id) {
return boardRepository.findById(id)
.orElseThrow(()->{
return new IllegalArgumentException("글 상세보기 실패 : 아이디를 찾을 수 없습니다.");
});
}
}
BoardController.java
package com.cos.blog.controller;
import com.cos.blog.config.auth.PrincipalDetail;
import com.cos.blog.service.BoardService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@Controller
public class BoardController {
@Autowired
private BoardService boardService;
// 컨트롤로에서 세션을 어떻게 찾는지?
// @AuthenticationPrincipal PrincipalDetail principal
@GetMapping({"","/"})
public String index(Model model,@PageableDefault(size=3,sort="id",direction = Sort.Direction.DESC) Pageable pageable) {
model.addAttribute("boards",boardService.글목록(pageable));
return "index"; // viewResolver 작동!
}
@GetMapping("/board/{id}")
public String findById(@PathVariable int id,Model model) {
model.addAttribute("board",boardService.글상세보기(id));
return "board/detail";
}
// USER 권한이 필요
@GetMapping("/board/saveForm")
public String saveForm() {
return "board/saveForm";
}
}
detail.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ include file="../layout/header.jsp"%>
<div class="container">
<button class="btn btn-secondary"onclick="history.back()">돌아가기</button>
<button id="btn-update" class="btn btn-waring">수정</button>
<button id="btn-delete" class="btn btn-danger">삭제</button>
<br />
<br />
<div>
<h3>${board.title}</h3>
</div>
<hr />
<div>
<div>
${board.content}
</div>
</div>
<hr />
</div>
<script src="/js/board.js"></script>
<%@include file="../layout/footer.jsp"%>
57강 글 삭제하기
detail.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ include file="../layout/header.jsp"%>
<div class="container">
<button class="btn btn-secondary"onclick="history.back()">돌아가기</button>
<button id="btn-update" class="btn btn-waring">수정</button>
<c:if test="${board.user.id == principal.user.id}">
<button id="btn-delete" class="btn btn-danger">삭제</button>
</c:if>
<br/><br/>
<div>
글 번호 : <span id="id"><i>${board.id} </i></span>
작성자 : <span><i>${board.user.username} </i></span>
</div>
<br/>
<div>
<h3>${board.title}</h3>
</div>
<hr />
<div>
<div>
${board.content}
</div>
</div>
<hr />
</div>
<script src="/js/board.js"></script>
<%@include file="../layout/footer.jsp"%>
BoardService.java
package com.cos.blog.service;
import com.cos.blog.model.Board;
import com.cos.blog.model.RoleType;
import com.cos.blog.model.User;
import com.cos.blog.repository.BoardRepository;
import com.cos.blog.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
// 스프링이 커포넌트 스캔을 통해서 Bean에 등록을 해줌. IoC를 해준다.
@Service
public class BoardService {
@Autowired
private BoardRepository boardRepository;
@Transactional
public void 글쓰기(Board board,User user) { // title, content
board.setContent("0");
board.setUser(user);
boardRepository.save(board);
}
@Transactional(readOnly = true)
public Page<Board> 글목록(Pageable pageable){
return boardRepository.findAll(pageable);
}
@Transactional(readOnly = true)
public Board 글상세보기(int id) {
return boardRepository.findById(id)
.orElseThrow(()->{
return new IllegalArgumentException("글 상세보기 실패 : 아이디를 찾을 수 없습니다.");
});
}
@Transactional
public void 글삭제하기(int id) {
boardRepository.deleteById(id);
}
}
BoardApiController.java
package com.cos.blog.controller.api;
import com.cos.blog.config.auth.PrincipalDetail;
import com.cos.blog.dto.ResponseDto;
import com.cos.blog.model.Board;
import com.cos.blog.model.User;
import com.cos.blog.service.BoardService;
import com.cos.blog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
@RestController
public class BoardApiController {
@Autowired
private BoardService boardService;
@PostMapping("/api/board")
public ResponseDto<Integer> save(@RequestBody Board board, @AuthenticationPrincipal PrincipalDetail principal){
boardService.글쓰기(board,principal.getUser());
return new ResponseDto<Integer>(HttpStatus.OK.value(),1); // 자바오브젝트를 JSON으로 변환해서 리턴 (Jackson)
}
@DeleteMapping("/api/board/{id}")
public ResponseDto<Integer> deleteById(@PathVariable int id) {
boardService.글삭제하기(id);
return new ResponseDto<Integer>(HttpStatus.OK.value(),1); // 자바오브젝트를 JSON으로 변환해서 리턴 (Jackson)
}
}
board.js
let index = {
init: function(){
$("#btn-save").on("click", ()=>{ // function(){}, ()=>{} this를 바인딩하기 위해서!!
this.save();
});
$("#btn-delete").on("click", ()=>{ // function(){}, ()=>{} this를 바인딩하기 위해서!!
this.deleteById();
});
},
save:function(){
//alert('user의 save함수 호출됨');
let data = {
title: $("#title").val(),
content: $("#content").val(),
};
$.ajax({
// 회원가입 수행 요청
type: "POST",
url: "/api/board",
data: JSON.stringify(data), // http body데이터
contentType: "application/json; charset=utf-8", // body데이터가 어떤 타입인지(MIME)
dataType: "json" // 요청을 서버로해서 응답이 왔을때 기본적으로 모든 것이 문자열 (생긴게 json이라면) => javascript오브젝트로 변경
}).done(function(resp){
alert("글쓰기가 완료되었습니다.");
location.href= "/";
}).fail(function(error) {
alert(JSON.stringify(error));
}); // ajax 통신을 이용해서 3개의 데이터를 json으로 변경하여 insert 요청!!
},
deleteById:function(){
var id = $("#id").text();
$.ajax({
// 회원가입 수행 요청
type: "DELETE",
url: "/api/board"+id,
dataType: "json" // 요청을 서버로해서 응답이 왔을때 기본적으로 모든 것이 문자열 (생긴게 json이라면) => javascript오브젝트로 변경
}).done(function(resp){
alert("삭제가 완료되었습니다.");
location.href= "/";
}).fail(function(error) {
alert(JSON.stringify(error));
}); // ajax 통신을 이용해서 3개의 데이터를 json으로 변경하여 insert 요청!!
},
}
index.init();
58강 글 수정하기
BoardController.java
package com.cos.blog.controller;
import com.cos.blog.config.auth.PrincipalDetail;
import com.cos.blog.service.BoardService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@Controller
public class BoardController {
@Autowired
private BoardService boardService;
// 컨트롤로에서 세션을 어떻게 찾는지?
// @AuthenticationPrincipal PrincipalDetail principal
@GetMapping({"","/"})
public String index(Model model,@PageableDefault(size=3,sort="id",direction = Sort.Direction.DESC) Pageable pageable) {
model.addAttribute("boards",boardService.글목록(pageable));
return "index"; // viewResolver 작동!
}
@GetMapping("/board/{id}")
public String findById(@PathVariable int id,Model model) {
model.addAttribute("board",boardService.글상세보기(id));
return "board/detail";
}
@GetMapping("/board/{id}/updateForm")
public String updateForm(@PathVariable int id, Model model) {
model.addAttribute("board",boardService.글상세보기(id));
return "board/updateForm";
}
// USER 권한이 필요
@GetMapping("/board/saveForm")
public String saveForm() {
return "board/saveForm";
}
}
board.js
let index = {
init: function(){
$("#btn-save").on("click", ()=>{ // function(){}, ()=>{} this를 바인딩하기 위해서!!
this.save();
});
$("#btn-delete").on("click", ()=>{ // function(){}, ()=>{} this를 바인딩하기 위해서!!
this.deleteById();
});
$("#btn-update").on("click", ()=>{ // function(){}, ()=>{} this를 바인딩하기 위해서!!
this.update();
});
},
save:function(){
//alert('user의 save함수 호출됨');
let data = {
title: $("#title").val(),
content: $("#content").val(),
};
$.ajax({
// 회원가입 수행 요청
type: "POST",
url: "/api/board",
data: JSON.stringify(data), // http body데이터
contentType: "application/json; charset=utf-8", // body데이터가 어떤 타입인지(MIME)
dataType: "json" // 요청을 서버로해서 응답이 왔을때 기본적으로 모든 것이 문자열 (생긴게 json이라면) => javascript오브젝트로 변경
}).done(function(resp){
alert("글쓰기가 완료되었습니다.");
location.href= "/";
}).fail(function(error) {
alert(JSON.stringify(error));
}); // ajax 통신을 이용해서 3개의 데이터를 json으로 변경하여 insert 요청!!
},
deleteById:function(){
let id = $("#id").text();
$.ajax({
// 회원가입 수행 요청
type: "DELETE",
url: "/api/board"+id,
dataType: "json" // 요청을 서버로해서 응답이 왔을때 기본적으로 모든 것이 문자열 (생긴게 json이라면) => javascript오브젝트로 변경
}).done(function(resp){
alert("삭제가 완료되었습니다.");
location.href= "/";
}).fail(function(error) {
alert(JSON.stringify(error));
}); // ajax 통신을 이용해서 3개의 데이터를 json으로 변경하여 insert 요청!!
},
update:function(){
let id = $("#id").val();
let data = {
title: $("#title").val(),
content: $("#content").val(),
};
$.ajax({
// 회원가입 수행 요청
type: "PUT",
url: "/api/board"+id,
data: JSON.stringify(data), // http body데이터
contentType: "application/json; charset=utf-8", // body데이터가 어떤 타입인지(MIME)
dataType: "json" // 요청을 서버로해서 응답이 왔을때 기본적으로 모든 것이 문자열 (생긴게 json이라면) => javascript오브젝트로 변경
}).done(function(resp){
alert("글수정이 완료되었습니다.");
location.href= "/";
}).fail(function(error) {
alert(JSON.stringify(error));
}); // ajax 통신을 이용해서 3개의 데이터를 json으로 변경하여 insert 요청!!
},
}
index.init();
BoardApiController.java
package com.cos.blog.controller.api;
import com.cos.blog.config.auth.PrincipalDetail;
import com.cos.blog.dto.ResponseDto;
import com.cos.blog.model.Board;
import com.cos.blog.model.User;
import com.cos.blog.service.BoardService;
import com.cos.blog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
@RestController
public class BoardApiController {
@Autowired
private BoardService boardService;
@PostMapping("/api/board")
public ResponseDto<Integer> save(@RequestBody Board board, @AuthenticationPrincipal PrincipalDetail principal){
boardService.글쓰기(board,principal.getUser());
return new ResponseDto<Integer>(HttpStatus.OK.value(),1); // 자바오브젝트를 JSON으로 변환해서 리턴 (Jackson)
}
@DeleteMapping("/api/board/{id}")
public ResponseDto<Integer> deleteById(@PathVariable int id) {
boardService.글삭제하기(id);
return new ResponseDto<Integer>(HttpStatus.OK.value(),1); // 자바오브젝트를 JSON으로 변환해서 리턴 (Jackson)
}
@PutMapping("/api/board/{id}")
public ResponseDto<Integer> update(@PathVariable int id, @RequestBody Board board) {
boardService.글수정하기(id, board);
return new ResponseDto<Integer>(HttpStatus.OK.value(),1);
}
}
BoardService.java
package com.cos.blog.service;
import com.cos.blog.model.Board;
import com.cos.blog.model.RoleType;
import com.cos.blog.model.User;
import com.cos.blog.repository.BoardRepository;
import com.cos.blog.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
// 스프링이 커포넌트 스캔을 통해서 Bean에 등록을 해줌. IoC를 해준다.
@Service
public class BoardService {
@Autowired
private BoardRepository boardRepository;
@Transactional
public void 글쓰기(Board board, User user) { // title, content
board.setContent("0");
board.setUser(user);
boardRepository.save(board);
}
@Transactional(readOnly = true)
public Page<Board> 글목록(Pageable pageable) {
return boardRepository.findAll(pageable);
}
@Transactional(readOnly = true)
public Board 글상세보기(int id) {
return boardRepository.findById(id)
.orElseThrow(() -> {
return new IllegalArgumentException("글 상세보기 실패 : 아이디를 찾을 수 없습니다.");
});
}
@Transactional
public void 글삭제하기(int id) {
System.out.println("글삭제하기:" + id);
boardRepository.deleteById(id);
}
@Transactional
public void 글수정하기(int id, Board requsetBoard) {
Board board = boardRepository.findById(id)
.orElseThrow(() -> {
return new IllegalArgumentException("글 찾기 실패 : 아이디를 찾을 수 없습니다.");
}); //영속화 완료
board.setTitle(requsetBoard.getTitle());
board.setContent(requsetBoard.getContent());
// 해당 함수로 종료시(Service가 종료될 때) 트랜잭션이 종료됩니다. 이때 더티체킹 - 자동 업데이트가 됨. db flush
}
}
59강 스프링작동원리 복습
60강 회원수정 1
updateForm.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ include file="../layout/header.jsp" %>
<div class="container">
<form>
<input type="hidden" id="id" value="${principal.user.id}"/>
<div class="form-group">
<label for="username">Username</label>
<input type="text" value ="${principal.username}" class="form-control" placeholder="Enter username" id="username" readonly>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" placeholder="Enter password" id="password">
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" value="${principal.user.email}" class="form-control" placeholder="Enter email" id="email">
</div>
</form>
<button id="btn-update" class="btn btn-primary">회원수정완료</button>
</div>
<script src="/js/user.js"></script>
<%@include file="../layout/footer.jsp" %>
user.js
let index = {
init: function(){
$("#btn-save").on("click", ()=>{ // function(){}, ()=>{} this를 바인딩하기 위해서!!
this.save();
});
$("#btn-update").on("click", ()=>{ // function(){}, ()=>{} this를 바인딩하기 위해서!!
this.update();
});
},
save:function(){
//alert('user의 save함수 호출됨');
let data = {
username: $("#username").val(),
password: $("#password").val(),
email: $("#email").val(),
};
$.ajax({
// 회원가입 수행 요청
type: "POST",
url: "/auth/joinProc",
data: JSON.stringify(data), // http body데이터
contentType: "application/json; charset=utf-8", // body데이터가 어떤 타입인지(MIME)
dataType: "json" // 요청을 서버로해서 응답이 왔을때 기본적으로 모든 것이 문자열 (생긴게 json이라면) => javascript오브젝트로 변경
}).done(function(resp){
alert("회원가입이 완료되었습니다.");
console.log(resp);
location.href= "/";
}).fail(function(error) {
alert(JSON.stringify(error));
}); // ajax 통신을 이용해서 3개의 데이터를 json으로 변경하여 insert 요청!!
},
update:function(){
//alert('user의 save함수 호출됨');
let data = {
id: $("#id").val(),
password: $("#password").val(),
email: $("#email").val(),
};
$.ajax({
// 회원가입 수행 요청
type: "PUT",
url: "/user",
data: JSON.stringify(data), // http body데이터
contentType: "application/json; charset=utf-8", // body데이터가 어떤 타입인지(MIME)
dataType: "json" // 요청을 서버로해서 응답이 왔을때 기본적으로 모든 것이 문자열 (생긴게 json이라면) => javascript오브젝트로 변경
}).done(function(resp){
alert("회원수정이 완료되었습니다.");
console.log(resp);
location.href= "/";
}).fail(function(error) {
alert(JSON.stringify(error));
}); // ajax 통신을 이용해서 3개의 데이터를 json으로 변경하여 insert 요청!!
},
// login:function(){
// //alert('user의 save함수 호출됨');
// let data = {
// username: $("#username").val(),
// password: $("#password").val(),
// };
//
// //console.log(data);
//
// // ajax호출시 default가 비동기 호출
// // ajax 통신을 이용해서 3개의 데이터를 json으로 변경하여 insert 요청!!
// // ajax가 통신을 성공하고 json을 리턴해주면 자동으로 자바 오브젝트로 변환해주네요.
// $.ajax({
// // 회원가입 수행 요청
// type: "POST",
// url: "/api/user/login",
// data: JSON.stringify(data), // http body데이터
// contentType: "application/json; charset=utf-8", // body데이터가 어떤 타입인지(MIME)
// dataType: "json" // 요청을 서버로해서 응답이 왔을때 기본적으로 모든 것이 문자열 (생긴게 json이라면) => javascript오브젝트로 변경
// }).done(function(resp){
// alert("로그인이 완료되었습니다.");
// location.href= "/";
// }).fail(function(error) {
// alert(JSON.stringify(error));
// }); // ajax 통신을 이용해서 3개의 데이터를 json으로 변경하여 insert 요청!!
// }
}
index.init();
package com.cos.blog.controller.api;
import com.cos.blog.dto.ResponseDto;
import com.cos.blog.model.RoleType;
import com.cos.blog.model.User;
import com.cos.blog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
@RestController
public class UserApiController {
@Autowired
private UserService userService;
@PostMapping("/auth/joinProc")
public ResponseDto<Integer> save(@RequestBody User user){ // username, password, email
System.out.println("UserApiController : save 호출됨");
// 실제로 DB에 insert를 하고 아래에서 return이 되면 되요.
userService.회원가입(user);
return new ResponseDto<Integer>(HttpStatus.OK.value(),1); // 자바오브젝트를 JSON으로 변환해서 리턴 (Jackson)
}
// 전통적인 방법
// @PostMapping("/api/user/login")
// public ResponseDto<Integer> login(@RequestBody User user, HttpSession session) {
// System.out.println("UserApiController : login 호출됨");
// User principal = userService.로그인(user); // principal (접근주체)
//
// if(principal != null) {
// session.setAttribute("principal",principal);
// }
// return new ResponseDto<Integer>(HttpStatus.OK.value(),1); // 자바오브젝트를 JSON으로 변환해서 리턴 (Jackson)
// }
@PutMapping("/user")
public ResponseDto<Integer> update(@RequestBody User user){ // Json 데이터 받고 싶으면 @RequestBody적음
userService.회원수정(user);
return new ResponseDto<Integer>(HttpStatus.OK.value(),1);
}
}
package com.cos.blog.service;
import com.cos.blog.model.RoleType;
import com.cos.blog.model.User;
import com.cos.blog.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
// 스프링이 커포넌트 스캔을 통해서 Bean에 등록을 해줌. IoC를 해준다.
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private BCryptPasswordEncoder encoder;
@Transactional
public void 회원가입(User user) {
String rawPassword = user.getPassword(); // 1234 원문
String encPassword = encoder.encode(rawPassword); // 해쉬
user.setPassword(encPassword);
user.setRole(RoleType.USER);
userRepository.save(user);
}
// @Transactional(readOnly = true) // Select할때 트랜잭션 시작, 서비스 종료시에 트랜잭션 종료 (정합성)
// public User 로그인(User user) {
// return userRepository.findByUsernameAndPassword(user.getUsername(),user.getPassword());
// }
@Transactional
public void 회원수정(User user) {
// 수정시에는 영속성 컨텍스트 User 오브젝트를 영속화시키고, 영속화된 User 오브젝트를 수정
// select를 해서 User오브젝트를 DB로부터 가져오는 이유는 영속화를 하기 위해서!
// 영속화된 오브젝트를 변경하면 자동으로 DB에 update문을 날려주거든요.
User persistance = userRepository.findById(user.getId()).orElseThrow(()->{
return new IllegalArgumentException("회원 찾기 실패");
});
String rawPassword = user.getPassword();
String encPassword = encoder.encode(rawPassword);
persistance.setPassword(encPassword);
persistance.setEmail(user.getEmail());
// 회원수정 함수 종료시 = 서비스 종료 = 트랜잭션 종료 = commit이 자동으로 됩니다.
// 영속화된 persistance 객체의 변화가 감지되면 더티체킹이 되어 update문을 날려줌.
}
}
회원수정을 통해서 DB값만 변경했어요. 다음시간에 session 값을 변경하도록 할께요.
'스프링부트 with JPA' 카테고리의 다른 글
스프링부트 with JPA 블로그 4(61~73강) (0) | 2024.02.22 |
---|---|
스프링부트 with JPA 블로그 2(21~40강) (0) | 2024.02.22 |
스프링부트 with JPA 블로그 1(1~20강) (0) | 2024.02.22 |