[Spring Boot]JPAExam

06_Backend 하위폴더에

04_JPAExam 폴더생성

JPAExam 프로젝트 만들기

버전 2.7.16으로 설정하고 종속성 5개 체크

build gradle (dependencies 수정)

dependencies {
//  TODO : 오라클 라이브러리( 19c )
    implementation 'co
m.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'

//  TODO : JPA 라이브러리
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'co
m.oracle.database.jdbc:ojdbc8'
    annotationProcessor 'org.projectlombok:lombok'
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

 

application.properties 수정하고

코끼리 버튼 누르기(gradle)

# 서버 포트
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

data.sql
0.00MB
log4jdbc.log4j2.properties
0.00MB
logback-spring.xml
0.00MB
schema.sql
0.00MB

파일 다운받고 resources에 넣고

com.example.japexam에 controller, dto, model, respository, service 폴더 생성

model 폴더에

BaseTimeEntity 생성

package com.example.jpaexam.model;

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 {
    //    todo: 공통속성 : yyyy-MM-dd HH:mm:ss 아니고 기본 패턴으로 보임
    private String insertTime;

    private String updateTime;

    //    todo: 해당 테이블에 데이터가 만들어 질때(insert 문) 실행되는 이벤트 함수
    @PrePersist
    void OnPrePersist() {
        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; // 생성일시 == 수정일시 동일하게 처리
    }
}

model 폴더에

Dept 만들기

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
@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"
    )
    @Column(columnDefinition = "NUMBER")
    private Integer dno; // 부서번호(기본키) - 시퀀스 기능 부여

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

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

repository 폴더에

DeptRepository

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> {
}

service 폴더에

