빅데이터 Java 개발자 교육 [Spring - (프로젝션, 식당리스트 및 메뉴관리기능 실습)]

Jun_Gyu·2023년 5월 11일
0
post-thumbnail

회원관련 프로젝션


Member1 Projection

interface로 생성

package com.example.entity;


public interface Member1Projection {
    //get + 변수()
    String getId();

    String getName();

    int getAge();
}


Member1 Repository (+method)

// 일부 컬럼만 출력
// JPQL => SELECT id, name, age FROM member1 ORDER BY id ASC;
public List<Member1Projection> findAllByOrderByIdAsc();


Member1 Controller (+method)

@GetMapping(value = "/selectlistprojection.do")
    public String selectListProjectionGET(Model model){
        try {
            List<Member1Projection> list = m1Repository.findAllByOrderByIdAsc();
            log.info(format, list.toString());
            for(Member1Projection obj : list) { // list형태로 왔기 때문에 확인하기 위해서는 반복문을 사용!
                log.info(format, obj.getId() + "," + obj.getName() + "," + obj.getAge());
            }
            model.addAttribute("list", list);
            return "/member1/selectlistprojection";
        } catch (Exception e) {
            e.printStackTrace();
            return "redirect:/home.do";
        }
    }


html

<!DOCTYPE html>
<html lang="ko">
<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>Document</title>
</head>
<body>
    <h3>회원조회(Member1Projection)</h3>
    <hr />
    <table border="1">
        <thead>
            <tr style="text-align: center;">
                <td>id</td>
                <td>name</td>
                <td>age</td>
            </tr>
        </thead>
        <tbody>
            <tr th:each="obj : ${list}">
                <td th:text="${obj.id}"></td>
                <td th:text="${obj.name}"></td>
                <td th:text="${obj.age}"></td>
            </tr>
        </tbody>
    </table>
</body>
</html>


회원 + 주소(외래키)


Address1 Projection

package com.example.entity;

public interface Address1Projection {

    long getNo(); // 주소번호

    String getAddress(); // 주소

    Member1 getMember1(); // 회원정보

    interface Member1 { // 외래키 항목
        String getId(); // 아이디
        String getName(); // 이름
    }

    // 조합 (주소번호 + 주소정보 합치기)
    default String getNoAddress() {
        return getNo() + "," + getAddress();
    }
}


Address1 Repository (+method)

// SELECT a.no, a.address, m.id, m.name FROM address1 a, member1 m ORDER BY no DESC;
List<Address1Projection> findAllByOrderByNoDesc();


Address1 Controller (+method)

@GetMapping(value = "/selectlistprojection.do")
    public String selectlistprojectGET(Model model){
        try {
            List<Address1Projection> list = a1Repository.findAllByOrderByNoDesc();
            model.addAttribute("list", list);
            return "/address1/selectlistprojection"; // html 위치
        } catch (Exception e) {
            e.printStackTrace();
            return "redirect:/home.do";
        }
    }


html

<!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>Document</title>
</head>
<body>
    <h1>회원정보 + 주소정보 (Address1Projection)</h1>
    <hr />
    <table border="1">
    <thead>
        <tr style="text-align: center;">
            <td>주소번호</td>
            <td>주소</td>
            <td>회원아이디</td>
            <td>회원이름</td>
            <td>조합 ('주소번호' , '주소정보')</td>
        </tr>
    </thead>
    <tbody>
        <tr th:each="obj : ${list}">
            <td th:text="${obj.no}"></td>
            <td th:text="${obj.address}"></td>
            <td th:text="${obj.member1.id}"></td>
            <td th:text="${obj.member1.name}"></td>
            <td th:text="${obj.noAddress}"></td>
        </tr>
    </tbody>
</table>
    
</body>
</html>

실습

시퀀스 및 식당 테이블생성

-- Create Seq
CREATE SEQUENCE seq_restaurant1_no 
        INCREMENT BY 1
        START WITH 1;


