본문 바로가기
TIL/Spring

23.06.13

by J1-H00N 2023. 6. 13.

어제 잘 실행되던 mvc 폴더에서 intellij를 실행했을 때 아무런 에러메시지도 없이 무한로딩이 되는 에러가 발생해 1시간 가량 튜터님과 뭐가 문제인지 파악하려 했으나 결국엔 실패했다. 원인을 알아내기 위해 폴더나 파일들을 완벽히 카피한 폴더도 만들었으나 이 폴더는 문제없이 실행됐다. 결국 원인도 못 알아낸채로 1시간 가량 제자리 걸음을 하게 된 셈이지만 intellij가 가끔 이유없이 오류가 나는 상황도 있다고 하니 어쩔 수 없이 복사한 폴더에서 진행하기로 했다. 원인이 뭐였는지는 아직까지도 미스테리다...

 

데이터가 요청되는 방식

Path Variable : 서버에 보내려고 하는 데이터를 url 경로에 추가 할 수 있다.

// [Request sample]
// GET http://localhost:8080/hello/request/star/Robbie/age/95
@GetMapping("/star/{name}/age/{age}") // 데이터를 넣을 장소에 {}를 넣고 그 안에 변수를 넣는다.
@ResponseBody
public String helloRequestPath(@PathVariable String name, @PathVariable int age)
{
    return String.format("Hello, @PathVariable.<br> name = %s, age = %d", name, age);
}

실행 한 뒤 url에서 해당 위치를 수정(http://localhost:8080/hello/request/star/Robbert/age/100)하면 반환값도 바뀐다. >

Hello, @PathVariable.
name = Robbert, age = 100

 

Request Param (Query String) : url 경로 마지막에 ?와 &을 사용하여 데이터를 추가하는 형식

    // [Request sample]
    // GET http://localhost:8080/hello/request/form/param?name=Robbie&age=95
    @GetMapping("/form/param")
    @ResponseBody
    public String helloGetRequestParam(@RequestParam String name, @RequestParam int age) {
        return String.format("Hello, @RequestParam.<br> name = %s, age = %d", name, age);
    }

int age앞에 @RequestParam은 생략가능하다. 단, 이 경우 name을 요청하지 않는 상황이 오면 오류가 발생한다. 그래서 어떤 것을 요청하지 않는 상황이 올 수 있기 때문에 둘 모두 붙여주는 것이다.

대신 @RequestParam(required = false)로 하면 해당 데이터가 요청되지 않아도 오류가 생기지 않는다.

(required = false)는 모든 요청형식에 붙일 수 있다. ex) @PathVariable(required = false)

