[SpringBoot(2)] DB연동

배지원·2022년 11월 9일
0

실습

목록 보기
12/24

📄 JPA 사용

JPA에 관한 세부내용은 이전에 정리해둔 JPA란 무엇인가?블로그를 참고하길 바람.

이전 시간 실습에 이어 실습을 진행함

1. JPA 연동

(1) dependencies 추가


추가 후 코끼리 이모티콘 눌러 최신화 꼭 하기


(2) application.yml 설정값 추가

(1)
spring:
 datasource:
   driver-class-name: com.mysql.cj.jdbc.Driver
   url: jdbc:mysql://localhost:3306/likelion-db
   username: root
   password: password
(2)
 jpa:
   show-sql: true
   database-platform: org.hibernate.dialect.MySQL8Dialect
   database : mysql
   hibernate.ddl-auto : update

(1) DB에 해당하는 주소와 연동하는데 필요한 정보를 설정함
(2) show-sql: true → jpa가 자동으로 만들어주는 query문을 console에서 볼 것인지 여부
database-platform → 사용할 DB의 Dialect설정
database : mysql → 사용할 DB종류
hibernate.ddl-auto —> update 가장 중요한 옵션 초보자일 때 create쓰지 말 것. 인생망함

create, update 사용시 매우 주의!!!
비교적 덜 위험한 validate 사용 권장!!


  • DB의 값을 지키기 위해 EC2 주소는 따로 Environment variables에 넣어 yml에서 설정한 주소보다 우선순위를 높여 Environment variables에 넣은 주소로 연결되도록 함

2. JPA DB 접근

  • JPA를 통해 DB에 CRUD를 해보도록 하겠다.

(1) DB에 데이터 넣기(Create)

Entity 파일 추가

  • DB와 데이터를 주고 받는 저장소
  • DB에 데이터를 보내기 위해 DB에 맞게 데이터를 가공하는 곳
  • DB에 테이블 이름과 데이터 값들이 그대로 똑같이 들어감
  • @GeneratedValue에 관한 내용은 GeneratedValue정리 블로그에서 자세히 확인할 수 있다.
@Entity
@Table(name = "article2")
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Article {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String content;

    public Article(String title,String content) {
        this.title = title;
        this.content = content;
    }
}

Repository 파일 추가

  • DAO의 추상화된 버전
  • JpaRepository안에 이미 CRUD쿼리문이 있기때문에 따로 쿼리문 작성필요없이 호출해서 사용하면 됨
public interface ArticleRepository extends JpaRepository<Article,Long> {   
}

Dto 수정

  • Controller와 Repository에서 값을 주고 받기 위한 저장소
  • Dto에서 DB로 보낼 값을 Entity로 보내 저장한다.
@Getter
@ToString
public class ArticleDto {
    private Long id;
    private String title;
    private String content;

    public ArticleDto(Long id, String title, String content) {
        this.id = id;
        this.title = title;
        this.content = content;
    }

    public Article toEntity(){
        return new Article(title,content);
    }
}

Controller 수정

  • 사용자가 입력한 값을 Form으로 받아와 Dto의 toEntity 메서드를 이용하여 Entity에 값 저장함
@Controller
@Slf4j
@RequestMapping("/articles")
public class ArticleController {

    @Autowired
    private final ArticleRepository articleRepository;

    public ArticleController(ArticleRepository articleRepository) {
        this.articleRepository = articleRepository;
    }


    @GetMapping("/new")
    public String newArticleForm(){
        return "articles/new";
    }

    @PostMapping("/posts")
    public String createArticle(ArticleDto form){
        log.info(form.toString());
        Article article = form.toEntity();  // form 데이터를 Entity 형식으로 변경(JPA로 DB에 데이터를 보내기 위해 JPA형식으로 가공한 셈) 
        articleRepository.save(article);    // Repository를 통해 DB에 값 저장
        return "";
    }
}

결과화면

  • 연결해둔 DB에 스키마만 만들어 두면 위에서 설정을 했기때문에 JPA가 자동으로 테이블을 만들어 준다.

  • view 화면에 사용자가 값을 입력하면 JPA가 자동으로 쿼리문을 만들어 DB에 넣어준다.