-- Create Table
CREATE TABLE restaurant1
(  	no      	NUMBER(19) 		DEFAULT SEQ_RESTAURANT1_NO.nextval,
    phone    	VARCHAR2(20)    PRIMARY KEY,
    name      	varchar2,
    address     varchar2,
    type		varchar2(30),
    regdate  	TIMESTAMP    	DEFAULT 	CURRENT_TIMESTAMP
);


일반적인 방법으로 entity를 추가하게 되면 시퀀스가 null값이 대입되는 오류가 발생한다.
여기서 해결 방법이 두가지로 나뉜다.

1. 복합키 사용 (ID가 2개)

Restauant1 entity

package com.example.entity;

import java.math.BigInteger;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

import org.hibernate.annotations.CreationTimestamp;
import org.springframework.format.annotation.DateTimeFormat;

import lombok.Data;

@Entity
@Data
@Table(name = "RESTAURANT1")
@SequenceGenerator(name = "SEQ_RESTAURANT1_NO", sequenceName = "SEQ_RESTAURANT1_NO", initialValue = 1, allocationSize = 1)
@IdClass(Restaurant1ID.class) // 복합키 사용
public class Restaurant1 {


    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_RESTAURANT1_NO")
    private BigInteger no;

    @Id
    private String phone;

    private String name;

    private String address;


    private String type;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
    @CreationTimestamp
    @Column(updatable = false)
    private Date regdate;

}

ID를 두개 지정해 준 뒤,

Restaurant1ID 라는 새로운 entity 생성.

package com.example.entity;

import java.io.Serializable;
import java.math.BigInteger;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Restaurant1ID implements Serializable{
    private BigInteger no;
    private String phone;
    
}

기존 entity를 상속하여 키 값을 두개 지정.

이후 처음의 Restaurant1 entity에

@IdClass(Restaurant1ID.class)

어노테이션을 추가해준다.

2. 단일키 사용 (ID가 1개)

Restauant1 entity

package com.example.entity;

import java.math.BigInteger;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime;
import org.springframework.format.annotation.DateTimeFormat;

import lombok.Data;

@Data
@Entity
@Table(name = "RESTAURANT1")
@SequenceGenerator(name = "SEQ_RESTAURANT1_NO", sequenceName = "SEQ_RESTAURANT1_NO", initialValue = 1, allocationSize = 1)
public class Restaurant1 {

    @Generated(GenerationTime.INSERT)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_RESTAURANT1_NO")
    private BigInteger no;

    @Id
    private String phone;

    private String name;

    private String address;

    private String type;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
    @CreationTimestamp
    @Column(updatable = false)
    private Date regdate;

    
}

no 컬럼에서

@Generated(GenerationTime.INSERT)

어노테이션을 추가한다. 자동으로 시퀀스가 추가되게 하는 기능이다.



Restaurant1 Repository

package com.example.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.example.entity.Restaurant1;

@Repository
public interface Restaurant1Repository extends JpaRepository<Restaurant1, String>{

}

Restaurant1 Controller

package com.example.controller.jpa;

import java.math.BigInteger;

import org.springframework.stereotype.Controller;
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 com.example.entity.Restaurant1;
import com.example.repository.Restaurant1Repository;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequestMapping(value = "/restaurant1")
@RequiredArgsConstructor
public class Restaurant1Controller {

    final Restaurant1Repository r1Repository;
    
    @GetMapping(value = "/insert.food" )
    public String insertGET() {
        try {
            return "/restaurant1/insert";
        } catch (Exception e) {
            e.printStackTrace();
            return "redirect:/home.do";
        }
    }

    @PostMapping(value = "/insert.food")
    public String insertPOST(
        @RequestParam(name = "phone") String phone ,
        @RequestParam(name = "name") String name,
        @RequestParam(name = "address") String address,
        @RequestParam(name = "type") String type) {
        try { 
            Restaurant1 obj = new Restaurant1();
            // 식당 등록하기 기능
            obj.setPhone(type);
            obj.setName(name);
            obj.setAddress(address);
            obj.setType(type);
            r1Repository.save(obj);
            return "redirect:/restaurant1/selectlist.food";
        } catch (Exception e) {
            e.printStackTrace();
            return "redirect:/home.do";
        }
    }
}

