Spring 강의 day 7

주세환·2023년 5월 8일
0

Spring

목록 보기
7/18

Member

테이블 생성

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개씩 한 페이지로 묶인 것을 확인할 수 있다.


Board

테이블 생성

@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에서 제외시킨다는 뜻이다.


이렇게 정상적으로 답글작성, 작성한 답글을 불러올 수 있다.

0개의 댓글