DeptService

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) {

//      existsById : jpa 함수 - 리턴값: 있으면 true, 없으면 false
        if(deptRepository.existsById(dno)) {
            deptRepository.deleteById(dno); // DB 삭제(dno)
            return true;
        }
        return false;
    }

}

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.*;

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) {
//                성공
                return new ResponseEntity<>(list, HttpStatus.OK);
            } else {
//                데이터 없음
                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) {
//                성공
                return new ResponseEntity<>(optionalDept.get(), HttpStatus.OK);
            } else {
//                데이터 없음
                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 {
//            jpa 서비스 저장 함수 호출 : 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(
            @PathVariable int dno,
            @RequestBody Dept dept
    ) {
        try {
//            jpa 서비스 수정 함수 호출 : 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) {
    //                성공
                    return new ResponseEntity<>(HttpStatus.OK);
                } else {
    //                0건 삭제
                    return new ResponseEntity<>(HttpStatus.NO_CONTENT);
                }

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

}

결과

전체조회

상세조회

저장

수정

삭제

Emp

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 : Emp
 * author : GGG
 * date : 2023-10-16
 * description :
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-16         GGG          최초 생성
 */
@Entity
// todo: @Table(name = "생성될테이블명")
@Table(name = "TB_EMP")
// todo 사용법 : @SequenceGenerator(
//        name = "시퀀스함수이름"
//        , sequenceName = "DB에생성된시퀀스이름"
//        , initialValue = 시작값
//        , allocationSize = jpa에서관리용숫자(성능지표)
//)
@SequenceGenerator(
        name = "SQ_EMP_GENERATOR"
        , sequenceName = "SQ_EMP"
        , 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 Emp extends BaseTimeEntity {
    @Id
//  todo: @GeneratedValue(strategy = GenerationType.SEQUENCE
//            , generator = "시퀀스함수이름"
//    )
    @GeneratedValue(strategy = GenerationType.SEQUENCE
            , generator = "SQ_EMP_GENERATOR"
    )
    @Column(columnDefinition = "NUMBER")
    private Integer eno; // 사원번호(기본키) - 시퀀스 기능 부여

    @Column(columnDefinition = "VARCHAR2(255)")
    private String ename; // 사원명

    @Column(columnDefinition = "VARCHAR2(255)")
    private String job;   // 직위
    @Column(columnDefinition = "VARCHAR2(255)")
    private Integer manager;   // 매니저
    @Column(columnDefinition = "VARCHAR2(255)")
    private String hiredate;   // 입사일
    @Column(columnDefinition = "VARCHAR2(255)")
    private Integer salary;   // 급여
    @Column(columnDefinition = "VARCHAR2(255)")
    private Integer commission;   // 상여금
    @Column(columnDefinition = "VARCHAR2(255)")
    private Integer dno;   // 부서번호
}

EmpRepository

package com.example.jpaexam.repository;

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

/**
 * packageName : com.example.jpaexam.repository
 * fileName : EmpRepository
 * author : GGG
 * date : 2023-10-16
 * description :
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-16         GGG          최초 생성
 */
@Repository
public interface EmpRepository extends JpaRepository<Emp, Integer> {
}

EmpService

package com.example.jpaexam.service;

import com.example.jpaexam.model.Emp;
import com.example.jpaexam.repository.EmpRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;

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

/**
 * packageName : com.example.jpaexam.service
 * fileName : EmpService
 * author : GGG
 * date : 2023-10-16
 * description :
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-16         GGG          최초 생성
 */
@Service
public class EmpService {
    @Autowired
    EmpRepository empRepository; // DI 객체 가져오기

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

    /** 상세조회(1건조회) */
    public Optional<Emp> findById(int eno) {
        Optional<Emp> optionalEmp = empRepository.findById(eno);

        return optionalEmp;
    }

    /** 저장(수정)함수 */
    public Emp save(Emp emp) {
//      todo: jpa 저장함수 호출 ( 기본키 없으면 insert, 있으면 update )
        Emp emp2 = empRepository.save(emp);

        return emp2; // 저장된 사원객체
    }

    /** 삭제함수 */
    public boolean removeById(int eno) {

//      existsById : jpa 함수 - 리턴값: 있으면 true, 없으면 false
        if(empRepository.existsById(eno)) {
            empRepository.deleteById(eno); //
            return true;
        }
        return false;
    }

}

EmpController

package com.example.jpaexam.controller.exam01;

import com.example.jpaexam.model.Dept;
import com.example.jpaexam.model.Emp;
import com.example.jpaexam.service.EmpService;
import lombok.Getter;
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 : EmpController
 * author : GGG
 * date : 2023-10-17
 * description : 회원 컨트롤러 - @RestController
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-17         GGG          최초 생성
 */
@Slf4j
@RestController
@RequestMapping("/exam01")
public class EmpController {

    @Autowired
    EmpService empService; // DI

    /** 전체 조회 */
    @GetMapping("/emp")
    public ResponseEntity<Object> getEmpAll() {
        try {
//          todo: 전체 조회 함수 호출
            List<Emp> list = empService.findAll();

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

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

    /** 상세 조회 */
    @GetMapping("/emp/{eno}")
    public ResponseEntity<Object> getEmpId(
            @PathVariable int eno
    ) {
        try {
//          todo: 상세 조회 함수 호출
            Optional<Emp> optionalEmp = empService.findById(eno);

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

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

    /** 저장함수 */
    @PostMapping("/emp")
    public ResponseEntity<Object> createEmp(
            @RequestBody Emp emp
    ) {
        try {
//            jpa 서비스 저장 함수 호출 : emp2(DB 저장된 객체)
            Emp emp2 = empService.save(emp);

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

    /** 수정함수 */
    @PutMapping("/emp/edit/{eno}")
    public ResponseEntity<Object> updateDept(
            @PathVariable int eno,
            @RequestBody Emp emp
    ) {
        try {
//            jpa 서비스 수정 함수 호출 : emp2(DB 수정된 객체)
            Emp emp2 = empService.save(emp);

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

    /** 삭제함수 */
    @DeleteMapping("/emp/delete/{eno}")
    public ResponseEntity<Object> deleteEmp(
            @PathVariable int eno
    ) {
        try {
//          todo: 삭제 함수 호출
            boolean bSuccess = empService.removeById(eno);

            if (bSuccess == true) {
//                성공
                return new ResponseEntity<>(HttpStatus.OK);
            } else {
//                0건 삭제
                return new ResponseEntity<>(HttpStatus.NO_CONTENT);
            }

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

'Spring Boot' 카테고리의 다른 글

[Spring Boot] CRUD (create, read, update, delete)  (0) 2023.10.10
[Spring boot] 파라메터  (0) 2023.10.05
[Spring boot]스프링 부트 프로젝트  (1) 2023.10.04