html

<!DOCTYPE html>
<html lang="ko">
<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>Document</title>
</head>
<body>
    <h1>식당등록</h1>
    <hr />

    <form th:action="@{/restaurant1/insert.food}" method="post">
        <input type="text" name="phone" value="051-111-" placeholder="전화번호" /><br />
        <input type="text" name="name" value="OO반점" placeholder="가게명" /><br />
        <input type="text" name="address" value="부산시" placeholder="주소" /><br />
        <select name="type" class="form-select" id="inputGroupSelect1">
            <option value="한식">한식</option>
            <option value="중식">중식</option>
            <option value="양식">양식</option>
            <option value="일식">일식</option>
            <option value="인스턴트">인스턴트</option>
            <option value="채식">채식</option>
        </select>
        <input type="submit" value="등록하기" />
    </form>
    
</body>
</html>

이번엔 등록된 식당들을 조회해보도록 하겠다.

Restaurant Repository

package com.example.repository;

import java.util.List;

import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.example.entity.Restaurant1;


@Repository
public interface Restaurant1Repository extends JpaRepository<Restaurant1, String>{

    // SELECT * FROM restaurant1 WHERE phone LIKE '% || ? || %' ORDER BY no DESC;
    List<Restaurant1> findByPhoneContainingOrderByNoDesc(String phone, Pageable pageable);

    // SELECT COUNT(*) FROM restaurant1 WHERE phone LIKE '% || ? || %'
    long countByPhoneContaining(String phone);

    //--------------------------------------------------------------------------------------//

    // SELECT * FROM restaurant1 WHERE name LIKE '% || ? || %' ORDER BY no DESC;
    List<Restaurant1> findByNameContainingOrderByNoDesc(String name, Pageable pageable);

    // SELECT COUNT(*) FROM restaurant1 WHERE name LIKE '% || ? || %'
    long countByNameContaining(String name);

    //--------------------------------------------------------------------------------------//

    // SELECT * FROM restaurant1 WHERE type LIKE '% || ? || %' ORDER BY no DESC;
    List<Restaurant1> findByTypeContainingOrderByNoDesc(String type, Pageable pageable);

    // SELECT COUNT(*) FROM restaurant1 WHERE TYPE=?
    long countByType(String type);

    //--------------------------------------------------------------------------------------//

    // SELECT * FROM restaurant1 WHERE address LIKE '% || ? || %' ORDER BY no DESC;
    List<Restaurant1> findByAddressContainingOrderByNoDesc(String address, Pageable pageable);

    // SELECT COUNT(*) FROM restaurant1 WHERE address LIKE '% || ? || %'
    long countByAddressContaining(String address);

    //--------------------------------------------------------------------------------------//
}

Restaurant Controller

package com.example.controller.jpa;


import java.util.List;

import javax.servlet.http.HttpSession;

import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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 org.springframework.web.bind.annotation.RequestParam;

import com.example.dto.Search;
import com.example.entity.Restaurant1;
import com.example.repository.Restaurant1Repository;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequestMapping(value = "/restaurant1")
@RequiredArgsConstructor
public class Restaurant1Controller {

    final Restaurant1Repository r1Repository;
    final HttpSession httpSession;// 세션객체