이 형식도 해당위치를 수정(http://localhost:8080/hello/request/form/param?name=Robert&age=100)하면 반환값도 바뀐다.

 

form 태그 POST

    // [Request sample]
    // POST http://localhost:8080/hello/request/form/param
    // Header
    //  Content type: application/x-www-form-urlencoded
    // Body
    //  name=Robbie&age=95
    @PostMapping("/form/param")
    @ResponseBody
    public String helloPostRequestParam(@RequestParam String name, @RequestParam int age) {
        return String.format("Hello, @RequestParam.<br> name = %s, age = %d", name, age);
    }

html의 form태그를 사용하여 post 방식으로 http 요청을 보낼 수 있다.

이때 해당 데이터는 HTTP Body에 name=Robbie&age=95 형태로 담겨져서 서버로 전달

 

위에서 Body 부분에 자바 객체가 들어갔을 때 자료를 받는 방법

@ModelAttribute

    // [Request sample]
    // POST http://localhost:8080/hello/request/form/model
    // Header
    //  Content type: application/x-www-form-urlencoded
    // Body
    //  name=Robbie&age=95
    @PostMapping("/form/model")
    @ResponseBody
    public String helloRequestBodyForm(@ModelAttribute Star star) {
        return String.format("Hello, @ModelAttribute.<br> (name = %s, age = %d) ", star.name, star.age);
    }

단, Jackson 때와 마찬가지로 해당 객체의 필드 변수명과 key값이 일치해야 자료를 보낼 수 있다.

 

RequestParam으로 여려개의 자료를 받아오기는 번거롭기 때문에 query형식도 객체로 받아 올 수 있다.

// [Request sample]
// GET http://localhost:8080/hello/request/form/param/model?name=Robbie&age=95
@GetMapping("/form/param/model")
@ResponseBody
public String helloRequestParam(@ModelAttribute Star star) {
    return String.format("Hello, @ModelAttribute.<br> (name = %s, age = %d) ", star.name, star.age);
}

@ModelAttribute,  @RequestParam는 생략이 가능하다. Spring이 매개변수가 SimpleValueType(int, Interger, Date, ...)이면 @RequestParam, 객체라면 @ModelAtrribute으로 자동으로 간주한다.

 

@RequestBody : 매개변수가 객체고 Body에서 json형식으로 넘어올 때 사용, 생략 불가

// [Request sample]
// POST http://localhost:8080/hello/request/form/json
// Header
//  Content type: application/json
// Body
//  {"name":"Robbie","age":"95"}
@PostMapping("/form/json")
@ResponseBody
public String helloPostRequestJson(@RequestBody Star star) {
    return String.format("Hello, @RequestBody.<br> (name = %s, age = %d) ", star.name, star.age);
}

 

html파일명이 index라면 따로 url에 /index.html을 붙여 찾지 않아도 default로 반응해 호출한다.

 

메모장 구현하기

DTO : 데이터 전송 및 이동을 위해 생성되는 객체

클라이언트에서 보내오는 데이터를 객체로 처리할 때 사용

서버의 계층 간 이동에도 사용

DB와의 소통을 담당하는 Java 클래스를 그대로 Client에 반환하는 것이 아니라 DTO로 한번 변환한 후 반환할 때도 사용

 

postMan에서 바디에 작성하는 방법 : url 적는 곳 아래 body > raw > json 선택 > {}에 json형태로 작성

 

메모장 구현 코드

package com.sparta.memo.dto;

import lombok.Getter;

@Getter
public class MemoRequestDto {
    private String username;
    private String contents;
}
package com.sparta.memo.dto;

import com.sparta.memo.entity.Memo;
import lombok.Getter;

@Getter
public class MemoResponseDto {
    private Long id;
    private String username;
    private String contents;

    public MemoResponseDto(Memo memo) {
        this.id = memo.getId();
        this.username = memo.getUsername();
        this.contents = memo.getContents();
    }
}
package com.sparta.memo.controller;

import com.sparta.memo.dto.MemoRequestDto;
import com.sparta.memo.dto.MemoResponseDto;
import com.sparta.memo.entity.Memo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api")
public class MemoController {

    private final Map<Long, Memo> memoList = new HashMap<>();

    @PostMapping("/memos")
    public MemoResponseDto createMemo(@RequestBody MemoRequestDto requsestDto) { // 입력 내용은 바디에서 받기 때문에
        // RequestDto -> Entity
        Memo memo = new Memo(requsestDto);

        // Memo Max ID Check
        Long maxId = memoList.size() > 0 ? Collections.max(memoList.keySet()) + 1 : 1;
        // memoList안에 key 중에서 가장 큰 값을 가져와서 1 더하기, 없으면 1
        memo.setId(maxId);

        // DB 저장
        memoList.put(memo.getId(), memo);

        // Entity -> ResponseDTO
        MemoResponseDto memoResponseDto = new MemoResponseDto(memo);

        return memoResponseDto;
    }

    @GetMapping("/memos")
    public List<MemoResponseDto> getMemos() {
        // Map -> List
        List<MemoResponseDto> responseList = memoList.values().stream()
                .map(MemoResponseDto::new).toList();

        return responseList;
    }

    @PutMapping("/memos/{id}")
    public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) { // id는 데이터를 받아오는 것이고 수정할 내용은 다시 Body에서 입력할 것이기 때문
        // 해당 메모가 DB에 존재하는지 확인
        if (memoList.containsKey(id)) {
            // 해당 메모 가져오기
            Memo memo = memoList.get(id);
            // 가져온 메모 수정
            memo.update(requestDto);
            return memo.getId();
        } else {
            throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
        }
    }

    @DeleteMapping("/memos/{id}")
    public Long deleteMemo(@PathVariable Long id) {
        // 해당 메모가 DB에 존재하는지 확인
        if (memoList.containsKey(id)) {
            // 해당 메모 가져오기
            memoList.remove(id);
            return id;
        } else {
            throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
        }
    }

}

 

 

DB

DBMS : DB를 관리하는 소프트웨어

테이블(table)이라는 최소 단위로 구성되며, 이 테이블은 열(column)과 행(row)으로 이루어져 있다.

우리가 사용할 DB는 MySQL이다.

 

