👨🏫 쿼리문 연습 뿐만이 아니라
SQL문을 이용하여 스프링에 연결하는것까지 연습해야한다! 화면구현도 많이 해보기
최종적으로 SQL을 Spring에 연동해서 자유자재로 사용가능한 정도가 되어야 한다
SQL Mapper와 JPA(Java Persistence API)는 비슷한 일 다른 이름으로 일한다
SQL <—매핑—> Object 필드
SQL Mapper는 SQL 문장으로 직접 데이터베이스 데이터를 다룬다
즉, SQL Mapper는 SQL을 명시해줘야 한다
- Object와 SQL의 필드를 매핑하여 데이터를 객체화
- 객체와 테이블 간의 관계를 매핑하는 것이 아님
- SQL문을 직접 작성하고 쿼리 수행 결과를 어떠한 객체에 매핑할지 바인딩 하는 방법
- DBMS에 종속적인 문제
EX) JdbcTemplate, MyBatis
개발자가 JPA를 사용하면, JPA 내부에서 JDBC API를 사용하여 SQL을 호출하여 DB와 통신
- JAVA ORM(Object Relational Mapping)의 기술 표준
- 대표적인 오픈소스로
Hibernate
가 있다- CRUD 메소드 기본 제공
- 쿼리를 만들지 않아도 됨
MyBatis
는 쿼리가 수정되어 데이터 정보가 바뀌면 그에 사용 되고 있던 DTO와 함께 수정해주어야 하는 반면에,JPA
는 객체만 바꾸면 된다 => 객체 중심으로 개발 가능- but 복잡한 쿼리는 해결이 어려움
Oracle MEMBERTBL 데이터 스프링으로 가져오기
JAP와 SERVICE가 일하듯이 MAPPER와 SERVICE가 일한다
<!-- ORACLE DB -->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
📁 resources/templates/application.properties 에서 DB연결에 대한 설정
- mongodb와 같이 실행한다
- 💡 DB연결 후 일정시간이 지났는데 사용하지 않고있으면 연결이 끊긴다
연결에 대한 부분을 미리 설정해서 주기적으로 연결이 끊어지지 않게 관리하도록 한다
# oracle
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@1.234.5.158:11521/xe
spring.datasource.username=ds207
spring.datasource.password=pw207
# mysql, mariadb
# spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
# spring.datasource.url=jdbc:mysql://localhost:3306/DB
# spring.datasource.username=아이디
# spring.datasource.password=암호
#연결에 대한 부분 설정 => 주기적으로 연결 끊어지지 않게 관리
spring.datasource.hikari.connection-test-query=SELECT 1 FROM DUAL
spring.datasource.hikari.connection-timeout=600000
spring.datasource.hikari.maximum-pool-size=500
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.minimum-idle=20
spring.datasource.hikari.validation-timeout=3000
spring.datasource.hikari.idle-timeout=60000
JPA = repository = 데이터 일치해야함
mybatis = 쿼리문(네이티브)사용하여 데이터를 가져온다
MemberVO
는 DB랑 연관이 없고 데이터가 들어갈 공간만 있으면 된다
➡️컨트롤러에서 서비스 사용 위한 용도- 여기서 데이터 조작은 불가하며 mybatis의 데이터 조작은 네이티브 쿼리문으로만 진행된다
- 단지 전송을 위한 용도이기 때문에 프로젝션 상관없고 클래스, 변수명도 DB 데이터와 일치하지 않아도 됨
package com.example.vo;
import java.sql.Date;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@NoArgsConstructor
public class MemberVO {
private String userid;
private String userpw;
private int age;
private String phone;
private String gender;
private Date regdate;
}
회원 추가/조회 서비스 생성
- Mapper에 쿼리문 생성
- 쿼리문 알아야 DB에서 데이터 조작할 수 있다
MemberMapper
의value
에 들어가는 값이SELECT M.* FROM MEMBERTBL M;
에 들어가는 값과 같다- JPA에서는
@Repository
사용했듯이 MYBATIS에서는@Mapper
를 사용해준다
- 쿼리문 마지막에 ;(세미콜론) 사용하지 않는다
autocommit
으로 설정되어 있기 때문에 Commit할 필요가 없다!
➡️ Commit도 불가하듯이 ROLLBACK도 불가하다
// @Repository => JPA
@Mapper // => MYBATIS
public interface MemberMapper {
// 회원 등록
@Insert(value = {
"<script>",
" INSERT INTO MEMBERTBL(USERID, USERPW, AGE, PHONE, GENDER, REGDATE) ",
" VALUES(#{obj.userid}, #{obj.userpw}, #{obj.age}, #{obj.phone}, #{obj.gender}, CURRENT_DATE ) ",
"</script>"
})
public int insertMember(@Param(value = "obj") MemberVO member);
// 회원 조회
// @Select(value = {"SELECT * FROM MEMBERTBL "})
@Select(value = {
"<script>",
"SELECT M.* FROM MEMBERTBL M ORDER BY M.USERID DESC ",
"</script>"
})
public List<MemberVO> selectMemberList();
}
회원등록 작성 후 SERVICE로 돌아간다
MyBatisMemberService.java 생성
@Autowired
로MemberMapper mMapper;
이용하여 서비스 생성 후 컨트롤러에서 MemberController 생성 후 서비스 호출
@Service
public class MyBatisMemberService {
@Autowired
MemberMapper mMapper;
// 회원 추가
// Oracle => INSERT INTO .. 와 같다
public int insertMember (MemberVO member) {
try {
return mMapper.insertMember(member);
} catch (Exception e) {
e.printStackTrace();
return -1;
}
}
// 회원 조회
// Oracle => SELECT * FROM... 과 같다
public List<MemberVO> selectMemberList() {
try {
return mMapper.selectMemberList();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Autowired
MyBatisMemberService mmService;
// mybatis이용한 회원 조회
// 127.0.0.1:8080/ROOT/member/memberlist.do
@GetMapping(value = "/memberlist.do")
public String memberListGET(Model model){
List<MemberVO> list = mmService.selectMemberList();
model.addAttribute("list", list);
return "memberlist";
}
Boot20220824Application.java에 mapper 등록!
➡️ mapper scan 되어야 사용가능하다
// mybatis mapper의 위치
@MapperScan(basePackages = {"com.example.mapper"})
- 데이터베이스에 접근하고 객체들을 사용하도록 생성한 사용자에게 권한을 주거나 회수하는 명령어
- 사용자를 만들고 권한을 부여하는게 DCL
- 대부분 웹개발자는 DBA(최고 권한자가 지정해준 권한만 사용한다)
-- 최고권한자가 사용자 생성시 아래 코드를 사용한다 (아이디 aaa, 암호 bbb)
CREATE USER 'aaa' IDENTIFIED BY 'bbb';
-- 최고권한자가 생성한 사용자의 권한 지정시
-- 사용자별로 생성/수정/삭제/등 세밀하게 지정가능
-- SELECT만 가능한사람, DELETE만 가능한 사람 등...
GRANT CONNECT, RESOURCE, DBA TO aaa;
-- 최고권한자가 생성한 사용자 삭제시
DROP USER aaa CASCADE;
DDL(Data Definition Language)은 데이터 베이스에 데이터를 보관하기 위한 객체를 생성, 변경, 삭제등의 기능을 수행하는 명령어를 말한다
INSERT, UPDATE, DELETE, SELECT
- SELECT : 데이터베이스에 들어 있는 데이터를 조회하거나 검색하기 위한 명령어를 말하는 것으로 RETRIEVE 라고도 한다
- INSERT, UPDATE, DELETE
➡️ 데이터베이스의 테이블에 들어 있는 데이터에 변형을 가하는 종류(데이터 삽입, 수정, 삭제)의 명령어들을 말함
논리적인 작업의 단위를 묶어 DML에 의해 조작된 결과를 작업단위(트랜잭션)별로 제어하는 명령어를 말한다 ➡️ COMMIT, ROLLBACK, SAVEPOINT
- COMMIT : 모든 작업들을 정상 처리하겠다고 확정하는 명령어(데이터 변경)
- ROLLBACK : 트랜잭션의 처리 과정에서 발생한 변경사항을 취소하는 명령어(데이터 변경)
- SAVEPOINT : COMMIT전 특정 시점까지만 반영하거나 Rollback하겠다는 명령어
TIMESTAMP
를DEFAULT
로 넣으면 ORACLE에서 현재 날짜를 자동으로 입력해준다!
CREATE TABLE CLASSTBL(
CLSCODE CHAR(1), -- PK
CLSNAME VARCHAR2(20) NOT NULL,
CLSREGDATE TIMESTAMP DEFAULT CURRENT_DATE,
CONSTRAINT PK_CLASSTBL_CODE PRIMARY KEY(CLSCODE)
);
잘못만들어서 테이블 삭제후 재생성함
--DROP TABLE CATSSTBL;
INSERT INTO CLASSTBL(CLSCODE, CLSNAME) VALUES('A', '601호');
INSERT INTO CLASSTBL(CLSCODE, CLSNAME) VALUES('B', '602호');
INSERT INTO CLASSTBL(CLSCODE, CLSNAME) VALUES('C', '603호');
COMMIT;
SELECT C.* FROM CLASSTBL C;
외래키로
CLASSTBL
(클래스(반) 테이블)의CLSCODE
를 참조한다
TIMESTAMP
를DEFAULT
로 넣으면 ORACLE에서 현재 날짜를 자동으로 입력해준다!
-- 학생테이블 생성
CREATE TABLE STUDENTTBL(
STDNO NUMBER, -- PK
STDNAME VARCHAR2(30) NOT NULL,
STDCLASS CHAR(1),
STDSCOREKOR NUMBER,
STDSCOREENG NUMBER,
STDSCOREMATH NUMBER,
STDREGDATE TIMESTAMP DEFAULT CURRENT_DATE,
CONSTRAINT PK_STUDENT_NO PRIMARY KEY(STDNO),
CONSTRAINT FK_CLASS_CODE FOREIGN KEY(STDCLASS) REFERENCES CLASSTBL(CLSCODE)
);
COMMIT;
-- 학생클래스 데이터 입력
INSERT INTO STUDENTTBL(STDNO, STDNAME, STDCLASS, STDSCOREKOR, STDSCOREMATH, STDSCOREENG)
VALUES(1006, '학생6', 'A', 50, 85, 40);
INSERT INTO STUDENTTBL(STDNO, STDNAME, STDCLASS, STDSCOREKOR, STDSCOREMATH, STDSCOREENG)
VALUES(1007, '학생7', 'B', 30, 75, 80);
INSERT INTO STUDENTTBL(STDNO, STDNAME, STDCLASS, STDSCOREKOR, STDSCOREMATH, STDSCOREENG)
VALUES(1008, '학생8', 'C', 80, 95, 100);
INSERT INTO STUDENTTBL(STDNO, STDNAME, STDCLASS, STDSCOREKOR, STDSCOREMATH, STDSCOREENG)
VALUES(1009, '학생9', 'A', 60, 55, 82);
INSERT INTO STUDENTTBL(STDNO, STDNAME, STDCLASS, STDSCOREKOR, STDSCOREMATH, STDSCOREENG)
VALUES(1010, '학생10', 'C', 95, 80, 75);
COMMIT;
SELECT S.* FROM STUDENTTBL S;
- CHAR(s) : 고정 길이 문자열 정보
- VARCHAR(s) : s만큼의 최대 길이를 갖지만 가변 길이로 조정이 되기 때문에 할당된 변수값의 바이트만 적용된다
예를 들어 CHAR(10) 인데 ‘test’라는 4자짜리 문자열을 insert 하게 되면 남는 공간은 6개의 공백으로 채우게 된다
이때 남는 공간은 공백(스페이스)으로 채워지므로 공간의 낭비가 발생한다.
값을 받아 올 때 이 공백은 자동으로 제거되지만 무조건 처음 선언된 10byte가 소요되게 된다
따라서 반드시 고정길이에 해당하는 데이터만 CHAR로 선언하는 것이 좋다
데이터를 삽입 시 데이터값 이외에 삽입된 문자열의 길이도 함께 저장되는데,
255글자 이하에는 1바이트 그 이상은 2바이트의 추가 공간을 필요로 한다
즉 실질적인 데이터와 함께 길이 정보도 같이 저장된다!
예를 들어 VARCHAR(10)에 ‘test’라는 4자짜리 문자열을 삽입하면
4byte + 1byte(길이를 저장하기 위한 메모리) = 5byte가 소모된다
VARCHAR 유형은 가변 길이이므로 필요한 영역은 실제 데이터 크기뿐이다
그렇기 때문에 길이가 다양한 컬럼과 정의된 길이와 실제 데이터 길이에 차이가 있는 컬럼에 사용하기 적합하다
저장 측면에서도 VARCHAR 유형이 CHAR 유형보다 작은 영역에 저장할 수 있는 장점이 있다
날짜
DATE
➡️ String 으로 변경하기
--초 설정시 FF 사용한다! FF3사용시 초 3자리까지 출력된다
SELECT
S.*, TO_CHAR(S.STDREGDATE, 'YYYY-MM-DD HH24:MI:SS.FF') REGDATE1
-- S.*, TO_CHAR(S.STDREGDATE, 'YYYY-MM-DD HH24:MI:SS') 여기 변수지정안하면 테이블 이름이 주어진 값으로 지정된다
FROM
STUDENTTBL S;
UPDATE 테이블명 SET 컬럼명 = 바꿀 변경값들..
UPDATE studenttbl SET STDSCOREKOR = 98 WHERE STDNO IN(1001, 1005, 1007);
COMMIT;
SELECT S.* FROM STUDENTTBL S;
UPDATE 테이블명 SET 컬럼명 = 바꿀 변경값들.. WHERE = 조건
UPDATE studenttbl SET STDSCOREKOR = 60 WHERE STDSCOREKOR < 40;
COMMIT;
SELECT S.* FROM STUDENTTBL S;
DELETE FROM 테이블명 WHERE 조건;
DELETE FROM studenttbl WHERE STDNO = 1007;
COMMIT;
SELECT S.* FROM STUDENTTBL S;
-- STUDENTTBL을 S로 사용할것이다
SELECT S.* FROM STUDENTTBL S;
-- S안의 다른 데이터만 꺼내올 수도 있고 전체 테이블과 같이 사용할 수 있다
SELECT S.STDNO, S.STDNAME, S.* FROM STUDENTTBL S;
SELECT
SUM(S.STDSCOREKOR) TOT,
AVG(S.STDSCOREKOR) AVG
FROM
studenttbl S
WHERE
STDCLASS = 'A';
SELECT
SUM(S.STDSCOREKOR), SUM(S.STDSCOREENG), SUM(S.STDSCOREMATH)
FROM
studenttbl S
WHERE
STDCLASS = 'B';
SELECT
S.*
FROM
STUDENTTBL S
WHERE
S.STDSCOREKOR >= 10
ORDER BY STDNO DESC;
ROUND(숫자, 자릿수) ➡️ 반올림해주는 함수(반올림할 숫자, 출력할 자릿수)
SELECT S.STDNO, S.STDNAME,
ROUND((S.STDSCOREKOR + S.STDSCOREENG + S.STDSCOREMATH) / 3 , 2) AVG
FROM
STUDENTTBL S;
WHERE
-GROUP BY
-HAVING
의 순서
SELECT SUM(컬럼명)
, AVG(컬럼명)
, MAX(컬럼명)
, MIN(컬럼명)
, COUNT(컬럼명)
FROM 테이블명
WHERE 조건
GROUP BY 그룹화할 컬럼
HAVING 그룹에 대한 조건
그룹화한 항목과 조회할 갯수가 같게 바뀌어야한다 ➡️ 1:1 또는 N:N 이어야 함
SELECT S.* FROM STUDENTTBL S;
SELECT S.* FROM STUDENTTBL S GROUP BY STDCLASS; -- 이렇게 하면 오류난다
SELECT S.STDCLASS FROM STUDENTTBL S GROUP BY STDCLASS; -- S.STDCLASS와 STDCLASS는 항목의 개수가 같으니 그룹화해서 출력가능
GROUP BY 식에서 사용한 컬럼이 TO_CHAR() 함수에 의해 값이 변경되었다면 변형된 형태로 SELECT 절에 사용되어야 한다.
🌎참고 : GROUP BY
SELECT
SUBSTR(TO_CHAR(B.REGDATE, 'YYYY-MM-DD HH24:MM'),12 , 2),
ROUND(AVG(B.HIT), 2) AGVHIT,
COUNT(*)
FROM
BOARDTBL B
GROUP BY
SUBSTR(TO_CHAR(B.REGDATE, 'YYYY-MM-DD HH24:MM'),12 , 2);
SELECT
S.STDCLASS,
SUM(S.STDSCOREKOR),
COUNT(*)
FROM
STUDENTTBL S
GROUP BY
STDCLASS;
WHERE
에서 먼저 필터링 된 후 그룹화 된다
1. SELECT~WHERE 먼저 실행 후
2. GROUP BY 실행
SELECT
S.STDCLASS,
SUM(S.STDSCOREMATH),
COUNT(*)
FROM
STUDENTTBL S
WHERE
S.STDSCOREKOR >= 50
GROUP BY
STDCLASS;
그룹화 먼저 진행하고 그룹화 결과에 따라 조건 필터링한다
그룹화 이후에 진행되는 조건은 WHERE 이 아니라 HAVING 사용한다
1. GROUP BY 먼저 진행 후
2. SELECT~HAVING 실행
SELECT
S.STDCLASS,
SUM(S.STDSCOREKOR),
COUNT(*)
FROM
STUDENTTBL S
GROUP BY
STDCLASS
HAVING
SUM(S.STDSCOREKOR) >= 100;
오라클 자체에서 제공되는 테이블이며,
간단하게 함수를 이용해서 계산 결과값을 확인 할 때 사용하는 테이블이다.
SELECT B.* FROM BOARDTBL B;
--문제 풀기전 확인시
SELECT TO_CHAR(CURRENT_DATE, 'YYYY-MM-DD HH24') FROM DUAL;
SUBSTR 함수는 문자단위로 시작위치와 자를 길이를 지정하여 문자열을 자른다.
함수 : SUBSTR("문자열", "시작위치", "길이")
오라클 내장함수 SUBSTR 사용한다
SUBSTR('원하는 날짜 또는 시간의 자리', 가져오기시작할자릿수, 선택한자릿수부터 몇개가져올지)
SELECT SUBSTR('2022-09-17 14', 12, 2);
SELECT
TO_CHAR(B.REGDATE, 'YYYY-MM-DD'),
SUM(B.HIT),
COUNT(*)
FROM
BOARDTBL B
GROUP BY
TO_CHAR(B.REGDATE, 'YYYY-MM-DD');
vscode에서 StudentTest 실습하기
컨트롤러 서비스 화면 안만들어도 되니 데이터 가져오는 연습만 해보기
=> 단위테스트(apptest에서 실행)로 연습 ⇒ 디버그 콘솔로 확인
서비스 만들지 말고 mapper 로 바로 전송하여 사용
최종적으로 sql을 spring에 연동해서 사용가능한 정도가 되어야 함
@@ 최종 완성 후 다시한번 코드수정하여 올리기!@@
@@ 최종 완성 후 다시한번 코드수정하여 올리기!@@
- 학생 전체목록 가져오기
- 학생이름에서 검색한 데이터가 포함되는 데이터만 가져오기
- 학생목록에서 학번, 학생, 합, 평균 가져오기
=>vo에 쿼리 만들어도 되고 map으로 반환시키면 vo를 안만들어도 된다- 반별 국어, 영어, 수학 평균 가져오기
--1. 반별로 학생 그룹화 하기
--2. 그룹의 평균 구하기- ? 학생목록 5개씩 표시하는 페이지네이션(날짜는 년월일만 가져오기)
--ROWN 지정
-- 학생 전체목록 가져오기
SELECT S.* FROM STUDENTTBL S;
-- 학생이름에서 검색한 데이터가 포함되는 데이터만 가져오기
SELECT S.* FROM STUDENTTBL S WHERE S.STDNAME LIKE '%' || '김' || '%';
-- 학생목록에서 학번, 학생, 합, 평균 가져오기
=>vo에 쿼리 만들어도 되고 map으로 반환시키면 vo를 안만들어도 된다
SELECT
S.STDNO, S.STDNAME, (S.STDSCOREKOR + S.STDSCOREENG + S.STDSCOREMATH) SUM, ROUND((S.STDSCOREKOR + S.STDSCOREENG + S.STDSCOREMATH) / 3 , 2) AVG
FROM
STUDENTTBL S;
-- ? 반별 국어, 영어, 수학 평균 가져오기
--1. 반별로 학생 그룹화 하기
--2. 그룹의 평균 구하기
SELECT
S.STDCLASS,
COUNT(*),
ROUND((AVG(STDSCOREKOR) + AVG(STDSCOREENG) + AVG(STDSCOREMATH))/3 , 2) AVG,
ROUND(SUM(STDSCOREKOR)/COUNT(*), 2) KORAVG,
SUM(STDSCOREKOR),
ROUND(SUM(STDSCOREENG)/COUNT(*), 2) ENGAVG,
SUM(STDSCOREENG),
ROUND(SUM(STDSCOREMATH)/COUNT(*), 2) MATHAVG,
SUM(STDSCOREMATH)
FROM
STUDENTTBL S
GROUP BY
STDCLASS;
--HAVING
-- ? 학생목록 5개씩 표시하는 페이지네이션(날짜는 년월일만 가져오기)
--ROWN 지정
SELECT S.*, ROW_NUMBER() OVER (ORDER BY STDNO ASC) ROWN FROM STUDENTTBL S;
SELECT * FROM (SELECT S.*, ROW_NUMBER() OVER (ORDER BY STDNO ASC) ROWN FROM STUDENTTBL S) WHERE ROWN BETWEEN 1 AND 5;
package com.example.vo;
import java.sql.Date;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@NoArgsConstructor
public class StudentVO {
private int stdno;
private String stdname;
private char stdclass;
private int stdscorekor;
private int stdscoreeng;
private int stdscoremath;
private Date stdregdate;
}
@@ 최종 완성 후 다시한번 코드수정하여 올리기!@@
package com.example.mapper;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface StudentMapper {
// 학생 전체목록
@Select(value = {
"<script>",
" SELECT S.* FROM STUDENTTBL S ",
"</script>"
})
// public List<StudentVO> selectStudentList();
// StudentVO 대신에 Map<String, Object> 키는 컬럼명, value 실제값
public List<Map<String, Object>> allStudentList();
// 학생이름에 검색되는 데이터만 가져오기
@Select(value = {
"<script>",
" SELECT S.* FROM STUDENTTBL S ",
" WHERE S.STDNAME LIKE '%' || #{text} || '%' ",
"</script>"
})
public List<Map<String, Object>> searchStudentList(@Param("text") String text);
// 학생목록에서 학번, 학생, 합, 평균 가져오기
@Select(value = {
"<script>",
" SELECT S.STDNO, S.STDNAME, (S.STDSCOREKOR + S.STDSCOREENG + S.STDSCOREMATH) SUM, ROUND((S.STDSCOREKOR + S.STDSCOREENG + S.STDSCOREMATH) / 3 , 2) AVG FROM STUDENTTBL S ",
"</script>"
})
public List<Map<String, Object>> studentData();
}