    // 127.0.0.1:9090/ROOT/restaurant1/selectlist.food?page=1&type=phone&text=
    // 식당 전체목록 표시(페이지네이션, 연락처, 이름별, 종류별, 주소별 검색)
    @GetMapping(value = "/selectlist.food")
    public String selectlistGET(
        Model model, @ModelAttribute Search obj) {
            
        try {
            log.info("Restaurant1Controller => {}", obj.toString()); 
            // 페이지네이션
            PageRequest pageRequest = PageRequest.of(obj.getPage()-1,10);

            // 연락처 검색
            List<Restaurant1> list = r1Repository.findByPhoneContainingOrderByNoDesc(obj.getText(), pageRequest);
            long total = r1Repository.countByPhoneContaining(obj.getText());

            if (obj.getType().equals("name")){
                list = r1Repository.findByNameContainingOrderByNoDesc(obj.getText(), pageRequest);
                total = r1Repository.countByNameContaining(obj.getText());
            }
            else if(obj.getType().equals("address")){
                list = r1Repository.findByAddressContainingOrderByNoDesc(obj.getText(), pageRequest);
                total = r1Repository.countByAddressContaining(obj.getText());
            }
            else if(obj.getType().equals("type")) {
                list = r1Repository.findByTypeContainingOrderByNoDesc(obj.getText(), pageRequest);
                total = r1Repository.countByType(obj.getText());
            }
            model.addAttribute("list", list);
            model.addAttribute("pages", (total-1) + 1 / 10);
            model.addAttribute("search", obj);
            return "/restaurant1/selectlist";
        } catch (Exception e) {
            e.printStackTrace();
            return "redirect:/home.do";
        }
    }

    @GetMapping(value = "/insert.food" )
    public String insertGET() {
        try {
            return "/restaurant1/insert";
        } catch (Exception e) {
            e.printStackTrace();
            return "redirect:/home.do";
        }
    }

    @PostMapping(value = "/insert.food")
    public String insertPOST(
        @RequestParam(name = "phone") String phone,
        @RequestParam(name = "name") String name,
        @RequestParam(name = "address") String address,
        @RequestParam(name = "type") String type) {
        try { 
            Restaurant1 obj = new Restaurant1();
            // 식당 등록하기 기능
            obj.setPhone(phone);
            obj.setName(name);
            obj.setAddress(address);
            obj.setType(type);
            r1Repository.save(obj);
            return "redirect:/restaurant1/selectlist.food";
        } catch (Exception e) {
            e.printStackTrace();
            return "redirect:/home.do";
        }
    }
}

html

<!DOCTYPE html>
<html lang="ko">

<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>Document</title>
</head>

<body>

    <h1>식당목록</h1>
    <a th:href="@{/restaurant1/insert.food}"><button>매장등록</button></a>
    <hr />

    <form th:action="@{/restaurant1/selectlist.food}" method="get" th:object="${search}">
        <input type="hidden" name="page" value="1" />
        <select th:field="${search.type}">
            <option th:each="tmp, idx : ${search.typeCode}" th:value="${tmp}" th:text="${search.typeName[idx.index]}">
            </option>
        </select>
        <input type="text" th:field="${search.text}" placeholder="검색어" />
        <input type="submit" value="검색">
    </form>
    <hr />

    <table border="1">
        <thead>
            <tr>
                <th>번호</th>
                <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.phone}" name="phone"></td>
                <td th:text="${obj.name}" name="name"></td>
                <td th:text="${obj.address}" name="address"></td>
                <td th:text="${obj.type}" name="type"></td>
                <td th:text="${obj.regdate}"></td>
                <td><a th:href="@{/menu1/insert.food(rno=${obj.no}, rphone=${obj.phone})}"><button>메뉴등록</button></a></td>
            </tr>
        </tbody>
    </table>

    <th:block th:each="num : ${#numbers.sequence( 1, pages )}">
        <a th:href="@{/restaurant1/selectlist.food(page=${num}, type=${search.type}, text=${search.text})}"
            th:text="${num}"></a>
    </th:block>


</body>

</html>

Select 박스를 이용한 검색기능과 페이지네이션 기능도 추가하였다.

추가로 각 식당마다 메뉴를 등록하고 조회를 할 수 있도록 기능도 넣었으며, 각각의 메뉴를 수정하거나 삭제할 수 있도록 버튼을 주가 할 예정이다.

아직 DB에 메뉴 데이터가 입력이 되지 않아 이부분에 대해서는 내일 추가로 정리해보도록 하겠다.

profile
시작은 미약하지만, 그 끝은 창대하리라

0개의 댓글