반응형

📖 연관관계 매핑 종류와 방향

연관관계를 맺는 두 엔티티 간에 생성할 수 있는 연관관계의 종류는 아래와 같습니다.

  • One To One : 일 대 일(1:1)
  • One To Many : 일 대 다(1:N)
  • Many To One : 다 대 일(N:1)
  • Many To Many : 다 대 다(N:M)

데이터베이스에서는 두 테이블의 연관관계를 설정하면 외래키(FK)를 통해 서로 조인해서 참조하는 구조로 생성되지만,

JPA를 사용하는 객체지향 모델링에서는 엔티티 간 참조 방향을 설정할 수 있습니다.

데이터베이스와 관계를 일치시키기 위해 양방향으로 설정해도 무관하지만, 비즈니스 로직 관점에서 봤을 때는 단방향 관계만 설정해도 해결되는 경우가 많습니다. 단방향과 양방향 관계에 대해 아래에 간단하게 정리하면 다음과 같습니다.

  • 단방향 : 두 엔티티의 관계에서 한쪽의 엔티티만 참조하는 형식
  • 양방향 : 두 엔티티의 관계에서 각 엔티티가 서로의 엔티티를 참조하는 형식

연관관계가 설정되면 한 테이블에서 다른 테이블의 기본값을 외래키로 갖게 됩니다.

이런 관계에서는 주인(Owner)이라는 개념이 사용됩니다. 일반적으로 외래키를 가진 테이블이 그 관계의 주인이 되며, 주인은 외래키를 사용할 수 있으나 상대 엔티티는 읽는 작업만 수행할 수 있습니다.

 

이전 예제에서 부서(Department)와 사원(Employee) 테이블의 관계는 1:N(일대다) 관계이며 예제에서는 일 대 다 관계에 대해 설명하겠습니다.

 

JPA에서 테이블간 조인을 할 경우 어노테이션을 이용하여 조인을 합니다.

*         JPA 조인 : @(어노테이션)을 이용해서 조인함
*         부모(부서)-자식(사원) 관계
*                            1) 1:N - @OneToMany(부서), @ManytoOne(사원)
*                            2) 1:1 - @OneToOne(핸드폰), @OneToOne(사람)
*                            3) N:M - @ManyToMany (x)
 *             추천 : 1) 1 : N  : @OneToMany(부서), @ManyToOne(사원)
 *                       => 양방향 조인(불가피할 때 사용 -> 사용시 여러가지 문제 발생)
 *                 1-1) : 부서쪽에는 어노테이션 x, 사원에는 @ManyToOne 사용
 *                       => 단방향 조인(추천)

📖 일대다, 다대일 매핑

부서 테이블과 사원 테이블은 부서 테이블의 입장에서는 일대다(하나의 부서에는 여러명의 사원 존재), 사원 테이블의 입장에서는 다대일 관계(사원은 하나의 부서에만 존재할 수 있음)로 볼 수 있습니다.

이러한 관계는 어떻게 구현해야 할지 직접 매핑하면서 알아보겠습니다 :)

 

📂 Employee.java

package com.example.jpacustomexam.model.exam04;

import com.example.jpacustomexam.model.BaseTimeEntity;
import lombok.*;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;

import javax.persistence.*;

@Entity
@Table(name = "TB_EMPLOYEE")
@SequenceGenerator(
        name = "SQ_EMPLOYEE_GENERATOR"
        , sequenceName = "SQ_EMPLOYEE"
        , initialValue = 1
        , allocationSize = 1
)
@Setter
@Getter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@DynamicInsert
@DynamicUpdate
public class Employee extends BaseTimeEntity {
    //    @Id : Primary Key 에 해당
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE
            , generator = "SQ_EMPLOYEE_GENERATOR"
    )
    @Column(columnDefinition = "NUMBER")
    private Integer eno;

    @Column(columnDefinition = "VARCHAR2(255)")
    private String ename;

    @Column(columnDefinition = "VARCHAR2(255)")
    private String job;

    @Column(columnDefinition = "NUMBER")
    private Integer manager;

    @Column(columnDefinition = "VARCHAR2(255)")
    private String hiredate;

    @Column(columnDefinition = "NUMBER")
    private Integer salary;

    @Column(columnDefinition = "NUMBER")
    private Integer commission;

//    @Column(columnDefinition = "NUMBER")
//    private Integer dno; // 공통 컬럼(생략, 자동으로 생성해줌)
//    단방향 조인
//    사용법 : @JoinColumn(name = "조인컬럼명")
//    양방향 조인 시 : N + 1 문제 발생
//                1) 즉시 로딩(EAGER) : 기본 설정 -> 지연 로딩(LAZY)
//                                     (fetch = FetchType.LAZY)
//                      optional = false -> 조인방법(inner, outerjoin)
    @ManyToOne
    @JoinColumn(name = "dno")
    private Department department;


}

 

우선 사원테이블에 단방향 조인을 설정합니다.

사용법은 @ManyToOne 어노테이션을 작성합니다.

그리고 조인할 컬럼을 설정해야하는데 @JoinColumn 어노테이션을 사용합니다. 예제에서는 dno(부서번호)가 외래키로 지정되어 있으므로 @JoinColumn(name = "dno")로 설정하였습니다.

 

📂 DepartmentRepository.java

DepartmentRepository 인터페이스를 새로 생성합니다.

package com.example.jpacustomexam.repository.exam04;

import com.example.jpacustomexam.model.exam04.Department;
import com.example.jpacustomexam.model.exam04.Employee;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

/**
 * packageName : com.example.jpacustomexam.repository.exam04
 * fileName : DepartmentRepository
 * author : GGG
 * date : 2023-10-19
 * description : 부서 레포지토리 (CRUD)
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-19         GGG          최초 생성
 */
@Repository
public interface DepartmentRepository extends JpaRepository<Department, Integer> {
    @Query(value = "select e from Employee e inner join e.department d ")
    Page<Employee> selectJoinPage2(Pageable pageable);
}

객체쿼리를 사용하여 쿼리문을 작성합니다

 

📂 DepartmentService.java

DepartmentService 파일을 새로생성합니다.

package com.example.jpacustomexam.service.exam04;

import com.example.jpacustomexam.model.exam04.Employee;
import com.example.jpacustomexam.repository.exam04.DepartmentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

@Service
public class DepartmentService {
    @Autowired
    DepartmentRepository departmentRepository;

//    단방향 조인 예제
    public Page<Employee> selectJoinPage2(Pageable pageable){
        Page<Employee> page = departmentRepository.selectJoinPage2(pageable);
        return page;
    }
}

📂 DepartmentController.java

 DepartmentController 파일을 새로 생성합니다.

package com.example.jpacustomexam.controller.exam04;

import com.example.jpacustomexam.dto.DeptEmpDto;
import com.example.jpacustomexam.model.exam04.Employee;
import com.example.jpacustomexam.service.exam04.DepartmentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@RestController
@RequestMapping("/exam04")
public class DepartmentController {
    @Autowired
    DepartmentService departmentService;

   /** 단방향 조인 예제 */
    @GetMapping("/dept/join/paging2")
    public ResponseEntity<Object> selectJoinPage2(Pageable pageable) {
        try {
            Page<Employee> page
                    = departmentService.selectJoinPage2(pageable);
//          todo : Map 자료구조 정보 저장 : 1) 부서 객체, 2) 페이징 정보 (3개)
            Map<String, Object> response = new HashMap<>();
            response.put("emp", page.getContent());                         // 사원 객체
            response.put("currentPage", page.getNumber());                  // 현재페이지 번호
            response.put("totalItems", page.getTotalElements());            // 전체 테이블 건수
            response.put("totalPage", page.getTotalPages());                // 전체페이지 수

            if (page.isEmpty() == false) {
//                성공
                return new ResponseEntity<>(response, HttpStatus.OK);
            } else {
//                데이터 없음
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }

        } catch (Exception e){
            log.debug(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}

📌 실행결과

더보기
GET http://localhost:8000/exam04/dept/join/paging2

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 19 Oct 2023 01:37:45 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "totalItems": 14,
  "totalPage": 1,
  "emp": [
    {
      "insertTime": "2023-10-19 01:37:39",
      "updateTime": null,
      "eno": 7369,
      "ename": "SMITH",
      "job": "CLERK",
      "manager": 7902,
      "hiredate": "1980-12-17 00:00:00",
      "salary": 800,
      "commission": null,
      "department": {
        "insertTime": "2023-10-19 01:37:39",
        "updateTime": null,
        "dno": 20,
        "dname": "RESEARCH",
        "loc": "DALLAS"
      }
    },
    {
      "insertTime": "2023-10-19 01:37:39",
      "updateTime": null,
      "eno": 7499,
      "ename": "ALLEN",
      "job": "SALESMAN",
      "manager": 7698,
      "hiredate": "1981-02-20 00:00:00",
      "salary": 1600,
      "commission": 300,
      "department": {
        "insertTime": "2023-10-19 01:37:39",
        "updateTime": null,
        "dno": 30,
        "dname": "SALES",
        "loc": "CHICAGO"
      }
    },
    {
      "insertTime": "2023-10-19 01:37:39",
      "updateTime": null,
      "eno": 7521,
      "ename": "WARD",
      "job": "SALESMAN",
      "manager": 7698,
      "hiredate": "1981-02-22 00:00:00",
      "salary": 1250,
      "commission": 500,
      "department": {
        "insertTime": "2023-10-19 01:37:39",
        "updateTime": null,
        "dno": 30,
        "dname": "SALES",
        "loc": "CHICAGO"
      }
    },
    {
      "insertTime": "2023-10-19 01:37:39",
      "updateTime": null,
      "eno": 7566,
      "ename": "JONES",
      "job": "MANAGER",
      "manager": 7839,
      "hiredate": "1981-04-02 00:00:00",
      "salary": 2975,
      "commission": null,
      "department": {
        "insertTime": "2023-10-19 01:37:39",
        "updateTime": null,
        "dno": 20,
        "dname": "RESEARCH",
        "loc": "DALLAS"
      }
    },
    {
      "insertTime": "2023-10-19 01:37:39",
      "updateTime": null,
      "eno": 7654,
      "ename": "MARTIN",
      "job": "SALESMAN",
      "manager": 7698,
      "hiredate": "1981-09-28 00:00:00",
      "salary": 1250,
      "commission": 1400,
      "department": {
        "insertTime": "2023-10-19 01:37:39",
        "updateTime": null,
        "dno": 30,
        "dname": "SALES",
        "loc": "CHICAGO"
      }
    },
    {
      "insertTime": "2023-10-19 01:37:39",
      "updateTime": null,
      "eno": 7698,
      "ename": "BLAKE",
      "job": "MANAGER",
      "manager": 7839,
      "hiredate": "1981-05-01 00:00:00",
      "salary": 2850,
      "commission": null,
      "department": {
        "insertTime": "2023-10-19 01:37:39",
        "updateTime": null,
        "dno": 30,
        "dname": "SALES",
        "loc": "CHICAGO"
      }
    },
    {
      "insertTime": "2023-10-19 01:37:39",
      "updateTime": null,
      "eno": 7782,
      "ename": "CLARK",
      "job": "MANAGER",
      "manager": 7839,
      "hiredate": "1981-06-09 00:00:00",
      "salary": 2450,
      "commission": null,
      "department": {
        "insertTime": "2023-10-19 01:37:39",
        "updateTime": null,
        "dno": 10,
        "dname": "ACCOUNTING",
        "loc": "NEW YORK"
      }
    },
    {
      "insertTime": "2023-10-19 01:37:39",
      "updateTime": null,
      "eno": 7788,
      "ename": "SCOTT",
      "job": "ANALYST",
      "manager": 7566,
      "hiredate": "1987-07-13 00:00:00",
      "salary": 3000,
      "commission": null,
      "department": {
        "insertTime": "2023-10-19 01:37:39",
        "updateTime": null,
        "dno": 20,
        "dname": "RESEARCH",
        "loc": "DALLAS"
      }
    },
    {
      "insertTime": "2023-10-19 01:37:39",
      "updateTime": null,
      "eno": 7839,
      "ename": "KING",
      "job": "PRESIDENT",
      "manager": null,
      "hiredate": "1981-11-17 00:00:00",
      "salary": 5000,
      "commission": null,
      "department": {
        "insertTime": "2023-10-19 01:37:39",
        "updateTime": null,
        "dno": 10,
        "dname": "ACCOUNTING",
        "loc": "NEW YORK"
      }
    },
    {
      "insertTime": "2023-10-19 01:37:39",
      "updateTime": null,
      "eno": 7844,
      "ename": "TURNER",
      "job": "SALESMAN",
      "manager": 7698,
      "hiredate": "1981-09-08 00:00:00",
      "salary": 1500,
      "commission": 0,
      "department": {
        "insertTime": "2023-10-19 01:37:39",
        "updateTime": null,
        "dno": 30,
        "dname": "SALES",
        "loc": "CHICAGO"
      }
    },
    {
      "insertTime": "2023-10-19 01:37:39",
      "updateTime": null,
      "eno": 7876,
      "ename": "ADAMS",
      "job": "CLERK",
      "manager": 7788,
      "hiredate": "1987-07-13 00:00:00",
      "salary": 1100,
      "commission": null,
      "department": {
        "insertTime": "2023-10-19 01:37:39",
        "updateTime": null,
        "dno": 20,
        "dname": "RESEARCH",
        "loc": "DALLAS"
      }
    },
    {
      "insertTime": "2023-10-19 01:37:39",
      "updateTime": null,
      "eno": 7900,
      "ename": "JAMES",
      "job": "CLERK",
      "manager": 7698,
      "hiredate": "1981-12-03 00:00:00",
      "salary": 950,
      "commission": null,
      "department": {
        "insertTime": "2023-10-19 01:37:39",
        "updateTime": null,
        "dno": 30,
        "dname": "SALES",
        "loc": "CHICAGO"
      }
    },
    {
      "insertTime": "2023-10-19 01:37:39",
      "updateTime": null,
      "eno": 7902,
      "ename": "FORD",
      "job": "ANALYST",
      "manager": 7566,
      "hiredate": "1981-12-03 00:00:00",
      "salary": 3000,
      "commission": null,
      "department": {
        "insertTime": "2023-10-19 01:37:39",
        "updateTime": null,
        "dno": 20,
        "dname": "RESEARCH",
        "loc": "DALLAS"
      }
    },
    {
      "insertTime": "2023-10-19 01:37:39",
      "updateTime": null,
      "eno": 7934,
      "ename": "MILLER",
      "job": "CLERK",
      "manager": 7782,
      "hiredate": "1982-01-23 00:00:00",
      "salary": 1300,
      "commission": null,
      "department": {
        "insertTime": "2023-10-19 01:37:39",
        "updateTime": null,
        "dno": 10,
        "dname": "ACCOUNTING",
        "loc": "NEW YORK"
      }
    }
  ],
  "currentPage": 0
}
응답 파일이 저장되었습니다.
> 2023-10-19T103745.200.json

Response code: 200; Time: 623ms (623 ms); Content length: 4092 bytes (4.09 kB)

📖 양방향 조인

📂 Department.java 추가

//    양방향 조인 : 1) 순환참조 문제 - 해결 : @JsonManagedReference(부서)
//                                       @JsonBackReference(사원)
//                2) N + 1 문제
//    사용법 : @OneToMany(mappedBy = "사원_연결속성")
    @OneToMany(mappedBy = "department")
    @JsonManagedReference
    private Set<Employee> employee = new HashSet<>(); // 1:n(사원)
}

📂 Employee.java 추가

package com.example.jpacustomexam.model.exam04;

import com.example.jpacustomexam.model.BaseTimeEntity;
import com.fasterxml.jackson.annotation.JsonBackReference;
import lombok.*;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;

import javax.persistence.*;

/**
 * packageName : com.example.jpacustomexam.model.exam04
 * fileName : Employee
 * author : GGG
 * date : 2023-10-19
 * description :
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-19         GGG          최초 생성
 */
@Entity
@Table(name = "TB_EMPLOYEE")
@SequenceGenerator(
        name = "SQ_EMPLOYEE_GENERATOR"
        , sequenceName = "SQ_EMPLOYEE"
        , initialValue = 1
        , allocationSize = 1
)
@Setter
@Getter
@ToString(exclude = "department")   // 순환참조 방지
@Builder
@NoArgsConstructor
@AllArgsConstructor
@DynamicInsert
@DynamicUpdate
public class Employee extends BaseTimeEntity {
    //    @Id : Primary Key 에 해당
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE
            , generator = "SQ_EMPLOYEE_GENERATOR"
    )
    @Column(columnDefinition = "NUMBER")
    private Integer eno;