SQL은 ‘Structured Query Language’ 의 약자로 RDBMS에서 사용되는 언어입니다. 수 많은 정보를 Database에서 조작하고 관리하기 위해서는 SQL 언어를 사용해야합니다.

  • 국제표준화기구에서 SQL에 대한 표준을 정해서 발표하고 있습니다.
  • DBMS를 만드는 회사가 여러 곳이기 때문에 DBMS 마다 표준 SQL을 준수하되, 각 제품의 특성을 반영하기 위한 약간의 차이가 존재합니다.

용어 정리

DDL : 테이블이나 관계의 구조를 생성하는데 사용

  • CREATE : 새로운 데이터베이스 및 테이블을 생성해 줍니다.
CREATE DATABASE 데이터베이스이름;
CREATE TABLE 테이블이름
(
		필드이름1 필드타입1,
    필드이름2 필드타입2,
    ...
);
  • ALTER : 데이터베이스와 테이블의 내용을 수정할 수 있습니다.
ALTER TABLE 테이블이름 ADD 필드이름 필드타입;
ALTER TABLE 테이블이름 DROP 필드이름;
ALTER TABLE 테이블이름 MODIFY COLUMN 필드이름 필드타입;
  • DROP : 데이터베이스와 테이블을 삭제할 수 있습니다. 데이터 및 테이블 전체를 삭제합니다.
DROP DATABASE 데이터베이스이름;
DROP TABLE 테이블이름;
  • TRUNCATE : 데이터베이스와 테이블을 삭제할 수 있습니다. 최초 테이블이 만들어졌던 상태 즉, 컬럼값만 남깁니다. 
TRUNCATE DATABASE 데이터베이스이름;
TRUNCATE TABLE 테이블이름;

 

DCL : 데이터의 사용 권한을 관리하는데 사용

DML : 테이블에 데이터를 검색, 삽입, 수정, 삭제하는데 사용

  • INSERT : 테이블에 새로운 row를 추가할 수 있습니다.
INSERT INTO 테이블이름(필드이름1, 필드이름2, 필드이름3, ...) VALUES(데이터값1, 데이터값2, 데이터값3, ...);
INSERT INTO 테이블이름 VALUES(데이터값1, 데이터값2, 데이터값3, ...);
  • SELECT : 테이블의 row를 선택할 수 있습니다.
SELECT 필드이름 FROM 테이블이름 [WHERE 조건];
  • UPDATE : 테이블의 row의 내용을 수정할 수 있습니다.
UPDATE 테이블이름 SET 필드이름1=데이터값1, 필드이름2=데이터값2, ... WHERE 필드이름=데이터값;
  • DELETE : 테이블의 row를 삭제할 수 있습니다.
DELETE FROM 테이블이름 WHERE 필드이름=데이터값;

 

SQL  기능 정리

  • Create
    • 제약 조건
      • AUTO_INCREMENT : 컬럼의 값이 중복되지 않게 1씩 자동으로 증가하게 해줘 고유번호를 생성해 줍니다.
      • NOT NULL : 해당 필드는 NULL 값을 저장할 수 없게 됩니다.
      • UNIQUE : 해당 필드는 서로 다른 값을 가져야만 합니다.
      • PRIMARY KEY : 해당 필드가 NOT NULL과 UNIQUE 제약 조건의 특징을 모두 가지게 됩니다.
      • FOREIGN KEY : 하나의 테이블을 다른 테이블에 의존하게 만들며 데이터의 무결성을 보장해 줍니다.
      • CASCADE : FOREIGN KEY 로 연관된 데이터를 삭제,변경할 수 있습니다.
  • ALTER
    • 두 테이블 foreign key로 연결
  • INSERT
  • UPDATE
  • DELETE
  • SELECT
  • JOIN
    • 나누어진 테이블을 하나로 합치기
    • ON 이라는 키워드를 통해 기준이 되는 컬럼을 선택하여 2개의 테이블을 합친다
    • 적어도 하나의 컬럼을 서로 공유하고 있어야 하기 때문에 테이블에 외래 키가 설정 되어 있다면 해당 컬럼을 통해 JOIN을 하면 조건을 충족할 수 있다
    • 외래 키를 설정하면 데이터 무결성을 확인하는 추가 연산이 발생하고 무결성을 지켜야하기 때문에 상황에 따라 개발하는데 불편할 수 있다

예시

더보기
CREATE TABLE IF NOT EXISTS MAJOR
(	
	major_code varchar(100) primary key comment '주특기코드', 
	major_name varchar(100) not null comment '주특기명',
	tutor_name varchar(100) not null comment '튜터'
);

