example 아래에 entity 폴더를 생성한다.
package com.example.entity;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.CreationTimestamp;
import org.springframework.format.annotation.DateTimeFormat;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "MEMBER1") // 생성하고자 하는 테이블, 또는 생성되어 있는 테이블 매칭
public class Member1 {
@Id //import javax.persistence.Id;
@Column(name="ID", length = 50)
private String id; // @Column을 생략하면 변수명이 컬럼명
private String pw;
private String name;
private int age;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
@CreationTimestamp
private Date regdate;
}
entity 폴더에 Member1.java를 생성한다.
@EntityScan(basePackages = {"com.example.entity"}) // 엔티티 위치
Boot20230427Application.java에 엔티티 위치를 추가한다.
# ddl
# create => 엔티티의 정보를 읽어서 테이블을 생성, 재구동시 다시 생성(DROP + CREATE)
# update => 엔티티의 정보를 읽어서 변경사항 발생 시 생성(CREATE)
# none => ddl 사용하지 않음
# validate => 엔티티와 테이블이 정상 매핑되었는지 확인
spring.jpa.hibernate.ddl-auto=update
application.properties에 위 코드를 추가한다.
DB를 보면 MEMBER1 테이블이 생성된 것을 볼 수 있다.
spring.jpa.hibernate.ddl-auto=validate
validate로 변경한다.
example 아래에 repository를 생성하고.
package com.example.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.example.entity.Member1;
// 엔티티, 엔티티의 기본키 타입
@Repository
public interface Member1Repository extends JpaRepository< Member1, String >{
}
Member1Repository를 생성한다.
@EnableJpaRepositories(basePackages = {"com.example.repository"}) // 저장소 위치
Boot20230427Application.java에 저장소 위치를 추가한다.
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.entity.Member1;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Controller
@RequestMapping(value = "/member1")
public class Member1Controller {
final String format ="Member1Controller => {}";
@GetMapping(value = "/join.do")
public String joinGET(){
return "/member1/join"; // templates / member1 / join.html
}
@PostMapping(value = "/join.do")
public String joinPOST(@ModelAttribute Member1 member1){
log.info(format, member1.toString());
return "redirect:/member1/join.do";
}
}
Member1Controller.java를 생성한다.
<!DOCTYPE html>
<html lang="en">
<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>회원가입(member1)</h3>
<form th:action="@{/member1/join.do}" method="post">
<input type="text" name="id" /><br />
<input type="text" name="pw" value="a"/><br />
<input type="text" name="name" value="b"/><br />
<input type="number" name="age" value="1"/><br />
<input type="submit" value="회원가입" />
</form>
</body>
</html>
templates에 member1폴더를 만들고 join.html을 생성한다.
이렇게 회원가입창이 생성된다.
아이디에 aaa를 입력하고 가입을 회원가입하면 로그에 입력한 값이 출력된다.
@RequiredArgsConstructor
public class Member1Controller {
final String format ="Member1Controller => {}";
final Member1Repository member1Repository; // 저장소 객체
// model.addAttribute + return html혼합
@GetMapping(value = "/selectlist.do")
public ModelAndView selectListGET(){
List<Member1> list = m1Repository.findAll();
return new ModelAndView("/member1/selectlist", "list", list);
}
...
@PostMapping(value = "/join.do")
public String joinPOST(@ModelAttribute Member1 member1){
log.info(format, member1.toString());
m1Repository.save(member1);
return "redirect:/member1/join.do";
}
Member1Controller.java에 위 코드처럼 추가한다.
<body>
<h3>회원목록(member1)</h3>
<table border="1">
<tbody>
<tr th:each="obj:${list}">
<td th:text="${obj.id}"></td>
<td th:text="${obj.pw}"></td>
<td th:text="${obj.name}"></td>
<td th:text="${obj.age}"></td>
<td th:text="${obj.regdate}"></td>
<td>
수정, 삭제
</td>
</tr>
</tbody>
</table>
</body>
member1 폴더에 selectlist.html을 생성한다.
<body>
<h3>회원목록(member1)</h3>
<a th:href="@/member1/join.do">회원가입</a>
...
<td>
수정,
<form th:action="@{/member1/delete.do}" method="post">
<input type="hidden" name="id" th:value="${obj.id}" />
<input type="submit" value="삭제" />
</form>
</td>
selectlist.html을 위처럼 수정하고
@PostMapping(value = "/delete.do")
public String deletePost(@RequestParam(name="id") String id){
try{
m1Repository.deleteById(id);
return "redirect:/member1/selectlist.do";
}catch (Exception e){
e.printStackTrace();
return "redirect:/home.do";
}
}
Member1Controller.java에 delete를 추가한다.
<a th:href="@{/member1.update.do(id=${obj.id})}">수정</a>
selectlist.html의 수정 부분을 위처럼 수정하고.
@PostMapping(value = "/update,do")
public String updatePOST(@ModelAttribute Member1 member1){
try{
log.info(format, member1, toString());
// 기존 데이터를 읽음(아이디를 이용해서)
Member1 member2 = m1Repository.findById(member1.getId()).orElse(null);
// 변경항목을 바꿈(이름, 나이)
member2.setName(member1.getName());
member2.setAge(member1.getAge());
// 다시 저장함(기본키인 아이디 정보가 있어야 됨. 없으면 추가됨)
m1Repository.save(member2);
return "redirect:/member1/selectlist.do";
}
catch (Exception e){
e.printStackTrace();
return "redirect:/home.do";
}
}
@GetMapping(value = "/update.do")
public ModelAndView updateGET(@RequestParam(name = "id") String id) {
Member1 member1 = m1Repository.findById(id).orElse(null);
return new ModelAndView("/member1/update", "member1", member1);
}
Member1Controller.java에 위 코드를 추가한다.
<body>
<h3>회원정보수정(member1)</h3>
<form th:action="@{/member1/update.do}" th:object="${member1}" method="post">
<input type="text" th:field="${member1.id}" readonly /><br />
<input type="text" th:field="${member1.pw}" readonly /><br />
<input type="text" th:field="${member1.name}" /><br />
<input type="number" th:field="${member1.age}" /><br />
<input type="submit" value="회원정보변경" />
</form>
</body>
update.html를 위처럼 수정한다.
aaa를 수정해보자.
이렇게 b와 1에서 c와 2로 변한 모습을 볼 수 있다.
// 엔티티, 엔티티의 기본키 타입
@Repository
public interface Member1Repository extends JpaRepository< Member1, String >{
// JPQL => select m.* from Member1 m order by m.name desc
public List <Member1> findAllByOrderByNameDesc();
}
Member1Repository.java를 위처럼 수정하고
// model.addAttribute + return html혼합
@GetMapping(value = "/selectlist.do")
public ModelAndView selectListGET(){
List<Member1> list = m1Repository.findAllByOrderByNameDesc();
log.info(format, list.toString());
return new ModelAndView("/member1/selectlist", "list", list);
}
Member1Controller.java의 selectlist를 위처럼 수정한다.
이렇게 이름 순으로 정렬되는 것을 볼 수 있다.
// JPQL => select m.* from Member1 m where m.name like '%?%' order by m.name desc
public List<Member1> findByNameContainingOrderByNameDesc(String name);
// JPQL => select m.* from Member1 m where m.name like '&?&' order by m.name desc limit 페이지네이션
public List<Member1> findByNameContainingOrerByNameDesc(String name, Pageable pageable);
Member1Repository.java에 위 코드를 추가한다.
<body>
<h3>회원목록(member1)</h3>
<a th:href="@{/member1/join.do}">회원가입</a>
<form th:action = "@{/member1/selectlist.do}" method="get">
<input type="text" name="text" placeholder="검색어" />
<input type="hidden" name="page" value="1" />
<input type="submit" value="검색" />
</form>
...
selectlist.html에 위 코드를 추가한다.
위에 검색창이 추가되었고
검색창은 작동하지 않지만 주소에 검색창에 입력한 asdfasdf가 보인다. GET으로 추가하였기 때문이다.
// model.addAttribute + return html혼합
@GetMapping(value = "/selectlist.do")
public String selectListGET(
Model model,
@RequestParam(name="text", defaultValue = "", required = false) String text,
@RequestParam(name="page", defaultValue = "1", required = false) int page ){
if(page == 0){ // 페이지 정보가 없으면 자동으로 page=1로 변경
return "redirect:/member1/selectlist.do?text=" + text + "&page=1";
}
List<Member1> list = m1Repository.findAllByOrderByNameDesc();
model.addAttribute("list", list);
return "/member1/selectlist";
}
Member1Controller.java의 selectListGet을 위처럼 수정한다.
page가 0이면 자동으로 주소가 selectlist.do?text=&page=1로 변경된다.
// model.addAttribute + return html혼합
@GetMapping(value = "/selectlist.do")
public String selectListGET(
Model model,
@RequestParam(name="text", defaultValue = "", required = false) String text,
@RequestParam(name="page", defaultValue = "1", required = false) int page ){
if(page == 0){ // 페이지 정보가 없으면 자동으로 page=1로 변경
return "redirect:/member1/selectlist.do?text=" + text + "&page=1";
}
// 페이지네이션 만들기(페이지번호 0부터, 가져올 갯수 10개)
PageRequest pageRequest = PageRequest.of(page-1,10);
List<Member1> list = m1Repository.findByNameContainingOrderByNameDesc(text, pageRequest);
model.addAttribute("list", list);
return "/member1/selectlist";
}
selectListGET을 위처럼 한 번 더 수정한다.
c를 검색하면 이름이 c인 데이터만 불러온다.
<body>
<h3>회원목록(member1)</h3>
<a th:href="@{/member1/join.do}">회원가입</a>
<form th:action = "@{/member1/selectlist.do}" method="get">
<input type="text" name="text" placeholder="검색어" />
<input type="hidden" name="page" value="1" />
<input type="submit" value="검색" />
</form>
<table border="1">
<tbody>
<tr th:each="obj : ${list}">
<td th:text="${obj.id}"></td>
<td th:text="${obj.pw}"></td>
<td th:text="${obj.name}"></td>
<td th:text="${obj.age}"></td>
<td th:text="${obj.regdate}"></td>
<td>
<a th:href="@{/member1/update.do(id=${obj.id})}">수정</a>
<form th:action="@{/member1/delete.do}" method="post">
<input type="hidden" name="id" th:value="${obj.id}" />
<input type="submit" value="삭제" />
</form>
</td>
</tr>
</tbody>
</table>
<th:block th:each="num :${#numbers.sequence( 1, pages )}">
<a th:href="@{/member1/selectlist.do(text=${param.text}, page=${num})}" th:text="${num}"></a>
</th:block>
</body>
</html>
selectlist.html을 위처럼 수정 및 추가한다.
@GetMapping(value="/selectlist.do")
public String selectListGET(
Model model,
@RequestParam(name="text", defaultValue = "", required = false) String text,
@RequestParam(name="page", defaultValue = "0", required = false) int page) {
if(page == 0) { //페이지 정보가 없으면 자동으로 page=1로 변경
return "redirect:/member1/selectlist.do?text=" + text +"&page=1";
}
// 페이지 네이션 만들기(페이지번호0부터, 가져올개수10개)
// PageRequest pageRequest = PageRequest.of((page-1), 10);
long total = m1Repository.countByNameContaining(text);
// 1 => 1, 10
// 2 => 11, 20
//List<Member1> list = m1Repository.findByNameIgnoreCaseContainingOrderByNameDesc(text, pageRequest);
List<Member1> list = m1Repository.selectByNameContainingPagenation(text, (page*10)-9, page*10);
model.addAttribute("list", list);
model.addAttribute("pages", (total-1)/10+1); // 페이지 수
return "/member1/selectlist";
}
Member1Controller.java의 selectlistGET을 위처럼 수정 및 추가한다.
10개씩 한 페이지로 묶인 것을 확인할 수 있다.
@Data
@Entity
@Table(name = "BOARD1")
@SequenceGenerator(name = "SEQ_B1", sequenceName = "SEQ_BOARD1_NO", initialValue = 1, allocationSize = 1)
public class Board1 {
// 글번호 => 기본키이고 시퀀스를 사용함.
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_B1")
private long no;
private String title; // 글제목
// 글내용 => 타입이 clob
@Lob
private String content; // 글내용
private String writer; // 작성자
@Column(columnDefinition="long default 1")
private long hit=1; // 조회수
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
@CreationTimestamp
private Date regdate;
@OneToMany(mappedBy = "board1")
List<Reply1> list = new ArrayList<>();
}
Board1.java
@Data
@Entity
@Table(name = "REPLY1")
@SequenceGenerator(name = "SEQ_R1", sequenceName = "SEQ_REPLY1_NO", initialValue = 1, allocationSize = 1)
public class Reply1 {
// 답글번호 => 기본키 시퀀스 사용
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_R1")
private long no;
@Lob
private String content; // 답글내용
private String writer; // 답글 작성자
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
@CreationTimestamp
private Date regdate;
// 답글과 게시글의 관계 (앞이 현재)
@ManyToOne
@JoinColumn(name = "BRDNO", referencedColumnName = "NO")
private Board1 board1;
}
Reply1.java
# ddl
# create => 엔티티의 정보를 읽어서 테이블을 생성, 재구동시 다시 생성(DROP + CREATE)
# update => 엔티티의 정보를 읽어서 변경사항 발생 시 생성(CREATE)
# none => ddl 사용하지 않음
# validate => 엔티티와 테이블이 정상 매핑되었는지 확인
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
# spring.jpa.hibernate.ddl-auto=validate
spring.jpa.hibernate.ddl-auto=update
application.properties를 위처럼 수정한다.
BOARD1, REPLY1 테이블, SEQ_BOARD1_NO, SEQ_REPLY1_NO 시퀀스가 생성된 것을 확인할 수 있다.
@Repository
public interface Board1Repository extends JpaRepository<Board1, Long>{
}
Board1Repository.java
@Repository
public interface Reply1Repository extends JpaRepository<Reply1, Long>{
}
Reply1Repository.java
repository에 Board1Repository.java Reply1Repository.java를 생성한다.
@Slf4j
@Controller
@RequestMapping(value = "/board1")
@RequiredArgsConstructor
public class Board1Controller {
final String format = "Board1Controller => {}";
final Board1Repository b1Repository;
@GetMapping(value = "/insert.do")
public String insertGET(){
return "/board1/insert";
}
@PostMapping(value = "/insert.do")
public String insertPOST(@ModelAttribute Board1 board1){
log.info(format, board1.toString());
b1Repository.save(board1);
return "redirect:/board1/selectlist.do";
}
}
Board1Controller.java를 생성하여 작성한다.
<body>
<form th:action="@{/board1/insert.do}" method="post">
<input type="text" name="title" value="t" /><br />
<input type="text" name="content" value="c" /><br />
<input type="text" name="writer" value="w" /><br />
<input type="submit" value="글쓰기" /><br />
</form>
</body>
</html>
templates 아래에 board1 폴더를 생성하여 insert.html을 생성한다.
글쓰기를 누르면
정상적으로 selectlist.do로 이동하게 된다.
// 글번호 기준으로 내림차순 전체 게시글 조회
@GetMapping(value="/selectlist.do")
public String selectlistGET(Model model){
return "/board1/selectlist";
}
Board1Controller.java에 추가하고
@Repository
public interface Board1Repository extends JpaRepository<Board1, Long>{
public List<Board1> findAllByOrderByNoDesc();
}
Board1Repository.java에 위 코드를 추가한다.
<body>
<h3>게시글글목록(board1)</h3>
<a th:href="@{/board1/insert.do}">글쓰기</a>
<table border="1">
<thead>
<tr>
<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><a th:href="@{/board1/selectone.do(no=${obj.no})}" th:text="${obj.title}"></a></td>
<td th:text="${obj.writer}"></td>
<td th:text="${obj.hit}"></td>
<td th:text="${obj.regdate}"></td>
</tr>
</tbody>
</table>
</body>
</html>
templates - board1에 selectlist.html을 생성하여 작성한다.
게시글의 목록이 나오게 된다.
@GetMapping(value="/selectone.do")
public String selectoneGET(Model model, @RequestParam(name = "no") long no){
Board1 board1 = b1Repository.findById(no).orElse(null);
log.info(format, board1.toString());
model.addAttribute("board1", board1);
return "/board1/selectone";
}
Board1Controller.java에 selectoneGET을 추가
<body>
<h3>게시글 상세</h3>
<a th:href="@{/board1/selectlist.do}">목록으로</a><br />
번호 : <label th:text="${board1.no}"></label><br />
제목 : <label th:text="${board1.title}"></label><br />
내용 : <label th:text="${board1.content}"></label><br />
작성자 : <label th:text="${board1.writer}"></label><br />
조회수 : <label th:text="${board1.hit}"></label><br />
날짜 : <label th:text="${board1.regdate}"></label><br />
<hr />
<h3>답글목록</h3>
<table>
<thead>
<tr th:each="tmp : ${board1.list}">
<td th:text="${tmp.no}"></td>
<td th:text="${tmp.content}"></td>
<td th:text="${tmp.writer}"></td>
<td th:text="${tmp.regdate}"></td>
</tr>
</thead>
</table>
<hr />
<h3>답글작성</h3>
<form th:action="@{/reply1/insert.do}" method="post">
<input type="text" name="board1.no" th:value="${board1.no}" />
<input type="text" name="content" placeholder="답글내용" />
<input type="text" name="writer" placeholder="답글작성자" />
<input type="submit" value="답글작성" />
</form>
</body>
templates - board1 폴더에 selectone.html을 생성하여 작성한다.
이렇게 게시글 상세 페이지가 생성되었다.
@Slf4j
@Controller
@RequestMapping(value="/reply1")
@RequiredArgsConstructor
public class Reply1Controller {
final String format = "Reply1Controller => {}";
final Reply1Repository r1Repository;
@PostMapping(value="/insert.do")
public String insertPOST(@ModelAttribute Reply1 reply1){
log.info(format, reply1.toString());
r1Repository.save(reply1);
return "redirect:/board1/selectone.do?no=" + reply1.getBoard1().getNo();
}
// 글번호 기준으로 내림차순 전체 게시글 조회
@GetMapping(value="/insert.do")
public String insertGET(Model model, @ModelAttribute Reply1 reply1){
List<Reply1> list = r1Repository.findAllByOrderByNoDesc();
model.addAttribute("list", list);
return "redirect:/board1/selectone.do?no=" + reply1.getBoard1().getNo();
}
}
Reply1Controller를 생성하여 작성
@Repository
public interface Reply1Repository extends JpaRepository<Reply1, Long>{
public List<Reply1> findAllByOrderByNoDesc();
}
Reply1Repository.java에 추가
500 오류가 뜬다.
@GetMapping(value="/selectone.do")
public String selectoneGET(Model model, @RequestParam(name = "no") long no){
Board1 board1 = b1Repository.findById(no).orElse(null);
// log.info(format, board1.toString());
model.addAttribute("board1", board1);
return "/board1/selectone";
}
Board1Controller.java에서 log.info를 주석처리한다.
board1의 list에 reply1이 있고, reply1에 board1의 no가 있는데 board1을 불러오는 과정에서
또 reply1이 있다. 무한반복이 되므로 위 board1.toString()을 주석처리하거나
@ToString.Exclude
@OneToMany(mappedBy = "board1")
List<Reply1> list= new ArrayList<>();
board1.java에 @ToString.Exclude를 추가한다.
ToString에서 제외시킨다는 뜻이다.
이렇게 정상적으로 답글작성, 작성한 답글을 불러올 수 있다.