ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Springboot JPA 폼으로 데이터 입력받아 db에 insert하기
    Spring 2023. 6. 26. 01:13

    프리랜서로 활동하면서 개발 의뢰를 받을 수 있을까 싶어 웹사이트를 만들고 있다.

    front-end쪽은 bootstrap과 snippet을 사용해 html, css 화면을 만들었다.

     

    문제는 back-end인데 contact 메뉴에서 폼으로 데이터를 입력받아 db에 저장하고 싶어 springboot JPA를 이용해 기능을 구현해보기로 했다.

     

    contact 메뉴 화면

     

     

     

     

    Springboot JPA 폼으로 데이터 입력받아 db에 insert하기 위해 해야할 것들은 다음과 같다.

     

    1. 엔티티 클래스 생성: 먼저, 데이터베이스의 테이블과 매핑될 엔티티 클래스를 생성한다.  이 클래스는 데이터베이스의 테이블과 필드에 해당하는 멤버 변수를 가지고 있어야 한다.
    2. 폼 작성: Thymeleaf를 사용하여 데이터를 입력받을 폼을 작성합니다. 폼 요소들은 엔티티 클래스의 멤버 변수와 매핑한다. <input> 요소의 name 속성 값은 엔티티 클래스의 멤버 변수 이름과 일치해야 한다.
    3. 컨트롤러 작성: Spring의 컨트롤러에서는 폼 데이터를 처리하는 메서드를 작성한다. 이 메서드는 HTTP POST 요청을 처리하며, 엔티티 객체를 생성하고 폼 데이터를 해당 객체에 바인딩한다.
    4. 서비스 및 리포지토리 작성: 데이터베이스와 상호작용하는 서비스 및 리포지토리 클래스를 작성한다. 서비스 클래스는 컨트롤러에서 호출되어 엔티티를 저장하는 역할을 수행한다. 리포지토리 클래스는 실제로 데이터베이스와 상호작용하여 데이터를 저장하고 조회하는 역할을 수행한다.
    5. 폼 데이터 처리: 컨트롤러에서 서비스 클래스를 호출하여 엔티티를 저장하는 로직을 추가한다. 서비스 클래스에서는 리포지토리를 사용하여 엔티티를 데이터베이스에 저장한다.
    6. DB url 설정하기

    폴더 구조

     

     

    1. 엔티티 클래스 생성

     

    먼저 entity 폴더를 만들어서 FormData 클래스를 생성해보자.

     

    폼에서 입력받을 데이터는 각각 name(이름), category(기업 or 개인), mail(이메일), phone(연락처), message(의뢰 내용)

    로 멤버변수를 설정했다.

    package web.webmaestro.Entity;
    
    import javax.persistence.*;
    
    @Entity
    public class FormData {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)//auto-increment, primary key 설정
        private Long id;
    
        //문자열 길이 설정
        @Column(length = 255)
        private String name;
    
        @Column(length = 255)
        private String category;
    
        @Column(length = 255)
        private String mail;
    
        @Column(length = 255)
        private String phone;
    
        @Column(columnDefinition = "TEXT")//text 설정
        private String message;
    
        public FormData(String name, String category, String mail, String phone, String message) {
            this.name = name;
            this.category = category;
            this.mail = mail;
            this.phone = phone;
            this.message = message;
        }
        
       public FormData() {
            
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getCategory() {
            return category;
        }
    
        public void setCategory(String category) {
            this.category = category;
        }
    
        public String getMail() {
            return mail;
        }
    
        public void setMail(String mail) {
            this.mail = mail;
        }
    
        public String getPhone() {
            return phone;
        }
    
        public void setPhone(String phone) {
            this.phone = phone;
        }
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    }

    db에서 table을 별도로 만들지 않고 JPA와 FormData 엔티티 클래스로 자동으로 테이블을 생성하므로

    칼럼 별 제약 설정을 해주었다.

     

    데이터를 입력받을 생성자와 getter, setter 설정을 같이 해주었다.

     

    @Entity 어노테이션은 해당 클래스가 JPA(Java Persistence API)의 엔티티임을 나타낸다.

     

    @Id 어노테이션은 엔티티의 기본 키를 나타내는 멤버 변수를 표시한다.

     

    @GeneratedValue 어노테이션은 데이터를 입력받을 때마다 자동으로 id 값이 올라가는 auto-increment와 기본 키의 값을 생성하도록 설정한다.

     

    2. 폼 작성

    contact.html에서 form action="/form/save" method="post" 로 수정해주었다. 

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>WebMaestro homepage</title>
        <!--css-->
        <link rel="stylesheet" type="text/css" href="css/home.css">
        <!--bootstrap-->
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/5.3.0/css/bootstrap.min.css">
    </head>
    <header>
        <!--네비게이션 bar-->
        <nav class="navbar navbar-expand-lg bg-primary" data-bs-theme="dark">
            <div class="container-fluid">
                <a class="navbar-brand" href="/">WebMaestro</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarColor01" aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="collapse navbar-collapse" id="navbarColor01">
                    <ul class="navbar-nav me-auto">
                        <li class="nav-item">
                            <a class="nav-link" href="/">Portfolio</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="/service">Services</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link active" href="/contact">Contact
                                <span class="visually-hidden">(current)</span>
                            </a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <body>
    <!--contact form 시작 -->
        <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css" rel="stylesheet">
        <div class="container">
            <div class="contact__wrapper shadow-lg mt-n9">
                <div class="row no-gutters">
                    <div class="col-lg-5 contact-info__wrapper gradient-brand-color p-5 order-lg-2">
                        <h3 class="color--white mb-5">Get in Touch</h3>
    
                        <ul class="contact-info__list list-style--none position-relative z-index-101">
                            <li class="mb-4 pl-4">
                                <span class="position-absolute"><i class="fas fa-envelope"></i></span> &nbsp;&nbsp;webmaestro.info@gmail.com
                            </li>
                            <li class="mb-4 pl-4">
                                <span class="position-absolute"><i class="fas fa-phone"></i></span> &nbsp;&nbsp;010-3247-3877
                            </li>
                            <li class="mb-4 pl-4">
                                <span class="position-absolute"><i class="fas fa-map-marker-alt"></i></span> &nbsp;&nbsp;경기 광주시
                                <br>양벌로 173
    
                                <div class="mt-3">
                                    <a href="https://www.google.com/maps" target="_blank" class="text-link link--right-icon text-white">Get directions <i class="link__icon fa fa-directions"></i></a>
                                </div>
                            </li>
                        </ul>
    
                        <figure class="figure position-absolute m-0 opacity-06 z-index-100" style="bottom:0; right: 10px">
                        </figure>
                    </div>
    
                    <div class="col-lg-7 contact-form__wrapper p-5 order-lg-1">
                        <form action="/form/save" method="post" class="contact-form form-validate" novalidate="novalidate">
                            <div class="row">
                                <div class="col-sm-6 mb-3">
                                    <div class="form-group">
                                        <label class="required-field" for="name">성함</label>
                                        <input type="text" class="form-control" id="name" name="name" placeholder="홍길동">
                                    </div>
                                </div>
    
                                <div class="col-sm-6 mb-3">
                                    <div class="form-group">
                                        <label for="category">기업 OR 개인 고객</label>
                                        <input type="text" class="form-control" id="category" name="category" placeholder="회사명을 입력해주세요.">
                                    </div>
                                </div>
    
                                <div class="col-sm-6 mb-3">
                                    <div class="form-group">
                                        <label class="required-field" for="mail">Email</label>
                                        <input type="text" class="form-control" id="mail" name="mail" placeholder="honggildong@naver.com">
                                    </div>
                                </div>
    
                                <div class="col-sm-6 mb-3">
                                    <div class="form-group">
                                        <label for="phone">Phone Number</label>
                                        <input type="tel" class="form-control" id="phone" name="phone" placeholder="010-111-1234">
                                    </div>
                                </div>
    
                                <div class="col-sm-12 mb-3">
                                    <div class="form-group">
                                        <label class="required-field" for="message">원하시는 의뢰 내용을 입력해주세요. .</label>
                                        <textarea class="form-control" id="message" name="message" rows="4" placeholder="예) 크롤링, 웹개발, 파싱 등 자세한 내용은 상담을 통해 견적을 산출합니다."></textarea>
                                    </div>
                                </div>
    
                                <div class="col-sm-12 mb-3">
                                    <button type="submit" name="submit" class="btn btn-primary">문의 하기</button>
                                </div>
    
                            </div>
                        </form>
                    </div>
                    <!-- End Contact Form Wrapper -->
                </div>
            </div>
        </div>
    
    
    <!-- ======= Footer ======= -->
    <footer id="footer" class="footer">
        <div class="container">
            <div class="copyright">
                &copy; Copyright <strong><span>WebMaestro</span></strong>. All Rights Reserved
            </div>
            <div class="credits">
            </div>
        </div>
    </footer>
    <!-- End Footer -->
    </body>
    </html>

     

    form action은 나중에 controller에서 처리하는 엔드포인트다. method는 post 요청으로 처리하겠다는 뜻이다.

    각 폼의 하위 태그에서 name은 엔티티 클래스에서 설정한 멤버변수와 매핑된다.

     

    3. 컨트롤러 작성

     

    package web.webmaestro.controller;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import web.webmaestro.Entity.FormData;
    import web.webmaestro.service.FormDataService;
    
    //서버 측에서 /form 엔드포인트에 대한 요청을 처리하는 컨트롤러
    
    @Controller
    @RequestMapping("/form")
    public class FormController {
    
        private final FormDataService formDataService;
    
        @Autowired
        public FormController(FormDataService formDataService) {
            this.formDataService = formDataService;
        }
    
        @GetMapping("/create")
        public String showCreateForm(Model model) {
            model.addAttribute("formData", new FormData());
            return "create-form";
        }
    
        @PostMapping("/save")
        public String saveFormData(@RequestParam("name") String name,
                                   @RequestParam("category")String category,
                                   @RequestParam("mail") String mail,
                                   @RequestParam("phone") String phone,
                                   @RequestParam("message") String message) {
         
            return "redirect:/form/success";
        }
    
        //성공 시 contactcomplete.html 페이지 보여주기
        @GetMapping("/success")
        public String showSuccessPage() {
            return "contactcomplete";
        }
    
    }

     

    FormController의 showCreateForm메서드는 FormData 객체를 생성한다.  /form/create 경로에 대한 GET 요청을 처리하여 create-form 뷰를 반환한다.

     

    saveFormData 메서드는 /form/save 경로에 대한  POST 요청을 처리하여 FormDataService를 통해 데이터를 저장한다. 저장 후에는 /form/success 경로로 리다이렉션된다.

     

    또한 @RequestParam 어노테이션을 사용하여 요청 파라미터에서 값을 받아온다.

     

     

    4. 서비스 및 라포지토리 작성

     

    먼저 FormDataRepository 클래스를 작성한다.

    repository는 데이터베이스 조작을 처리하는 인터페이스이며, Spring Data JPA는 이 인터페이스를 구현한 구현체를 자동으로 생성해준다. 

    JpaRepository 인터페이스를 사용해주는 것으로 Spring Data JPA를 사용할 수 있다.

    Spring Data JPA의 장점은 데이터베이스와의 상호작용을 단순화하고 개발 생산성을 높이기 위한 기능을 제공한다는 점이다.

    package web.webmaestro.repository;
    
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.stereotype.Repository;
    import web.webmaestro.Entity.FormData;
    
    //데이터베이스 조작 처리
    
    @Repository
    public interface FormDataRepository extends JpaRepository<FormData, Long> {
    }

     

    여기서 FormData는 엔티티 클래스이며, Long은 엔티티의 식별자(ID) 타입이다.


    JpaRepository 인터페이스는 Spring Data JPA에서 제공하는 기본적인 CRUD 작업을 지원하는 인터페이스이며

    이미 구현되어 있으므로 해당 인터페이스의 구현체를 따로 작성할 필요가 없다.

     


     

    다음은 FormDataService 클래스에서 비즈니스 로직을 구현한다. 서비스 클래스는 컨트롤러와 라포지토리 사이의 중간 계층으로 작동 된다. 

    FormDataService는 FormDataRepository를 주입받아 사용, 생성자 주입을 통해 의존성을 해결한다. 

     

    도중에 mariadb utf-8 인코딩 변환 문제로 로직을 추가하는 등의 이슈가 있었으나, 

    my.ini 파일의 인코딩을 수정하는 것으로 문제를 해결하여 해당 로직은 필요가 없어졌다. 

     

    해결방법 :Springboot JPA mariadb에서 CRUD할 때 인코딩 오류 발생 :: Hunt for Data (tistory.com)

     

    package web.webmaestro.service;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import web.webmaestro.Entity.FormData;
    import web.webmaestro.repository.FormDataRepository;
    
    import java.io.UnsupportedEncodingException;
    import java.nio.charset.StandardCharsets;//문자열 인코딩 값 변환
    
    
    @Service
    public class FormDataService {
    
        private final FormDataRepository formDataRepository;
    
        @Autowired
        public FormDataService(FormDataRepository formDataRepository) {
            this.formDataRepository = formDataRepository;
        }
    
        //formDataRepository를 통해 폼 데이터를 저장하는 역할, 이 메서드를 통해 폼 데이터를 데이터베이스에 저장
        public void saveFormData(String name, String category, String mail, String phone, String message) {
            //String utf8Category = convertToUTF8(category);
    
            FormData formData = new FormData(name, category, mail, phone, message);
            formDataRepository.save(formData);//인코딩 변환한 카테고리 저장
        }
    
        //문자열 값 받을 때 인코딩 변환하는 메서드
        /*
        private String convertToUTF8(String value) {
            try {
                byte[] utf8Bytes = value.getBytes("UTF-8");
                return new String(utf8Bytes, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                // 처리할 예외 처리 코드 추가
                return value; // 예외 발생 시 변환하지 않은 값 반환
            }
        }
        */
    }

     

    saveFormData메서드는 formDataRepository를 통해 폼 데이터를 데이터베이스에 저장한다.

     

     

    5. 폼 데이터 처리

     

    마지막으로 FormController 클래스에서 FormDataService의 saveFormData 메서드를 호출하여 데이터를 저장한다.

     

    package web.webmaestro.controller;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import web.webmaestro.Entity.FormData;
    import web.webmaestro.service.FormDataService;
    
    //서버 측에서 /form 엔드포인트에 대한 요청을 처리하는 컨트롤러
    
    @Controller
    @RequestMapping("/form")
    public class FormController {
    
        private final FormDataService formDataService;
    
        @Autowired
        public FormController(FormDataService formDataService) {
            this.formDataService = formDataService;
        }
    
       //FormData 클래스로 table 생성
        @GetMapping("/create")
        public String showCreateForm(Model model) {
            model.addAttribute("formData", new FormData());
            return "create-form";
        }
    
        //폼에서 입력받은 데이터 저장
        @PostMapping("/save")
        public String saveFormData(@RequestParam("name") String name,
                                   @RequestParam("category")String category,
                                   @RequestParam("mail") String mail,
                                   @RequestParam("phone") String phone,
                                   @RequestParam("message") String message) {
                                   
            formDataService.saveFormData(name, category, mail, phone, message);//폼 데이터 처리하기
            
            return "redirect:/form/success";
        }
    
        //성공 시 contactcomplete.html 페이지 보여주기
        @GetMapping("/success")
        public String showSuccessPage() {
            return "contactcomplete";
        }
    
    }

     

    5. db url 설정하기

     

    application.yml 파일에서 db url, username, password 등을 입력한다. 엔티티 클래스를 생성할 때 table이 자동으로 만들어지는 ddl-auto: update 설정을 한다. (create는 기존 걸 삭제하고 새로 만들어지는 것이므로 update로 설정해야 한다.)

     

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/db_name
        username: user
        password: 1234
        driver-class-name: com.mysql.cj.jdbc.Driver
        initialization-fail-fast: true
    
      jpa:
          database-platform: org.hibernate.dialect.MySQL8Dialect
          hibernate:
            ddl-auto: update
            naming:
              physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
            properties:
              hibernate:
                dialect: org.hibernate.dialect.MySQL8Dialect
                hbm2ddl:
                  charset_name: UTF-8

     

    build.gradle 파일에 의존성도 제대로 설정되어 있는지 확인한다.

     

     

    plugins {
    	id 'java'
    	id 'org.springframework.boot' version '2.7.12'
    	id 'io.spring.dependency-management' version '1.0.15.RELEASE'
    }
    
    group = 'web'
    version = '0.0.1-SNAPSHOT'
    sourceCompatibility = '11'
    
    repositories {
    	mavenCentral()
    }
    
    dependencies {
    	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    	implementation 'org.springframework.boot:spring-boot-starter-web'
        implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    	implementation 'com.mysql:mysql-connector-j:8.0.32'
    	testImplementation 'org.springframework.boot:spring-boot-starter-test'
    	// email 인증
    	implementation 'org.springframework.boot:spring-boot-starter-mail'
    
    
    }
    
    tasks.named('test') {
    	useJUnitPlatform()

     

    db를 만들땐 intellij의 Database 메뉴란에서 사용하고자 하는 db를 선택해서 생성한다.

     

     

    나 같은 경우는 Wnmp의 mariadb를 사용했기 때문에 mariadb 설치시 입력한 User와 password를 입력했다.

     

     

    User, password, db 이름만 입력하면 된다. Test Connection을 누른 후 성공하면 db는 생성된 것이다. 

    db는 datagrip을 통해 관리하는 것이 편하기 때문에 datagrip과 주로 같이 사용한다.

     


     

    이렇게 설정을 다해준 후 front-end 화면에서 데이터 입력했더니, db에 data가 insert가 되는 것을 확인했다.

     


     

    이후 보완해야 할 점

     

    확장성을 고려하지 않고 엔티티 클래스만 생성하여 기능을 구현하였다.

    여기에 DTO클래스를 추가로 생성하여 확장성을 고려해야 한다. 

    반응형

    'Spring' 카테고리의 다른 글

    Springboot JPA mariadb에서 CRUD할 때 인코딩 오류 발생  (0) 2023.06.25
    Mybatis란?  (0) 2021.03.25
Designed by Tistory.