CREATE TABLE IF NOT EXISTS STUDENT
(
	student_code varchar(100) primary key comment '수강생코드', 
	name varchar(100) not null comment '이름',
	birth varchar(8) null comment '생년월일',
	gender varchar(1) not null comment '성별',
	phone varchar(11) null comment '전화번호',
	major_code varchar(100) not null comment '주특기코드',
	foreign key(major_code) references major(major_code)
);

CREATE TABLE IF NOT EXISTS EXAM
(
	student_code varchar(100) not null comment '수강생코드', 
	exam_seq int not null comment '시험주차', 
	score decimal(10,2) not null comment '시험점수',
	result varchar(1) not null comment '합불'
);

// student_code와 exam_seq foreign key 설정
ALTER TABLE EXAM ADD PRIMARY KEY(student_code, exam_seq); 
ALTER TABLE EXAM ADD CONSTRAINT exam_fk_student_code FOREIGN KEY(student_code) REFERENCES STUDENT(student_code);

 

INSERT INTO STUDENT VALUES('s1', '최원빈', '20220331', 'M', '01000000001', 'm1');
INSERT INTO STUDENT VALUES('s2', '강준규', '20220501', 'M', '01000000002', 'm1');
INSERT INTO STUDENT VALUES('s3', '김영철', '20220711', 'M', '01000000003', 'm1');
INSERT INTO STUDENT VALUES('s4', '예상기', '20220408', 'M', '01000000004', 'm6');
INSERT INTO STUDENT VALUES('s5', '안지현', '20220921', 'F', '01000000005', 'm6');
INSERT INTO STUDENT VALUES('s6', '이대호', '20221111', 'M', '01000000006', 'm7');
INSERT INTO STUDENT VALUES('s7', '정주혜', '20221117', 'F', '01000000007', 'm8');
INSERT INTO STUDENT VALUES('s8', '고미송', '20220623', 'F', '01000000008', 'm6');
INSERT INTO STUDENT VALUES('s9', '이용우', '20220511', 'M', '01000000009', 'm2');
INSERT INTO STUDENT VALUES('s10', '심선아', '20220504', 'F', '01000000010', 'm8');
INSERT INTO STUDENT VALUES('s11', '변정섭', '20220222', 'M', '01000000020', 'm2');
INSERT INTO STUDENT(student_code, name, gender, major_code) VALUES('s12', '권오빈', 'M', 'm3'); // 특정 데이터만 다르게 저장하기 위해 테이블 지정
INSERT INTO STUDENT VALUES('s13', '김가은', '20220121', 'F', '01000000030', 'm1');
INSERT INTO STUDENT(student_code, name, gender, major_code) VALUES('s14', '김동현', 'M', 'm4');
INSERT INTO STUDENT VALUES('s15', '박은진', '20221101', 'F', '01000000040', 'm1');
INSERT INTO STUDENT(student_code, name, birth, gender, phone, major_code) VALUES('s16', '정영호', '20221105', 'M', '01000000050', 'm5');
INSERT INTO STUDENT(student_code, name, gender, major_code) VALUES('s17', '박가현', 'F', 'm7');
INSERT INTO STUDENT(student_code, name, birth, gender, phone, major_code) VALUES('s18', '박용태', '20220508', 'M', '01000000060', 'm6');
INSERT INTO STUDENT VALUES('s19', '김예지', '20220505', 'F', '01000000070', 'm2');
INSERT INTO STUDENT VALUES('s20', '윤지용', '20220909', 'M', '01000000080', 'm3');
INSERT INTO STUDENT VALUES('s21', '손윤주', '20220303', 'F', '01000000090', 'm6');
// s12, s14, s17만 생일, 전화번호 데이터가 빠짐
INSERT INTO STUDENT VALUES('s0', '수강생', '20220331', 'M', '01000000005', 'm1');
UPDATE STUDENT SET major_code= 'm2' where student_code= 's0';
SELECT * FROM STUDENT;
SELECT * FROM STUDENT WHERE STUDENT_CODE = 's1';
SELECT name, major_code FROM STUDENT WHERE student_code = 's1';
SELECT s.name, s.major_code, m.major_name FROM STUDENT s JOIN MAJOR m ON s.major_code = m.major_code;
// 아래 코드로 실행해도 똑같은 결과가 나옴
SELECT s.name, s.major_code, m.major_name FROM STUDENT s, MAJOR m WHERE s.major_code = m.major_code;

 

 