(2) DB에서 데이터 찾기(Read)

이전 코드에서 추가되고 수정된 코드만 기재하도록 하겠다.

Back

Controller

@Controller
@RequestMapping("/articles")
@Slf4j
public class ArticleController {

    /* Spring이 DI하는 구간
       ArticleRepository는 interface지만 그 구현제(Article)를 SpringBoot가 넣어준다.

     */
    private final ArticleRepository articleRepository;


    public ArticleController(ArticleRepository articleRepository) {
        this.articleRepository = articleRepository;
    }

    // 데이터 입력 페이지 이동
    @GetMapping("/new")
    public String newArticleForm(){
        return "new";
    }

    // DB 전체 출력
    @GetMapping("/list")
    public String list(Model model){
        List<Article> articleList = articleRepository.findAll();
        model.addAttribute("articles", articleList);
        return "list";
    }

    // 아무것도 입력받지 않으면 /articles/list로 이동
    @GetMapping("")
    public String index(){
        return "redirect:/articles/list";
    }

    // id를 통한 데이터 출력 페이지 
    @GetMapping("/{id}")
    public String selectSingle(@PathVariable Long id, Model model){
        Optional<Article> optArticle = articleRepository.findById(id);  // id를 통해 데이터를 가져옴
        // Optional를 사용해 null 체크함

        if(!optArticle.isEmpty()) { // 데이터가 있다면
            model.addAttribute("article", optArticle.get());   // model에 id에 해당하는 title, content 데이터를 저장함
            return "show";
        }else {                     // 데이터가 null이라면
            return "error";
        }
    }

    // 사용자에게 입력받은 데이터 DB에 저장 후 /articles/%d url로 이동
    @PostMapping("")
    public String addAriticle(ArticleDto articledto){
        log.info(articledto.toString());
        Article saveArticle = articleRepository.save(articledto.toEntity());
        log.info("generatedId:{}", saveArticle.getId());
        return String.format("redirect:/articles/%d", saveArticle.getId());
    }
}
  • findAll()을 통해 DB에 저장된 데이터를 모두 model에 넣어 view로 보낸다.
  • PathVariable을 통해 찾고 싶은 id를 입력받는다
  • 입력받은 id를 통해 DB에서 데이터를 찾아온다. 이때, Optional을 통해 null값을 판별한다.
  • model에 데이터를 넣을때 .get( )을 사용해 optArticle의 데이터를 저장한다.
  • 값을 form을 통해 입력 받았을때 DB에 저장후 redirect를 통해 id값을 포함한 url로 이동하도록 연결해준다.

Redirect?

  • Re(다시) + direct(지시하다) 다시 지시하는것을 의미한다.
    예를들어 유저는 A라는 페이지에 요청을 보냈으나 서버는 권한이 없어서 B라는 페이지로 우회를 시킬때 사용함
    즉, 다른 웹페이지로 우회시킬때 사용함

Front

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bootstrap demo</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
</body>
</html>
  • 코드 중복을 방지하고자 header부분과 footer부분은 고정이므로 따로 파일을 만들어 필요할때마다 호출하는 방식으로 사용

error

<div>
    에러 페이지
</div>

list

