20220923 [Spring Boot, H2DB, MyBatis]

Yeoonnii·2022년 9월 24일
0

TIL

목록 보기
33/52
post-thumbnail

👨‍🏫
백엔드 + DBMS = 스프링부트 + Oracle

오라클 = 관계형 데이터베이스 SQL
➡️ 누적되는 데이터 증가시 비용적인 문제가 발생할 수 있다

문제해결을 위해 NOSQL = MongoDB 사용

중요한 데이터를 이체, 거래, 구매시에는 안전해야하며 무결성(=정확성)이어야 한다!

  • 중요한 정보를 조회하거나 이용하는 상황에서는
    ⇒ 관계형 데이터베이스 SQL(= Oracle) 사용해야한다

  • 간단한 데이터 = 단순 정보 수집, 페이지 이동, 클릭 등의 정보수집시의 상황에서는
    ⇒ NOSQL(=MongoDB)를 사용한다! NOSQL 은 간단한 데이터를 중점으로 분석한다

Oracle, MongoDB 파일기반의 DB(데이터베이스) 이며, 파일기반의 DB는 전원이 꺼져도 데이터가 남아있다

이와 반대로 메모리 기반의 DB(데이터베이스)는 전원이 꺼진경우 실행중인 데이터가 휘발된다
메모리란? 실행중인 것이라고 생각하면 된다(ex. 음악듣는것과 같다)

  • 중요한 정보는 파일기반 DB 이용 = 안정성 장점
  • 간단한 정보는 메모리기반 DB이용 = 속도측면의 장점

세션 = 들어오는 사용자의 정보를 임시저장
세션은 안정성보다 속도측면이 중요함

(ex. 로그인, 로그아웃, 장바구니에 상품 담음.. 등)
실행중의 상태를 저장하는 경우 세션을 이용하는데
세션으로 임시저장 = 안정되지 않아도 된다 = 안정성 낮음


Redis

Redis = 세션용DB

Redis 는 언제 사용하는가?
메모리 기반DB는 각 서버당 하나의 하위 기록이 필요하다

많은 서버에서 공통적인 기록이 필요한 경우 세션용DB인 Redis를 사용한다

이때까지 사용했던 DB접속 프로그램

  • Oracle ⇒ sqldeveloper : 접속프로그램일뿐, 데이터베이스가 아니다

  • MongoDB ⇒ NoSQLBooster for MongoDB : 접속프로그램일뿐, 데이터베이스가 아니다

  • redis ⇒ redis gui(공식, 유료) / another redis(무료)

  • redis gui(공식, 유료) / another redis(무료) : 접속프로그램일뿐, 실제 데이터베이스가 아니다

Redis(another redis) 사용하기

데이터 공유가 아닌 세션공유 목적

another redis 다운

Another-Redis-Desktop-Manager.1.5.8.exe 다운받기

redis 사용을 위한 라이브러리 설치

📁 pom.xml

<!-- 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>

환경설정

📁 application.properties

# 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

📁 Application.java

@EnableRedisHttpSession 추가


로그인시 redis에 세션이 기록되는것을 확인 할 수 있다


상속 이용하기

오버라이드 메서드 사용하는 경우 반환되는 타입과 반환값의 형태를 수정할 수 없다
다른 반환값을 반환하고싶거나, 기존 반환값에 새로운 정보를 추가로 반환하고 싶은경우
➡️ 상속을 통해 새로운 생성자 생성 후 변수를 추가하여 생성된,
새로운 생성자를 반환값으로 만들어 리턴하면 된다!

상속을 통해 반환값을 원하는 형태로 추가, 변형하여 사용할 수 있다
⇒ 실무에서 상속을 사용하는 대부분의 목적이다

로그인 시 반환되는 user타입에 추가로 필요한 고객의 정보를 같이 반환해보기

CustomerController.java

model에 user의 정보를 넘겨주어 로그인시 반환되는 user의 정보를 확인해 본다

 // 고객메인페이지로 이동
    @GetMapping(value = "/home.do")
    public String homeGET(@AuthenticationPrincipal User user, Model model) {
        model.addAttribute("user", user);
        return "customer/home";
    }

