20220919 [Spring Boot, H2DB, MyBatis]

Yeoonnii·2022년 9월 20일
0

TIL

목록 보기
29/52
post-thumbnail

Mybatis extension

extension > MYbatisX 설치

extension 설치 후 Mapper.java 파일에서
Mapper.xml을 확인할 수 있게하는 Preview창을 확인할 수 있다


Mapper.xml 추가설정

1. Mapper.xml의 경로설정

📁 application.properties

Mapper.xml의 위치를 설정해주는 MybatisConfig.java을 생성했었는데

# xml mapper의 위치설정
mybatis.mapper-locations=classpath:/mappers/*Mapper.xml

위 코드로 MybatisConfig.java를 대체할 수 있다
➡️ MybatisConfig.java 파일을 삭제해도 실행됨

2. Mapper.xml의 parameterType 경로설정

📁 application.properties

# DTO의 위치설정, 여러개는 , 로 구분해서 설정
mybatis.type-aliases-package=com.example.dto

memberMapper.xml에서 parameterType 입력시
"com.example.dto.MemberDTO”로 매번 경로를 지정해줘야 했었지만
위 코드로 DTO의 위치설정을 해주면 경로 설정 없이 DTO의 이름만 작성해도 된다
➡️ parameterType="MemberDTO”


Oarcle 세션

오라클로 세션 관리하기

📁 application.properties

오라클 세션 코드 추가

# oracle session
# 3600 = 1시간
server.servlet.session.timeout=3600
spring.session.store-type=jdbc
spring.session.jdbc.initialize-schema=always

오라클에 세션 테이블이 자동으로 생성된다

📁 HomeController.java

홈 화면 접속시 Model을 추가해서 리턴

@GetMapping(value = {"/", "/home", "/home.do"})
public String homeGET(@AuthenticationPrincipal User user, Model model){ 
    model.addAttribute("user", user);

    return "home";
}
  • @AuthenticationPrincipal
    ➡️ Security에서 로그인정보에 관한 세션을 가져옴
  • User user
    ➡️ UserDetails에서 반환된 리턴값 User
  • Model model
    ➡️ model에 user를 담아서 home으로 같이 리턴해준다

📁 home.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>
    <h3>홈화면</h3>

    <!-- 로그인 안된경우 -->
    <div th:if="${user == null}">
        <a th:href="@{/member/login.do}">로그인</a>
        <a th:href="@{/member/join.do}">회원가입</a>

    </div>

    <!-- 로그인 된경우 -->
    <div th:if="${user != null}">
        <p th:text="${user}"></p>
        <p th:text="${user.Username}"></p>
        <p th:text="${user.Authorities[0]}"></p>
        
        <form th:action="@{/member/logoutaction.do}" method="post">
            <input type="submit" value="로그아웃">
        </form>
        <hr />
    </div>

    <a th:href="@{/admin/home.do}">관리자 홈</a>
    <a th:href="@{/seller/home.do}">판매자 홈</a>
    <a th:href="@{/customer/home.do}">고객 홈</a>
</body>

</html>

H2DB (H2 Database)

기본 테이블, 뷰, 시퀀스 기능을 사용가능하다 (함수는 사용불가)
대규모 프로젝트 또는 서버 배포시 안정성과 성능이 부족하여 오류 발생가능성 있음

Embedded Mode vs Server Mode

  • Embedded Mode = 내장모드
    ➡️ spring.datasource.url=jdbc:h2:file:D:/java/h2db;MODE=Oracle
    Application 서버 실행 종료시 데이터 모두 손실(휘발)
    영속적이지 않음
    파일로 만들어지는 db
    다른 컴퓨터에서 작업하려면 파일을 가지고 있어야 한다
  • Server Mode = 서버모드
    ➡️ spring.datasource.url=jdbc:h2:tcp://1.234.5.158:21521/ds207;MODE=Oracle
    별도의 JVM을 이용하여 구동 (localhost:8080)
    영속적으로 사용할 수 있음

H2DB 사용 설정

📁 pom.xml (라이브러리 설치)

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope> <!-- *** 배포시에는 삭제 -->
</dependency>

📁 application.properties

Server Mode 사용
MODE를 Oracle로 설정하여 SQL문 사용한다 MODE=Oracle

spring.datasource.driver-class-name=org.h2.Driver
# Embedded Mode = 내장모드(파일이용시)
# spring.datasource.url=jdbc:h2:file:D:/java/h2db;MODE=Oracle
# Server Mode = 서버모드(서버이용시)
spring.datasource.url=jdbc:h2:tcp://1.234.5.158:21521/ds207;MODE=Oracle
spring.datasource.username=sa
spring.datasource.password=
# 서버 배포시 오류 방지용
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
# 옵션설정
spring.h2.console.enabled=true

📁 SecurityConfig.java

H2DB h2-console 사용을 위한 csrf 사용해제

SecurityConfig.java에서 선택적으로 csrf를 사용하지 않기로 설정
➡️ csrf를 모두 사용하지 않는경우는 보안에 취약하기 때문에 권장하지 않는다
/h2-console/로 시작하는 것만 csrf를 해제하도록 설정

csrf = Security로 POST전송시 필요함
csrf 없으면 Security로 전송불가

	// H2-console 사용하기 위해 모두 csrf 사용하지 않는경우
	// 보안 취약 (비권장) 
	// http.csrf().disable();

	// 일부의 주소(/h2-console/)만 csrf를 해제
	http.csrf().ignoringAntMatchers("/h2-console/**"); // /h2-console/로 시작하는 것만 csrf를 해제한다
	http.headers().frameOptions().sameOrigin();

H2DB 테이블 생성

Oracle에 있는 DB와 동일하게 H2DB에 테이블 생성

  • MEMBERTBL 테이블 생성
CREATE TABLE MEMBERTBL(
    USERID VARCHAR2(30),
    USERPW VARCHAR2(200),
    AGE NUMBER,
    PHONE VARCHAR2(15),
    GENDER VARCHAR2(1) CONSTRAINT MEMBER_GENDER_CK CHECK(GENDER IN('M', 'F')),
    REGDATE TIMESTAMP,
    ROLE VARCHAR2(20),
    CONSTRAINT PK_MEMBER_ID PRIMARY KEY(USERID)
);
  • ITEMTBL 테이블 생성
CREATE TABLE ITEMTBL (
  NO NUMBER NOT NULL, 
  NAME VARCHAR2(100 BYTE) NOT NULL, 
  CONTENT CLOB, 
  PRICE NUMBER, 
  QUANTITY NUMBER, 
  REGDATE TIMESTAMP, 
  CONSTRAINT PK_ITEM_NO PRIMARY KEY(NO)
);
  • ITEMTBL에 SELLER(판매자이름) 추가
    MEMBERTBL(USERID)SELLER의 외래키로 참조
ALTER TABLE ITEMTBL ADD SELLER VARCHAR2(30);

ALTER TABLE ITEMTBL ADD CONSTRAINT FK_ITEMTBL_SELLER FOREIGN KEY(SELLER) REFERENCES MEMBERTBL(USERID);
  • 물품번호 SEQUENCE 추가
CREATE SEQUENCE SEQ_ITEM_NO START WITH 1001 INCREMENT BY 1 NOMAXVALUE NOCACHE;
  1. 물품 1개씩 추가하기 + 물품 일괄추가(시퀀스 사용)
  2. 판매자 홈화면에서 목록 가져오기 + 페이지네이션을위한 물품개수 구하기

판매자(Seller) 페이지 물품추가

물품 1개씩 추가하기 + 물품 일괄추가(시퀀스 사용)

판매자 정보 가져오기

HomeController.java에서 @AuthenticationPrincipal 이용하여 USER의 정보를 가져왔었다

SellerController.java에서도 USER의 권한 확인을 위해 @AuthenticationPrincipal를 추가하여 판매자 정보 확보 후 물품 조회시 판매자의 아이디를 넣어 해당 판매자의 물품만 조회한다
➡️ item.setSeller(user.getUsername()

📁 SellerController.java

목록 가져오기 ⇒ 판매자 홈 화면에서 바로나오도록 한다
판매자의 홈화면으로 이동시 userid를 이용하여 로그인 된 판매자의 물품만 가져와야 하고,
가져올 목록의 시작과 끝 개수도 지정해준다 start, end ( ex. 1~10 )

물품일괄 추가시 물품 한개만 추가였다면 DTO로 받았을텐데 여러개 물품을 한꺼번에 추가해야 하니 name이 중복되기 때문에 @RequestParam으로 받는다
Mybatis에서 반복문 사용시 List<DTO>타입으로 전송하여 한번에 정보를 전송한다

홈화면에서 페이지네이션 버튼 생성값 cnt를 미리 계산하여 model에 담아 보낸다
➡️ model.addAttribute("cnt", ((cnt-1)/8)+1)

@Controller
@RequestMapping(value="/seller")
public class SellerController {

    @Autowired
    ItemMapper iMapper;

    // 판매자 홈화면으로 이동
    @GetMapping(value = "/home.do")
    public String homeGET( 
        @AuthenticationPrincipal User user, 
        @RequestParam(name = "page", defaultValue = "1") int page,
        Model model ){
        if( user == null ){ 
            return "redirect:/member/login.do";
        }
        long cnt = iMapper.countList(user.getUsername());

        Map<String, Object> map = new HashMap<>();
        map.put("userid", user.getUsername());
        map.put("start", (page*8)-7 ); // 원하는 페이지 * 가져올 게시물갯수 - (가져올 페이지갯수 -1) = 시작될 페이지
        map.put("end", page*8); // 원하는 페이지 * 가져올 게시물갯수 = 끝날 페이지

        List<ItemDTO> list = iMapper.selectList(map);
        model.addAttribute("list", list);
        model.addAttribute("cnt", ((cnt-1)/8)+1);
        return "seller/home";
    }

    // 물품 일괄추가 페이지로 이동
    @GetMapping(value = "/insert_item_batch.do")
    public String insertBatchGET() {
        return "seller/insert_item_batch";
    }

    // 물품 일괄추가하기
    public String insertBatchPOST(
        @AuthenticationPrincipal User user, 
        @RequestParam(name = "name") String[] name,
        @RequestParam(name = "content") String[] content,
        @RequestParam(name = "price") Long[] price,
        @RequestParam(name = "quantity") Long[] quantity) {

            // 1. ItemDTO생성하기 
            // 2. 위의 param으로 받은 배열을 List<DTO>타입으로 전송
            // 반복문에서 내부에서 1개씩 추가하는 방식 보다 => list로 한번에 데이터 주는게 좋다
            List<ItemDTO> list = new ArrayList<>();
            for(int i=0; i<name.length; i++){
                ItemDTO item = new ItemDTO();
                item.setName(name[i]);
                item.setContent(content[i]);
                item.setPrice(price[i]);
                item.setQuantity(quantity[i]);
                item.setSeller(user.getUsername());

                list.add(item);
                
                // 반복문 종료 후 list를 전달해서 일괄 추가 하는 방법이 가장 좋다
            }
            // 일괄 추가하기
            iMapper.insertBatchList(list);
        return "redirect:/seller/home.do";
    }
}

📁 Sellerhome.html

권한설정을 해뒀기 때문에 주소는 /seller로 시작해야한다

<!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>
    <h3>판매자 홈화면</h3>

    <a th:href="@{/seller/insert_item_batch.do}"><button>일괄추가</button></a>
    <hr />
    <br />
    <table border="1">
        <tr>
            <th>물품번호</th>
            <th>물품명</th>
            <th>물품내용</th>
            <th>가격</th>
            <th>수량</th>
            <th>등록일</th>
        </tr>
        <tr th:each="obj, idx : ${list}">
            <td th:text="${obj.no}"></td>
            <td th:text="${obj.name}"></td>
            <td th:text="${obj.content}"></td>
            <td th:text="${obj.price}"></td>
            <td th:text="${obj.quantity}"></td>
            <td th:text="${obj.regdate}"></td>
        </tr>

        <th:block th:each="i : ${#numbers.sequence(1,cnt)}">
            <a th:href="@{/seller/home.do(page=${i})}" th:text="${i}"></a>
        </th:block>
    </table>
</body>

</html>

📁 insert_item_batch.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>
    <a th:href="@{/seller/home.do}">홈으로</a>
    <hr />
    물품일괄추가
    <hr />
    
    <form th:action="@{/seller/insert_item_batch.do}" method="post">
        <th:block th:each="i : ${#numbers.sequence(1,3)}">
            <input type="text" name="name" placeholder="물품명" th:value="|물품명${i}|" />
            <input type="text" name="content" placeholder="물품내용" th:value="|물품내용${i}|" />
            <input type="number" name="price" placeholder="가격" th:value=" ${i} + 1000 " />
            <input type="number" name="quantity" placeholder="수량" th:value=" ${i} + 500 " />
            <br />
        </th:block>
        <hr />
        <input type="submit" value="일괄추가" />
    </form>
</body>

</html>

📁 ItemMapper.java 생성

서비스 생략하고 바로 Mapper생성!
전체 물품의 개수 조회시 로그인 한 판매자의 아이디를 넣어주어야
해당 사용자의 게시글의 개수만 가져올 수 있다

페이지네이션을 위한 전체물품의 갯수 구할때
로그인 한 판매자의 아이디로 해당 판매자 작성 물품글만 계산한다

@Mapper
public interface ItemMapper {
    // 아이템 등록하기
    public int insertBatch(ItemDTO item);

    // 목록 가져오기
    public List<ItemDTO> selectList(Map<String, Object> map);
    // Mybatis의 특징 : list타입을 가져와도 itemdto를 반환한다

    // 전체 물품의 개수
    public long countList(String userid);

    // 아이템 일괄등록
    public int insertBatchList(List<ItemDTO> list);
}

📁 itemMapper.xml

  • 물품 일괄추가시 반복문 foreach 사용하여 입력받은 값을 넣어준다
  • 물품 일괄추가시 시퀀스를 사용해야 하는데,
    INSERT ALL은 시퀀스 직접사용 불가하고 함수를 만들어 사용했었다
    ➡️ 하지만 H2DB에는 함수를 만들 수 없기 때문에
    INSERT를 이용하여 시퀀스를 입력SELECT SEQ_ITEM_NO.NEXTVAL 하고,
    시퀀스를 제외한 나머지 항목들은 반복자에 UNION ALL을 입력하여
    시퀀스 제외한 나머지 항목들만 반복문 안에 입력을 받는다
  • 목록 가져오기 실행시 로그인 판매자의 상품만 조회해야하니 판매자의 String userid가 필요하며, 페이지네이션을 사용을 위해 int start, int end 값이 필요하다
    이처럼 파라미터 타입을 여러개 받고 싶은경우 Map 또는 DTO를 사용하여 값을 보내 사용한다
    ➡️ Mapper.java 에서는 다른 타입의 여러개의 파라미터를 받을 수 있지만, xml에서는 하나의 파라미터만 넣을 수 있다
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.mapper.ItemMapper">


    <!-- 페이지네이션을 위한 물품개수 구하기 -->
    <select id="countList" parameterType="String" resultType="long">
        SELECT COUNT(*) CNT FROM ITEMTBL I
            WHERE I.SELLER=#{userid}
    </select>


    <!-- 목록 가져오기 -->
    <select id="selectList" parameterType="map" resultType="ItemDTO">
        SELECT * FROM(
        SELECT I.*, ROW_NUMBER() OVER (ORDER BY NO DESC) ROWN
            FROM ITEMTBL I 
            WHERE I.SELLER=#{userid}
            ) WHERE ROWN BETWEEN #{start} AND #{end}
            
            ORDER BY NO DESC;
    </select>

    
    <!-- 물품 1개씩 추가하기 -->
    <insert id="insertBatch" parameterType="ItemDTO">
     INSERT INTO ITEMTBL(NO, NAME, CONTENT, PRICE, QUANTITY, REGDATE, SELLER)
          VALUES (SEQ_ITEM_NO.NEXTVAL, #{name}, 
               #{content}, #{price}, #{quantity}, 
               CURRENT_DATE, #{seller})
    </insert>

    <!-- 시퀀스가 있는경우 일괄추가 -->
    <insert id="insertBatchList" parameterType="list">
    INSERT INTO ITEMTBL(NO, NAME, CONTENT, PRICE, QUANTITY, REGDATE, SELLER)
    SELECT SEQ_ITEM_NO.NEXTVAL, T1.* FROM(
        <foreach collection='list' item='item' separator='UNION ALL' >
        SELECT '${item.name}' NAME, 
        '${item.content}' CONTENT,
        '${item.price}' PRICE,
        '${item.quantity}' QUANTITY,
        CURRENT_DATE,
        '${item.seller}' SELLER FROM DUAL
        </foreach>
    )T1
    </insert>
</mapper>

0개의 댓글