👨🏫
백엔드 + DBMS
= 스프링부트 + Oracle
오라클 = 관계형 데이터베이스 SQL
➡️ 누적되는 데이터 증가시 비용적인 문제가 발생할 수 있다
문제해결을 위해 NOSQL
= MongoDB
사용
중요한 데이터를 이체, 거래, 구매시에는 안전해야하며 무결성(=정확성)이어야 한다!
중요한 정보를 조회하거나 이용하는 상황에서는
⇒ 관계형 데이터베이스 SQL(= Oracle) 사용해야한다
간단한 데이터 = 단순 정보 수집, 페이지 이동, 클릭 등의 정보수집시의 상황에서는
⇒ NOSQL(=MongoDB)를 사용한다! NOSQL 은 간단한 데이터를 중점으로 분석한다
Oracle, MongoDB 파일기반의 DB(데이터베이스) 이며, 파일기반의 DB는 전원이 꺼져도 데이터가 남아있다
이와 반대로 메모리 기반의 DB(데이터베이스)는 전원이 꺼진경우 실행중인 데이터가 휘발된다
메모리란? 실행중인 것이라고 생각하면 된다(ex. 음악듣는것과 같다)
세션 = 들어오는 사용자의 정보를 임시저장
세션은 안정성보다 속도측면이 중요함
(ex. 로그인, 로그아웃, 장바구니에 상품 담음.. 등)
실행중의 상태를 저장하는 경우 세션을 이용하는데
세션으로 임시저장 = 안정되지 않아도 된다 = 안정성 낮음
Redis = 세션용DB
Redis 는 언제 사용하는가?
메모리 기반DB는 각 서버당 하나의 하위 기록이 필요하다
많은 서버에서 공통적인 기록이 필요한 경우 세션용DB인 Redis를 사용한다
이때까지 사용했던 DB접속 프로그램
Oracle ⇒ sqldeveloper : 접속프로그램일뿐, 데이터베이스가 아니다
MongoDB ⇒ NoSQLBooster for MongoDB : 접속프로그램일뿐, 데이터베이스가 아니다
redis ⇒ redis gui(공식, 유료) / another redis(무료)
redis gui(공식, 유료) / another redis(무료) : 접속프로그램일뿐, 실제 데이터베이스가 아니다
데이터 공유가 아닌 세션공유 목적
Another-Redis-Desktop-Manager.1.5.8.exe 다운받기
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
# redis 세션 설정
spring.redis.host=1.234.5.158
spring.redis.port=16379
spring.redis.password=ds606
spring.redis.database=7
# 세션타입 redis로 변경
spring.session.store-type=redis
spring.session.redis.flush-mode=on-save
@EnableRedisHttpSession
추가
로그인시 redis에 세션이 기록되는것을 확인 할 수 있다
오버라이드 메서드 사용하는 경우 반환되는 타입과 반환값의 형태를 수정할 수 없다
다른 반환값을 반환하고싶거나, 기존 반환값에 새로운 정보를 추가로 반환하고 싶은경우
➡️ 상속을 통해 새로운 생성자 생성 후 변수를 추가하여 생성된,
새로운 생성자를 반환값으로 만들어 리턴하면 된다!
상속을 통해 반환값을 원하는 형태로 추가, 변형하여 사용할 수 있다
⇒ 실무에서 상속을 사용하는 대부분의 목적이다
로그인 시 반환되는 user타입에 추가로 필요한 고객의 정보를 같이 반환해보기
model에 user의 정보를 넘겨주어 로그인시 반환되는 user의 정보를 확인해 본다
// 고객메인페이지로 이동
@GetMapping(value = "/home.do")
public String homeGET(@AuthenticationPrincipal User user, Model model) {
model.addAttribute("user", user);
return "customer/home";
}
가져온 로그인 정보로 사용자 아이디 출력
<p th:text="${user}"></p>
<p th:text="|${user.Username}님 로그인|"></p>
결과 ➡️ 반환된 컬렉션 타입의 정보를 확인할 수 있다
반환되는 타입은 User이며, 반환값으로 getUserid(), getUserpw(), role을 갖는다
return new User(member.getUserid(), member.getUserpw(), role);
다른 정보를 반환값에 추가하기위해 userdetails.User
를 상속받아야 한다
오버라이드 메서드 사용하는 경우 반환되는 타입과 반환값의 형태를 수정할 수 없다!
loadUserByUsername
는 UserDetails의 User 형태로 반환되는데
User 안의 타입값을 추가하여 반환하기 위해
➡️ 상속을 이용하여 새로운 생성자 생성 후 변수를 추가하고 생성된 새로운 생성자를 리턴
User를 상속을 했기 때문에 CustomUserDTO 도 User타입으로 반환되어야 한다
= 기본적으로 제공되는 User의 형태 + 같이 추가하여 반환할 DB정보
@Getter
@Setter
@ToString
public class CustomUserDTO extends User {
// 기본적으로 제공되는 user의 형태
String username;
String password;
Collection<GrantedAuthority> authorities;
// 같이 추가할 DB정보
String phone;
int age;
// 아래 user형태 = 기본생성자는 사용하지 않으니 없어도 상관없음
// 기본 생성자(아이디, 암호, 권한)
// public CustomUserDTO(String username, String password, Collection<? extends GrantedAuthority> authorities) {
// super(username, password, authorities);
// }
// 기본 생성자에 새로운 변수를 추가한 생성자 구현
// 연락처, 나이를 추가
public CustomUserDTO(String username, String password, Collection<GrantedAuthority> authorities, String phone, int age) {
super(username, password, authorities);
this.username = username;
this.password = password;
this.authorities = authorities;
this.phone = phone;
this.age = age;
}
}
customer / home.html에서 user출력해보기
* 로그아웃 한 후 다시 들어가야 세션이 새롭게 생성되어 적용된다
return new User(member.getUserid(), member.getUserpw(), role);
CustomDetailsService 생성 후 새로운 타입의 USER반환
⇒return new CustomUserDTO(member.getUserid(), member.getUserpw(), role, member.getPhone(), member.getAge());
결과 ➡️ CustomUserDTO에서 생성한 새로운 리턴 타입이 생성된것을 확인할 수 있다
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>회원탈퇴</title>
</head>
<body>
<form th:action="@{/customer/delete.do}" method="post">
회원탈퇴를 위해 비밀번호를 입력해주세요<br />
<input type="password" name="userpw" /><br />
<input type="submit" value="회원탈퇴" /><br />
</form>
</body>
</html>
- 암호화된 hash 암호는 hash값이 계속 변경되기 때문에,
암호화된원본DB의암호
를 꺼내어 비교해야한다- 회원탈퇴 성공시 로그아웃 처리 해야한다! =
return "logout";
➡️ 회원정보 수정 후 알림창으로 이동하여 스크립트 처리한 로직과 비슷!
로그아웃 처리는 html생성 후 스크립트로 실행
// 회원탈퇴하기
@PostMapping(value = "/delete.do")
public String deletePost(
@AuthenticationPrincipal CustomUserDTO user,
@ModelAttribute MemberDTO member
){
// 세션에 있는 아이디를 member객체에 포함
member.setUserid( user.getUsername() );
System.out.println(user.getUsername());
System.out.println(member.toString());
// 1. 로그인한 회원의 아이디를 이용하여 정보를 가져와서 비교
MemberDTO member1 = mmapper.selectMemberOne(user.getUsername());
// 2. 암호가 일치하면 회원탈퇴 수행
// update로 userid를 제외하고, 나머지 정보를 null 또는 0으로 초기화
BCryptPasswordEncoder bcpe = new BCryptPasswordEncoder();
// matches(원본암호, 변경된암호)
if(bcpe.matches(member.getUserpw(), member1.getUserpw())){
// 3. 회원탈퇴가 되면 logout수행
int ret = mmapper.deleteMemberOne(member);
System.out.println(ret);
if( ret == 1 ){
//탈퇴 성공시
// 회원탈퇴 되면 로그아웃 해야함
return "logout";
} //탈퇴 실패시
}
return "redirect:/customer/delete.do";
}
// 회원정보삭제
public int deleteMemberOne(MemberDTO member);
USERID 는 MEMBERTBL 의 기본키로 다른테이블에 외래키로 사용중이기 때문에
값을 비우거나 삭제가 불가능하다 = USERID 기본키 제약조건NOT NULL
➡️ USER의 정보를 데이터 테이블에서 완전히 삭제하는게 아니라 사용자에게 삭제한것처럼만 보이면 된다
<!-- 회원 탈퇴하기 -->
<update id="deleteMemberOne" parameterType="com.example.dto.MemberDTO">
UPDATE MEMBERTBL SET USERPW=null, AGE=0, PHONE=null,
REGDATE=null, ROLE=null, GENDER=null
WHERE USERID=#{userid}
</update>
로그아웃 페이지 소스와 똑같이 스트립트 생성해줘야 한다
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<body>
<script>
// <form>생성
const form = document.createElement("form");
form.action = "[[@{/member/logoutaction.do}]]";
form.method="post";
const input = document.createElement("input");
input.type = "hidden";
input.name = "_csrf";
input.value = "[[${_csrf.token}]]";
// form 태그 안에 input을 넣으라는 뜻
form.appendChild(input);
// body 태그 안에 form을 넣으라는 뜻
document.body.appendChild(form);
// 바로 submit으로 이동
form.submit();
alert('처리가 완료되었습니다')
// => CustomLogoutSuccessHandler.java 에서 로그아웃 처리 후 홈으로 이동해준다
</script>
</body>
</head>
</html>
회원관리 / 게시물관리 ➡️ 버튼에 링크 걸어 이동
[ 회원관리 페이지 ]
[ 게시물관리 페이지 ]
관리자 홈화면에 전체 회원목록 출력하기
전체 회원목록을 회원아이디기준 오름차순으로 가져오기
<!-- 전체 회원 조회하기 -->
<select id="selectMemberList" resultType="com.example.dto.MemberDTO">
SELECT M.* FROM MEMBERTBL M ORDER BY USERID ASC
</select>
조회한 여러개의
MemberDTO
를List
형태로 반환
public List<MemberDTO> selectMemberList();
MemberMapper에서 가져온 member List을 model에 담아준 후 html에서 반복문으로 출력
// 관리자 홈페이지로 이동시
// 회원관리 버튼 클릭시 =>http://127.0.0.1:8080/BOOT1/admin/home.do?menu=1
@GetMapping(value = "/home.do")
public String homeGET(
Model model,
@RequestParam(name="menu", defaultValue = "0") int menu){
if(menu ==0){ //메뉴번호가 없을경우 1로 이동
return "redirect:/admin/home.do?menu=1";
}
if(menu ==1){
List<MemberDTO> list = mmapper.selectMemberList();
model.addAttribute("list", list);
}
else if(menu ==2){
}
model.addAttribute("menu", menu);
return "admin/home";
}
AdminController 에서 model에 담아준 데이터 list를 반복문으로 출력
<tr th:each="obj, idx : ${list}" >
<td><input type="checkbox" name="chk" th:value="${obj.userid}" /></td>
<td th:text="${obj.userid}"></td>
<td th:text="${obj.age}"></td>
<td th:text="${obj.phone}"></td>
<td th:text="${obj.gender}"></td>
<td th:text="${obj.regdate}"></td>
<td th:text="${obj.block}"></td>
</tr>
MEMBERTBL 에 회원 차단 여부 확인할 컬럼
BLOCK
추가
➡️BLOCK
값이 1인경우 = 차단안됨 / 0인경우 = 차단됨
ALTER TABLE MEMBERTBL ADD BLOCK NUMBER(1) DEFAULT 1;
기존데이터의 BLOCK 값 0으로 변경
UPDATE MEMBERTBL SET
BLOCK = (CASE
WHEN USERID='사용자1아이디' THEN 0
WHEN USERID='사용자2아이디' THEN 0
END)
WHERE USERID IN('사용자1아이디','사용자2아이디');
차단된 회원은 로그인 불가 하도록 설정
CustomDetailsService ➡️ 로그인 되면 오는곳
if문 사용하여 로그인 조건을 설정해준다
➡️ MEMBERTBL DB의 member != nullAND
getBlock == 1 (차단되지 않은 경우)
// + getBlock이 1인 경우 = 차단되지 않은경우
if(member != null && member.getBlock() == 1){
MEMBERTBL에 새로 생성한 컬럼
BLOCK
추가
➡️ DTO는 DB 테이블의 항목이 변경되면 수정해줘야한다
int block = 1;
- 관리자 홈화면에 회원 체크 버튼추가
➡️폼태그 사용시 input type="button"사용해야 값이 전달 안됨- 회원 체크후 차단버튼 클릭시 해당 회원 차단
➡️ Controller 이동시 선택된 회원의userid
값을 같이 넘겨준다
버튼을 누르면 스크립트로 이동 되고 스크립트에서 type에 따라 주소를 변경해준다
<input type="button" value="회원차단" th:onclick="|javascript:handleBlock(1)|" />
<input type="button" value="회원차단해제" th:onclick="|javascript:handleBlock(2)|"/>
...
<script>
const handleBlock = (type) => {
// form 찾기
const form = document.getElementById('form');
if(type ==1){ //type 1인경우
form.action="[[@{/admin/memberblock.do(type=1)}]]";
}
else if(type ==2){
form.action="[[@{/admin/memberblock.do(type=2)}]]";
}
form.submit();
}
</script>
mapper.xml에서 반복문을 실행해야 한다
예를들어 300개의 DB변경시 모두 변경되거나 모두 변경실패해야 하는게 좋은데
컨트롤러에서 반복문 실행하면 DB변경이 일부는 실행되고 일부는 변경실행이 되지 않을 수 있기 때문
mapper.xml에서 반복문 실행 위해
- home.html에서 type에 따라 변경될
chk
값
type = 1인경우 chk값 = 0으로 변경 = 차단하기
type = 2인경우 chk값 = 1로 변경 = 차단해제- userid의 정보를 담은 list = chk값에 관계없이 보내준다
Map<String, Object>
➡️ Object로 설정했기 때문에 타입이나 형태에 상관없이 전달값을 담을 수 있다
// 회원관리
// 회원 차단 => chk=1 / 회원차단해제 =>chk=2
@PostMapping(value = "/memberblock.do")
public String memberBlockPOST(
@RequestParam(name = "type") int type,
@RequestParam(name = "chk") List<String> list){
// map에 xml에 필요한 체크된 userid chk값 을 map으로 보내준다
Map<String, Object> map = new HashMap<>();
if(type == 1){
// BLOCK값을 0으로 변경
map.put("chk", 0);
}
else if(type == 2){
// BLOCK값을 1로 변경
map.put("chk", 1);
}
// list는 chk값에 관계없이 보내줘야 하기 때문에 if문 종료 후 담아준다
map.put("list", list);
// chk는 숫자로만 구성 0, 1
// list문자로만 된 목록 [a,b,c]
// Map<String, Object> => Object로 설정했기 때문에 타입이나 형태가 상관없이 담을 수 있다
int ret = mmapper.updateMemberBlock(map);
return "redirect:/home.do?menu=1";
}
// 회원정보 일괄수정
public int updateMemberBlock(Map<String, Object> map);
- AdminController에서 받아온 목록[a,b,c]를 xml에 넣을때는 반복문
foreach
사용- 반복문 list를 담을때
item=”tmp”
로 명시해주었으니 반복문 내에서#{tmp}
사용하면,
받아온 list가 해당위치에 반복하여 입력된다
- MyBatis에서 파라미터는 하나만 올수 있다
➡️ Controller에서 map에 여러 정보를 담아줬기 때문에
파라미터는map
하나만 받지만map
안에 여러 정보를 받아 사용할 수 있다
map.put("list", list);
= 체크한 아이디 목록과,
map.put("chk", 0 또는 1);
= 타입에 따라 받아온 chk값
UPDATE MEMBERTBL SET
BLOCK = (CASE
WHEN USERID='변경아이디' THEN 0
WHEN USERID='변경아이디' THEN 0
END)
WHERE USERID IN('변경아이디','변경아이디');
<update id="updateMemberBlock" parameterType="map">
UPDATE MEMBERTBL SET
BLOCK = (CASE
<foreach collection="list" item="tmp" separator=" ">
WHEN USERID=#{tmp} THEN #{chk}
</foreach>
END)
WHERE USERID IN(
<foreach collection="list" item="tmp" separator=", ">
#{tmp}
</foreach>
)
</update>