@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Item {
private long no;
private String name;
private String content; //clob
private long price;
private long quantity;
private Date regdate ;
private long imageNo; // 대표이미지 번호를 저장할 임시변수
}
dto에 Item.java를 생성한다.
@Mapper
public interface ItemMapper {
// 물품전체조회
public List<Item> selectItemList();
}
mapper폴더에 ItemMapper를 생성하고
resources - mappers 폴더에 itemMapper.xml에 itemMapper.xml을 생성하여 작성한다.
빨간색과 파란색은 서로 명이 동일해야 한다.
// 컨트롤러에서 실행하는 클래스
@Service
public interface ItemService {
// 물품 전체 조회
public List<Item> selectItemList();
}
example 아래에 service 폴더에 ItemService.java를 생성하고
같은 위치에 ItemServiceImpl.java를 생성한다.
@Controller
@RequestMapping(value ="/item")
public class ItemController {
@Autowired ItemService iService; // 서비스 객체 생성
// 127.0.0.1/9090/ROOT/item/selectlist.do
@GetMapping(value = "/selectlist.do")
public String selectListGET( Model model ){
// 1. 서비스를 호출하여 물품목록받기
List<Item> list = iService.selectItemList();
// 2. model을 이용하여 view로 받은 목록 전달하기
model.addAttribute("list", list);
// 3. view를 화면에 표시하기
return "/item/selectlist"; // resources/templates/item 폴더 생성 selectlist.html을 생성
}
}
controller폴더에 ItemController.java를 생성한다
<title>물품등록</title>
</head>
<body>
<table>
<thead>
<tr>
<th>물품번호</th>
<th>물품명</th>
<th>물품내용</th>
<th>물품가격</th>
<th>물품수량</th>
<th>등록일</th>
</tr>
</thead>
<tbody>
<tr th:each="obj : ${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>
<td><a th:href="@{/item/insertimage.do(no=${obj.no})}">이미지등록</a></td>
</tr>
</tbody>
</table>
</body>
</html>
templates - item 폴더에 selectlist.html을 생성하여 작성한다.
입력한 주소로 이동하면 db에 저장되어 있는 itemlist의 정보가 나열된다.
이미지 등록을 눌렀을 때
public class ItemController {
@Autowired ItemService iService; // 서비스 객체 생성
// /item/insertimage.do?no=11 => name값은 no이고 value값은 11 숫자가 전달
// <input type="text" name="no" value="11" />
@GetMapping(value ="/insertimage.do")
public String insertimageGET(
@RequestParam(name="no", defaultValue ="0", required = false) long no,
Model model ) {
if( no == 0 ){
return "redirect:selectlist.do"; // 상대경로로 이동, 가장 마지막 주소만 변경해서 이동
}
model.addAttribute("itemno", no);
return "/item/insertimage"; // resources/templates/item폴더/insertimage.html
}
기존에 작성하였던 ItemController.java에 insertimageGET을 추가한다.
@Getter
@Setter
@ToString(exclude = {"filedata"})
@NoArgsConstructor
@AllArgsConstructor
public class ItemImage {
private long no;
private String filename;
private long filesize;
private byte[] filedata; //blob
private String filetype;
private long itemno;
private Date regdate;
}
dto에 ItemImage.java를 추가한다.
<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="@{/item/insertimage.do}" method="post" enctype="multipart/form-data">
외래키(물품번호) : <input type="text" name="itemno" th:value="${itemno}"/><br />
첨부할 이미지 : <input type="file" name="file1" accept="image/*"/><br />
<input type="submit" value="업로드" />
</form>
</body>
</html>
insertimage.html를 작성하면
작성한 대로 화면이 나오게 된다.
파일선택을 누르면 이렇게 사진을 선택할 수 있다.
default.png를 선택한 모습.
Controller에 post를 만들지 않았기 때문에 405 에러가 뜬다.
public class ItemController {
@Autowired ItemService iService; // 서비스 객체 생성
// 파일은 dto에 자동으로 추가되지 않음. 수동으로 추가.
@PostMapping(value = "/insertimage.do")
public String insertImagePOST( @ModelAttribute ItemImage obj,
@RequestParam(name = "file1") MultipartFile file1) throws IOException {
obj.setFilename( file1.getOriginalFilename() );
obj.setFilesize( file1.getSize() );
obj.setFiletype( file1.getContentType() );
obj.setFiledata( file1.getBytes() ); // exception 발생됨.
System.out.println(obj.toString());
return "redirect:insertimage.do?no=" + obj.getItemno();
}
ItemController에 insertImagePOST를 추가한다.
...
</form>
<a th:href="@{/item/selectlist.do}">물품 목록으로</a>
insertimage.html에 물품 목록으로 가는 a태그를 추가한다.
정상적으로 추가된다.
public String insertImagePOST( @ModelAttribute ItemImage obj,
@RequestParam(name = "file1") MultipartFile file1) throws IOException {
obj.setFilename( file1.getOriginalFilename() );
obj.setFilesize( file1.getSize() );
obj.setFiletype( file1.getContentType() );
obj.setFiledata( file1.getBytes() ); // exception 발생됨.
System.out.println(obj.toString()); // 확인용
int ret = iService.insertItemImageOne(obj);
if( ret == 1 ){
return "redirect:insertimage.do?no=" + obj.getItemno();
}
return "redirect:insertimage.do?no=" + obj.getItemno();
}
ItemController에 위 항목을 추가한다.
// 물품 이미지 등록
public int insertItemImageOne(ItemImage obj);
insertItemImageOne을 추가하고
public int insertItemImageOne(ItemImage obj);
ItemService에도 똑같이 추가한다.
@Override
public int insertItemImageOne(ItemImage obj) {
try{
return iMapper.insertItemImageOne(obj);
}
catch(Exception e) {
e.printStackTrace();
return -1;
}
}
ItemServiceImpl도 마찬가지로 추가하고
...
<insert id="insertItemImageOne" parameterType="com.example.dto.ItemImage">
INSERT INTO itemimage(filename, filesize, filetype, filedata, itemno)
VALUES(#{filename}, #{filesize}, #{filetype}, #{filedata}, #{itemno})
</insert>
</mapper>
itemMapper.xml에 SQL문을 추가한다.
파일을 선택하고 업로드를 누르면
이미지 등록하는 주소로 다시 이동하고
정상적으로 등록이 된다.
// 이미지 번호가 전송되면 1개의 이미지 정보 반환
public ItemImage selectItemImageOne(long no);
// 물품 번호를 전송하면 해당하는 이미지 번호 n개를 반환
public List<Long> selectItemImageNo(long itemno);
ItemMapper.java에 위 코드를 추가한다.
<select id="selectItemImageOne" parameterType="long" resultType="com.example.dto.ItemImage">
SELECT * FROM itemimage WHERE no = #{no}
</select>
<select id="selectItemImageNo" parameterType="long" >
SELECT no FROM itemimage WHERE itemno = #{itemno}
</select>
itemMapper.xml에도 위 코드를 추가한다.
@Autowired ItemMapper iMapper; // 서비스를 사용하지 않고 매퍼 호출(일반적이지 않음.)
@Autowired ResourceLoader resourceLoader; // resources폴더의 파일을 읽기 위한 객체 생성
// <img src="@{/item/image(no=1)}">
// String = html파일을 표시
// ResponseEntity<byte[]> => 이미지, 동영상 등을 표시
// 127.0.0.1:9090/ROOT/item/image?no=1
@GetMapping(value = "/image")
public ResponseEntity<byte[]> image(@RequestParam(name = "no", defaultValue = "0") long no) throws IOException{
ItemImage obj = iMapper.selectItemImageOne(no);
HttpHeaders headers = new HttpHeaders(); // import org.springframework.http.HttpHeaders;
if( obj != null ){ // 이미지가 존재할 경우
if(obj.getFilesize() > 0){
headers.setContentType( MediaType.parseMediaType( obj.getFiletype() ) );
return new ResponseEntity<>( obj.getFiledata(), headers, HttpStatus.OK);
}
}
// 이미지가 없을 경우
InputStream is = resourceLoader.getResource("classpath:/static/images/default.png").getInputStream(); // exception 발생됨.
headers.setContentType(MediaType.IMAGE_PNG);
return new ResponseEntity<>( is.readAllBytes(), headers, HttpStatus.OK);
}
ItemController에 위 코드를 추가하고
resources-images 폴더에 default.png를 넣어주고 실행한다.
이미지가 존재할 경우 해당 이미지가 나온다.
이미지가 존재하지 않을 경우 지정한 default.png가 나온다.
resources 폴더에 global.properties 파일을 생성하고
# 개발자가 필요해서 만드는 환경설정 파일
default.image=claspath:/static/images/default.png
board.pagetotal=10
위처럼 작성한다.
// classpath => resources와 동일함.
@PropertySource(value = {"classpath.global.properties"}) // 직접만든 환경설정파일 위치
Boot20230427Application.java에 추가한다.
@Value("${default.image}") String defaultImage; // import org.springframework.beans.factory.annotation.Value;
...
// 이미지가 없을 경우
InputStream is = resourceLoader.getResource(defaultImage).getInputStream(); // exception 발생됨.
headers.setContentType(MediaType.IMAGE_PNG);
return new ResponseEntity<>( is.readAllBytes(), headers, HttpStatus.OK);
}
ItemController를 위처럼 추가, 수정한다.
# dto위치설정
mybatis.type-aliases-package=com.example.dto
application.properties에 위 코드도 추가한다.
com.example.dto.~~ 대신 ~~만 입력할 수 있다.
@GetMapping(value = "/insertimage.do")
public String insertimageGET(
@RequestParam(name="no", defaultValue ="0", required = false) long no,
Model model ) {
if( no == 0 ){
return "redirect:selectlist.do"; // 상대경로로 이동, 가장 마지막 주소만 변경해서 이동
}
// 현재 물품에 해당하는 이미지 번호
List<Long> imgNo = iMapper.selectItemImageNo(no);
System.out.println("insertimage.do => " + imgNo.toString());
model.addAttribute("imgno", imgNo);
model.addAttribute("itemno", no);
return "/item/insertimage"; // resources/templates/item폴더/insertimage.html
}
ItemController의 insertimageGET을 위처럼 수정한다.
<body>
<form th:action="@{/item/insertimage.do}" method="post" enctype="multipart/form-data">
외래키(물품번호) : <input type="text" name="itemno" th:value="${itemno}" readonly /><br />
첨부할 이미지 : <input type="file" name="file1" accept="image/*"/><br />
<input type="submit" value="업로드" />
</form>
<a th:href="@{/item/selectlist.do}">물품 목록으로</a>
<hr />
<div th:each="tmp : ${imgno}" style="display:inline-block">
<img th:src="@{/item/image( no=${tmp} )}" style="width:50px; height:50px" />
</div>
</body>
insertimage.html을 위처럼 수정하여 사진이 나올 곳을 생성한다.
물품번호에 해당하는 이미지가 모두 나오는 것을 확인할 수 있다.
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Member {
private String id;
private String password;
private String newpw;
private String name;
private int age;
private Date regdate ;
private String role; // 고객 customer, 판매자 seller
}
dto 폴더에 Member.java를 생성한다.
@Controller
@RequestMapping(value = "/member")
@Slf4j // 디버깅
public class MemberController {
@Autowired MemberMapper mMapper; // 매퍼객체 생성하기
@GetMapping(value ="/join.do")
public String joinGET(@ModelAttribute Member obj){
log.info("member=> {}", "joinGET");
// 디버깅
return "/member/join";
}
@PostMapping(value="/join.do")
public String joinPOST( @ModelAttribute Member obj ){
log.info("join.do POST => {}", obj.toString());
// 여기서 매퍼 호출 후 회원가입하기
int ret = mMapper.insertMemberOne(obj);
if( ret == 1 ){
// 127.0.0.1:9090/ROOT/member/home.do
return "redirect:/home.do"; // 성공시 홈으로
}
// return "redirect:/member/join.do"; // 실패시 회원가입으로
return "redirect:join.do"; // 실패시 회원가입으로
}
MemberController를 생성하여 위 코드를 작성하고
<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="@{/member/join.do}" method ="post">
아이디 : <input type="text" name="id" autofocus/><br />
암호 : <input type="text" name="password" value="a"/><br />
이름 : <input type="text" name="name" value="b"/><br />
나이 : <input type="number" name="age" value="1"/><br />
권한 : <input type="text" name="role" value="CUSTOMER" /><br />
<input type="submit" value="회원가입" />
</form>
</body>
</html>
join.html을 생성한다
@Mapper
public interface MemberMapper {
public int insertMemberOne(Member member);
}
MemberMapper에 추가하고
<mapper namespace="com.example.mapper.MemberMapper">
<insert id="insertMemberOne" parameterType="com.example.dto.Member">
INSERT INTO member(id, password, name, age, role)
VALUES( #{id}, #{password}, #{name}, #{age}, #{role})
</insert>
</mapper>
memberMapper도 일치하게 작성한다.
<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="@{/member/login.do}" method ="post">
아이디 : <input type="text" name="id" autofocus/><br />
암호 : <input type="text" name="password" value="a"/><br />
<input type="submit" value="로그인" />
</form>
</body>
</html>
login.html을 생성한다.
@GetMapping(value="/login.do")
public String loginGET(){
return "/member/login";
}
@PostMapping(value="/login.do")
public String loginPOST(@ModelAttribute Member member){
log.info("login.do => {}", member.toString());
int ret = mMapper.selectMemberOne(member);
if( ret == 1 ){
return "redirect:/home.do"; // 로그인 성공 시
}
return "redirect:/login.do"; // 로그인 실패 시
}
loginGET, loginPOST를 작성한다.
public Member selectMemberOne(Member member);
MemberMapper에 selectMemberOne을 추가한다.
<select id="selectMemberOne" parameterType="com.example.dto.Member" resultType="com.example.dto.Member">
SELECT m.id, m.name, m.age, m.role FROM member m WHERE m.id = #{id} AND m.password = #{password}
</select>
memberMapper.xml에도 일치하게 작성한다.
@RequiredArgsConstructor
...
final MemberMapper mMapper; // 매퍼객체 생성하기
final HttpSession httpSession;
...
@GetMapping(value="/login.do")
public String loginGET(){
return "/member/login";
}
@PostMapping(value="/login.do")
public String loginPOST(@ModelAttribute Member member){
log.info("login.do => {}", member.toString()); // view에서 잘 전송되었는지
Member ret = mMapper.selectMemberOne(member); // 로그인한 사용자의 정보 반환
if( ret != null ){
log.info("login1.do => {}", ret.toString());
// 세션에 2개의 정보 아이디와 이름 추가하기 (기본시간 30분)
// 다른 페이지에서 세션의 아이디가 존재하는지 확인 후 로그인 여부 판단
httpSession.setAttribute("USERID", ret.getId());
httpSession.setAttribute("USERNAME", ret.getName());
return "redirect:/home.do"; // 로그인 성공 시
}
return "redirect:/login.do"; // 로그인 실패 시
}
이렇게 수정한다.
회원가입창
회원가입 후 home.do로 이동
로그인창
로그인 성공시 home.do로 이동
<!-- DB로 세션관리 -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
</dependency>
pom.xml에 dependency를 하나 추가한다.
# db로 세션관리
server.servlet.session.timeout=3600
spring.session.store-type=jdbc
spring.session.jdbc.initialize-schema=always
application.properties에 위 코드를 추가한다.
이렇게 하면 로그인 정보를 세션에 저장할 수 있다.
크롬과 엣지로 각각 로그인을 한 상태이다. DB를 확인해보면 2가지의 정보가 들어가있는 것을 볼 수 있다.
// GET, POST가 같은 동작을 함
@RequestMapping(value="/logout.do", method = {RequestMethod.GET, RequestMethod.POST})
public String logoutPOST(){
httpSession.invalidate(); // 세션의 정보를 다 지움
return "redirect:/home.do";
}
세션의 정보를 다 지우는 로그아웃 기능을 추가하였다.
<body>
<h3>홈 화면</h3>
<a th:href="@{/board/insert.do}">게시판 글쓰기</a>
<div th:if="${session.USERID == null}">
<a th:href="@{/member/login.do}">로그인</a>
<a th:href="@{/member/join.do}">회원가입</a>
</div>
<div th:if="${session.USERID != null}">
<p th:text="|${session.USERNAME} 님 로그인|"></p>
<a th:href="@{/member/logout.do}">로그아웃</a>
</div>
</body>
home.html을 수정한다.
세션에 로그인 정보가 없으면 로그인과 회원가입, 정보가 있으면 로그인 된 정보의 이름을 불러오고 로그아웃으로 이어지는 a태그를 추가한다.
로그인을 하지 않았을 때
로그인했을 때.
httpSession.setAttribute("USERID", ret.getId());
httpSession.setAttribute("USERNAME", ret.getName());
httpSession.setAttribute("ROLE", ret.getRole());
ROLE도 추가해주고
<div th:if="${session.USERID != null}">
<p th:text="|${session.USERNAME} 님 로그인|"></p>
<a th:href="@{/member/logout.do}">로그아웃</a>
<div th:if="${#strings.equals(session.ROLE, 'SELLER')}">
<a th:href="@{/seller/home.do}">판매자 홈</a>
</div>
</div>
ROLE의 String이 SELLER와 같을 경우 판매자 홈으로 이동할 수 있도록 수정하였다.
판매자로 로그인한 상태이다.
판매자 홈을 누른 상태.
seller/home.do를 만들지 않았으니 404 오류가 뜨는 것이 당연하다.