JDBC

서버와 데이터베이스가 소통하기 위해선 우선 커넥션을 연결하고, SQL을 완성한 다음 이 커넥션을 통해서 SQL을 요청해야 한다. 그리고나서 요청한 SQL에 대한 응답을 기다려야 한다.

그런데 평소에 사용하던 SQL이 아닌 다른 SQL로 변경해 요청을 보내려고 하는데, 연결하는 방법, 요청하는 방법, 응답을 받는 방법이 모두 다를 수 있고 그러면 서버에 작성했던 로직들을 전부 수정해야 한다.

이런 문제를 해결하고자 생긴게 JDBC 표준 인터페이스이다.

JDBC는 Java Database Connectivity로 DB에 접근할 수 있도록 Java에서 제공하는 API이다.

JDBC에 연결해야하는 DB의 JDBC 드라이버를 제공하면 DB 연결 로직을 변경할 필요없이 DB 변경이 가능합니다.

따라서 MySQL 드라이버를 사용해 DB에 연결을 하다 PostgreSQL 서버로 변경이 필요할 때 드라이버만 교체하면 손쉽게 DB 변경이 가능하다.

 

JdbcTemplate

JDBC의 등장으로 손쉽게 DB교체가 가능해졌지만 아직도 DB에 연결하기 위해 여러가지 작업 로직들을 직접 작성해야한다는 불편함을 해결하기 위해 커넥션 연결, statement 준비 및 실행, 커넥션 종료 등의 반복적이고 중복되는 작업들을 대신 처리해주는 JdbcTemplate이 등장

 

JdbcTemplate 사용예시 (실무에서 사용할 경험을 거의 없을 것)

resources 폴더 > application.properties 

spring.datasource.url=jdbc:mysql://localhost:3306/memo
spring.datasource.username=root
spring.datasource.password={비밀번호}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

build.gardle > dependencies

// MySQL
implementation 'mysql:mysql-connector-java:8.0.28'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'

MySQL cmd > 적절한 데이터베이스 추가(여기선 memo) > intellij > 우측 Database > new > Database : memo , 적용 > 적당한 table 생성 > MemoController.java

package com.sparta.memo.controller;

import com.sparta.memo.dto.MemoRequestDto;
import com.sparta.memo.dto.MemoResponseDto;
import com.sparta.memo.entity.Memo;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.web.bind.annotation.*;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

@RestController
@RequestMapping("/api")
public class MemoController {

    // Jdbc template을 사용하기 위해 변수 선언
    private final JdbcTemplate jdbcTemplate;

    public MemoController(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @PostMapping("/memos")
    public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
        // RequestDto -> Entity
        Memo memo = new Memo(requestDto);

        // DB 저장
        KeyHolder keyHolder = new GeneratedKeyHolder(); // 기본 키를 반환받기 위한 객체

        // Insert 되는 값을 동적으로 처리하기 위해 ?로 둔다.
        String sql = "INSERT INTO memo (username, contents) VALUES (?, ?)";
        jdbcTemplate.update( con -> {
                    PreparedStatement preparedStatement = con.prepareStatement(sql,
                            Statement.RETURN_GENERATED_KEYS);

                    preparedStatement.setString(1, memo.getUsername());
                    preparedStatement.setString(2, memo.getContents());
                    return preparedStatement;
                },
                keyHolder);

        // DB Insert 후 받아온 기본키 확인
        Long id = keyHolder.getKey().longValue();
        memo.setId(id);

        // Entity -> ResponseDto
        MemoResponseDto memoResponseDto = new MemoResponseDto(memo);

        return memoResponseDto;
    }

    @GetMapping("/memos")
    public List<MemoResponseDto> getMemos() {
        // DB 조회
        String sql = "SELECT * FROM memo";

        return jdbcTemplate.query(sql, new RowMapper<MemoResponseDto>() {
            @Override
            public MemoResponseDto mapRow(ResultSet rs, int rowNum) throws SQLException {
                // SQL 의 결과로 받아온 Memo 데이터들을 MemoResponseDto 타입으로 변환해줄 메서드
                Long id = rs.getLong("id");
                String username = rs.getString("username");
                String contents = rs.getString("contents");
                return new MemoResponseDto(id, username, contents);
            }
        });
    }