{{>layouts/header}}
<table class="table">
    <thead>
    <tr>
        <th scope="col">Id</th>
        <th scope="col">Title</th>
        <th scope="col">Content</th>
    </tr>
    </thead>
    <tbody class="table-group-divider">
    {{#articles}}
        <tr>
            <th>{{id}}</th>
            <td><a href="/articles/{{id}}">{{title}}</a></td>
            <td>{{content}}</td>
        </tr>
    {{/articles}}
    </tbody>
</table>
<a href="/articles/new">new article</a>
<a href="/articles">articles List</a>
{{>layouts/footer}}
  • model을 통해 DB에 저장된 모든 값을 Table형식을 통해 출력함

new

{{>layouts/header}}
<form class="container" action="/articles" method="post">
    <div class="mb-3">
        <label for="exampleInputEmail1" class="form-label">Title</label>
        <input type="text" class="form-control" name="title" id="exampleInputEmail1" aria-describedby="title">
        <div id="emailHelp" class="form-text">제목을 입력하세요.</div>
        <br>
        <label for="exampleInputEmail1" class="form-label">Content</label>
        <input type="text" class="form-control" name="content" id="exampleInputEmail1" aria-describedby="content">
        <div id="emailHelp" class="form-text">내용을 입력하세요.</div>
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>
</form>
{{>layouts/footer}}

show

{{>layouts/header}}
<div class="card" style="width: 18rem;">
    {{#article}}
    <div class="card-body">
        <h5 class="card-title">{{title}}</h5>
        <h6 class="card-subtitle mb-2 text-muted">{{id}}</h6>
        <p class="card-text">{{content}} </p>
        <a href="/articles/new">new articles</a>
        <a href="/articles">list articles</a>
    </div>
    {{/article}}
</div>
{{>layouts/footer}}
  • {{#article}} : article 클래스의 데이터를 가져온다(entity 의 값 호출)
  • {{id}} {[content}} {{title}} : 변수명에 해당하는 값 호출


결과화면

  • DB에 있는 값 찾기
  • DB에 없는 값 찾기

  • DB 모든 값 출력


(3) DB에서 수정, 삭제하기

Back

Controller

@Controller
@RequestMapping("/articles")
@Slf4j
public class ArticleController {

    /* Spring이 DI하는 구간
       ArticleRepository는 interface지만 그 구현제(Article)를 SpringBoot가 넣어준다.

     */
    private final ArticleRepository articleRepository;


    public ArticleController(ArticleRepository articleRepository) {
        this.articleRepository = articleRepository;
    }

    // 데이터 입력 페이지 이동
    @GetMapping("/new")
    public String newArticleForm(){
        return "new";
    }

    // 사용자에게 입력받은 데이터를 articledto를 통해 DTO에 저장하고 난후 toEntity를 통해 entity클래스에
    // 저장하고난 후 DB에 저장 한다. 그리고 나서 redirect로  /articles/%d url 이동
    @PostMapping("")
    public String addAriticle(ArticleDto articledto){
        log.info(articledto.toString());
        Article saveArticle = articleRepository.save(articledto.toEntity());
        log.info("generatedId:{}", saveArticle.getId());
        return String.format("redirect:/articles/%d", saveArticle.getId());
    }

    // DB 전체 출력
    @GetMapping("/list")
    public String list(Model model){
        List<Article> articleList = articleRepository.findAll();
        model.addAttribute("articles", articleList);
        return "list";
    }

    // 아무것도 입력받지 않으면 /articles/list로 이동
    @GetMapping("")
    public String index(){
        return "redirect:/articles/list";
    }

    // id를 통한 데이터 세부 출력 페이지
    @GetMapping("/{id}")
    public String selectSingle(@PathVariable Long id, Model model){
        Optional<Article> optArticle = articleRepository.findById(id);  // id를 통해 데이터를 가져옴
        // Optional를 사용해 null 체크함

        if(!optArticle.isEmpty()) { // 데이터가 있다면
            model.addAttribute("article", optArticle.get());   // model에 id에 해당하는 title, content 데이터를 저장함
            return "show";
        }else {                     // 데이터가 null이라면
            return "error";
        }
    }

    // edit(수정) 페이지 이동
    // 기존의 데이터를 출력하기 위해 id를 통해 데이터를 모두 호출함
    @GetMapping("/{id}/edit")
    public String edit(@PathVariable Long id, Model model){
        Optional<Article> optionArticle = articleRepository.findById(id);

        if(!optionArticle.isEmpty()){
            model.addAttribute("article",optionArticle.get());
            log.info(String.valueOf(optionArticle));
            return "edit";
        }else{
            model.addAttribute("errormessage",String.format("%d가 없습니다",id));
            return "error";
        }
    }

    // 수정 url
    // 프론트에서 수정한 값을 Form을 통해 articleDto에 저장하고 그값을 toEntity, JPA를 통해 DB에 저장한다.
    // 그 후 모델에 담아 수정된 상세 페이지에 데이터를 보내 수정된 데이터를 출력시킨다.
    @PostMapping("/{id}/update")
    public String update(@PathVariable Long id, ArticleDto articleDto,Model model){
        Article article = articleRepository.save(articleDto.toEntity());        // DB에 수정한 데이터 저장함
        model.addAttribute("article",article);                      // model에 article 데이터 저장
        return String.format("redirect:/articles/%d",article.getId());          // 수정한 상세 페이지 이동
    }

    // 삭제 url
    @GetMapping("/{id}/delete")
    public String Delete(@PathVariable Long id,Model model){
        articleRepository.deleteById(id);
        return "redirect:/articles";
    }

}
  • 수정 페이지로 이동, 수정하여 DB저장, 삭제하는 API를 추가했다.
  • 수정 페이지로 이동할때는 기존의 값을 출력해줘야 하기 때문에 id를 통해 DB에서 데이터를 가져와 model에 담아 view로 넘겨줬다.
  • 수정을 할때는 post를 통해 view에서 수정한 데이터를 받아와 toEntity를 통해 JPA 형식으로 변환하여 DB에 저장하였다.
  • 삭제할때는 현재 페이지 id값만 받아서 해당 id의 데이터를 삭제할 수 있도록 했다.

Duplicated Entry에러가 안나는 이유

  • .save( )를 할 때 id가 있다면 insert대신 update가 실행 되기 때문에 오류가 발생하지 않고 JPA에서 자동으로 신규 작성과 수정을 구분하여 DB에 저장할때 새로운 Column의 추가여부를 결정한다.

id있는 경우
Hibernate: update article2 set content=?, title=? where id=?
id없는 경우
Hibernate: insert into article2 (content, title) values (?, ?)

DTO

@Getter
@Setter
@ToString
@AllArgsConstructor
public class ArticleDto {
    private Long id;
    private String title;
    private String content;

    public Article toEntity(){
        return new Article(this.id,this.title,this.content);
    }
}
  • update를 할때 데이터를 ArticleDto 형식으로 id, title, content 데이터를 받기 때문에 id 변수를 추가해줌

Entity

@Entity
@Table(name = "article2")
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Article {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String content;

    public Article(String title,String content) {
        this.title = title;
        this.content = content;
    }
}

Front

edit

<div>
    edit page 입니다.
    {{#article}}
        <form action="/articles/{{id}}/update" method="post">
            <!-- update 컨트롤러에 id,title,content값을 넘겨줘야 하기 때문에 id는 hidden으로 따로 저장함
                 만약 프론트 말고 백에서 처리하고 싶다면 setter를 통해 dto의 id값을 초기화 시켜주면 된다. -->
            <input type="hidden" name="id" value="{{id}}">
            <input type="text" name="title" value="{{title}}"/>
            <textarea rows="3"  name="content" >{{content}}</textarea>
            <input type="submit"/>
        </form>
    <a href="/articles/{{id}}"> 상세페이지</a>
    <a href="/articles/list"> 메인화면</a>
    {{/article}}
</div>
  • 수정하는 view 추가

show

{{>layouts/header}}
{{#article}}
<div class="card" style="width: 18rem;">

    <div class="card-body">
        <h5 class="card-title">{{title}}</h5>
        <h6 class="card-subtitle mb-2 text-muted">{{id}}</h6>
        <p class="card-text">{{content}} </p>
        <button type="button" class="btn btn-outline-success"><a href="/articles/new">새로만들기</a></button>
        <button type="button" class="btn btn-outline-info"><a href="/articles">리스트 출력</a></button>
    </div>
</div>
<button type="button" class="btn btn-danger"><a href="/articles/{{id}}/edit">수정</a></button>
<button type="button" class="btn btn-danger"><a href="/articles/{{id}}/delete">삭제</a></button>
{{/article}}
{{>layouts/footer}}
  • 세부화면에서 수정버튼 추가

파일 구조


profile
Web Developer

0개의 댓글