customer / home.html

가져온 로그인 정보로 사용자 아이디 출력

<p th:text="${user}"></p>
    <p th:text="|${user.Username}님 로그인|"></p>

결과 ➡️ 반환된 컬렉션 타입의 정보를 확인할 수 있다

CustomDetailsService.java

반환되는 타입은 User이며, 반환값으로 getUserid(), getUserpw(), role을 갖는다

return new User(member.getUserid(), member.getUserpw(), role);

다른 정보를 반환값에 추가하기위해 userdetails.User를 상속받아야 한다

CustomUserDTO.java

오버라이드 메서드 사용하는 경우 반환되는 타입과 반환값의 형태를 수정할 수 없다!
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;
    }
}

CustomDetailsService.java

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에서 생성한 새로운 리턴 타입이 생성된것을 확인할 수 있다


회원탈퇴

customer / delete.html 생성

<!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>

CustomerController.java

  • 암호화된 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";
    }    

MemberMapper.java

// 회원정보삭제
    public int deleteMemberOne(MemberDTO member);

memberMapper.xml

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>

logout.html

로그아웃 페이지 소스와 똑같이 스트립트 생성해줘야 한다

<!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>

관리자 회원관리 페이지

회원관리 / 게시물관리 ➡️ 버튼에 링크 걸어 이동

[ 회원관리 페이지 ]

  • 회원목록조회 테이블 생성
  • 선택 회원 일괄 차단/차단해제 기능

[ 게시물관리 페이지 ]

  • 선택 게시글 일괄삭제 기능
  • 선택 게시글 일괄수정 기능

회원관리 페이지 전체회원목록

관리자 홈화면에 전체 회원목록 출력하기

memberMapper.xml

전체 회원목록을 회원아이디기준 오름차순으로 가져오기

<!-- 전체 회원 조회하기 -->
    <select id="selectMemberList" resultType="com.example.dto.MemberDTO">
        SELECT M.* FROM MEMBERTBL M ORDER BY USERID ASC
    </select>

MemberMapper.java

조회한 여러개의 MemberDTOList형태로 반환

public List<MemberDTO> selectMemberList();

AdminController.java

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";
    }

admin/home.html

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>

관리자 회원차단/해제

H2DB

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.java

차단된 회원은 로그인 불가 하도록 설정

CustomDetailsService ➡️ 로그인 되면 오는곳
if문 사용하여 로그인 조건을 설정해준다
➡️ MEMBERTBL DB의 member != null AND getBlock == 1 (차단되지 않은 경우)

//  +  getBlock이 1인 경우 = 차단되지 않은경우
        if(member != null && member.getBlock() == 1){

MemberDTO.java

MEMBERTBL에 새로 생성한 컬럼 BLOCK 추가
➡️ DTO는 DB 테이블의 항목이 변경되면 수정해줘야한다

int block = 1;

admin/home.html

  • 관리자 홈화면에 회원 체크 버튼추가
    ➡️폼태그 사용시 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>

AdminController.java

mapper.xml에서 반복문을 실행해야 한다
예를들어 300개의 DB변경시 모두 변경되거나 모두 변경실패해야 하는게 좋은데
컨트롤러에서 반복문 실행하면 DB변경이 일부는 실행되고 일부는 변경실행이 되지 않을 수 있기 때문

mapper.xml에서 반복문 실행 위해

  1. home.html에서 type에 따라 변경될chk
    type = 1인경우 chk값 = 0으로 변경 = 차단하기
    type = 2인경우 chk값 = 1로 변경 = 차단해제
  2. 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";
        }

MemberMapper.java

    // 회원정보 일괄수정
    public int updateMemberBlock(Map<String, Object> map);

memberMapper.xml

  • 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값

SQL문

UPDATE MEMBERTBL SET
    BLOCK = (CASE
        WHEN USERID='변경아이디' THEN 0
        WHEN USERID='변경아이디' THEN 0
    END)
WHERE USERID IN('변경아이디','변경아이디');

memberMapper.xml

    <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>

0개의 댓글