    @PutMapping("/memos/{id}")
    public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
        // 해당 메모가 DB에 존재하는지 확인
        Memo memo = findById(id);
        if(memo != null) {
            // memo 내용 수정
            String sql = "UPDATE memo SET username = ?, contents = ? WHERE id = ?";
            jdbcTemplate.update(sql, requestDto.getUsername(), requestDto.getContents(), id);

            return id;
        } else {
            throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
        }
    }

    @DeleteMapping("/memos/{id}")
    public Long deleteMemo(@PathVariable Long id) {
        // 해당 메모가 DB에 존재하는지 확인
        Memo memo = findById(id);
        if(memo != null) {
            // memo 삭제
            String sql = "DELETE FROM memo WHERE id = ?";
            jdbcTemplate.update(sql, id);

            return id;
        } else {
            throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
        }
    }

    private Memo findById(Long id) {
        // DB 조회
        String sql = "SELECT * FROM memo WHERE id = ?";

        return jdbcTemplate.query(sql, resultSet -> {
            if(resultSet.next()) {
                Memo memo = new Memo();
                memo.setUsername(resultSet.getString("username"));
                memo.setContents(resultSet.getString("contents"));
                return memo;
            } else {
                return null;
            }
        }, id);
    }
}

 

1주차 숙제 풀기

1. 수강생을 관리하는 Manager 테이블 만들기

create table Manager create table if not exists Manager 
(
    id       bigint       primary key ,
    name varchar(500) not null CHECK ( LENGTH(name) > 2 ),
    student_code varchar(255) not null,
    CONSTRAINT manager_fk_student_code FOREIGN KEY (student_code) REFERENCES student(student_code)
);

2. ALTER, MODIFY를 이용하여 MANAGER 테이블의 id 컬럼에 AUTO_INCREMENT 기능을 부여하세요.

ALTER TABLE manager modify column id BIGINT auto_increment;

3. INSERT를 이용하여 수강생 s1, s2, s3, s4, s5를 관리하는 managerA와 s6, s7, s8, s9를 관리하는 managerB를 추가하세요.(AUTO_INCREMENT 기능을 활용하세요)

INSERT INTO MANAGER(name, student_code) VALUES('managerA', 's1');
INSERT INTO MANAGER(name, student_code) VALUES('managerA', 's2');
INSERT INTO MANAGER(name, student_code) VALUES('managerA', 's3');
INSERT INTO MANAGER(name, student_code) VALUES('managerA', 's4');
INSERT INTO MANAGER(name, student_code) VALUES('managerA', 's5');

INSERT INTO MANAGER(name, student_code) VALUES('managerB', 's6');
INSERT INTO MANAGER(name, student_code) VALUES('managerB', 's7');
INSERT INTO MANAGER(name, student_code) VALUES('managerB', 's8');
INSERT INTO MANAGER(name, student_code) VALUES('managerB', 's9');

4. JOIN을 사용하여 managerA가 관리하는 수강생들의 이름과 시험 주차 별 성적을 가져오세요.

SELECT s.name, e.exam_seq, e.score
FROM MANAGER m JOIN STUDENT S on m.student_code  = s.student_code
    JOIN EXAM e on m.student_code  = e.student_code WHERE m.name = 'managerA';

5. STUDENT 테이블에서 s1 수강생을 삭제했을 때 EXAM에 있는 s1수강생의 시험성적과 MANAGER의 managerA가 관리하는 수강생 목록에 자동으로 삭제될 수 있도록 하세요.

// student_code가 s1인 데이터를 지우려면 student_code를 foreign key로 가지고 있는 테이블에서 foreign key를 지워줘야 함.
ALTER TABLE EXAM DROP CONSTRAINT exam_fk_student_code; // 연관되어있는 foreign key 삭제
ALTER TABLE EXAM ADD CONSTRAINT exam_fk_student_code FOREIGN KEY(student_code) REFERENCES STUDENT(student_code) ON DELETE CASCADE; // delete cascade 옵션이 추가된 foreign key로 재생성
ALTER TABLE MANAGER DROP CONSTRAINT manager_fk_student_code; // manager도 마찬가지
ALTER TABLE MANAGER ADD CONSTRAINT manager_fk_student_code FOREIGN KEY(student_code) REFERENCES STUDENT(student_code) ON DELETE CASCADE;

DELETE FROM STUDENT WHERE student_code = 's1';

'TIL > Spring' 카테고리의 다른 글

23.06.20  (0) 2023.06.20
23.06.19  (0) 2023.06.19
23.06.15  (0) 2023.06.15
23.06.14  (0) 2023.06.14
23.06.12  (0) 2023.06.12