extension > MYbatisX 설치
extension 설치 후 Mapper.java 파일에서
Mapper.xml을 확인할 수 있게하는 Preview창을 확인할 수 있다
Mapper.xml
의 위치를 설정해주는 MybatisConfig.java
을 생성했었는데
# xml mapper의 위치설정
mybatis.mapper-locations=classpath:/mappers/*Mapper.xml
위 코드로 MybatisConfig.java
를 대체할 수 있다
➡️ MybatisConfig.java
파일을 삭제해도 실행됨
# DTO의 위치설정, 여러개는 , 로 구분해서 설정
mybatis.type-aliases-package=com.example.dto
memberMapper.xml에서 parameterType
입력시
"com.example.dto.MemberDTO”
로 매번 경로를 지정해줘야 했었지만
위 코드로 DTO의 위치설정을 해주면 경로 설정 없이 DTO의 이름만 작성해도 된다
➡️ parameterType="MemberDTO”
오라클로 세션 관리하기
오라클 세션 코드 추가
# oracle session
# 3600 = 1시간
server.servlet.session.timeout=3600
spring.session.store-type=jdbc
spring.session.jdbc.initialize-schema=always
오라클에 세션 테이블이 자동으로 생성된다
홈 화면 접속시 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에서 반환된 리턴값 UserModel model
➡️ model에 user를 담아서 home으로 같이 리턴해준다
로그인 된 경우/로그인 되지 않은경우에 따라 나누어 화면 구현
<!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>
기본 테이블, 뷰, 시퀀스 기능을 사용가능하다 (함수는 사용불가)
대규모 프로젝트 또는 서버 배포시 안정성과 성능이 부족하여 오류 발생가능성 있음
Embedded Mode
vsServer 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)
영속적으로 사용할 수 있음
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope> <!-- *** 배포시에는 삭제 -->
</dependency>
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
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();
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개씩 추가하기 + 물품 일괄추가(시퀀스 사용)
판매자 정보 가져오기
HomeController.java
에서@AuthenticationPrincipal
이용하여 USER의 정보를 가져왔었다
SellerController.java
에서도 USER의 권한 확인을 위해@AuthenticationPrincipal
를 추가하여 판매자 정보 확보 후 물품 조회시 판매자의 아이디를 넣어 해당 판매자의 물품만 조회한다
➡️item.setSeller(user.getUsername()
목록 가져오기 ⇒ 판매자 홈 화면에서 바로나오도록 한다
판매자의 홈화면으로 이동시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";
}
}
권한설정을 해뒀기 때문에 주소는
/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>
물품 일괄추가 화면생성
여러개를 한번에 등록하니 반복문 이용하여 보내준다
<!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>
서비스 생략하고 바로 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);
}
- 물품 일괄추가시 반복문
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>