    @Column(columnDefinition = "VARCHAR2(255)")
    private String ename;

    @Column(columnDefinition = "VARCHAR2(255)")
    private String job;

    @Column(columnDefinition = "NUMBER")
    private Integer manager;

    @Column(columnDefinition = "VARCHAR2(255)")
    private String hiredate;

    @Column(columnDefinition = "NUMBER")
    private Integer salary;

    @Column(columnDefinition = "NUMBER")
    private Integer commission;

//    @Column(columnDefinition = "NUMBER")
//    private Integer dno; // 공통 컬럼(생략, 자동으로 생성해줌)
//    단방향 조인
//    사용법 : @JoinColumn(name = "조인컬럼명")
//    양방향 조인 시 : N + 1 문제 발생 - 조인이 안되고 각각 SQL문이 생성되는 현상
//                1) 즉시 로딩(EAGER) : 기본 설정 -> 지연 로딩(LAZY)
//                                     (fetch = FetchType.LAZY)
//                      optional = false -> 조인방법(inner, outerjoin)
//                2) inner join fetch : 조인 쿼리 생성
//                3) application.properties :
//                   N + 1 sql -> where in (?,?....?) 값으로 변경하는 옵션
//              spring.jpa.properties.hibernate.default_batch_fetch_size=1000
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "dno")
    @JsonBackReference
    private Department department;


}

📖 지연 로딩과 즉시로딩

JPA에서 지연로딩(lazy loading)과 즉시로딩(eager loading)은 중요한 개념중 하나입니다.

엔티티라는 객체의 개념으로 데이터베이스를 구현했기 때문에 연관관계를 가진 각 엔티티 클래스에는 연관관계가 있는 객체들이 필드에 존재하게 됩니다. 연관관계와 상관업싱 즉각 해당 엔티티의 값만 조회하고 싶거나 연관관계를 가진 테이블의 값도 조회하고 싶은 경우 등 여러 조건들을 만족하기 위해 등장한 개념이 지연로딩과 즉시로딩입니다.

 

📂 DepartmentService.java 추가

//    양방향 조인 예제 : fetch join
public List<Department> selectFetchJoin() {
    List<Department> page = departmentRepository.selectFetchJoin();

    return page;
}

📂 DepartmentRepository.java 추가

    //    양방향 조인 : fetch join -> Paging 안됨
    @Query(value = "select distinct d from Department d inner join fetch d.employee e ")
    List<Department> selectFetchJoin();

📂 DepartmentController.java 추가

    /** 양방향 조인 예제 */
    //    page=현재페이지번호(0 ~ n), size=전체페이지수
    @GetMapping("/dept/fetch/join")
    public ResponseEntity<Object> selectFetchJoin() {
        try {
            List<Department> page
                    = departmentService.selectFetchJoin();

            if (page.isEmpty() == false) {
//                성공
                return new ResponseEntity<>(page, HttpStatus.OK);
            } else {
//                데이터 없음
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }

        } catch (Exception e){
            log.debug(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

✅ 실행결과

더보기
GET http://localhost:8000/exam04/dept/fetch/join

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 19 Oct 2023 02:26:02 GMT
Keep-Alive: timeout=60
Connection: keep-alive

[
  {
    "insertTime": "2023-10-19 02:20:28",
    "updateTime": null,
    "dno": 20,
    "dname": "RESEARCH",
    "loc": "DALLAS",
    "employee": [
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7902,
        "ename": "FORD",
        "job": "ANALYST",
        "manager": 7566,
        "hiredate": "1981-12-03 00:00:00",
        "salary": 3000,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7788,
        "ename": "SCOTT",
        "job": "ANALYST",
        "manager": 7566,
        "hiredate": "1987-07-13 00:00:00",
        "salary": 3000,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7566,
        "ename": "JONES",
        "job": "MANAGER",
        "manager": 7839,
        "hiredate": "1981-04-02 00:00:00",
        "salary": 2975,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7369,
        "ename": "SMITH",
        "job": "CLERK",
        "manager": 7902,
        "hiredate": "1980-12-17 00:00:00",
        "salary": 800,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7876,
        "ename": "ADAMS",
        "job": "CLERK",
        "manager": 7788,
        "hiredate": "1987-07-13 00:00:00",
        "salary": 1100,
        "commission": null
      }
    ]
  },
  {
    "insertTime": "2023-10-19 02:20:28",
    "updateTime": null,
    "dno": 30,
    "dname": "SALES",
    "loc": "CHICAGO",
    "employee": [
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7900,
        "ename": "JAMES",
        "job": "CLERK",
        "manager": 7698,
        "hiredate": "1981-12-03 00:00:00",
        "salary": 950,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7698,
        "ename": "BLAKE",
        "job": "MANAGER",
        "manager": 7839,
        "hiredate": "1981-05-01 00:00:00",
        "salary": 2850,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7844,
        "ename": "TURNER",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-09-08 00:00:00",
        "salary": 1500,
        "commission": 0
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7499,
        "ename": "ALLEN",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-02-20 00:00:00",
        "salary": 1600,
        "commission": 300
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7521,
        "ename": "WARD",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-02-22 00:00:00",
        "salary": 1250,
        "commission": 500
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7654,
        "ename": "MARTIN",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-09-28 00:00:00",
        "salary": 1250,
        "commission": 1400
      }
    ]
  },
  {
    "insertTime": "2023-10-19 02:20:28",
    "updateTime": null,
    "dno": 30,
    "dname": "SALES",
    "loc": "CHICAGO",
    "employee": [
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7900,
        "ename": "JAMES",
        "job": "CLERK",
        "manager": 7698,
        "hiredate": "1981-12-03 00:00:00",
        "salary": 950,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7698,
        "ename": "BLAKE",
        "job": "MANAGER",
        "manager": 7839,
        "hiredate": "1981-05-01 00:00:00",
        "salary": 2850,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7844,
        "ename": "TURNER",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-09-08 00:00:00",
        "salary": 1500,
        "commission": 0
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7499,
        "ename": "ALLEN",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-02-20 00:00:00",
        "salary": 1600,
        "commission": 300
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7521,
        "ename": "WARD",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-02-22 00:00:00",
        "salary": 1250,
        "commission": 500
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7654,
        "ename": "MARTIN",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-09-28 00:00:00",
        "salary": 1250,
        "commission": 1400
      }
    ]
  },
  {
    "insertTime": "2023-10-19 02:20:28",
    "updateTime": null,
    "dno": 20,
    "dname": "RESEARCH",
    "loc": "DALLAS",
    "employee": [
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7902,
        "ename": "FORD",
        "job": "ANALYST",
        "manager": 7566,
        "hiredate": "1981-12-03 00:00:00",
        "salary": 3000,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7788,
        "ename": "SCOTT",
        "job": "ANALYST",
        "manager": 7566,
        "hiredate": "1987-07-13 00:00:00",
        "salary": 3000,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7566,
        "ename": "JONES",
        "job": "MANAGER",
        "manager": 7839,
        "hiredate": "1981-04-02 00:00:00",
        "salary": 2975,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7369,
        "ename": "SMITH",
        "job": "CLERK",
        "manager": 7902,
        "hiredate": "1980-12-17 00:00:00",
        "salary": 800,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7876,
        "ename": "ADAMS",
        "job": "CLERK",
        "manager": 7788,
        "hiredate": "1987-07-13 00:00:00",
        "salary": 1100,
        "commission": null
      }
    ]
  },
  {
    "insertTime": "2023-10-19 02:20:28",
    "updateTime": null,
    "dno": 30,
    "dname": "SALES",
    "loc": "CHICAGO",
    "employee": [
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7900,
        "ename": "JAMES",
        "job": "CLERK",
        "manager": 7698,
        "hiredate": "1981-12-03 00:00:00",
        "salary": 950,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7698,
        "ename": "BLAKE",
        "job": "MANAGER",
        "manager": 7839,
        "hiredate": "1981-05-01 00:00:00",
        "salary": 2850,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7844,
        "ename": "TURNER",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-09-08 00:00:00",
        "salary": 1500,
        "commission": 0
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7499,
        "ename": "ALLEN",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-02-20 00:00:00",
        "salary": 1600,
        "commission": 300
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7521,
        "ename": "WARD",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-02-22 00:00:00",
        "salary": 1250,
        "commission": 500
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7654,
        "ename": "MARTIN",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-09-28 00:00:00",
        "salary": 1250,
        "commission": 1400
      }
    ]
  },
  {
    "insertTime": "2023-10-19 02:20:28",
    "updateTime": null,
    "dno": 30,
    "dname": "SALES",
    "loc": "CHICAGO",
    "employee": [
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7900,
        "ename": "JAMES",
        "job": "CLERK",
        "manager": 7698,
        "hiredate": "1981-12-03 00:00:00",
        "salary": 950,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7698,
        "ename": "BLAKE",
        "job": "MANAGER",
        "manager": 7839,
        "hiredate": "1981-05-01 00:00:00",
        "salary": 2850,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7844,
        "ename": "TURNER",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-09-08 00:00:00",
        "salary": 1500,
        "commission": 0
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7499,
        "ename": "ALLEN",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-02-20 00:00:00",
        "salary": 1600,
        "commission": 300
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7521,
        "ename": "WARD",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-02-22 00:00:00",
        "salary": 1250,
        "commission": 500
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7654,
        "ename": "MARTIN",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-09-28 00:00:00",
        "salary": 1250,
        "commission": 1400
      }
    ]
  },
  {
    "insertTime": "2023-10-19 02:20:28",
    "updateTime": null,
    "dno": 10,
    "dname": "ACCOUNTING",
    "loc": "NEW YORK",
    "employee": [
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7839,
        "ename": "KING",
        "job": "PRESIDENT",
        "manager": null,
        "hiredate": "1981-11-17 00:00:00",
        "salary": 5000,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7782,
        "ename": "CLARK",
        "job": "MANAGER",
        "manager": 7839,
        "hiredate": "1981-06-09 00:00:00",
        "salary": 2450,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7934,
        "ename": "MILLER",
        "job": "CLERK",
        "manager": 7782,
        "hiredate": "1982-01-23 00:00:00",
        "salary": 1300,
        "commission": null
      }
    ]
  },
  {
    "insertTime": "2023-10-19 02:20:28",
    "updateTime": null,
    "dno": 20,
    "dname": "RESEARCH",
    "loc": "DALLAS",
    "employee": [
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7902,
        "ename": "FORD",
        "job": "ANALYST",
        "manager": 7566,
        "hiredate": "1981-12-03 00:00:00",
        "salary": 3000,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7788,
        "ename": "SCOTT",
        "job": "ANALYST",
        "manager": 7566,
        "hiredate": "1987-07-13 00:00:00",
        "salary": 3000,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7566,
        "ename": "JONES",
        "job": "MANAGER",
        "manager": 7839,
        "hiredate": "1981-04-02 00:00:00",
        "salary": 2975,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7369,
        "ename": "SMITH",
        "job": "CLERK",
        "manager": 7902,
        "hiredate": "1980-12-17 00:00:00",
        "salary": 800,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7876,
        "ename": "ADAMS",
        "job": "CLERK",
        "manager": 7788,
        "hiredate": "1987-07-13 00:00:00",
        "salary": 1100,
        "commission": null
      }
    ]
  },
  {
    "insertTime": "2023-10-19 02:20:28",
    "updateTime": null,
    "dno": 10,
    "dname": "ACCOUNTING",
    "loc": "NEW YORK",
    "employee": [
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7839,
        "ename": "KING",
        "job": "PRESIDENT",
        "manager": null,
        "hiredate": "1981-11-17 00:00:00",
        "salary": 5000,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7782,
        "ename": "CLARK",
        "job": "MANAGER",
        "manager": 7839,
        "hiredate": "1981-06-09 00:00:00",
        "salary": 2450,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7934,
        "ename": "MILLER",
        "job": "CLERK",
        "manager": 7782,
        "hiredate": "1982-01-23 00:00:00",
        "salary": 1300,
        "commission": null
      }
    ]
  },
  {
    "insertTime": "2023-10-19 02:20:28",
    "updateTime": null,
    "dno": 30,
    "dname": "SALES",
    "loc": "CHICAGO",
    "employee": [
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7900,
        "ename": "JAMES",
        "job": "CLERK",
        "manager": 7698,
        "hiredate": "1981-12-03 00:00:00",
        "salary": 950,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7698,
        "ename": "BLAKE",
        "job": "MANAGER",
        "manager": 7839,
        "hiredate": "1981-05-01 00:00:00",
        "salary": 2850,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7844,
        "ename": "TURNER",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-09-08 00:00:00",
        "salary": 1500,
        "commission": 0
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7499,
        "ename": "ALLEN",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-02-20 00:00:00",
        "salary": 1600,
        "commission": 300
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7521,
        "ename": "WARD",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-02-22 00:00:00",
        "salary": 1250,
        "commission": 500
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7654,
        "ename": "MARTIN",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-09-28 00:00:00",
        "salary": 1250,
        "commission": 1400
      }
    ]
  },
  {
    "insertTime": "2023-10-19 02:20:28",
    "updateTime": null,
    "dno": 20,
    "dname": "RESEARCH",
    "loc": "DALLAS",
    "employee": [
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7902,
        "ename": "FORD",
        "job": "ANALYST",
        "manager": 7566,
        "hiredate": "1981-12-03 00:00:00",
        "salary": 3000,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7788,
        "ename": "SCOTT",
        "job": "ANALYST",
        "manager": 7566,
        "hiredate": "1987-07-13 00:00:00",
        "salary": 3000,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7566,
        "ename": "JONES",
        "job": "MANAGER",
        "manager": 7839,
        "hiredate": "1981-04-02 00:00:00",
        "salary": 2975,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7369,
        "ename": "SMITH",
        "job": "CLERK",
        "manager": 7902,
        "hiredate": "1980-12-17 00:00:00",
        "salary": 800,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7876,
        "ename": "ADAMS",
        "job": "CLERK",
        "manager": 7788,
        "hiredate": "1987-07-13 00:00:00",
        "salary": 1100,
        "commission": null
      }
    ]
  },
  {
    "insertTime": "2023-10-19 02:20:28",
    "updateTime": null,
    "dno": 30,
    "dname": "SALES",
    "loc": "CHICAGO",
    "employee": [
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7900,
        "ename": "JAMES",
        "job": "CLERK",
        "manager": 7698,
        "hiredate": "1981-12-03 00:00:00",
        "salary": 950,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7698,
        "ename": "BLAKE",
        "job": "MANAGER",
        "manager": 7839,
        "hiredate": "1981-05-01 00:00:00",
        "salary": 2850,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7844,
        "ename": "TURNER",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-09-08 00:00:00",
        "salary": 1500,
        "commission": 0
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7499,
        "ename": "ALLEN",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-02-20 00:00:00",
        "salary": 1600,
        "commission": 300
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7521,
        "ename": "WARD",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-02-22 00:00:00",
        "salary": 1250,
        "commission": 500
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7654,
        "ename": "MARTIN",
        "job": "SALESMAN",
        "manager": 7698,
        "hiredate": "1981-09-28 00:00:00",
        "salary": 1250,
        "commission": 1400
      }
    ]
  },
  {
    "insertTime": "2023-10-19 02:20:28",
    "updateTime": null,
    "dno": 20,
    "dname": "RESEARCH",
    "loc": "DALLAS",
    "employee": [
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7902,
        "ename": "FORD",
        "job": "ANALYST",
        "manager": 7566,
        "hiredate": "1981-12-03 00:00:00",
        "salary": 3000,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7788,
        "ename": "SCOTT",
        "job": "ANALYST",
        "manager": 7566,
        "hiredate": "1987-07-13 00:00:00",
        "salary": 3000,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7566,
        "ename": "JONES",
        "job": "MANAGER",
        "manager": 7839,
        "hiredate": "1981-04-02 00:00:00",
        "salary": 2975,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7369,
        "ename": "SMITH",
        "job": "CLERK",
        "manager": 7902,
        "hiredate": "1980-12-17 00:00:00",
        "salary": 800,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7876,
        "ename": "ADAMS",
        "job": "CLERK",
        "manager": 7788,
        "hiredate": "1987-07-13 00:00:00",
        "salary": 1100,
        "commission": null
      }
    ]
  },
  {
    "insertTime": "2023-10-19 02:20:28",
    "updateTime": null,
    "dno": 10,
    "dname": "ACCOUNTING",
    "loc": "NEW YORK",
    "employee": [
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7839,
        "ename": "KING",
        "job": "PRESIDENT",
        "manager": null,
        "hiredate": "1981-11-17 00:00:00",
        "salary": 5000,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7782,
        "ename": "CLARK",
        "job": "MANAGER",
        "manager": 7839,
        "hiredate": "1981-06-09 00:00:00",
        "salary": 2450,
        "commission": null
      },
      {
        "insertTime": "2023-10-19 02:20:28",
        "updateTime": null,
        "eno": 7934,
        "ename": "MILLER",
        "job": "CLERK",
        "manager": 7782,
        "hiredate": "1982-01-23 00:00:00",
        "salary": 1300,
        "commission": null
      }
    ]
  }
]
응답 파일이 저장되었습니다.
> 2023-10-19T112602.200.json

