
📃 요약
네이버 카페 등의 게시판을 기본 기능을 제작하는 예제입니다.
기본적인 DB CRUD 를 사용해 제작합니다.
DB 프레임워크는 JPA 를 사용해 자동화기능을 강화합니다.
요소 기술 및 테이블 설계는 아래와 같습니다.
요소 기술 :
– 프론트엔드 : JSP
– 벡엔드 : 스프링부트 & JPA & Oracle 18xe(Oracle Cloud 19c)
결과 화면 :

프로젝트 탐색기 :

함수 URL :
메소드 | URL | 설명 |
---|---|---|
GET | dept | 전체 조회 |
GET | dept/{dno} | 상세조회 |
GET | /dept/addition | 추가페이지 열기 |
POST | /dept/add | 저장 |
GET | /dept/edition/{dno} | 수정페이지 열기(상세조회) |
PUT | /dept/edit/{dno} | 수정 |
DELETE | /dept/delete/{dno} | 삭제 |
📃 기술 구현
스펙 :
- jdk 17
- spring boot 3.x
- gradle
- jdk 17
- spring boot 3.x
- gradle
- jdk 17 - spring boot 3.x - gradle
테이블 설계
-- 부서 게시판
DROP SEQUENCE SQ_DEPT;
CREATE SEQUENCE SQ_DEPT START WITH 50 INCREMENT BY 10;
CREATE TABLE TB_DEPT (
DNO NUMBER NOT NULL PRIMARY KEY,
DNAME VARCHAR2(255),
LOC VARCHAR2(255),
INSERT_TIME VARCHAR2(255),
UPDATE_TIME VARCHAR2(255)
);
-- 부서 게시판
DROP SEQUENCE SQ_DEPT;
CREATE SEQUENCE SQ_DEPT START WITH 50 INCREMENT BY 10;
CREATE TABLE TB_DEPT (
DNO NUMBER NOT NULL PRIMARY KEY,
DNAME VARCHAR2(255),
LOC VARCHAR2(255),
INSERT_TIME VARCHAR2(255),
UPDATE_TIME VARCHAR2(255)
);
-- 부서 게시판 DROP SEQUENCE SQ_DEPT; CREATE SEQUENCE SQ_DEPT START WITH 50 INCREMENT BY 10; CREATE TABLE TB_DEPT ( DNO NUMBER NOT NULL PRIMARY KEY, DNAME VARCHAR2(255), LOC VARCHAR2(255), INSERT_TIME VARCHAR2(255), UPDATE_TIME VARCHAR2(255) );
부서 게시판 구현을 위한 테이블 설계입니다.
데이터베이스를 오라클을 사용하여 구현해 보겠습니다.
모델 : 엔티티
공통 모델
package com.example.jpaexam.model.common;
import lombok.Getter;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 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 {
private String insertTime;
private String updateTime;
}
package com.example.jpaexam.model.common;
import lombok.Getter;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 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 {
private String insertTime;
private String updateTime;
}
package com.example.jpaexam.model.common; import lombok.Getter; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import javax.persistence.EntityListeners; import javax.persistence.MappedSuperclass; import javax.persistence.PrePersist; import javax.persistence.PreUpdate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; /** * 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 { private String insertTime; private String updateTime; }
부서 게시판 : 엔티티
package com.example.jpaexam.model.entity.basic;
import com.example.jpaexam.model.common.BaseTimeEntity;
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
@NoArgsConstructor
@AllArgsConstructor
// 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: @GeneratedValue(strategy = GenerationType.SEQUENCE
// , generator = "시퀀스함수이름"
// )
@GeneratedValue(strategy = GenerationType.SEQUENCE
, generator = "SQ_DEPT_GENERATOR"
)
private Integer dno; // 부서번호(기본키) - 시퀀스 기능 부여
private String dname; // 부서명
private String loc; // 부서위치
}
package com.example.jpaexam.model.entity.basic;
import com.example.jpaexam.model.common.BaseTimeEntity;
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
@NoArgsConstructor
@AllArgsConstructor
// 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: @GeneratedValue(strategy = GenerationType.SEQUENCE
// , generator = "시퀀스함수이름"
// )
@GeneratedValue(strategy = GenerationType.SEQUENCE
, generator = "SQ_DEPT_GENERATOR"
)
private Integer dno; // 부서번호(기본키) - 시퀀스 기능 부여
private String dname; // 부서명
private String loc; // 부서위치
}
package com.example.jpaexam.model.entity.basic; import com.example.jpaexam.model.common.BaseTimeEntity; 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 @NoArgsConstructor @AllArgsConstructor // 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: @GeneratedValue(strategy = GenerationType.SEQUENCE // , generator = "시퀀스함수이름" // ) @GeneratedValue(strategy = GenerationType.SEQUENCE , generator = "SQ_DEPT_GENERATOR" ) private Integer dno; // 부서번호(기본키) - 시퀀스 기능 부여 private String dname; // 부서명 private String loc; // 부서위치 }
JPA 어노테이션 :
(1) 클래스에 붙임
1) @Entity
: JPA 프레임워크 사용시 기능을 대상 클래스 부여하는 어노테이션
2) @Table(name = "테이블명")
: JPA ddl(테이블,인덱스등생성) 생성기능 사용시
그 이름으로 DB 생성해주는 어노테이션
3) @SequenceGenerator(
name = "시퀀스_제너레이터이름"
, sequenceName = "DB시퀀스명"
, initialValue = 초기값 ( 시퀀스 처음값 )
, allocationSize = 할당값 (JPA 공간에서 생성될 값: 보통 1 사용)
: 오라클 DB 제품의 시퀀스를 JPA 에서 사용하기 위한 어노테이션,
클래스 위에 붙임
4) @DynamicInsert(옵션)
: 옵션 기능임, JPA 에서 insert SQL 자동 생성시
null 값 들어오는 컬럼은 제외하고 sql 작성해주는 어노테이션
예) insert into dept(dno, dname, loc)
values(1,2, null);
=> insert into dept(dno, dname)
values(1,2); // 이렇게 바뀜(null 에러 방지)
5) @DynamicUpdate(옵션)
: 옵션 기능임, JPA 에서 update SQL 자동 생성시
null 값 들어오는 컬럼은 제외하고 sql 작성해주는 어노테이션
6) 기타
: 롬북 어노테이션 상황에따라 추가 (setter/getter 등)
```
(2) 속성(필드)에 붙임
```
1) @Id
: 속성(필드) 기본키를 정의하는 어노테이션 (필수)
2) @GeneratedValue(strategy = GenerationType.SEQUENCE
, generator = "시퀀스_제너레이터이름"
: 시퀀스를 어느 속성(필드)에 연결할 것인가를 지시하는 어노테이션
보통 기본키 컬럼(속성)에 사용함
3) @Column(columnDefinition = "DB컬럼자료형")
: 생략가능, JPA 의 ddl(테이블,인덱스등) 생성기능을 사용한다면 DB 테이블을 만들때
지시된 자료형으로 생성함
1) @Entity
: JPA 프레임워크 사용시 기능을 대상 클래스 부여하는 어노테이션
2) @Table(name = "테이블명")
: JPA ddl(테이블,인덱스등생성) 생성기능 사용시
그 이름으로 DB 생성해주는 어노테이션
3) @SequenceGenerator(
name = "시퀀스_제너레이터이름"
, sequenceName = "DB시퀀스명"
, initialValue = 초기값 ( 시퀀스 처음값 )
, allocationSize = 할당값 (JPA 공간에서 생성될 값: 보통 1 사용)
: 오라클 DB 제품의 시퀀스를 JPA 에서 사용하기 위한 어노테이션,
클래스 위에 붙임
4) @DynamicInsert(옵션)
: 옵션 기능임, JPA 에서 insert SQL 자동 생성시
null 값 들어오는 컬럼은 제외하고 sql 작성해주는 어노테이션
예) insert into dept(dno, dname, loc)
values(1,2, null);
=> insert into dept(dno, dname)
values(1,2); // 이렇게 바뀜(null 에러 방지)
5) @DynamicUpdate(옵션)
: 옵션 기능임, JPA 에서 update SQL 자동 생성시
null 값 들어오는 컬럼은 제외하고 sql 작성해주는 어노테이션
6) 기타
: 롬북 어노테이션 상황에따라 추가 (setter/getter 등)
```
(2) 속성(필드)에 붙임
```
1) @Id
: 속성(필드) 기본키를 정의하는 어노테이션 (필수)
2) @GeneratedValue(strategy = GenerationType.SEQUENCE
, generator = "시퀀스_제너레이터이름"
: 시퀀스를 어느 속성(필드)에 연결할 것인가를 지시하는 어노테이션
보통 기본키 컬럼(속성)에 사용함
3) @Column(columnDefinition = "DB컬럼자료형")
: 생략가능, JPA 의 ddl(테이블,인덱스등) 생성기능을 사용한다면 DB 테이블을 만들때
지시된 자료형으로 생성함
1) @Entity : JPA 프레임워크 사용시 기능을 대상 클래스 부여하는 어노테이션 2) @Table(name = "테이블명") : JPA ddl(테이블,인덱스등생성) 생성기능 사용시 그 이름으로 DB 생성해주는 어노테이션 3) @SequenceGenerator( name = "시퀀스_제너레이터이름" , sequenceName = "DB시퀀스명" , initialValue = 초기값 ( 시퀀스 처음값 ) , allocationSize = 할당값 (JPA 공간에서 생성될 값: 보통 1 사용) : 오라클 DB 제품의 시퀀스를 JPA 에서 사용하기 위한 어노테이션, 클래스 위에 붙임 4) @DynamicInsert(옵션) : 옵션 기능임, JPA 에서 insert SQL 자동 생성시 null 값 들어오는 컬럼은 제외하고 sql 작성해주는 어노테이션 예) insert into dept(dno, dname, loc) values(1,2, null); => insert into dept(dno, dname) values(1,2); // 이렇게 바뀜(null 에러 방지) 5) @DynamicUpdate(옵션) : 옵션 기능임, JPA 에서 update SQL 자동 생성시 null 값 들어오는 컬럼은 제외하고 sql 작성해주는 어노테이션 6) 기타 : 롬북 어노테이션 상황에따라 추가 (setter/getter 등) ``` (2) 속성(필드)에 붙임 ``` 1) @Id : 속성(필드) 기본키를 정의하는 어노테이션 (필수) 2) @GeneratedValue(strategy = GenerationType.SEQUENCE , generator = "시퀀스_제너레이터이름" : 시퀀스를 어느 속성(필드)에 연결할 것인가를 지시하는 어노테이션 보통 기본키 컬럼(속성)에 사용함 3) @Column(columnDefinition = "DB컬럼자료형") : 생략가능, JPA 의 ddl(테이블,인덱스등) 생성기능을 사용한다면 DB 테이블을 만들때 지시된 자료형으로 생성함
부서 레포지토리
package com.example.jpaexam.repository.basic;
import com.example.jpaexam.model.entity.advanced.FileDb;
import com.example.jpaexam.model.entity.basic.Dept;
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.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 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> {
// dname like 검색
// Page<Dept> findAllByDnameContaining(String dname, Pageable pageable);
@Query(value = "SELECT TD.* FROM TB_DEPT TD " +
"WHERE TD.DNAME LIKE '%' || :dname || '%' "
, countQuery = "SELECT COUNT(*) FROM TB_DEPT D " +
"WHERE D.DNAME LIKE '%' || :dname || '%'"
, nativeQuery = true)
Page<Dept> findAllByDnameContaining(@Param("dname") String dname, Pageable pageable);
}
package com.example.jpaexam.repository.basic;
import com.example.jpaexam.model.entity.advanced.FileDb;
import com.example.jpaexam.model.entity.basic.Dept;
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.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 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> {
// dname like 검색
// Page<Dept> findAllByDnameContaining(String dname, Pageable pageable);
@Query(value = "SELECT TD.* FROM TB_DEPT TD " +
"WHERE TD.DNAME LIKE '%' || :dname || '%' "
, countQuery = "SELECT COUNT(*) FROM TB_DEPT D " +
"WHERE D.DNAME LIKE '%' || :dname || '%'"
, nativeQuery = true)
Page<Dept> findAllByDnameContaining(@Param("dname") String dname, Pageable pageable);
}
package com.example.jpaexam.repository.basic; import com.example.jpaexam.model.entity.advanced.FileDb; import com.example.jpaexam.model.entity.basic.Dept; 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.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; /** * 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> { // dname like 검색 // Page<Dept> findAllByDnameContaining(String dname, Pageable pageable); @Query(value = "SELECT TD.* FROM TB_DEPT TD " + "WHERE TD.DNAME LIKE '%' || :dname || '%' " , countQuery = "SELECT COUNT(*) FROM TB_DEPT D " + "WHERE D.DNAME LIKE '%' || :dname || '%'" , nativeQuery = true) Page<Dept> findAllByDnameContaining(@Param("dname") String dname, Pageable pageable); }
Repository 인터페이스 : DB CRUD 함수가 있는 인터페이스
사용법 : interface 이름 extends JpaRepository<엔티티명,기본키속성자료형>
=> 엔티티명 : DB 와 연결될 엔티티 클래스명
=> 기본키속성자료형 : 엔티티 클래스의 기본키 속성 자료형 명시
=> JPA 기본 함수 사용 가능
- findAll() : 전체 조회 , 자동 sql 문 생성
- findById(기본키) : 상세 조회(1건), 자동 sql 문 생성
- save(객체) : 저장/수정을 알아서 실행함
저장 : 기본키가 없으면 insert
수정 : 기본키가 있으면 update
- deleteById(기본키): 삭제 , 자동 sql 문 생성
사용법 : interface 이름 extends JpaRepository<엔티티명,기본키속성자료형>
=> 엔티티명 : DB 와 연결될 엔티티 클래스명
=> 기본키속성자료형 : 엔티티 클래스의 기본키 속성 자료형 명시
=> JPA 기본 함수 사용 가능
- findAll() : 전체 조회 , 자동 sql 문 생성
- findById(기본키) : 상세 조회(1건), 자동 sql 문 생성
- save(객체) : 저장/수정을 알아서 실행함
저장 : 기본키가 없으면 insert
수정 : 기본키가 있으면 update
- deleteById(기본키): 삭제 , 자동 sql 문 생성
사용법 : interface 이름 extends JpaRepository<엔티티명,기본키속성자료형> => 엔티티명 : DB 와 연결될 엔티티 클래스명 => 기본키속성자료형 : 엔티티 클래스의 기본키 속성 자료형 명시 => JPA 기본 함수 사용 가능 - findAll() : 전체 조회 , 자동 sql 문 생성 - findById(기본키) : 상세 조회(1건), 자동 sql 문 생성 - save(객체) : 저장/수정을 알아서 실행함 저장 : 기본키가 없으면 insert 수정 : 기본키가 있으면 update - deleteById(기본키): 삭제 , 자동 sql 문 생성
@Query : 직접 sql 문 작성 기능, 오라클 기반 쿼리(nativeQuery = true)
검색어를 넣어 like 검색을 할수 있게 하는 함수로 sql 문을 직접 작성가능
페이징 처리를 위해 countQuery 가 필요함 : 조건에 따른 결과 개수세기
@Query 사용법 :
@Query(value = "SELECT 컬럼명 FROM 테이블명 별명 " +
"WHERE 별명.컬럼명 LIKE '%' || :변수명 || '%' "
, countQuery = "SELECT COUNT(*) FROM 테이블명 별명 " +
"WHERE 별명.컬럼명 LIKE '%' || :변수명 || '%'"
, nativeQuery = true)
Page<자료형> 함수명(@Param("변수명") 자료형 변수명, Pageable pageable);
@Query 사용법 :
@Query(value = "SELECT 컬럼명 FROM 테이블명 별명 " +
"WHERE 별명.컬럼명 LIKE '%' || :변수명 || '%' "
, countQuery = "SELECT COUNT(*) FROM 테이블명 별명 " +
"WHERE 별명.컬럼명 LIKE '%' || :변수명 || '%'"
, nativeQuery = true)
Page<자료형> 함수명(@Param("변수명") 자료형 변수명, Pageable pageable);
@Query 사용법 : @Query(value = "SELECT 컬럼명 FROM 테이블명 별명 " + "WHERE 별명.컬럼명 LIKE '%' || :변수명 || '%' " , countQuery = "SELECT COUNT(*) FROM 테이블명 별명 " + "WHERE 별명.컬럼명 LIKE '%' || :변수명 || '%'" , nativeQuery = true) Page<자료형> 함수명(@Param("변수명") 자료형 변수명, Pageable pageable);
부서 게시판 서비스
package com.example.jpaexam.service.basic;
import com.example.jpaexam.model.entity.advanced.FileDb;
import com.example.jpaexam.model.entity.basic.Dept;
import com.example.jpaexam.repository.basic.DeptRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
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;
}
/** 전체조회 : 페이징 */
public Page<Dept> findAll(Pageable pageable) {
Page<Dept> list = deptRepository.findAll(pageable); // db 전체조회 함수 호출
return list;
}
public Page<Dept> findAllByDnameContaining(String dname, Pageable pageable) {
Page<Dept> page = deptRepository.findAllByDnameContaining(dname, pageable);
return page;
}
/** 상세조회(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) {
// existsById : jpa 함수 - 리턴값: 있으면 true, 없으면 false
if(deptRepository.existsById(dno)) {
deptRepository.deleteById(dno); // DB 삭제(dno)
return true;
}
return false;
}
}
package com.example.jpaexam.service.basic;
import com.example.jpaexam.model.entity.advanced.FileDb;
import com.example.jpaexam.model.entity.basic.Dept;
import com.example.jpaexam.repository.basic.DeptRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
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;
}
/** 전체조회 : 페이징 */
public Page<Dept> findAll(Pageable pageable) {
Page<Dept> list = deptRepository.findAll(pageable); // db 전체조회 함수 호출
return list;
}
public Page<Dept> findAllByDnameContaining(String dname, Pageable pageable) {
Page<Dept> page = deptRepository.findAllByDnameContaining(dname, pageable);
return page;
}
/** 상세조회(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) {
// existsById : jpa 함수 - 리턴값: 있으면 true, 없으면 false
if(deptRepository.existsById(dno)) {
deptRepository.deleteById(dno); // DB 삭제(dno)
return true;
}
return false;
}
}
package com.example.jpaexam.service.basic; import com.example.jpaexam.model.entity.advanced.FileDb; import com.example.jpaexam.model.entity.basic.Dept; import com.example.jpaexam.repository.basic.DeptRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; 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; } /** 전체조회 : 페이징 */ public Page<Dept> findAll(Pageable pageable) { Page<Dept> list = deptRepository.findAll(pageable); // db 전체조회 함수 호출 return list; } public Page<Dept> findAllByDnameContaining(String dname, Pageable pageable) { Page<Dept> page = deptRepository.findAllByDnameContaining(dname, pageable); return page; } /** 상세조회(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) { // existsById : jpa 함수 - 리턴값: 있으면 true, 없으면 false if(deptRepository.existsById(dno)) { deptRepository.deleteById(dno); // DB 삭제(dno) return true; } return false; } }
부서 게시판 컨트롤러
package com.example.jpaexam.controller.basic;
import com.example.jpaexam.model.entity.basic.Dept;
import com.example.jpaexam.service.basic.DeptService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.view.RedirectView;
import java.util.Optional;
/**
* packageName : com.example.mybatisexam.controller.exam01
* fileName : DeptController
* author : GGG
* date : 2023-10-12
* description : 부서 컨트롤러 : jsp 연동 : @Controller
* 요약 :
* <p>
* ===========================================================
* DATE AUTHOR NOTE
* —————————————————————————————
* 2023-10-12 GGG 최초 생성
*/
@Slf4j
@Controller
@RequestMapping("/basic")
public class DeptJspController {
@Autowired
DeptService deptService; // 서비스 객체 가져오기
/** 전체 조회 : dname like 기능 (+) */
// todo: @RequestParam - url?변수=값&변수2=값2 (쿼리스트링 방식)
@GetMapping("/dept")
public String getDeptAll(
@RequestParam(defaultValue = "") String dname
, @RequestParam(defaultValue = "0") int page
, @RequestParam(defaultValue = "3") int size
, Model model
){
// todo: 페이징 요청 객체에 정보 저장
// page : 현재페이지 번호, size : 1 페이지당 개수
Pageable pageable = PageRequest.of(page, size);
// todo: 전체 조회 함수 호출
Page<Dept> pageRes
= deptService.findAllByDnameContaining(dname, pageable);
// todo: jsp 정보전달( 부서배열, 페이징정보 )
model.addAttribute("dept", pageRes.getContent()); // 부서배열
model.addAttribute("currentPage", pageRes.getNumber()); // 현재 페이지 번호(0 ~)
model.addAttribute("totalItems", pageRes.getTotalElements()); // 전체 테이블 건수
model.addAttribute("totalPages", pageRes.getTotalPages()); // 전체 페이지 개수
// 공식 : 블럭 시작페이지 번호 = (Math.floor(현재페이지번호/1페이지당개수)) * 1페이지당개수
// 예) 1블럭 : 0(블럭시작페이지번호) ~ 2(블럭끝페이지번호) , 2블럭 : 3(블럭시작페이지번호) ~ 5(블럭끝페이지번호), 3블럭 : 6(블럭시작페이지번호) ~ 8(블럭끝페이지번호)
// 현재페이지 2, 1페이지당 개수 3 => Math.floor(2/3) * 3 = 0
// 현재페이지 5, 1페이지당 개수 3 => Math.floor(5/3) * 3 = 3
long blockStartPage = (long) Math.floor((double)(pageRes.getNumber()) / size) * size;
model.addAttribute("startPage", blockStartPage); // 블럭 시작 페이지 번호(0 ~)
// 공식 : 블럭 끝페이지 번호 = 블럭 시작페이지번호 + 1페이자당개수 - 1
// 만약 블럭 끝페이지번호 >= 전체페이지수 =>(블럭 끝페이지번호는 전체페이지수보다 클수 없음) 블럭끝페이지번호 = 전체페이지수 -1 넣기(0부터 시작)
long blockEndPage = blockStartPage + size - 1;
blockEndPage = (blockEndPage >= pageRes.getTotalPages())? pageRes.getTotalPages() - 1 : blockEndPage;
model.addAttribute("endPage", blockEndPage); // 현재 끝 페이지 번호
log.debug(model.toString()); // 로그 출력
return "basic/dept/dept_all.jsp";
}
/** 상세조회 */
@GetMapping("/dept/{dno}")
public String getDeptId(@PathVariable int dno,
Model model
) {
// 서비스 상세조회 함수 호출
Optional<Dept> optionalDept = deptService.findById(dno);
model.addAttribute("dept", optionalDept.get());
return "basic/dept/dept_id.jsp";
}
/** 저장함수 : 저장 페이지로 이동 */
@GetMapping("/dept/addition")
public String addDept(){
return "basic/dept/add_dept.jsp";
}
/** 저장함수 : db 저장 */
@PostMapping("/dept/add")
public RedirectView createDept(
@ModelAttribute Dept dept
){
deptService.save(dept); // db 저장
// 전체 조회 페이지로 강제 이동
return new RedirectView("/basic/dept");
}
/** 수정함수 : 수정 페이지로 이동 + 상세조회(1건조회) */
@GetMapping("/dept/edition/{dno}")
public String editDept(@PathVariable int dno,
Model model
) {
// 서비스 상세조회 함수 호출
Optional<Dept> optionalDept = deptService.findById(dno);
// jsp 전달
model.addAttribute("dept", optionalDept.get());
return "basic/dept/update_dept.jsp";
}
/** 수정함수 : db 수정 저장 */
@PutMapping("/dept/edit/{dno}")
public RedirectView updateDept(@PathVariable int dno,
@ModelAttribute Dept dept
) {
log.debug("---------------start-------------------------------");
deptService.save(dept); // db 수정 저장
log.debug("---------------end-------------------------------");
// 전체 조회 페이지로 강제 이동
return new RedirectView("/basic/dept");
}
/** 삭제함수 */
@DeleteMapping("/dept/delete/{dno}")
public RedirectView deleteDept(@PathVariable int dno) {
deptService.removeById(dno); // db 삭제
return new RedirectView("basic/dept");
}
// todo: 연습 5) 부서 클래스를 참고하여 사원 삭제기능을 추가하세요
// empDao, emp.xml, EmpService, EmpController, update_emp.jsp 수정
// url : /emp/delete/{eno}
// redirect jsp : /emp
}
package com.example.jpaexam.controller.basic;
import com.example.jpaexam.model.entity.basic.Dept;
import com.example.jpaexam.service.basic.DeptService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.view.RedirectView;
import java.util.Optional;
/**
* packageName : com.example.mybatisexam.controller.exam01
* fileName : DeptController
* author : GGG
* date : 2023-10-12
* description : 부서 컨트롤러 : jsp 연동 : @Controller
* 요약 :
* <p>
* ===========================================================
* DATE AUTHOR NOTE
* —————————————————————————————
* 2023-10-12 GGG 최초 생성
*/
@Slf4j
@Controller
@RequestMapping("/basic")
public class DeptJspController {
@Autowired
DeptService deptService; // 서비스 객체 가져오기
/** 전체 조회 : dname like 기능 (+) */
// todo: @RequestParam - url?변수=값&변수2=값2 (쿼리스트링 방식)
@GetMapping("/dept")
public String getDeptAll(
@RequestParam(defaultValue = "") String dname
, @RequestParam(defaultValue = "0") int page
, @RequestParam(defaultValue = "3") int size
, Model model
){
// todo: 페이징 요청 객체에 정보 저장
// page : 현재페이지 번호, size : 1 페이지당 개수
Pageable pageable = PageRequest.of(page, size);
// todo: 전체 조회 함수 호출
Page<Dept> pageRes
= deptService.findAllByDnameContaining(dname, pageable);
// todo: jsp 정보전달( 부서배열, 페이징정보 )
model.addAttribute("dept", pageRes.getContent()); // 부서배열
model.addAttribute("currentPage", pageRes.getNumber()); // 현재 페이지 번호(0 ~)
model.addAttribute("totalItems", pageRes.getTotalElements()); // 전체 테이블 건수
model.addAttribute("totalPages", pageRes.getTotalPages()); // 전체 페이지 개수
// 공식 : 블럭 시작페이지 번호 = (Math.floor(현재페이지번호/1페이지당개수)) * 1페이지당개수
// 예) 1블럭 : 0(블럭시작페이지번호) ~ 2(블럭끝페이지번호) , 2블럭 : 3(블럭시작페이지번호) ~ 5(블럭끝페이지번호), 3블럭 : 6(블럭시작페이지번호) ~ 8(블럭끝페이지번호)
// 현재페이지 2, 1페이지당 개수 3 => Math.floor(2/3) * 3 = 0
// 현재페이지 5, 1페이지당 개수 3 => Math.floor(5/3) * 3 = 3
long blockStartPage = (long) Math.floor((double)(pageRes.getNumber()) / size) * size;
model.addAttribute("startPage", blockStartPage); // 블럭 시작 페이지 번호(0 ~)
// 공식 : 블럭 끝페이지 번호 = 블럭 시작페이지번호 + 1페이자당개수 - 1
// 만약 블럭 끝페이지번호 >= 전체페이지수 =>(블럭 끝페이지번호는 전체페이지수보다 클수 없음) 블럭끝페이지번호 = 전체페이지수 -1 넣기(0부터 시작)
long blockEndPage = blockStartPage + size - 1;
blockEndPage = (blockEndPage >= pageRes.getTotalPages())? pageRes.getTotalPages() - 1 : blockEndPage;
model.addAttribute("endPage", blockEndPage); // 현재 끝 페이지 번호
log.debug(model.toString()); // 로그 출력
return "basic/dept/dept_all.jsp";
}
/** 상세조회 */
@GetMapping("/dept/{dno}")
public String getDeptId(@PathVariable int dno,
Model model
) {
// 서비스 상세조회 함수 호출
Optional<Dept> optionalDept = deptService.findById(dno);
model.addAttribute("dept", optionalDept.get());
return "basic/dept/dept_id.jsp";
}
/** 저장함수 : 저장 페이지로 이동 */
@GetMapping("/dept/addition")
public String addDept(){
return "basic/dept/add_dept.jsp";
}
/** 저장함수 : db 저장 */
@PostMapping("/dept/add")
public RedirectView createDept(
@ModelAttribute Dept dept
){
deptService.save(dept); // db 저장
// 전체 조회 페이지로 강제 이동
return new RedirectView("/basic/dept");
}
/** 수정함수 : 수정 페이지로 이동 + 상세조회(1건조회) */
@GetMapping("/dept/edition/{dno}")
public String editDept(@PathVariable int dno,
Model model
) {
// 서비스 상세조회 함수 호출
Optional<Dept> optionalDept = deptService.findById(dno);
// jsp 전달
model.addAttribute("dept", optionalDept.get());
return "basic/dept/update_dept.jsp";
}
/** 수정함수 : db 수정 저장 */
@PutMapping("/dept/edit/{dno}")
public RedirectView updateDept(@PathVariable int dno,
@ModelAttribute Dept dept
) {
log.debug("---------------start-------------------------------");
deptService.save(dept); // db 수정 저장
log.debug("---------------end-------------------------------");
// 전체 조회 페이지로 강제 이동
return new RedirectView("/basic/dept");
}
/** 삭제함수 */
@DeleteMapping("/dept/delete/{dno}")
public RedirectView deleteDept(@PathVariable int dno) {
deptService.removeById(dno); // db 삭제
return new RedirectView("basic/dept");
}
// todo: 연습 5) 부서 클래스를 참고하여 사원 삭제기능을 추가하세요
// empDao, emp.xml, EmpService, EmpController, update_emp.jsp 수정
// url : /emp/delete/{eno}
// redirect jsp : /emp
}
package com.example.jpaexam.controller.basic; import com.example.jpaexam.model.entity.basic.Dept; import com.example.jpaexam.service.basic.DeptService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.view.RedirectView; import java.util.Optional; /** * packageName : com.example.mybatisexam.controller.exam01 * fileName : DeptController * author : GGG * date : 2023-10-12 * description : 부서 컨트롤러 : jsp 연동 : @Controller * 요약 : * <p> * =========================================================== * DATE AUTHOR NOTE * ————————————————————————————— * 2023-10-12 GGG 최초 생성 */ @Slf4j @Controller @RequestMapping("/basic") public class DeptJspController { @Autowired DeptService deptService; // 서비스 객체 가져오기 /** 전체 조회 : dname like 기능 (+) */ // todo: @RequestParam - url?변수=값&변수2=값2 (쿼리스트링 방식) @GetMapping("/dept") public String getDeptAll( @RequestParam(defaultValue = "") String dname , @RequestParam(defaultValue = "0") int page , @RequestParam(defaultValue = "3") int size , Model model ){ // todo: 페이징 요청 객체에 정보 저장 // page : 현재페이지 번호, size : 1 페이지당 개수 Pageable pageable = PageRequest.of(page, size); // todo: 전체 조회 함수 호출 Page<Dept> pageRes = deptService.findAllByDnameContaining(dname, pageable); // todo: jsp 정보전달( 부서배열, 페이징정보 ) model.addAttribute("dept", pageRes.getContent()); // 부서배열 model.addAttribute("currentPage", pageRes.getNumber()); // 현재 페이지 번호(0 ~) model.addAttribute("totalItems", pageRes.getTotalElements()); // 전체 테이블 건수 model.addAttribute("totalPages", pageRes.getTotalPages()); // 전체 페이지 개수 // 공식 : 블럭 시작페이지 번호 = (Math.floor(현재페이지번호/1페이지당개수)) * 1페이지당개수 // 예) 1블럭 : 0(블럭시작페이지번호) ~ 2(블럭끝페이지번호) , 2블럭 : 3(블럭시작페이지번호) ~ 5(블럭끝페이지번호), 3블럭 : 6(블럭시작페이지번호) ~ 8(블럭끝페이지번호) // 현재페이지 2, 1페이지당 개수 3 => Math.floor(2/3) * 3 = 0 // 현재페이지 5, 1페이지당 개수 3 => Math.floor(5/3) * 3 = 3 long blockStartPage = (long) Math.floor((double)(pageRes.getNumber()) / size) * size; model.addAttribute("startPage", blockStartPage); // 블럭 시작 페이지 번호(0 ~) // 공식 : 블럭 끝페이지 번호 = 블럭 시작페이지번호 + 1페이자당개수 - 1 // 만약 블럭 끝페이지번호 >= 전체페이지수 =>(블럭 끝페이지번호는 전체페이지수보다 클수 없음) 블럭끝페이지번호 = 전체페이지수 -1 넣기(0부터 시작) long blockEndPage = blockStartPage + size - 1; blockEndPage = (blockEndPage >= pageRes.getTotalPages())? pageRes.getTotalPages() - 1 : blockEndPage; model.addAttribute("endPage", blockEndPage); // 현재 끝 페이지 번호 log.debug(model.toString()); // 로그 출력 return "basic/dept/dept_all.jsp"; } /** 상세조회 */ @GetMapping("/dept/{dno}") public String getDeptId(@PathVariable int dno, Model model ) { // 서비스 상세조회 함수 호출 Optional<Dept> optionalDept = deptService.findById(dno); model.addAttribute("dept", optionalDept.get()); return "basic/dept/dept_id.jsp"; } /** 저장함수 : 저장 페이지로 이동 */ @GetMapping("/dept/addition") public String addDept(){ return "basic/dept/add_dept.jsp"; } /** 저장함수 : db 저장 */ @PostMapping("/dept/add") public RedirectView createDept( @ModelAttribute Dept dept ){ deptService.save(dept); // db 저장 // 전체 조회 페이지로 강제 이동 return new RedirectView("/basic/dept"); } /** 수정함수 : 수정 페이지로 이동 + 상세조회(1건조회) */ @GetMapping("/dept/edition/{dno}") public String editDept(@PathVariable int dno, Model model ) { // 서비스 상세조회 함수 호출 Optional<Dept> optionalDept = deptService.findById(dno); // jsp 전달 model.addAttribute("dept", optionalDept.get()); return "basic/dept/update_dept.jsp"; } /** 수정함수 : db 수정 저장 */ @PutMapping("/dept/edit/{dno}") public RedirectView updateDept(@PathVariable int dno, @ModelAttribute Dept dept ) { log.debug("---------------start-------------------------------"); deptService.save(dept); // db 수정 저장 log.debug("---------------end-------------------------------"); // 전체 조회 페이지로 강제 이동 return new RedirectView("/basic/dept"); } /** 삭제함수 */ @DeleteMapping("/dept/delete/{dno}") public RedirectView deleteDept(@PathVariable int dno) { deptService.removeById(dno); // db 삭제 return new RedirectView("basic/dept"); } // todo: 연습 5) 부서 클래스를 참고하여 사원 삭제기능을 추가하세요 // empDao, emp.xml, EmpService, EmpController, update_emp.jsp 수정 // url : /emp/delete/{eno} // redirect jsp : /emp }
- 전체 조회 : getDeptAll() 은 검색어가 “” 전달되면 전체조회가 되고
“검색어” 넣으면 like 검색이 실행됨 - 페이징 처리 : 기본 요약 페이징 처리 부분 참고
jsp 페이지
jsp 전체조회 페이지
결과 화면 :


<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ 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>
<%--header--%>
<jsp:include page="../../common/header.jsp"/>
<div class="container">
<%-- todo: 검색어 시작--%>
<%-- form 안에 input 태그의 value 값들이 -> springboot 함수로 전달됨 --%>
<form class="row g-3 justify-content-center" action="/basic/dept" method="get">
<div class="col-auto">
<%-- todo: 부서명 라벨 --%>
<label for="dname" class="visually-hidden">Dname</label>
<%-- todo: 검색창--%>
<input type="text" class="form-control" id="dname" placeholder="dname" name="dname">
<%-- todo: hidden(숨김) page = 0, size = 3 --%>
<input type="hidden" class="form-control" id="page" name="page" value="0">
<input type="hidden" class="form-control" id="size" name="size" value="3">
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary mb-3">Search</button>
</div>
</form>
<%-- todo: 검색어 끝--%>
<%-- todo: 테이블 반복문 시작--%>
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">ID</th>
<th scope="col">Name</th>
<th scope="col">위치</th>
<th scope="col">등록일자</th>
<th scope="col">수정일자</th>
</tr>
</thead>
<tbody>
<c:forEach var="data" items="${dept}">
<tr>
<td><a href="/basic/dept/edition/${data.dno}">${data.dno}</a></td>
<td>${data.dname}</td>
<td>${data.loc}</td>
<td>${data.insertTime}</td>
<td>${data.updateTime}</td>
</tr>
</c:forEach>
</tbody>
</table>
<%-- todo: 테이블 반복문 끝--%>
<%-- todo: 페이지 번호 시작--%>
<div class="d-flex justify-content-center">
<ul class="pagination">
<%-- todo: 첫페이지 번호 --%>
<%-- startPage : 0부터 시작 --%>
<%-- currentPage : 0부터 시작--%>
<li class="page-item ${(startPage+1==1)? 'disabled': ''}">
<a class="page-link" href="/basic/dept?page=${startPage-1}&size=">Previous</a>
</li>
<%-- todo: 실제 페이지 번호들 --%>
<%-- 사용법 : <c:forEach var="data" begin="시작값" end="끝값">반복문</c:forEach>--%>
<c:forEach var="data" begin="${startPage}" end="${endPage}">
<li class="page-item ${(currentPage==data)? 'active': ''}">
<a class="page-link" href="/basic/dept?page=${data}&size=">
${data+1}
</a>
</li>
</c:forEach>
<%-- todo: 끝페이지 번호--%>
<li class="page-item ${(endPage+1==totalPages)? 'disabled': ''}">
<a class="page-link" href="/basic/dept?page=${endPage+1}&size=">Next</a>
</li>
</ul>
</div>
<%-- todo: 페이지 번호 끝--%>
<%-- todo: Add 버튼 추가 --%>
<div class="text-center">
<a href="/basic/dept/addition" class="btn btn-primary center">Add</a>
</div>
</div>
<%--footer--%>
<jsp:include page="../../common/footer.jsp"/>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ 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>
<%--header--%>
<jsp:include page="../../common/header.jsp"/>
<div class="container">
<%-- todo: 검색어 시작--%>
<%-- form 안에 input 태그의 value 값들이 -> springboot 함수로 전달됨 --%>
<form class="row g-3 justify-content-center" action="/basic/dept" method="get">
<div class="col-auto">
<%-- todo: 부서명 라벨 --%>
<label for="dname" class="visually-hidden">Dname</label>
<%-- todo: 검색창--%>
<input type="text" class="form-control" id="dname" placeholder="dname" name="dname">
<%-- todo: hidden(숨김) page = 0, size = 3 --%>
<input type="hidden" class="form-control" id="page" name="page" value="0">
<input type="hidden" class="form-control" id="size" name="size" value="3">
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary mb-3">Search</button>
</div>
</form>
<%-- todo: 검색어 끝--%>
<%-- todo: 테이블 반복문 시작--%>
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">ID</th>
<th scope="col">Name</th>
<th scope="col">위치</th>
<th scope="col">등록일자</th>
<th scope="col">수정일자</th>
</tr>
</thead>
<tbody>
<c:forEach var="data" items="${dept}">
<tr>
<td><a href="/basic/dept/edition/${data.dno}">${data.dno}</a></td>
<td>${data.dname}</td>
<td>${data.loc}</td>
<td>${data.insertTime}</td>
<td>${data.updateTime}</td>
</tr>
</c:forEach>
</tbody>
</table>
<%-- todo: 테이블 반복문 끝--%>
<%-- todo: 페이지 번호 시작--%>
<div class="d-flex justify-content-center">
<ul class="pagination">
<%-- todo: 첫페이지 번호 --%>
<%-- startPage : 0부터 시작 --%>
<%-- currentPage : 0부터 시작--%>
<li class="page-item ${(startPage+1==1)? 'disabled': ''}">
<a class="page-link" href="/basic/dept?page=${startPage-1}&size=">Previous</a>
</li>
<%-- todo: 실제 페이지 번호들 --%>
<%-- 사용법 : <c:forEach var="data" begin="시작값" end="끝값">반복문</c:forEach>--%>
<c:forEach var="data" begin="${startPage}" end="${endPage}">
<li class="page-item ${(currentPage==data)? 'active': ''}">
<a class="page-link" href="/basic/dept?page=${data}&size=">
${data+1}
</a>
</li>
</c:forEach>
<%-- todo: 끝페이지 번호--%>
<li class="page-item ${(endPage+1==totalPages)? 'disabled': ''}">
<a class="page-link" href="/basic/dept?page=${endPage+1}&size=">Next</a>
</li>
</ul>
</div>
<%-- todo: 페이지 번호 끝--%>
<%-- todo: Add 버튼 추가 --%>
<div class="text-center">
<a href="/basic/dept/addition" class="btn btn-primary center">Add</a>
</div>
</div>
<%--footer--%>
<jsp:include page="../../common/footer.jsp"/>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ 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> <%--header--%> <jsp:include page="../../common/header.jsp"/> <div class="container"> <%-- todo: 검색어 시작--%> <%-- form 안에 input 태그의 value 값들이 -> springboot 함수로 전달됨 --%> <form class="row g-3 justify-content-center" action="/basic/dept" method="get"> <div class="col-auto"> <%-- todo: 부서명 라벨 --%> <label for="dname" class="visually-hidden">Dname</label> <%-- todo: 검색창--%> <input type="text" class="form-control" id="dname" placeholder="dname" name="dname"> <%-- todo: hidden(숨김) page = 0, size = 3 --%> <input type="hidden" class="form-control" id="page" name="page" value="0"> <input type="hidden" class="form-control" id="size" name="size" value="3"> </div> <div class="col-auto"> <button type="submit" class="btn btn-primary mb-3">Search</button> </div> </form> <%-- todo: 검색어 끝--%> <%-- todo: 테이블 반복문 시작--%> <table class="table"> <thead class="thead-dark"> <tr> <th scope="col">ID</th> <th scope="col">Name</th> <th scope="col">위치</th> <th scope="col">등록일자</th> <th scope="col">수정일자</th> </tr> </thead> <tbody> <c:forEach var="data" items="${dept}"> <tr> <td><a href="/basic/dept/edition/${data.dno}">${data.dno}</a></td> <td>${data.dname}</td> <td>${data.loc}</td> <td>${data.insertTime}</td> <td>${data.updateTime}</td> </tr> </c:forEach> </tbody> </table> <%-- todo: 테이블 반복문 끝--%> <%-- todo: 페이지 번호 시작--%> <div class="d-flex justify-content-center"> <ul class="pagination"> <%-- todo: 첫페이지 번호 --%> <%-- startPage : 0부터 시작 --%> <%-- currentPage : 0부터 시작--%> <li class="page-item ${(startPage+1==1)? 'disabled': ''}"> <a class="page-link" href="/basic/dept?page=${startPage-1}&size=">Previous</a> </li> <%-- todo: 실제 페이지 번호들 --%> <%-- 사용법 : <c:forEach var="data" begin="시작값" end="끝값">반복문</c:forEach>--%> <c:forEach var="data" begin="${startPage}" end="${endPage}"> <li class="page-item ${(currentPage==data)? 'active': ''}"> <a class="page-link" href="/basic/dept?page=${data}&size="> ${data+1} </a> </li> </c:forEach> <%-- todo: 끝페이지 번호--%> <li class="page-item ${(endPage+1==totalPages)? 'disabled': ''}"> <a class="page-link" href="/basic/dept?page=${endPage+1}&size=">Next</a> </li> </ul> </div> <%-- todo: 페이지 번호 끝--%> <%-- todo: Add 버튼 추가 --%> <div class="text-center"> <a href="/basic/dept/addition" class="btn btn-primary center">Add</a> </div> </div> <%--footer--%> <jsp:include page="../../common/footer.jsp"/> </body> </html>
jsp 추가 페이지
결과 화면 :

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<!-- thymeleaf 설정 -->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<%-- header 시작 --%>
<jsp:include page="../../common/header.jsp" />
<%-- header 끝 --%>
<div class="container mt-5">
<div>
<%-- todo: /basic/dept/add 해당되는 컨트롤러 함수 실행 --%>
<form action="/basic/dept/add" method="post">
<%-- todo: 부서명 --%>
<div class="mb-3">
<label for="dname" class="form-label">부서 이름</label>
<input type="text" class="form-control" id="dname" required name="dname">
</div>
<%-- todo: 부서위치 --%>
<div class="mb-3">
<label for="loc" class="form-label">부서 위치</label>
<input type="text" class="form-control" id="loc" required name="loc">
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</div>
<%-- header 시작 --%>
<jsp:include page="../../common/footer.jsp"/>
<%-- header 끝 --%>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<!-- thymeleaf 설정 -->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<%-- header 시작 --%>
<jsp:include page="../../common/header.jsp" />
<%-- header 끝 --%>
<div class="container mt-5">
<div>
<%-- todo: /basic/dept/add 해당되는 컨트롤러 함수 실행 --%>
<form action="/basic/dept/add" method="post">
<%-- todo: 부서명 --%>
<div class="mb-3">
<label for="dname" class="form-label">부서 이름</label>
<input type="text" class="form-control" id="dname" required name="dname">
</div>
<%-- todo: 부서위치 --%>
<div class="mb-3">
<label for="loc" class="form-label">부서 위치</label>
<input type="text" class="form-control" id="loc" required name="loc">
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</div>
<%-- header 시작 --%>
<jsp:include page="../../common/footer.jsp"/>
<%-- header 끝 --%>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <!-- thymeleaf 설정 --> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <%-- header 시작 --%> <jsp:include page="../../common/header.jsp" /> <%-- header 끝 --%> <div class="container mt-5"> <div> <%-- todo: /basic/dept/add 해당되는 컨트롤러 함수 실행 --%> <form action="/basic/dept/add" method="post"> <%-- todo: 부서명 --%> <div class="mb-3"> <label for="dname" class="form-label">부서 이름</label> <input type="text" class="form-control" id="dname" required name="dname"> </div> <%-- todo: 부서위치 --%> <div class="mb-3"> <label for="loc" class="form-label">부서 위치</label> <input type="text" class="form-control" id="loc" required name="loc"> </div> <div class="mb-3"> <button type="submit" class="btn btn-primary">Submit</button> </div> </form> </div> </div> <%-- header 시작 --%> <jsp:include page="../../common/footer.jsp"/> <%-- header 끝 --%> </body> </html>
jsp 수정 페이지
결과 화면 :

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<!-- thymeleaf 설정 -->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<%-- header 시작 --%>
<jsp:include page="../../common/header.jsp" />
<%-- header 끝 --%>
<div class="container mt-5">
<div>
<form action="/basic/dept/edit/${dept.dno}" method="post">
<%-- TODO: springboot 에서 아래와 같이 hidden 값을 전송하면 : put 방식으로 인식해서 연결해줌 --%>
<input type="hidden" name="_method" value="put"/>
<input type="hidden" name="dno" value="${dept.dno}"/>
<div class="mb-3">
<label for="dname" class="form-label">부서 이름</label>
<input type="text"
class="form-control"
id="dname"
required
name="dname"
value="${dept.dname}"
>
</div>
<div class="mb-3">
<label for="loc" class="form-label">부서 위치</label>
<input type="text"
class="form-control"
id="loc"
required
name="loc"
value="${dept.loc}"
>
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary">Update</button>
</div>
</form>
<%-- todo: 삭제 버튼 form --%>
<form id="delete-form" action="/basic/dept/delete/${dept.dno}" method="post">
<%-- TODO: springboot 에서 아래와 같이 hidden 값을 전송하면 : delete 방식으로 인식해서 연결해줌 --%>
<input type="hidden" name="_method" value="delete"/>
<button type="submit" class="btn btn-danger">Delete</button>
</form>
</div>
</div>
<%-- header 시작 --%>
<jsp:include page="../../common/footer.jsp"/>
<%-- header 끝 --%>
</script>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<!-- thymeleaf 설정 -->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<%-- header 시작 --%>
<jsp:include page="../../common/header.jsp" />
<%-- header 끝 --%>
<div class="container mt-5">
<div>
<form action="/basic/dept/edit/${dept.dno}" method="post">
<%-- TODO: springboot 에서 아래와 같이 hidden 값을 전송하면 : put 방식으로 인식해서 연결해줌 --%>
<input type="hidden" name="_method" value="put"/>
<input type="hidden" name="dno" value="${dept.dno}"/>
<div class="mb-3">
<label for="dname" class="form-label">부서 이름</label>
<input type="text"
class="form-control"
id="dname"
required
name="dname"
value="${dept.dname}"
>
</div>
<div class="mb-3">
<label for="loc" class="form-label">부서 위치</label>
<input type="text"
class="form-control"
id="loc"
required
name="loc"
value="${dept.loc}"
>
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary">Update</button>
</div>
</form>
<%-- todo: 삭제 버튼 form --%>
<form id="delete-form" action="/basic/dept/delete/${dept.dno}" method="post">
<%-- TODO: springboot 에서 아래와 같이 hidden 값을 전송하면 : delete 방식으로 인식해서 연결해줌 --%>
<input type="hidden" name="_method" value="delete"/>
<button type="submit" class="btn btn-danger">Delete</button>
</form>
</div>
</div>
<%-- header 시작 --%>
<jsp:include page="../../common/footer.jsp"/>
<%-- header 끝 --%>
</script>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <!-- thymeleaf 설정 --> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <%-- header 시작 --%> <jsp:include page="../../common/header.jsp" /> <%-- header 끝 --%> <div class="container mt-5"> <div> <form action="/basic/dept/edit/${dept.dno}" method="post"> <%-- TODO: springboot 에서 아래와 같이 hidden 값을 전송하면 : put 방식으로 인식해서 연결해줌 --%> <input type="hidden" name="_method" value="put"/> <input type="hidden" name="dno" value="${dept.dno}"/> <div class="mb-3"> <label for="dname" class="form-label">부서 이름</label> <input type="text" class="form-control" id="dname" required name="dname" value="${dept.dname}" > </div> <div class="mb-3"> <label for="loc" class="form-label">부서 위치</label> <input type="text" class="form-control" id="loc" required name="loc" value="${dept.loc}" > </div> <div class="mb-3"> <button type="submit" class="btn btn-primary">Update</button> </div> </form> <%-- todo: 삭제 버튼 form --%> <form id="delete-form" action="/basic/dept/delete/${dept.dno}" method="post"> <%-- TODO: springboot 에서 아래와 같이 hidden 값을 전송하면 : delete 방식으로 인식해서 연결해줌 --%> <input type="hidden" name="_method" value="delete"/> <button type="submit" class="btn btn-danger">Delete</button> </form> </div> </div> <%-- header 시작 --%> <jsp:include page="../../common/footer.jsp"/> <%-- header 끝 --%> </script> </body> </html>
📃 결론
부서 게시판 샘플 예제를 살펴보았습니다.
기본적은 CRUD 기능을 구현했으며 게시판의 페이징 처리도 해봤습니다.
DB 프레임워크는 JPA 를 이용해서 sql 문을 직접 제작하지 않고 자동화기능을 이용해 구현했습니다.
Mybatis 의 직접 sql 문 제작 기능에 대응해 자주 반복되고 쉬운 기능은 JPA 의 sql 자동생성 기능을 이용하고,
복잡한 sql 문은 @Query 를 이용해 직접 작성할 수 있어 요즘 많이 서비스 업체 기준으로 사용되고 있습니다.
감사합니다.