Response code: 200; Time: 17ms (17 ms); Content length: 13967 bytes (13.97 kB)

 

📖 1 : 1 관계

2) 1:1 - @OneToOne(핸드폰), @OneToOne(사람)
  • Person 과 Phone 모델 클래스 생성

📂 Person.java

package com.example.jpacustomexam.model.exam05;

import com.example.jpacustomexam.model.BaseTimeEntity;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import lombok.*;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;

import javax.persistence.*;

/**
 * packageName : com.example.jpacustomexam.model.exam05
 * fileName : Person
 * author : GGG
 * date : 2023-10-19
 * description :
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-19         GGG          최초 생성
 */
@Entity
@Table(name="TB_PERSON")
@SequenceGenerator(
        name = "SQ_PERSON_GENERATOR"
        , sequenceName = "SQ_PERSON"
        , initialValue = 1
        , allocationSize = 1
)
@Getter
@Setter
//@ToString(exclude = "emp")
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@DynamicInsert
@DynamicUpdate
public class Person extends BaseTimeEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SQ_PERSON_GENERATOR")
    @Column(columnDefinition = "NUMBER")
    private Integer no;     // 기본키

    @Column(columnDefinition = "VARCHAR2(255)")
    private String name;

    @Column(columnDefinition = "VARCHAR2(255)")
    private String job;

//    1:1 관계 설정 : @OneToOne(사람:부모), @OneToOne(폰:자식)
//    사용법 : @OneToOne(mappedBy = "자식 속성명")
    @OneToOne(mappedBy = "person")
//    순환참조 해결
    @JsonManagedReference
    private Phone phone;

}

📂 Phone.java

package com.example.jpacustomexam.model.exam05;

import com.example.jpacustomexam.model.BaseTimeEntity;
import com.fasterxml.jackson.annotation.JsonBackReference;
import lombok.*;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;

import javax.persistence.*;

/**
 * packageName : com.example.jpacustomexam.model.exam05
 * fileName : Phone
 * author : GGG
 * date : 2023-10-19
 * description :
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-19         GGG          최초 생성
 */
@Entity
@Table(name="TB_PHONE")
@SequenceGenerator(
        name = "SQ_PHONE_GENERATOR"
        , sequenceName = "SQ_PHONE"
        , initialValue = 1
        , allocationSize = 1
)
@Getter
@Setter
//@ToString(exclude = "emp")
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@DynamicInsert
@DynamicUpdate
public class Phone extends BaseTimeEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SQ_PHONE_GENERATOR")
    @Column(columnDefinition = "NUMBER")
    private Integer pno;

    @Column(columnDefinition = "VARCHAR2(255)")
    private String pname;

    @Column(columnDefinition = "VARCHAR2(255)")
    private String vender;

//    1:1 관계
//    사용법 : @JoinColumn(name ="부모 공통컬럼")
    @OneToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "no")
    @JsonBackReference
    private Person person;

}

 

반응형
반응형
// --------------------------------------------
// JPA 페이징 처리 : 요청 페이징 객체 - Pageable(매개변수)
//                 결과 페이징 객체 - Page     (리턴값)
// --------------------------------------------

📂 DeptRepository.java

✅ 쿼리 메소드 페이징 처리

Page<Dept> findAllByDnameContaining(String dname, Pageable pageable);

📂 DeptService.java

 //--------------------------------------------
    // 페이징 예제
    //--------------------------------------------
    // 1)
    public Page<Dept> findAllByDnameContaining(String dname, Pageable pageable) {
        Page<Dept> page
                = deptRepository.findAllByDnameContaining(dname, pageable);

        return page;
    }

    // 2) 기본 제공되는 전체조회 함수 : 페이징처리 기능 추가
    public Page<Dept> findAllPage(Pageable pageable) {
        Page<Dept> page
                = deptRepository.findAll(pageable);

        return page;
    }

📂 DeptController.java

//--------------------------------------------
    // 페이징 예제
    //--------------------------------------------
    // 1)
    @GetMapping("/dept/dname/{dname}/paging")
    public ResponseEntity<Object> getDeptDnamePage(@PathVariable String dname, Pageable pageable) {
        try {
            Page<Dept> page
                    = deptService.findAllByDnameContaining(dname, pageable);
//          todo : Map 자료구조 정보 저장 : 1) 부서 객체, 2) 페이징 정보 (3개)
            Map<String, Object> response = new HashMap<>();
            response.put("dept", page.getContent());                        // 부서 객체
            response.put("currentPage", page.getNumber());                  // 현재페이지 번호
            response.put("totalItems", page.getTotalElements());            // 전체 테이블 건수
            response.put("totalPage", page.getTotalPages());                // 전체페이지 수

            if (page.isEmpty() == false) {
//                성공
                return new ResponseEntity<>(response, HttpStatus.OK);
            } else {
//                데이터 없음
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }

        } catch (Exception e){
            log.debug(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    // 2) findAll() + 페이징
    // page=현재페이지번호(0~n), size=전체페이지수
    @GetMapping("/dept/all/paging")
    public ResponseEntity<Object> getDeptAllPage(Pageable pageable) {
        try {
            Page<Dept> page
                    = deptService.findAllPage(pageable);
//          todo : Map 자료구조 정보 저장 : 1) 부서 객체, 2) 페이징 정보 (3개)
            Map<String, Object> response = new HashMap<>();
            response.put("dept", page.getContent());                        // 부서 객체
            response.put("currentPage", page.getNumber());                  // 현재페이지 번호
            response.put("totalItems", page.getTotalElements());            // 전체 테이블 건수
            response.put("totalPage", page.getTotalPages());                // 전체페이지 수

            if (page.isEmpty() == false) {
//                성공
                return new ResponseEntity<>(response, HttpStatus.OK);
            } else {
//                데이터 없음
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }

        } catch (Exception e){
            log.debug(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

✅ 결과 - 1번 예제

GET http://localhost:8000/dept/dname/S/paging

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 18 Oct 2023 06:28:37 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "content": [
    {
      "insertTime": "2023-10-18 06:28:21",
      "updateTime": null,
      "dno": 20,
      "dname": "RESEARCH",
      "loc": "DALLAS"
    },
    {
      "insertTime": "2023-10-18 06:28:21",
      "updateTime": null,
      "dno": 30,
      "dname": "SALES",
      "loc": "CHICAGO"
    },
    {
      "insertTime": "2023-10-18 06:28:21",
      "updateTime": null,
      "dno": 40,
      "dname": "OPERATIONS",
      "loc": "BOSTON"
    }
  ],
  "pageable": {
    "sort": {
      "empty": true,
      "sorted": false,
      "unsorted": true
    },
    "offset": 0,
    "pageNumber": 0,
    "pageSize": 20,
    "paged": true,
    "unpaged": false
  },
  "last": true,
  "totalElements": 3,
  "totalPages": 1,
  "size": 20,
  "number": 0,
  "sort": {
    "empty": true,
    "sorted": false,
    "unsorted": true
  },
  "first": true,
  "numberOfElements": 3,
  "empty": false
}
응답 파일이 저장되었습니다.
> 2023-10-18T152837.200.json

Response code: 200; Time: 302ms (302 ms); Content length: 609 bytes (609 B)

✅ 결과 - 2번 예제

GET http://localhost:8000/dept/all/paging

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 18 Oct 2023 07:18:52 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "totalItems": 4,
  "totalPage": 1,
  "dept": [
    {
      "insertTime": "2023-10-18 07:18:02",
      "updateTime": null,
      "dno": 10,
      "dname": "ACCOUNTING",
      "loc": "NEW YORK"
    },
    {
      "insertTime": "2023-10-18 07:18:02",
      "updateTime": null,
      "dno": 20,
      "dname": "RESEARCH",
      "loc": "DALLAS"
    },
    {
      "insertTime": "2023-10-18 07:18:02",
      "updateTime": null,
      "dno": 30,
      "dname": "SALES",
      "loc": "CHICAGO"
    },
    {
      "insertTime": "2023-10-18 07:18:02",
      "updateTime": null,
      "dno": 40,
      "dname": "OPERATIONS",
      "loc": "BOSTON"
    }
  ],
  "currentPage": 0
}
응답 파일이 저장되었습니다.
> 2023-10-18T161852.200.json

Response code: 200; Time: 424ms (424 ms); Content length: 451 bytes (451 B)

✅ @Query 페이징 처리

📂 DeptRepository.java

 // 2) @Query : 페이징 처리(오라클 쿼리 : countQuery 속성 추가)
    @Query(value = "SELECT D.* FROM TB_DEPT D " +
            "WHERE D.DNAME LIKE '%'|| :dname || '%'",
            countQuery = "SELECT COUNT(*) FROM TB_DEPT D " +
                    "WHERE D.DNAME LIKE '%'|| :dname || '%'",
            nativeQuery = true)
    Page<Dept> selectByDnamePage(@Param("dname") String dname, Pageable pageable);

    // 3) @Query 페이징 + 조인(부서 + 사원) : DeptEmpDto
        @Query(value = "SELECT D.*, E.ENO, E.ENAME, E.SALARY " +
                "FROM TB_DEPT D, TB_EMP E " +
                "WHERE D.DNO = E.DNO",
                countQuery = "SELECT COUNT(*) " +
                        "FROM TB_DEPT D, TB_EMP E " +
                        "WHERE D.DNO = E.DNO",
                nativeQuery = true)
        Page<DeptEmpDto> selectNativeJoinPage(Pageable pageable);

📂 DeptService.java

// --------------------------------------------
// @Query : 페이징 처리
// --------------------------------------------
// 2)
public Page<Dept> selectByDnamePage(String dname, Pageable pageable) {
    Page<Dept> page
            = deptRepository.selectByDnamePage(dname, pageable);

    return page;
}

// 3) @Query 페이징 + 조인(부서 + 사원) : DeptEmpDto
public Page<DeptEmpDto> selectNativeJoinPage(Pageable pageable) {
    Page<DeptEmpDto> page
            = deptRepository.selectNativeJoinPage(pageable);

    return page;
}

📂 DeptController.java

// --------------------------------------------
    // @Query : 페이징 처리
    // --------------------------------------------
    // 2)
    @GetMapping("/dept/dname/{dname}/page")
    public ResponseEntity<Object> selectByDnamePage(String dname, Pageable pageable) {
        try {
            Page<Dept> page
                    = deptService.selectByDnamePage(dname, pageable);
//          todo : Map 자료구조 정보 저장 : 1) 부서 객체, 2) 페이징 정보 (3개)
            Map<String, Object> response = new HashMap<>();
            response.put("dept", page.getContent());                        // 부서 객체
            response.put("currentPage", page.getNumber());                  // 현재페이지 번호
            response.put("totalItems", page.getTotalElements());            // 전체 테이블 건수
            response.put("totalPage", page.getTotalPages());                // 전체페이지 수

            if (page.isEmpty() == false) {
//                성공
                return new ResponseEntity<>(response, HttpStatus.OK);
            } else {
//                데이터 없음
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }

        } catch (Exception e){
            log.debug(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    // 3) @Query 페이징 + 조인(부서 + 사원) : DeptEmpDto
    @GetMapping("/dept/native/join/page")
    public ResponseEntity<Object> selectNativeJoinPage(Pageable pageable) {
        try {
            Page<DeptEmpDto> page
                    = deptService.selectNativeJoinPage(pageable);
//          todo : Map 자료구조 정보 저장 : 1) 부서 객체, 2) 페이징 정보 (3개)
            Map<String, Object> response = new HashMap<>();
            response.put("dept", page.getContent());                        // 부서 객체
            response.put("currentPage", page.getNumber());                  // 현재페이지 번호
            response.put("totalItems", page.getTotalElements());            // 전체 테이블 건수
            response.put("totalPage", page.getTotalPages());                // 전체페이지 수

            if (page.isEmpty() == false) {
//                성공
                return new ResponseEntity<>(response, HttpStatus.OK);
            } else {
//                데이터 없음
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }

        } catch (Exception e){
            log.debug(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

 

반응형
반응형

💡 SQL을 추상화한 JPQL

JPA : ORM(Object-Relational Mapping) 프레임워크

JPQL(Java Persistence Query Language)

JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어를 제공합니다.

따라서 테이블을 대상으로 쿼리 하는 것이 아닌 엔티티 객체를 대상으로 쿼리합니다.

JPQL은 SQL을 추상화했기 때문에 특정 데이터베이스 SQL에 의존하지 않는 장점이 있습니다.

JPQL은 SQL과 문법이 유사하며, SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN을 지원합니다.


💡 쿼리메소드를 사용한 쿼리문 만들기

✅ TODO 1) 전체 조회 + 정렬(내림차순)

📂 DeptRepository.java

package com.example.jpacustomexam.repository;

import com.example.jpacustomexam.model.Dept;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * packageName : com.example.jpacustomexam.repository
 * fileName : DeptRepository
 * author : GGG
 * date : 2023-10-17
 * description : 부서 레포지토리 (기본 CRUD 함수)
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-17         GGG          최초 생성
 */
@Repository
public interface DeptRepository extends JpaRepository<Dept, Integer> {
    // 개발자가 직접 SQL 작성 하는 기능(JPQL) : 1) 쿼리메소드
    //                                      2) @Query 쓰는 방법
    // todo: 1) 전체조회 + 정렬(내림차순)
    List<Dept> findAllByOrderByDnoDesc();
}

📂 DeptService.java

package com.example.jpacustomexam.service;

import com.example.jpacustomexam.model.Dept;
import com.example.jpacustomexam.repository.DeptRepository;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * packageName : com.example.jpacustomexam.service
 * fileName : DeptService
 * author : GGG
 * date : 2023-10-17
 * description : 부서 서비스
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-17         GGG          최초 생성
 */
@Service
public class DeptService {
    @Autowired
    DeptRepository deptRepository;

    /** 전체조회 + 정렬(dno 내림차순) */
    // todo) 쿼리메소드를 사용한 함수
    public List<Dept> findAllByOrderByDnoDesc(){
        List<Dept> list = deptRepository.findAllByOrderByDnoDesc();
        return list;
    }
}

 

📂 DeptController.java

package com.example.jpacustomexam.controller;

import com.example.jpacustomexam.model.Dept;
import com.example.jpacustomexam.service.DeptService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * packageName : com.example.jpacustomexam.controller
 * fileName : DeptController
 * author : GGG
 * date : 2023-10-17
 * description : 부서 컨트롤러 (리액트 용)
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-17         GGG          최초 생성
 */
@Slf4j
@RestController
public class DeptController {
    @Autowired
    DeptService deptService;    // DI

    /** 전체 조회 + 정렬(dno 내림차순) */
    @GetMapping("/dept/desc")
    public ResponseEntity<Object> getDeptAllDesc(){
        try {
            // todo : 전체 조회 + 정렬(dno 내림차순)함수 호출
            List<Dept> list = deptService.findAllByOrderByDnoDesc();
            if (list.isEmpty() == false) {
//                성공
                return new ResponseEntity<>(list, HttpStatus.OK);
            } else {
//                데이터 없음
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }
        } catch (Exception e) {
            log.debug(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}

 

✔ 서버 재시작 후 API 테스트 

부서번호가 내림차순으로 정렬된 것을 알 수 있습니다.

1) 쿼리메소드 : 함수이름으로 sql문 생성

예) findAllByOrderByDnoDesc

findAllByOrderByDnoDesc
findAll 전체조회
OrderBy 정렬
Dno 대상컬럼명
Desc 내림차순

 

💡 JPA의 쿼리메소드 샘플은 다음과 같습니다.

출처:https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods         

Distinct findDistinctByLastnameAndFirstname select distinct …​ where x.lastname = ?1 and x.firstname = ?2
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is, Equals findByFirstname,findByFirstnameIs,findByFirstnameEquals … where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age <= ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull, Null findByAge(Is)Null … where x.age is null
IsNotNull, NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1 (parameter bound with appended %)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection<Age> ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection<Age> ages) … where x.age not in ?1
True findByActiveTrue() … where x.active = true
False findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstname) = UPPER(?1)

✅ TODO 2) 전체 조회 + 정렬(내림차순) - 부서명으로 내림차순, 부서번호 오름차순

📂 DeptRepository.java 추가

    // todo: 2) 전체조회 + 정렬(부서명 내림차순)
    List<Dept> findAllByOrderByDnameDesc();

    // todo: 3) 전체조회 + 정렬(부서번호 오름차순) : Asc 생략가능
    List<Dept> findAllByOrderByDno();
}

📂 DeptService.java 추가

/** 전체조회 + 정렬(dname 내림차순) : 쿼리메소드 */
public List<Dept> findAllByOrderByDnameDesc(){
    List<Dept> list = deptRepository.findAllByOrderByDnameDesc();
    return list;
}

/** 전체조회 + 정렬(dno 오름차순) : 쿼리메소드 */
public List<Dept> findAllByOrderByDno(){
    List<Dept> list = deptRepository.findAllByOrderByDno();
    return list;
}

📂 DeptController.java 추가

 /** 전체 조회 + 정렬(dname 내림차순) */
    @GetMapping("/dept/dname/desc")
    public ResponseEntity<Object> findAllByOrderByDnameDesc(){
        try {
            // todo : 전체 조회 + 정렬(dno 내림차순)함수 호출
            List<Dept> list = deptService.findAllByOrderByDnameDesc();
            if (list.isEmpty() == false) {
//                성공
                return new ResponseEntity<>(list, HttpStatus.OK);
            } else {
//                데이터 없음
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }
        } catch (Exception e) {
            log.debug(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
    /** 전체 조회 + 정렬(dno 오름차순) */
    @GetMapping("/dept/dno/asc")
    public ResponseEntity<Object> findAllByOrderByDno(){
        try {
            // todo : 전체 조회 + 정렬(dno 내림차순)함수 호출
            List<Dept> list = deptService.findAllByOrderByDno();
            if (list.isEmpty() == false) {
//                성공
                return new ResponseEntity<>(list, HttpStatus.OK);
            } else {
//                데이터 없음
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }
        } catch (Exception e) {
            log.debug(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

✔ 서버 재시작 후 API 테스트 

1) 전체조회 - 부서이름으로 내림차순

2) 전체조회 - 부서번호 오름차순

✅ TODO 3) 전체조회 + dname(부서명) like 검색 + dname 내림차순 조회

📂 DeptRepository.java 추가

// todo: 4) dname like 검색(DnameContaining)+ dname 내림차순 조회
List<Dept> findAllByDnameContainingOrderByDnameDesc(String dname);

📂 DeptService.java 추가

/** dname(부서명) like 검색 + dname 내림차순 조회 */
public List<Dept> findAllByDnameContainingOrderByDnameDesc(String dname){
    List<Dept> list 
            = deptRepository.findAllByDnameContainingOrderByDnameDesc(dname);
    return list;
}

📂 DeptController.java 추가

 /** 전체조회 + dname(부서명) like 검색 + dname 내림차순 조회 */
    @GetMapping("/dept/dname/containing/desc/{dname}")
    public ResponseEntity<Object> findAllByDnameContainingOrderByDnameDesc(
            @PathVariable String dname
    ){
        try {
            // todo : 전체 조회 + 정렬(dno 내림차순)함수 호출
            List<Dept> list
                    = deptService
                    .findAllByDnameContainingOrderByDnameDesc(dname);
            if (list.isEmpty() == false) {
//                성공
                return new ResponseEntity<>(list, HttpStatus.OK);
            } else {
//                데이터 없음
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }
        } catch (Exception e) {
            log.debug(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

✔ 서버 재시작 후 API 테스트 

1) 전체조회 - 부서명 like 검색 + dname 내림차순 조회

✅ TODO 4) 쿼리메소드 응용 연습문제

// todo) 응용연습
//    todo: 응용 연습문제 
//    todo: 연습 4) EMP 테이블에서 Job 이 manager 이고
//              매개변수로 부서번호(dno)를 받는 함수를 작성하세요.

//    todo: 연습 5) Emp 테이블에서 salary 가 1000 ~ 1500 사이의 값을 같는
//     사원을 조회하려고 합니다.  함수를 작성해 주세요
더보기

📂 EmpRepository.java

 

List<Emp> findAllByJobAndDno(String job, int dno);

List<Emp> findAllBySalaryBetween(int first, int last);

📂 EmpService.java

//    todo: 연습 4) EMP 테이블에서 Job 이 manager 이고
//              매개변수로 부서번호(dno)를 받는 함수를 작성하세요.
public List<Emp> findAllByJobAndDno(String job, int dno){
    List<Emp> list = empRepository.findAllByJobAndDno(job, dno);
    return list;
}

//    todo: 연습 5) Emp 테이블에서 salary 가 1000 ~ 1500 사이의 값을 같는
//     사원을 조회하려고 합니다.  함수를 작성해 주세요
public List<Emp> findAllBySalaryBetween(int first, int last){
    List<Emp> list = empRepository.findAllBySalaryBetween(first, last);
    return list;
}

📂 EmpController.java

  /** 연습 4) : 쿼리메소드 */
    @GetMapping("/emp/dno/{dno}")
    public ResponseEntity<Object> findAllByEnameContaining(
            @PathVariable int dno
    ) {
        try {
//         전체 조회 + 정렬(dno 내림차순) 호출
            List<Emp> list
                    = empService.findAllByJobAndDno("MANAGER", dno);

            if (list.isEmpty() == false) {
//                성공
                return new ResponseEntity<>(list, HttpStatus.OK);
            } else {
//                데이터 없음
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }

        } catch (Exception e){
            log.debug(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    /** 연습5 */

    @GetMapping("emp/salary/{first}/{last}")
    public ResponseEntity<Object> findAllBySalaryBetween(@PathVariable int first, @PathVariable int last){
        try {
            List<Emp> list
                    = empService.findAllBySalaryBetween(first, last);
            if(list.isEmpty() == false){
                return new ResponseEntity<>(list, HttpStatus.OK);
            } else {
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }

        } catch (Exception e) {
            log.debug(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

✔ 서버 재시작 후 API 테스트 

연습 4)

 

연습 5)

[
  {
    "insertTime": "2023-10-17 06:38:45",
    "updateTime": null,
    "eno": 7521,
    "ename": "WARD",
    "job": "SALESMAN",
    "manager": 7698,
    "hiredate": "1981-02-22 00:00:00",
    "salary": 1250,
    "commission": 500,
    "dno": 30
  },
  {
    "insertTime": "2023-10-17 06:38:45",
    "updateTime": null,
    "eno": 7654,
    "ename": "MARTIN",
    "job": "SALESMAN",
    "manager": 7698,
    "hiredate": "1981-09-28 00:00:00",
    "salary": 1250,
    "commission": 1400,
    "dno": 30
  },
  {
    "insertTime": "2023-10-17 06:38:45",
    "updateTime": null,
    "eno": 7844,
    "ename": "TURNER",
    "job": "SALESMAN",
    "manager": 7698,
    "hiredate": "1981-09-08 00:00:00",
    "salary": 1500,
    "commission": 0,
    "dno": 30
  },
  {
    "insertTime": "2023-10-17 06:38:45",
    "updateTime": null,
    "eno": 7876,
    "ename": "ADAMS",
    "job": "CLERK",
    "manager": 7788,
    "hiredate": "1987-07-13 00:00:00",
    "salary": 1100,
    "commission": null,
    "dno": 20
  },
  {
    "insertTime": "2023-10-17 06:38:45",
    "updateTime": null,
    "eno": 7934,
    "ename": "MILLER",
    "job": "CLERK",
    "manager": 7782,
    "hiredate": "1982-01-23 00:00:00",
    "salary": 1300,
    "commission": null,
    "dno": 10
  }
]
응답 파일이 저장되었습니다.
> 2023-10-17T153953.200.json

Response code: 200; Time: 25ms (25 ms); Content length: 933 bytes (933 B)

💡 @Query 이용한 쿼리문 작성

데이터베이스에서 값을 가져올 때는 위의 코드처럼 메서드의 이름만으로 쿼리 메서드를 생성할 수 도 있습니다.

이번에는 @Query 어노테이션을 사용해 직접 JPQL을 작성해봅시다.

 

📂 DeptRepository.java

    //    ----------------------------------------
    //    @Query 예제
    //    1) 오라클 쿼리
    //    2) 객체 쿼리
    //    ----------------------------------------
    //    todo 1) ename like 검색
    @Query(value = "SELECT TD.* FROM TB_EMP TD WHERE TD.ENAME LIKE '%' || :dname || '%'", nativeQuery = true)
    List<Emp> selectByEname(@Param("ename") String ename);

위 코드를 다르게 작성하는 방법(JDK 버전에 따라 사용 불가능할 수도 있음)

//    todo: 1-1) 위의 코드를 다르게 코딩
    @Query(value = "SELECT TD.* FROM TB_DEPT TD WHERE TD.DNAME LIKE '%'||:dname||'%'",
            nativeQuery = true)
    List<Dept> selectByDname(String dname);
//    todo: 1-2) 위의 코드를 다르게 코딩 : 참고
@Query(value = "SELECT TD.* FROM TB_DEPT TD WHERE TD.DNAME LIKE '%'|| ?1 ||'%'",
        nativeQuery = true)
List<Dept> selectByDname(String dname);

✅ SQL에서 쿼리문 결과가 올바른지 확인

📂 DeptService.java

/** 전체조회 + dname like 검색 : @Query */
public List<Dept> selectByDname(String dname) {
    List<Dept> list
            = deptRepository.selectByDname(dname);

    return list;
}

📂 DeptController.java

 /** 전체조회 + dname like 검색 : @Query */
    @GetMapping("/dept/dname/{dname}")
    public ResponseEntity<Object> selectByDname(
            @PathVariable String dname
    ) {
        try {
//         전체 조회 + 정렬(dno 오름차순) 호출
            List<Dept> list
                    = deptService.selectByDname(dname);

            if (list.isEmpty() == false) {
//                성공
                return new ResponseEntity<>(list, HttpStatus.OK);
            } else {
//                데이터 없음
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }

        } catch (Exception e){
            log.debug(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

✅ 실행결과

부서명 중에 'S'가 들어가는 부서명 검색하기

GET http://localhost:8000/dept/dname/S

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 18 Oct 2023 00:11:16 GMT
Keep-Alive: timeout=60
Connection: keep-alive

[
  {
    "insertTime": "2023-10-18 00:11:06",
    "updateTime": null,
    "dno": 20,
    "dname": "RESEARCH",
    "loc": "DALLAS"
  },
  {
    "insertTime": "2023-10-18 00:11:06",
    "updateTime": null,
    "dno": 30,
    "dname": "SALES",
    "loc": "CHICAGO"
  },
  {
    "insertTime": "2023-10-18 00:11:06",
    "updateTime": null,
    "dno": 40,
    "dname": "OPERATIONS",
    "loc": "BOSTON"
  }
]
응답 파일이 저장되었습니다.
> 2023-10-18T091116.200.json

Response code: 200; Time: 277ms (277 ms); Content length: 295 bytes (295 B)

✅ 객체쿼리를 이용한 쿼리문 작성

//    todo: 2) dname like 검색 (객체 쿼리)
    // 객체 쿼리 만드는 방법 : 테이블 -> 클래스명
    //                      컬럼명 -> 속성명
    //                      * -> 사용하지 않음, 클래스명의 별칭을 붙여서 사용
    //                      (대소문자를 구분함, nativeQuery=true(생략))
    @Query(value = "SELECT TD FROM Dept TD WHERE TD.dname LIKE '%'||:dname||'%'")
    List<Dept> selectByDname(@Param("dname") String dname);

 

✅ TODO) 부서 테이블에서 부서명(dname)과 위치(loc)를 매개변수로 받아 조회하는 함수 만들기

📂 DeptRepository.java

@Query(value = "SELECT D.* FROM TB_DEPT D " +
        "WHERE D.DNAME = :dname " +
        "AND   D.LOC = :loc ", nativeQuery = true)
List<Dept> selectByDnameAndLoc(@Param("dname") String dname,@Param("loc") String loc);

📂 DeptService.java

/** 전체조회 + dname like 검색 : @Query */
public List<Dept> selectByDnameAndLoc(String dname, String loc) {
    List<Dept> list
            = deptRepository.selectByDnameAndLoc(dname, loc);

    return list;
}

📂 DeptController.java

    /** 전체조회 + dname like 검색 : @Query */
    @GetMapping("/dept/dname/{dname}/loc/{loc}")
    public ResponseEntity<Object> selectByDnameAndLoc(
            @PathVariable String dname,
            @PathVariable String loc
    ) {
        try {
//         전체 조회 + 정렬(dno 오름차순) 호출
            List<Dept> list
                    = deptService.selectByDnameAndLoc(dname, loc);

            if (list.isEmpty() == false) {
//                성공
                return new ResponseEntity<>(list, HttpStatus.OK);
            } else {
//                데이터 없음
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }

        } catch (Exception e){
            log.debug(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

 

반응형

'Spring Boot > 스프링부트 예제' 카테고리의 다른 글

JPA - 연관관계 매핑  (0) 2023.10.19
JPA 페이징 처리  (1) 2023.10.18
JPA 상세조회, 저장함수, 수정함수, 삭제함수  (0) 2023.10.17
JPA를 활용한 CRUD 구현하기  (0) 2023.10.16
JSTL 라이브러리  (0) 2023.10.06
반응형

이전 블로그 글 : https://devjunyeok.tistory.com/181

 

JPA를 활용한 CRUD 구현하기

안녕하세요! 이번 시간에는 JPA 를 활용한 예제 프로젝트를 생성해보겠습니다. 들어가기 전에 JPA의 특징으로는 SQL문을 자동생성하는 것이 특징입니다. 💡 프로젝트 준비 및 환경설정 프로젝트

devjunyeok.tistory.com

📂 DeptService.java

package com.example.jpaexam.service;

import com.example.jpaexam.model.Dept;
import com.example.jpaexam.repository.DeptRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

/**
 * packageName : com.example.jpaexam.service
 * fileName : DeptService
 * author : GGG
 * date : 2023-10-16
 * description : 부서 업무 서비스
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-16         GGG          최초 생성
 */
@Service
public class DeptService {

    @Autowired
    DeptRepository deptRepository; // DI 객체 가져오기

    /** 전체조회 */
    public List<Dept> findAll() {
        List<Dept> list = deptRepository.findAll(); // db 전체조회 함수 호출
        return list;
    }

    /** 상세조회(1건조회) */
    public Optional<Dept> findById(int dno) {
        Optional<Dept> optionalDept = deptRepository.findById(dno);

        return optionalDept;
    }

    /** 저장(수정)함수 */
    public Dept save(Dept dept) {
//      todo: jpa 저장함수 호출 ( 기본키 없으면 insert, 있으면 update )
        Dept dept2 = deptRepository.save(dept);

        return dept2; // 저장된 부서객체
    }

    /** 삭제함수 */
    public boolean removeById(int dno){
        // todo : existsById : jpa 함수 - 리턴값 있으면 true, 없으면 false
        if(deptRepository.existsById(dno)) {
            deptRepository.deleteById(dno); // db 삭제(dno)
            return true;
        }
        return false;
    }


}

📂 DeptController.java

package com.example.jpaexam.controller.exam01;

import com.example.jpaexam.model.Dept;
import com.example.jpaexam.service.DeptService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;

/**
 * packageName : com.example.jpaexam.controller.exam01
 * fileName : DeptController
 * author : GGG
 * date : 2023-10-16
 * description : 부서 컨트롤러 : @RestController 사용
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-16         GGG          최초 생성
 */
@Slf4j
@RestController
@RequestMapping("/exam01")
public class DeptController {

    @Autowired
    DeptService deptService;    // 객체 가져오기 (DI)

    /** 전체조회 함수 */
    @GetMapping("/dept")
    public ResponseEntity<Object> getDeptAll() {
        try {
            // todo) 전체조회 함수 호출
            List<Dept> list = deptService.findAll();
            if (list.isEmpty() == false) {
                // todo) 성공
                return new ResponseEntity<>(list, HttpStatus.OK);
            } else {
                // todo) 데이터 없음
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }
        } catch (Exception e) {
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    /** 상세 조회 함수 */
    @GetMapping("/dept/{dno}")
    public ResponseEntity<Object> getDeptId(@PathVariable int dno){
        try {
            // todo) 전체조회 함수 호출
            Optional<Dept> optionalDept = deptService.findById(dno);
            if (optionalDept.isEmpty() == false) {
                // todo) 성공
                return new ResponseEntity<>(optionalDept.get(), HttpStatus.OK);
            } else {
                // todo) 데이터 없음
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }
        } catch (Exception e) {
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    /** 저장함수 */
    @PostMapping("/dept")
    public ResponseEntity<Object> createDept(@RequestBody Dept dept){
        try {
            // todo : jap 서비스 저장함수 호출 : dept2(DB 저장된 객체)
            Dept dept2 = deptService.save(dept);
            return new ResponseEntity<>(dept2, HttpStatus.OK);
        } catch (Exception e) {
            log.debug(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    /** 수정함수 */
    @PutMapping("/dept/edit/{dno}")
    public ResponseEntity<Object> updateDept(@RequestBody Dept dept, @PathVariable int dno){
        try {
            // todo : jap 서비스 수정함수 호출 : dept2(DB 저장된 객체)
            Dept dept2 = deptService.save(dept);
            return new ResponseEntity<>(dept2, HttpStatus.OK);
        } catch (Exception e) {
            log.debug(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    /** 삭제함수 */
    @DeleteMapping("/dept/delete/{dno}")
    public ResponseEntity<Object> deleteDept(
            @PathVariable int dno
    ){
        try {
            // todo) 삭제 함수 호출
            boolean bSuccess = deptService.removeById(dno);
            if (bSuccess == true) {
                // todo) 삭제성공
                return new ResponseEntity<>(HttpStatus.OK);
            } else {
                // todo) 0건 삭제(삭제할 대상이 없을경우)
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }
        } catch (Exception e) {
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

}

📌 실행해보기

1) 상세조회

부서번호 10번을 조회

2) 저장함수

임의의 값을 지정해서 저장하기

저장함수 테스트시 기본키(dno)와 insertTime, updateTime 제외하고 입력

3) 수정함수

부서번호 10번 조회 - updateTime과 dname, loc가 수정된 것을 알 수 있습니다.

 

4) 삭제함수 - 부서번호 10번 삭제하기

전체조회해서 10번 부서에 해당하는 데이터가 삭제되었는지 확인

 

반응형
반응형

안녕하세요!

이번 시간에는 JPA 를 활용한 예제 프로젝트를 생성해보겠습니다.

 

들어가기 전에

JPA의 특징으로는 SQL문을 자동생성하는 것이 특징입니다.

 

💡 프로젝트 준비 및 환경설정


프로젝트 준비시 스프링부트의 종속성을 추가합니다.

생성 후 시간이 소요됩니다.

 

한글 깨짐 방지를 위해 설정 - 에디터 - 파일 인코딩 부분에 UTF-8로 변경해줍니다.

build.gradle 파일의 dependencies 부분에  오라클과 logback, log4jdbc 종속성을 추가해줍니다.

	//   todo: 오라클 라이브러리( 19c )
	implementation 'com.oracle.database.jdbc:ucp:19.14.0.0'
	implementation 'com.oracle.database.security:oraclepki:19.14.0.0'
	implementation 'com.oracle.database.security:osdt_cert:19.14.0.0'
	implementation 'com.oracle.database.security:osdt_core:19.14.0.0'
	//   todo: logback , log4jdbc 설정
	implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16'
	implementation 'ch.qos.logback:logback-classic:1.2.11'
	implementation 'org.slf4j:slf4j-api:1.7.36'
	implementation 'org.slf4j:jcl-over-slf4j:1.7.36'

 

 

그리고 main > resources > application.properties 파일에 설정을 추가합니다.

# 서버 포트
server.port=8000


# todo : docker db 설정
spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy

# todo : spring.datasource.url=jdbc:log4jdbc:oracle:thin:@ip주소:db포트번호/db이름
spring.datasource.url=jdbc:log4jdbc:oracle:thin:@localhost:1521/xepdb1

spring.datasource.username=scott
spring.datasource.password=!Ds1234567890

# todo : jpa 설정
# todo : ddl-auto - create(모든 테이블 재생성)/update(수정된 테이블만 생성)/none(안함)
spring.jpa.hibernate.ddl-auto=create
#spring.jpa.hibernate.ddl-auto=update
#spring.jpa.hibernate.ddl-auto=none

# todo : db 제품 연결 ( oracle 12 이상 : Oracle12cDialect )
spring.jpa.database-platform=org.hibernate.dialect.Oracle12cDialect

# todo : generate-ddl=true (테이블 자동 생성 옵션)
spring.jpa.generate-ddl=true

# todo : sql log 보기 (true/false)
spring.jpa.show-sql=true

# 1) resource/data.sql 자동 실행 ( DML 실행 )
#  -> data.sql ( dml 실행 ), schema.sql ( ddl 실행 )
# todo : DML, DDL 스크립트 (실습용 샘플 테이블) 실행을 위한 옵션
spring.jpa.defer-datasource-initialization=true

# todo :  sql log 좀 더 정리된 형태로 찍기
spring.jpa.properties.hibernate.format_sql=true

# todo : 로깅 레벨 : error < info < debug < trace (정보 많은 순서)
logging.level.org.hibernate=info

# 2)  resource/data.sql 자동 실행 ( DML 실행 )
#  -> data.sql ( dml 실행 ), schema.sql ( ddl 실행 )
spring.sql.init.mode=always

# sql 에러 무시하고 스프링 서버 로딩
spring.sql.init.continue-on-error=true


# 자바 소스 변경시 스프링 서버 자동 재시작
spring.devtools.restart.enabled=true

# PUT , DELETE 방식도 form 에서 사용할 수 있게 만들어줌 : jsp에서 사용함
spring.mvc.hiddenmethod.filter.enabled=true

실습을 위해 resources 폴더 아래에 4가지 파일을 추가합니다.

 

📂 data.sql

INSERT INTO TB_DEPT VALUES (10,'ACCOUNTING','NEW YORK',  TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), null);
INSERT INTO TB_DEPT VALUES (20,'RESEARCH','DALLAS',  TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), null);
INSERT INTO TB_DEPT VALUES (30,'SALES','CHICAGO',  TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), null);
INSERT INTO TB_DEPT VALUES (40,'OPERATIONS','BOSTON',  TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), null);

INSERT INTO TB_EMP VALUES (7369,'SMITH','CLERK',    7902,TO_CHAR(to_date('17-12-1980','dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'),800, NULL,20, TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), NULL);
INSERT INTO TB_EMP VALUES (7499,'ALLEN','SALESMAN', 7698,TO_CHAR(to_date('20-2-1981', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'),1600, 300,30, TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), NULL);
INSERT INTO TB_EMP VALUES (7521,'WARD','SALESMAN',  7698,TO_CHAR(to_date('22-2-1981', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'),1250, 500,30, TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), NULL);
INSERT INTO TB_EMP VALUES (7566,'JONES','MANAGER',  7839,TO_CHAR(to_date('2-4-1981',  'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'),2975,NULL,20, TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), NULL);
INSERT INTO TB_EMP VALUES (7654,'MARTIN','SALESMAN',7698,TO_CHAR(to_date('28-9-1981', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'),1250,1400,30, TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), NULL);
INSERT INTO TB_EMP VALUES (7698,'BLAKE','MANAGER',  7839,TO_CHAR(to_date('1-5-1981',  'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'),2850,NULL,30, TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), NULL);
INSERT INTO TB_EMP VALUES (7782,'CLARK','MANAGER',  7839,TO_CHAR(to_date('9-6-1981',  'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'),2450,NULL,10, TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), NULL);
INSERT INTO TB_EMP VALUES (7788,'SCOTT','ANALYST',  7566,TO_CHAR(to_date('13-07-1987','dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'),3000,NULL,20, TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), NULL);
INSERT INTO TB_EMP VALUES (7839,'KING','PRESIDENT', NULL,TO_CHAR(to_date('17-11-1981','dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'),5000,NULL,10, TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), NULL);
INSERT INTO TB_EMP VALUES (7844,'TURNER','SALESMAN',7698,TO_CHAR(to_date('8-9-1981',  'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'),1500,   0,30, TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), NULL);
INSERT INTO TB_EMP VALUES (7876,'ADAMS','CLERK',    7788,TO_CHAR(to_date('13-07-1987','dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'),1100,NULL,20, TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), NULL);
INSERT INTO TB_EMP VALUES (7900,'JAMES','CLERK',    7698,TO_CHAR(to_date('3-12-1981', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'),950, NULL,30, TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), NULL);
INSERT INTO TB_EMP VALUES (7902,'FORD','ANALYST',   7566,TO_CHAR(to_date('3-12-1981', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'),3000,NULL,20, TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), NULL);
INSERT INTO TB_EMP VALUES (7934,'MILLER','CLERK',   7782,TO_CHAR(to_date('23-1-1982', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'),1300,NULL,10, TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), NULL);


INSERT INTO TB_FAQ VALUES (1,'제목','해결방법',  TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), null);
INSERT INTO TB_FAQ VALUES (2,'제목2','해결방법2',  TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), null);
INSERT INTO TB_FAQ VALUES (3,'제목3','해결방법3',  TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), null);
INSERT INTO TB_FAQ VALUES (4,'제목4','해결방법4',  TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), null);

COMMIT;

📂log4jdbc.log4j2.properties

# sql \uB85C\uADF8 \uC124\uC815
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
# \uCD5C\uB300 \uBA87 \uB77C\uC778\uAE4C\uC9C0 \uCD9C\uB825\uD560 \uAC83\uC778\uAC00\uB97C \uACB0\uC815 : 0 \uC774\uBA74 \uC81C\uD55C\uC5C6\uC74C
log4jdbc.dump.sql.maxlinelength=0

📂 logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">

    <!-- Appenders -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
       <encoder>
          <Pattern>%d %5p [%c] %m%n</Pattern>
       </encoder>
    </appender>

    <appender name="console-infolog" class="ch.qos.logback.core.ConsoleAppender">
       <encoder>
          <Pattern>%d %5p %m%n</Pattern>
       </encoder>
    </appender>

    <!-- Logger -->
    <logger name="com.example.jpaexam" level="DEBUG" appender-ref="console" />
    <logger name="jdbc.sqlonly" level="INFO" appender-ref="console-infolog" />
    <logger name="jdbc.resultsettable" level="INFO" appender-ref="console-infolog" />

    <!-- Root Logger : 기본 OFF -->
<!--    하위 로거에서 재정의해서 사용함 : INFO, DEBUG 등으로 -->
    <root level="off">
       <appender-ref ref="console" />
    </root>
</configuration>

📂 shema.sql

DROP SEQUENCE SQ_DEPT;
CREATE SEQUENCE SQ_DEPT START WITH 50 INCREMENT BY  10;

DROP SEQUENCE SQ_EMP;
CREATE SEQUENCE SQ_EMP START WITH 8000 INCREMENT BY  1;

DROP SEQUENCE SQ_FAQ;
CREATE SEQUENCE SQ_FAQ START WITH 1 INCREMENT BY  1;

DROP TABLE TB_EMP CASCADE CONSTRAINT;
DROP TABLE TB_DEPT CASCADE CONSTRAINT;
DROP TABLE TB_FAQ CASCADE CONSTRAINT;

CREATE TABLE TB_DEPT (
                         DNO NUMBER NOT NULL PRIMARY KEY,
                         DNAME VARCHAR2(255),
                         LOC VARCHAR2(255),
                         INSERT_TIME VARCHAR2(255),
                         UPDATE_TIME VARCHAR2(255)
);

CREATE TABLE TB_EMP
(ENO NUMBER NOT NULL PRIMARY KEY,
 ENAME VARCHAR2(255),
 JOB   VARCHAR2(255),
 MANAGER  NUMBER,
 HIREDATE VARCHAR2(255),
 SALARY NUMBER,
 COMMISSION NUMBER,
 DNO NUMBER,
 INSERT_TIME VARCHAR2(255),
 UPDATE_TIME VARCHAR2(255)
);

CREATE TABLE TB_FAQ (
                        NO NUMBER NOT NULL PRIMARY KEY,
                        TITLE VARCHAR2(255),
                        CONTENT VARCHAR2(255),
                        INSERT_TIME VARCHAR2(255),
                        UPDATE_TIME VARCHAR2(255)
);

 

그리고 java > com.example.jpaexam 폴더 아래에 다음과 같이 폴더를 생성합니다.

 

💡 model 폴더에 공통모델 파일 생성


 

model 폴더 아래에 생성일자/수정일자를 만들어주는 추상클래스를 생성합니다.

실습에서는 파일명을 BaseTimeEntity 로 생성하였습니다.

📂 model > BaseTimeEntity.java

1) 먼저 추상클래스 위에 어노테이션 3개를 추가합니다.

package com.example.jpaexam.model;

import lombok.Getter;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;

/**
 * packageName : com.example.jpaexam.model
 * fileName : BaseTimeEntity
 * author : GGG
 * date : 2023-10-16
 * description : JPA 에서 자동으로 생성일자/수정일자를 만들어주는 클래스
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-16         GGG          최초 생성
 */
@Getter
// todo : 자동으로 생성일자/수정일자 컬럼을 sql 문에 추가시키는 어노테이션 2개
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {
    // todo) 공통 속성
    private  String insertTime;
    private  String updateTime;
}

 

여기서 @MappedSuperclass 어노테이션의 의미는

생성일자/수정일자를 모든 엔티티에 공통으로 가져가야하는 상황에서 위의 코드처럼

BaseTimeEntity를 정의해서 활용할 때 사용합니다.

 

2) 공통 속성의 형태를 변경하는 함수 추가

위의 코드는 time의 형태가 yyyy-MM-dd HH:mm:ss 형태가 아닌 기본 형태로 보이게 됩니다.

가독성을 좋게하기 위해 공통속성 코드 아래에 함수를 추가합니다.

 

...
private String updateTime;

// todo) 해당 테이블에 데이터가 만들어 질 때(insert 문) 실행되는 이벤트 함수
@PrePersist
void OnPerPersist(){
    this.insertTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

}
// todo) 해당 테이블에 데이터가 수정될 때(update 문) 실행되는 이벤트 함수
@PreUpdate
void OnPreUpdate(){
    this.updateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    this.insertTime = this.updateTime; // 생성일시 == 수정일시 동일하게 처리
}

 

💡 Dept 모델 생성


model 폴더 아래에 Dept 클래스를 생성합니다.

JPA에서 사용하는 어노테이션의 설명은 아래 코드에 todo로 표시되어있습니다.

📂 model > Dept.java

package com.example.jpaexam.model;

import lombok.*;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;

import javax.persistence.*;

/**
 * packageName : com.example.jpaexam.model
 * fileName : Dept
 * author : GGG
 * date : 2023-10-16
 * description : 부서 모델 클래스 ( 엔티티(entity) )
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-16         GGG          최초 생성
 */
// todo) @Entity - JPA 기능을 클래스에 부여하는 어노테이션
@Entity
// todo) @Table(name = "생성될 테이블명")
@Table(name = "TB_DEPT")
// todo 사용법 : @SequenceGenerator(
//        name = "시퀀스 함수 이름"
//        , sequenceName = "DB에 생성된 시퀀스 이름"
//        , initialValue = 시작값
//        , allocationSize = jpa에서 관리용숫자(성능지표)
//  )
@SequenceGenerator(
        name = "SQ_DEPT_GENERATOR"
        , sequenceName = "SQ_DEPT"
        , initialValue = 1
        , allocationSize = 1
)
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
// todo) JPA 어노테이션 SQL 자동 생성시 null 값 컬럼은 제외하고 생성하는 어노테이션
//  예) insert into 테이블명(컬럼1, 컬럼2, 컬럼3) values(1, 2, null)
//    => insert into 테이블명(컬럼1, 컬럼2) values(1, 2)
@DynamicInsert
@DynamicUpdate
public class Dept extends BaseTimeEntity{
    @Id // todo) 기본키임을 알려주는 어노테이션
    // todo) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "시퀀스함수이름")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SQ_DEPT_GENERATOR") // 시퀀스 기능 부여
    // todo : @Column(columnDefinition = "DB컬럼 자료형")
    @Column(columnDefinition = "NUMBER")
    private Integer dno;    // 부서번호(기본키) - 시퀀스 기능 부여

    @Column(columnDefinition = "VARCHAR2(255)")
    private String dname;   // 부서명

    @Column(columnDefinition = "VARCHAR2(255)")
    private String loc;     // 부서위치
}

 

repository 파일 만들기

DB 접속 함수들(CRUD)을 만들어주기 위한 인터페이스 파일을 생성합니다.

📂 repository > DeptRepository.java 

package com.example.jpaexam.repository;

import com.example.jpaexam.model.Dept;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
 * packageName : com.example.jpaexam.repository
 * fileName : DeptRepository
 * author : GGG
 * date : 2023-10-16
 * description : JPA 레포지토리 인터페이스 (DB 접속 함수들(CRUD) 있음)
 *                  == DAO랑 비슷함
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-16         GGG          최초 생성
 */
// todo) @Repository - 클래스위에 붙이고, 스프링서버가 실행될 때 자동으로 객체 1개를 만들어줌( IOC )
//    사용법 : 인터페이스명 extends JpaRepository<모델클래스명, 기본키의 자료형>
@Repository
public interface DeptRepository extends JpaRepository<Dept, Integer> {
}

JpaRepository를 상속받으면 DB접속에 사용할 수 있는 CRUD함수 기능을 사용할 수 있습니다.

 

💡 업무 service 클래스 생성

📂 service > DeptService.java

package com.example.jpaexam.service;

import com.example.jpaexam.model.Dept;
import com.example.jpaexam.repository.DeptRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * packageName : com.example.jpaexam.service
 * fileName : DeptService
 * author : GGG
 * date : 2023-10-16
 * description : 부서 업무 서비스
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-16         GGG          최초 생성
 */
@Service
public class DeptService {
    
    @Autowired
    DeptRepository deptRepository;  // DI 객체 가져오기
    
    /** 전체 조회 */
    public List<Dept> findAll(){
        List<Dept> list = deptRepository.findAll(); // db 전체조회 함수 호출
        return list;
    }
    
}

 

💡 controller 생성

📂 controller > DeptController

package com.example.jpaexam.controller.exam01;

import com.example.jpaexam.model.Dept;
import com.example.jpaexam.service.DeptService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * packageName : com.example.jpaexam.controller.exam01
 * fileName : DeptController
 * author : GGG
 * date : 2023-10-16
 * description : 부서 컨트롤러 : @RestController 사용
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-16         GGG          최초 생성
 */
@Slf4j
@RestController
@RequestMapping("/exam01")
public class DeptController {

    @Autowired
    DeptService deptService;    // 객체 가져오기 (DI)

    /** 전체조회 함수 */
    @GetMapping("/dept")
    public ResponseEntity<Object> getDeptAll() {
        try {
            // todo) 전체조회 함수 호출
            List<Dept> list = deptService.findAll();
            if (list.isEmpty() == false) {
                // todo) 성공
                return new ResponseEntity<>(list, HttpStatus.OK);
            } else {
                // todo) 데이터 없음
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }
        } catch (Exception e) {
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}

REST API 테스트를 위해 아래 그림의 빨간색 박스를 누릅니다.

테스트 창에서 ▶ 버튼을 눌러 실행합니다.

실행결과

GET http://localhost:8000/exam01/dept

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Mon, 16 Oct 2023 08:15:45 GMT
Keep-Alive: timeout=60
Connection: keep-alive

[
  {
    "insertTime": "2023-10-16 08:15:40",
    "updateTime": null,
    "dno": 10,
    "dname": "ACCOUNTING",
    "loc": "NEW YORK"
  },
  {
    "insertTime": "2023-10-16 08:15:40",
    "updateTime": null,
    "dno": 20,
    "dname": "RESEARCH",
    "loc": "DALLAS"
  },
  {
    "insertTime": "2023-10-16 08:15:40",
    "updateTime": null,
    "dno": 30,
    "dname": "SALES",
    "loc": "CHICAGO"
  },
  {
    "insertTime": "2023-10-16 08:15:40",
    "updateTime": null,
    "dno": 40,
    "dname": "OPERATIONS",
    "loc": "BOSTON"
  }
]
응답 파일이 저장되었습니다.
> 2023-10-16T171545.200.json

Response code: 200; Time: 297ms (297 ms); Content length: 397 bytes (397 B)

JPA에서 자동으로 만든 select 문을 확인해봅시다.

Hibernate: 
    select
        dept0_.dno as dno1_0_,
        dept0_.insert_time as insert_time2_0_,
        dept0_.update_time as update_time3_0_,
        dept0_.dname as dname4_0_,
        dept0_.loc as loc5_0_ 
    from
        tb_dept dept0_

 

반응형
반응형

💡 Core 라이브러리

JSTL의 core 라이브러리를 사용하기 위해서는 JSP 파일 상단에 코드를 선언합니다.

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

📖 <c:set> : 변수에 값을 저장

변수저장소(사용범위)
page(현재페이지) < request < session < application(전체프로그램)
사용법 : <c:set var="변수명" value="값" scope="page"(scope는 생략가능, 기본이 page)/>

📌 사용 예제

<%--     TODO : jstl 표현식--%>
        <p><c:set var="num" value="10" scope="page"/></p>
        <p><c:set var="num2" value="10" scope="page"/></p>
        <p><c:set var="total" value="${num + num2}" scope="page"/></p>
<%--     TODO : el 표현식--%>
        <p>${num}</p>
        <p>${num2}</p>
        <p>${total}</p>

📌 결과

📖 <c:forTokens> : 문자열 구분자로 구분하여 전체 출력하기

사용법 : 
<c:forTokens items="문자열" delims="구분자" var="변수명">
         실행문
</c:forTokens>

📌 사용예제

<c:forTokens items="1/2/3" delims="/" var="number">
    <h2>토큰 : ${number}</h2>
</c:forTokens>

📌 실행결과

📖 <c:catch> : try ~ catch 예외처리

사용법 : 
<c:catch var="에러변수"> 실행문 </c:catch>
실행문이 에러 발생하면 에러변수에 메세지가 들어감

📌 사용예제

<c:set var="num3" value="20" />
<c:catch var="err">
    <h2>${num3}</h2>
</c:catch>
<c:if test="${not empty err}">
    에러 메세지 : ${err.message}
</c:if>

📌 실행결과

📖 <c:redirect> : 페이지 강제이동

<c:redirect url="이동할_url주소" />

 

📖 <c:import> : 공통페이지 끼워 넣기

<c:import url="보여줄페이지" />

📌 사용예제

📖 <c:url> : url로 페이지 이동하기

<c:url value="url주소" var="변수명" />
[사용]
<a href="${변수명}">표시할내용</a>

📌 사용예제

 

💡 Function 라이브러리

fn 라이브러리를 사용하기전 상단 코드 선언

fn 라이브러리는 el 표현식과 함께 사용합니다.

<%--TODO: fn lib import--%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>

<%-- TODO: fmt lib import--%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

📌 변수 선언

<c:set var="chars" value="안녕하세요 홍길동입니다. Bye"/>
<c:set var="name" value="홍길동"/>

 

📖 fn:contains(string, substr)

string이 substr을 포함하면 return true 없으면 false return

<h2>indexOf : ${fn:indexOf(chars,"홍길동")}</h2>

📖 fn:indexOf(string, substr)

string 문자열에 "substr" 값이 있는 index 번호를 return

<h2>indexOf : ${fn:indexOf(chars,"홍길동")}</h2>

📖 fn:length(string)

전체 길이를 리턴

<h2>length : ${fn:length(chars)}</h2>

📖 fn:replace(string, "기존문자열", "바꿀문자열")

string 문자열에서 기존문자열을 바꿀문자열로 변경

<h2>replace : ${fn:replace(chars,"홍길동","장길산")}</h2>

📖 fn:split(string, 구분자)

string에서 구분자로 문자열 자르기, 리턴은 배열

<h2><c:set var="sChar" value='${fn:split(chars, "")}'/></h2>
<h2>${sChar[0]}</h2>
<h2>${sChar[1]}</h2>
<h2>${sChar[2]}</h2>

📖 fn:substring(string, 시작인덱스번호, 끝인덱스번호)

string 문자열에서 시작인덱스에서 끝인덱스번호-1까지 자르기

<h2>substring : ${fn:substring(chars,0 ,6)}</h2>

📖 fn:toLowerCase/toUpperCase(string)

대소문자 바꾸기

<h2>toLowerCase : ${fn:toLowerCase(chars)}</h2>
<h2>toUpperCase : ${fn:toUpperCase(chars)}</h2>

 

💡 fmt(format) 라이브러리

fmt 라이브러리는 날짜,시간, 숫자 텍스트를 포멧하고 표시하는 fomatter 기능을 제공합니다.

 

📌 변수선언

<c:set var="numtest" value="1234567890"/>

📖 fmt:formatNumber : 세 자리마다 쉼표(,) 추가하여 출력

<h2><fmt:formatNumber value="${numtest}"/> </h2>

1) 숫자에 통화 추가 : value 뒤에 type="currency" 추가

<h2>formatNumber : <fmt:formatNumber value="${numtest}" type="currency"/></h2>

📖 날짜 포멧 주기

날짜 포멧을 주기위해서는 스크립틀릿을 사용합니다. 스크립틀릿은 과거 코딩기법이라 사용은 하지 않습니다.

<%

  자바코딩 가능

 %>

 <%
        Date date = new java.util.Date();
    %>
<%--       TODO: 화면 출력시 : <%=변수명%>--%>
    <h2><%=date%></h2>
    <c:set var="today" value="<%=new java.util.Date()%>" />
    <h2><fmt:formatDate value="${today}" pattern="yyyy-MM-dd hh:mm:ss" /> </h2>

반응형
반응형

💡 파라미터 방식

앞 전 예제에서는 쿼리스트링 방식으로 매개변수를 전달하였습니다. 이번에는 쿼리스트링 방식을 개선한 파라미터 방식의 매개변수 전달 방법에 대해 알아보겠습니다.

 

📂PathVariableController.java

@Controller
@RequestMapping("/exam05")
public class PathVariableController {
//    TODO : url 테스트 파라미터 방식 : http://localhost:8000/exam05/path-variable/LeeJunHyuk
//                                  => @GetMapping("/path-variable/{웹매개변수명}")
//                                  => 웹 브라우저 주소창 사용 : url/값
//    TODO : url 테스트 쿼리스트링 방식 : http://localhost:8000/exam05/path-variable?name=LeeJunHyuk
    @GetMapping("/path-variable/{name}")
    public String getPathVariable(
            @PathVariable String name,
            Model model)
    {
        model.addAttribute("name", name);
        return "exam05/path_variable.jsp";
    }
}

📂path_variable.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%--TODO : JSTL 표현식 사용을 위한 import--%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<%-- TODO : el 표현식--%>
    <p>${name}</p>
</body>
</html>

파라미터 형식의 매개변수 전달방법은

@GetMapping("url/{웹매개변수명}") 으로 사용하고 메소드 매개변수로 @PathVariable를 사용한 것을 알 수 있습니다.

 

📌 실행결과

반응형
반응형

💡 Lombok 라이브러리

Lombok 라이브러리는 유용한 어노테이션을 모은 라이브러리입니다.

다음 예제 실습을 위한 몇 가지 롬북 어노테이션을 소개합니다.

//   @Setter : setter 함수를 만들어주는 어노테이션
//   @Getter : getter 함수를 만들어주는 어노테이션
//   @ToString : toString 재정의 함수 자동으로 만들어주는 어노테이션
//   @AllArgsConstructor : 모든 속성을 가진 생성자 자동 정의 어노테이션

먼저 실습을 하기 위해 아래와 같이 폴더를 구성합니다.

com.example.controllerexam 폴더 아래에 model 폴더를 생성합니다.

📂 model > Member.java

package com.example.controllerexam.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;


@Setter
@Getter
@ToString
@AllArgsConstructor
public class Member {
//    TODO : 속성, 생성자 함수(getter/setter)
    String id;      // 회원 id
    String name;    // 회원 이름
}

Lombok 어노테이션을 사용함으로써 코드가 간결해집니다.

 

📂 ObjectParamController.java

package com.example.controllerexam.controller.exam04;

import com.example.controllerexam.model.Member;
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.RequestMapping;

/**
 * packageName : com.example.controllerexam.controller.exam04
 * fileName : ObjectParamController
 * author : GGG
 * date : 2023-10-05
 * description : @ModelAttribute : 객체 형태로 변환하는 어노테이션
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-05         GGG          최초 생성
 */
@Controller
@RequestMapping("/exam04")
public class ObjectParamController {
//    TODO : URL TEST ) http://localhost:8000/exam04/object-param?id=junyeoke&name=lee
//     @ModelAttribute : 객체의 속성명으로 jsp로 각각 전달
//     Member 클래스의 속성명 == url의 매개변수명 : id, name
    @GetMapping("/object-param")
    public String getObjectParam(
            @ModelAttribute Member member){
        return "exam04/object_param.jsp";
    }
}

 

 

@ModelAttribute는 사용자가 요청시 전달하는 값을 오브젝트 형태로 매핑해주는 어노테이션입니다.

 

📂 object_param.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%--TODO : JSTL 표현식 사용을 위한 import--%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<%--    TODO : 매개변수 받기 --%>
    <h2>매개변수 받기</h2>
    <p>${member}</p>
</body>
</html>

 

📌 결과출력

반응형
반응형

💡 JSP의 조건문, URL 매개변수에 3개를 전달해서 조건에 따라 출력하기

📂 MultiParamController.java

   TODO : 예제 3) JSP의 조건문, URL 매개변수 3개를 전달해서 조건에 따라 출력하기
//     URL 테스트 : http://localhost:8000/exam03/condition?name=삼식이&color=brown&kind=dog
    @GetMapping("/condition")
    public String setCondition(Model model,
                               @RequestParam(defaultValue = "") String name,
                               @RequestParam(defaultValue = "") String color,
                               @RequestParam(defaultValue = "") String kind)
    {
        model.addAttribute("name", name);
        model.addAttribute("color", color);
        model.addAttribute("kind", kind);
        return "/exam03/condition.jsp";
    }
}

📂 condition.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%--TODO : JSTL 표현식 사용을 위한 import--%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <%--    TODO 부트스트랩 CSS CDN --%>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
<body>
    <div class="container">
        <h2>매개변수(파라메터) 전달받아 출력하기</h2>
        <p>${name}</p>
        <p>${color}</p>
        <p>${kind}</p>

<%--        TODO : if 조건문으로 출력하기: jstl 표현식, else 없음 사용--%>
<%--         사용법 : <c:if test="${조건식}">실행문</c:if>--%>
        <c:if test="${name == '삼식이'}">
            <p>삼식이가 맞습니다.</p>
        </c:if>
    </div>
</body>
</html>

✅ JSP의 if 조건문

JSP에서 if 조건문을 사용할 경우에는 JSTL 표현식을 사용합니다. 사용법은 아래와 같습니다.

<c:if test="${조건식}> 실행문 </c:if>

단 JSP의 if 조건문은 JAVA에서 처럼 else구문이 따로 없는것이 특징입니다.

 

그럼 else 구문을 사용하려면 어떻게 해야할까요?

 

✅ JSP의 choose 조건문

choose 조건문은 JAVA의 if ~ else if 구문과 비슷합니다.

 

JAVA의 else 구문과 같은 의미인 otherwise가 있습니다.

 

📌 choose 조건문

<%--        TODO : choose 조건문으로 출력하기, else 있음--%>
        <c:choose>
            <c:when test="${color == 'brown'}">
                갈색입니다.
            </c:when>
            <c:when test="${color == 'yellow'}">
                노란색입니다.
            </c:when>
            <c:otherwise>
                다른색입니다.
            </c:otherwise>
        </c:choose>

📌 사용법

// TODO: choose 조건문으로 출력하기 , else 있음

사용법 
<c:choose>
(if, else if): <c:when test="조건문">실행문</c:when>
(else): <c:otherwise>실행문</c:otherwise>
</c:choose>

📌 출력결과

반응형
반응형

💡 ArrayList 형태의 View에서의 출력

📂 MulitParamController.java

@GetMapping("/hello-array")
public String setHelloArray(Model model,
                            @RequestParam(defaultValue = "") String name,
                            @RequestParam(defaultValue = "") String id)
{
    List<String> list = new ArrayList<>();
    list.add(name);
    list.add(id);

    model.addAttribute("list",list);
    return "exam03/hello_array.jsp";
}

setHelloArray 메서드 내에서 문자열 형태의 ArrayList를 생성합니다.

그리고 .add() 를 사용하여 RequestParam의 변수를 넣어줍니다.

 

📂 hello_array.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%--TODO : JSTL 표현식 사용을 위한 import--%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <%--    TODO 부트스트랩 CSS CDN --%>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
<body>
<div class="container">
    <h2>예제 2) URL 매개변수를 ArrayList 로 View에 전달</h2>
    <p>${list}</p>

    <table class="table">
        <thead>
        <tr>
            <th scope="col">id</th>
            <th scope="col">name</th>
        </tr>
        </thead>
        <tbody>
        <tr>
            <%--    TODO: JSP 반복문 : jstl 표현식 사용    --%>
<%--                  <c:forEach var="일반변수" items="${배열}">   --%>
            <c:forEach var="data" items="${list}">
                <td>${data}</td>
            </c:forEach>
        </tr>
        </tbody>
    </table>
</div>
</body>
</html>

내용을 깔끔하게 보이게 하기 위해 부트스트랩의 테이블 소스를 사용하였습니다.

JSP에서의 반복문은 JSTL 표현식을 사용합니다.

<c:forEach var="변수명" items="${배열}">
${변수명}
</c:forEach>

📌 결과

 

📖 연습문제

//  TODO: 연습 2) 연습 1에서 작성한 dno, dname, loc 를 ArrayList 담아서 jsp 전달하세요
//    화면에 출력시 부트스트랩 테이블을 이용해서 출력하세요
//    url : /example02
//    jsp : exam03/example02.jsp
//    url 테스트 : http://localhost:8000/exam03/example02?dno=10&dname=Accounting&loc=NewYork
//    결과 : 테이블 형태로
//    부서번호 : 10
//    부서명 : Accounting
//    위치 : NewYork

 

📂 MulitParamController.java

@GetMapping("/example02")
public String setHelloArray2(Model model,
                             @RequestParam(defaultValue = "")int dno,
                             @RequestParam(defaultValue = "")String dname,
                             @RequestParam(defaultValue = "")String loc
                             )
{
    List<String> list = new ArrayList<>();
    list.add(String.valueOf(dno));
    list.add(dname);
    list.add(loc);

    model.addAttribute("list",list);
    return "exam03/example02.jsp";
}

📂 example02.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%--TODO : JSTL 표현식 사용을 위한 import--%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <%--    TODO 부트스트랩 CSS CDN --%>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
<body>
<div class="container">
    <h2> 연습1) URL 매개변수를 ArrayList 로 View에 전달</h2>
    <p>${list}</p>

    <table class="table">
        <thead>
        <tr>
            <th scope="col">부서번호</th>
            <th scope="col">부서명</th>
            <th scope="col">위치</th>
        </tr>
        </thead>
        <tbody>
        <tr>
            <%--    TODO: JSP 반복문 : jstl 표현식 사용    --%>
            <%--                  <c:forEach var="일반변수" items="${배열}">   --%>
            <c:forEach var="data" items="${list}">
                <td>${data}</td>
            </c:forEach>
        </tr>
        </tbody>
    </table>
</div>
</body>
</html>

📌 실행결과

 

//  TODO: 연습 3) 아래 결과를 ArrayList 담아서 jsp 전달하세요
//    화면에 출력시 부트스트랩 테이블을 이용해서 출력하세요
//    url : /example03
//    jsp : exam03/example03.jsp
//    url 테스트 : http://localhost:8000/exam03/example03?eno=7788&ename=Scott&salary=3000&job=Manager
//    결과 : 테이블 형태로
//      사원번호 : 7788
//      사원명 : Scott
//      월급 : 3000
//      직위 : Manager

📂  MulitParamController.java

@GetMapping("/example03")
public String setHelloArray3(Model model,
                             @RequestParam(defaultValue = "")int eno,
                             @RequestParam(defaultValue = "")String ename,
                             @RequestParam(defaultValue = "")int salary,
                             @RequestParam(defaultValue = "")String job
)
{
    List<String> list = new ArrayList<>();
    list.add(String.valueOf(eno));
    list.add(ename);
    list.add(String.valueOf(salary));
    list.add(job);

    model.addAttribute("list",list);
    return "exam03/example03.jsp";
}

📂 example03.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%--TODO : JSTL 표현식 사용을 위한 import--%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <%--    TODO 부트스트랩 CSS CDN --%>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
<body>
<div class="container">
    <h2> 연습3) URL 매개변수를 ArrayList 로 View에 전달</h2>
    <p>${list}</p>

    <table class="table">
        <thead>
        <tr>
            <th scope="col">eno</th>
            <th scope="col">ename</th>
            <th scope="col">salary</th>
            <th scope="col">job</th>
        </tr>
        </thead>
        <tbody>
        <tr>
            <%--    TODO: JSP 반복문 : jstl 표현식 사용    --%>
            <%--                  <c:forEach var="일반변수" items="${배열}">   --%>
            <c:forEach var="data" items="${list}">
                <td>${data}</td>
            </c:forEach>
        </tr>
        </tbody>
    </table>
</div>
</body>
</html>

📌 실행결과

반응형

'Spring Boot > 스프링부트 예제' 카테고리의 다른 글

객체 형태로 변환하는 어노테이션  (0) 2023.10.05
JSP의 조건문  (1) 2023.10.05
@RequestMapping, @RequestParam  (1) 2023.10.05
Model  (0) 2023.10.04
Hello SpringBoot!  (0) 2023.10.04
반응형

💡 @RequestParam

쿼리스트링 방식으로 변수에 값을 저장해서 다른 페이지로 전달하는 Annotation 입니다.

  • 파라미터 이름으로 바인딩
  • name 속성이 파라미터 이름으로 사용
사용법 : 함수명(Model model, @RequestParam 자료형 변수명) {}
url?변수명=값 (웹 브라우저 주소창 입력)

📌 코드작성해보기

📂 ParamController.java

@Controller
public class ParamController {

    @GetMapping("/exam02/hello-name")
    public String HelloName(Model model
            ,@RequestParam(defaultValue = "") String name) {
        model.addAttribute("greeting", "안녕하세요 " + name);
        return "exam02/hello.jsp";
    }

}

여기서

@RequestParam(defaultValue = "") String name)

defaultValue 부분은 생략이 가능합니다.

만약 defaultValue 부분을 생략하고 주소창에 값을 넘겨주지 않을 경우 오류가 발생하게 됩니다.

즉, 옵션을 생략할 경우 반드시 값을 넘겨줘야합니다!

  • 위의 코드는 HelloName 메서드에 @RequestParam 어노테이션을 사용하여 name 값이 전달이 안될 경우 빈문자열로 기본값을 설정하였습니다.

 

📂 hello.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%--TODO : JSTL 표현식 사용을 위한 import--%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <%--    TODO 부트스트랩 CSS CDN --%>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
<body>
<div class="container">
    <h2>매개변수(파라메터) 전달받아 출력하기</h2>
<%--    TODO : el(Experssion language) 표현식 : ${변수명} --%>
    <p>${greeting}</p>
</div>
</body>
</html>

📌 실행결과

기본 값일 경우 위의 코드처럼 빈 문자열로 출력됨

 

웹 페이지 주소창에 url?변수명=값을 넘겨 실행하면 name 변수에 있는 이순신이 출력됩니다.

✅ URL 매개변수를 2개를 각각 키이름으로 전달하기

위의 예제는 매개변수를 1개를 전달하는 방법이였습니다. 이번에는 매개변수 2개를 전달하는 방법에 대해 알아보겠습니다.

📂 MultiParamController.java

@Controller
@RequestMapping("/exam03")
public class MultiParamController {

//    TODO : 예제 1) URL 매개변수 2개를 각각 키이름으로 전달
//     URL TEST ) http://localhost:8000/exam03/hello-name-id?name=junyeok&id=junyeoke
    @GetMapping("/hello-name-id")
    public String setHelloNameId (Model model,
                                  @RequestParam(defaultValue = "")String name,
                                  @RequestParam(defaultValue = "")String id)
    {
        model.addAttribute("attrName","이름 : " + name);
        model.addAttribute("attrId","아이디 : " + id);
        return "exam03/hello_name_id.jsp";
    }
  • setHelloNameId 메서드에 @RequestParam 어노테이션이 두 개 사용된 것을 알 수 있습니다.

📂 example01.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%--TODO : JSTL 표현식 사용을 위한 import--%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <%--    TODO 부트스트랩 CSS CDN --%>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
<body>
<div class="container">
    <h2>URL 매개변수를 각각 키이름으로 전달</h2>
    <p>${dno}</p>
    <p>${dname}</p>
    <p>${loc}</p>
</div>
</body>
</html>

 

📌 실행결과

  • URL TEST 주소는 http://localhost:8000/exam03/hello-name-id?name=junyeok&id=junyeoke 이며 매개변수가 2개 이상일 경우 &을 사용하여 입력합니다.

 

💡 @RequestMapping

요청(Request) URL을 어떤 method가 처리할지 mapping 해주는 Annotation 입니다.

즉, 자주쓰는 공통 URL를 생략할수 있게 해주는 어노테이션이라 생각하시면 됩니다.

@RequestMapping("/공통url") : 공통 url을 함수의 url과 합쳐져서 사용됨
예) @RequestMapping("/exam02") + @GetMapping("/hello-name")
=> url : /exam02/hello-name

컨트롤러 클래스 위에 @RequestMapping 어노테이션을 사용합니다.

package com.example.controllerexam.controller.exam02;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("/exam02")
public class ParamController {

    @GetMapping("/hello-name")
    public String HelloName(Model model
            ,@RequestParam(defaultValue = "") String name) {
        model.addAttribute("greeting", "안녕하세요 " + name);
        return "exam02/hello.jsp";
    }

....
}

📖 연습문제

//  TODO: 연습 1) url : /exam02/hello-dname
//               jsp : exam02/example01.jsp
//               문제 : dname(Sales)의 값을 url 쿼리스트링으로 전달해서
//                example01.jsp 에 출력해 보세요
//               결과 : 부서명 : Sales

📂 ParamController.java

@GetMapping("/exam02/hello-dname")
public String HelloDname(Model model, @RequestParam(defaultValue = "") String dname) {
    model.addAttribute("greeting", "부서명 : " + dname);
    return "exam02/example01.jsp";
}

📂 example01.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%--TODO : JSTL 표현식 사용을 위한 import--%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <%--    TODO 부트스트랩 CSS CDN --%>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
<body>
<div class="container">
    <h2>dname(Sales)의 값을 url 쿼리스트링으로 전달해서 출력하기</h2>
    <%--    TODO : el(Experssion language) 표현식 : ${변수명} --%>
    <p>${greeting}</p>
</div>
</body>
</html>

📌 실행결과

url?dname=Sale 를 추가하면 부서명에 Sales가 출력됩니다.

 

//  TODO: 연습 2) 숫자로 url 매개변수를 전달해서 화면에 출력해 보세요.
//         url : /exam02/hello-no
//         jsp : exam02/example02.jsp
//         결과 : 20

 

📂 ParamController.java

    @GetMapping("/exam02/hello-no")
    public String HelloNo(Model model, @RequestParam(defaultValue = "0") int no) {
        model.addAttribute("greeting", + no);
        return "exam02/example02.jsp";
    }

📂 example02.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%--TODO : JSTL 표현식 사용을 위한 import--%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <%--    TODO 부트스트랩 CSS CDN --%>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
<body>
<div class="container">
    <h2>숫자로 url 매개변수를 전달해서 화면에 출력하기</h2>
    <%--    TODO : el(Experssion language) 표현식 : ${변수명} --%>
    <p>${greeting}</p>
</div>
</body>
</html>

📌 실행결과

 

TODO: 연습 1) URL 매개변수로 dno=10, dName=Accounting 받았다. 아래 결과를 화면에 출력하세요.
url : /example01
jsp : exam03/example01.jsp
<결과>
부서번호 : 10
부서명 : Accounting
위치 : NewYork
url 테스트 : http://localhost:8000/exam03/example01?dno=10&dname=Accounting&loc=NewYork

📂 MultiParamController.java

@GetMapping("/example01")
public String setDnoDname (Model model,
                           @RequestParam(defaultValue = "0")int dno,
                           @RequestParam(defaultValue = "")String dname,
                           @RequestParam(defaultValue = "")String loc){
    model.addAttribute("dno","부서번호 : " + dno);
    model.addAttribute("dname","부서명 : " + dname);
    model.addAttribute("loc","위치 : " + loc);
    return "exam03/example01.jsp";
}

📂 example01.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%--TODO : JSTL 표현식 사용을 위한 import--%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <%--    TODO 부트스트랩 CSS CDN --%>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
<body>
<div class="container">
    <h2>예제 2번 문제</h2>
    <p>${dno}</p>
    <p>${dname}</p>
    <p>${loc}</p>
</div>
</body>
</html>

📌 실행결과

반응형

'Spring Boot > 스프링부트 예제' 카테고리의 다른 글

객체 형태로 변환하는 어노테이션  (0) 2023.10.05
JSP의 조건문  (1) 2023.10.05
URL 매개변수를 ArrayList 형태로 View에 전달  (0) 2023.10.05
Model  (0) 2023.10.04
Hello SpringBoot!  (0) 2023.10.04
반응형

💡 모델을 통해 변수 등록하는 방법

모델은 컨트롤러의 메서드에서 매개변수로 받아옵니다. 모델에서 변수를 등록할 때는 addAttribute() 메서드를 사용합니다.

model.addAttribute("변수명", 변숫값);

📂 HelloController.java

package com.example.controllerexam.controller.exam01;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * packageName : com.example.controllerexam.controller.exam01
 * fileName : HelloController
 * author : GGG
 * date : 2023-10-04
 * description : JSP, 타임리프  : SSR  (Server Side Rendering)
 *                   1) 구조 => Springboot 소스가 같이 있음
 *                   2) html 파일을 Springboot에서 만들어서 웹브라우저로 전송함
 *               React / Vue  : CSR  (Client Side Rendering)
 *                   1) 구조 => 소스가 분리됨
 *                   2) html 파일을 최초 1회 전송하고
 *                     => 다음부터는 변경되는 부분만 서버에서 받고, 나머지는 웹 브라우저에서 자체 만듦
 *               @GetMapping("url")함수(){ return "jsp페이지명"; }
 *               => CRUD => Read 요청일때 사용하는 어노테이션
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-04         GGG          최초 생성
 */
// TODO 1) @Controller 붙이기
@Controller
public class HelloController {
    @GetMapping("/exam01/hello")
    public String Hello(Model model){
//     TODO: 해쉬맵 자료구조 : (키, 값) => jsp 페이지로 변수를(greeting, greeting2 ...) 전송
        model.addAttribute("greeting","안녕 Springboot");
        model.addAttribute("greeting2","Springboot 처음이지");
        model.addAttribute("greeting3","수고해!!!");

        return "exam01/hello.jsp";
    }
}

💡 JSP의  el (Expression Language) 표현식

el(Expression Language) 표현식은 자바 Spring에서 전송한(Model) 키 값을 출력할 수 있습니다.

${변수명}

 

📂 hello.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%--TODO : JSTL 표현식 사용을 위한 import--%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>


<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
<%--    TODO 부트스트랩 CSS CDN --%>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
<body>
<%-- jsp 주석 --%>
<%-- TODO: el(Expression Language) 표현식 : ${변수명} --%>
<%--        자바 Spring에서 전송한(Model) 키 값을 출력할 수 있음--%>
    <p>${greeting}</p>
    <p>${greeting2}</p>
    <p>${greeting3}</p>

<%-- TODO: JSTL 표현식(참고) : <c:out value="${변수명}" /> --%>
    <p><c:out value="${greeting}" /></p>
</body>
</html>

 

📌 실행결과

💡 JSP 페이지로 모든 자료형을 보내기

//    TODO: 예제 2) JSP 페이지로 모든 자료형을 보내기
//         url : /exam01/operation
//         jsp : exam01/operation.jsp

✅ JSP에서 JAVA 문법과 같이 사칙연산 / 논리합(곱) / 비교연산자 / 삼항연산자 사용가능!

TODO: 문자열로 생긴 숫자는 자동으로 형변환 됨 (사칙연산시)
JSP : Java Server Page, 자바 문법 모두 사용가능
JSP(.jsp) -> servlet(.java)--%>

 

📂 HelloController.java

@GetMapping("/exam01/operation")
public String Operation(Model model) {

    String strNum = "10";
    int iNum = 100;
    String strVal = "a";
    boolean bVal = true;

    model.addAttribute("strNum",strNum);
    model.addAttribute("iNum",iNum);
    model.addAttribute("strVal",strVal);
    model.addAttribute("bVal",bVal);


    return "exam01/operation.jsp";
}

📂 operation.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%--TODO : JSTL 표현식 사용을 위한 import--%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <%--bootstrap css cdn--%>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
<body>
<%--TODO: spring에서 전송한 값으로 사칙연산, 논리연산 해보기--%>
<div class="container p-3">
    <h2>산술연산자</h2>
    <div class="ms-3 mt-3">
<%--   TODO: 문자열로 생긴 숫자는 자동으로 형변환 됨 (사칙연산시)    --%>
<%--    JSP : Java Server Page, 자바 문법 모두 사용가능 --%>
<%--    JSP(.jsp) -> servlet(.java)--%>
        <p>덧셈 : ${strNum + 10}</p>
        <p>뺄셈 : ${strNum - 10}</p>
        <p>곱셈 : ${strNum * 10}</p>
        <p>나눗셈 : ${strNum / 10}</p>
        <p>나머지 : ${strNum % 10}</p>

        <p>숫자 연산 : ${iNum + 10}</p>

        <p>논리 합 : ${bVal || false}</p>
        <p>논리 곱 : ${bVal && false}</p>

        <p>같음 : ${iNum == 100}</p>
        <p>같지않음 : ${iNum != 100}</p>
        <p>보다큼 : ${iNum >= 100}</p>
        <p>보다작음 : ${iNum <= 100}</p>

        <p>조건(삼항) : ${(iNum == 100)? "100임" : "100이 아님"}</p>
<%--    TODO : empty(빈) : 변수의 값이 비었으면(null, 0, "") true, 아니면 false --%>
        <p>empty : ${empty iNum}</p>
        <p>not empty : ${not empty iNum}</p>
    </div>
</div>


</body>
</html>

📌 실행결과

📖 연습문제

//  TODO: 연습 1) url : /exam01/hello2 => "exam01/example01.jsp" 에 "안녕 hello2 페이지 " 출력하기
//                                                                 "Springboot 처음이지"
//                                                                 "수고해!!!"

 

📂 HelloController.java

package com.example.controllerexam.controller.exam01;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;


// TODO 1) @Controller 붙이기
@Controller
public class HelloController {
 
//  TODO: 연습 1) url : /exam01/hello2 => "exam01/example01.jsp" 에 "안녕 hello2 페이지 " 출력하기
//                                                                 "Springboot 처음이지"
//                                                                 "수고해!!!"

    @GetMapping("/exam01/hello2")
    public String Hello2(Model model){
        model.addAttribute("greeting", "안녕 hello2 페이지");
        model.addAttribute("greeting2", "Springboot 처음이지");
        model.addAttribute("greeting3", "수고해!!!");

        return "exam01/example01.jsp";
    }

}

📂 example01.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%--TODO : JSTL 표현식 사용을 위한 import--%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>


<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <%--    TODO 부트스트랩 CSS CDN --%>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
<body>
<%-- jsp 주석 --%>
<%-- TODO: el(Expression Language) 표현식 : ${변수명} --%>
<%--        자바 Spring에서 전송한(Model) 키 값을 출력할 수 있음--%>
<p>${greeting}</p>
<p>${greeting2}</p>
<p>${greeting3}</p>


</body>
</html>

📌 실행결과

반응형

+ Recent posts