기존 Mybatis의 동작방식
insert a from table
....

 

기존 mybatis를 써본사람은 알것이다.

DB에 통신을 보내기 위해선 VO를 만들고 DAO로 Mapper와 연결한뒤 Mapper에선 DB에 보낼 쿼리를 만드는 형태로 이뤄지게된다.

하지만 이전에 리팩토링을 진행했을때 myabtis의 한계를 확인할수 있었다.

유지보수의 어려움

VO에 생각보다 사용되지 않는 필드가 많았고 이를 VO에서 수정을 진행할때마다 mapper를 수정해야하는 번거로움이 존재했다.

만약 VO에 필드가 1개 변경되었다면 이와 연관되있는 mapper를 전부 변경해야하는 문제점이 존재했다.

<select id="GET_ASSET_LIST_BY_SOFT_DELETE" resultType="AssetDTO">
		select *
		from assets where asset_deleted=0
	</select>

	<select id="GET_ASSET_ONE_BY_ID" parameterType="int"
		resultType="AssetVO">
		select * from assets
		where asset_id = #{asset_id}
	</select>

 

다음과 같은 코드에서 asset_id를 만약 ASEET_ID로 변경한다고 가정시 모든 asset_id를 ASSET_ID로 변경해줘야 한다!

조회 데이터의 한계

비즈니스 코드에서 쿼리를 사용해 DB의 데이터를 가져올경우 비슷한 데이터를 가져와야 했지만 쿼리가 달라서 @ Overloading 하듯이 중복된 쿼리를 생성해야했다. (그냥 전체 데이터를 가져오는 쿼리를 써도 문제없지만 성능상 문제로 인해 피하는게 좋았다)

Member a = testdaoImpl.getmember();
Member b = testdaoImpl.getmemberPW();

 

패러다임의 불일치

먼저 짚고 넘어자면 객체와 RDMB이 서로 지향하는 목적이 다른게 가장 큰 원인이다.

객체 - 상속을 통한 전체 데이터를 갖고있는형태 (참조를 통해서 사용가능)

RDMB - FK를 통해 필요한 데이터를 그때그때 가져오는 형태 (외래키를 사용해서만 참조가능)

OOP의 목적

객체 위주로 모델링이 진행되며 각각의 객체는 자신의 데이터(필드) 및 메서드를 캡슐화 하는 형태로 데이터와 기능을 1개의 단위로 묶어 사용한다

member a = new member();
a.getmambername();

 

만약 a라는 객체가 다른 객체의 요소를 사용해야 할경우 상속을 통해 부모 - 자식관계를 형성한뒤 이를 활용한다.

RDMB의 목적

데이터 중심의 구조화된 연관관계를 외래키(FK)를 활용하여 참조한다.

슈퍼타입 VS 서브타입 

슈퍼타입 - 테이블이 존재하고 공통적인 속성들을 가진다. (OOP의 특성인 추상화, Animal)

서브타입 - 슈퍼타입을 상속받으면서 각각의 특정 속성을 추가로 갖는다. (Cat, Dog,)

차이점 슈퍼타입 서브타입
정의 서브타입에서 사용할 공통적인 속성을 가진다. 슈퍼타입의 상속을 받으며 특정 속성을 추가로 갖는다.
예시(상단 그림 참조) ITEM ALBUM, MOVIE, BOOK

 

서브타임의 객체를 가져와야 할경우 슈퍼타입에 존재하는 테이블을 항상 Join해야하는 불편함이 있으며 이를 토대로

모든 Join을 걸어버려야한다.

그렇다고 테이블에 맞춰 객체를 모델링할경우 OOP의 사용목적이 등한시된다.

엔티티 신뢰의 문제

MemberVO에 data를 설정해놨다고 하더라도 해당 member가 내가 필요로 하는 데이터를 전부 갖고있지 않을수도 있다.

select * from member;	//member전체 데이터
select member_id from member;	//memebr 특정 데이터

2개의 쿼리처럼 엔티티가 문제없이 데이터를 가져왔다고 해도 (B)쿼리를 실행했을경우 내가 가져온 엔티티를 신뢰하지 못하는 문제가 있다.

 

즉, 내가 주체가 아닌 SQL이 주체가 되어 dao를 건드리면 되는게 아니라 sql을 바꿔야하는 문제가 존재했다. -> 

(SQL에 의존적인 개발 이라고 한다.)

"그렇다면 객체를 자바 컬렉션에 저장하듯이 DB에 저장할수 없을까?"  라는 궁금증에서 시작한게 JPA이다.

 


JPA의 동작방식

memberDAO를 사용한다고 가정했을때 JPA가 지정해놓은 특정한 메서드를 사용할경우 해당 메서드에 해당하는 SQL 쿼리를 JPA가 자동으로 생성, DB에 요청을 보내게된다.

 


JPA는 Persistence라는 클래스가 존재하는데 해당 클래스가 persistence.xml을 확인하여 EntityManagerFactory클래스를 만들고 해당 Factory클래스가 EntityManager를 만든다

또한 JPA는 JDBC API 사이에서 동작 되며 MemberDAO가 Entity Object를 던지면 자동으로 JPA가 캐치해서 DB에 SQL쿼리를 쏴주는 형태로 해당방식의 이점은 다음과 같다.

 

1. 엔티티 간의 연관관계를 모델링해주고 이를 기분으로 데이터 조회가 가능해진다.

2. 테이블간 참조가 필요할경우 JPA가 자체적으로 외래키를 참조한다.

3. 객체그래프 탐색문제 해결 -> 지연로딩으로 인해 연관관계를 JPA는 필요로하는 시점에 적절한 쿼리를 실행하여 연관된 객체를 조회가 가능하다.

4. 엔티티의 동일성 보장

 

즉시조회 - 데이터를 조회할때 이와 연관된 객체의 데이터까지 전부 로드하는것으로 1:N의 관계를 맺고있을경우 1을 호출하면 이와 연관된 모든 데이터를 가져오는것

지연로딩

데이터가 필요한 시점에 해당 객체와 연관된 객체의 데이터를 불러오는것

초기 로드시간 감소 및 메모리 사용 최적화의 이점 

지연쓰기

엔티티의 변경사항을 즉시 반영하는게 아니라 트랜잭션 끝에서 데이터를 일괄적으로 처리하는형태

네트워크 호출횟수 최소화 및 성능상 이점

 

지연로딩 (Lazy Loading) VS  즉시로딩

지연로딩 (Lazy Loading) : 객체가 실제로 사용될때 로딩

즉시로딩 : Join SQL로 한번에 연관된 객체까지 모두 조회

해당 코드에선 DB에 쿼리를 날리는 형태를 구현하였는데

 

Mybatis의 경우 메서드를 호출할때마다 해당 메서드가 실행된다 즉, 해당 코드에선 3번의 sql 쿼리를 DB에 전송하게 된다.

그에 반해 JPA는 지연쓰기를 지원하는 형태이기에 3번의 sql이 진행되더라도 이를 한번에 보내기 때문에 DB에 쿼리를 날리는 횟수는 1번에 지나지 않는다!

 

//JPA의 insert
transaction.begin();	//트랜잭션 시작
em.persist(A);
em.persist(B);
em.persist(C);
//쿼리 대기중
transaction.commit();	//트랜잭션 종료(커밋), SQL 실행


//Mybaits insert
transaction.begin();	//트랜잭션 시작
em.insetData(A);	//SQL 쿼리 실행
em.insetData(B);	//SQL 쿼리 실행
em.insetData(C);	//SQL 쿼리 실행
transaction.commit();	//트랜잭션 종료(커밋)

 

엔티티의 동일성 보장

JPA가 JDBC와 DAO 사이에 존재한다는 그림과 JPA가 지연쓰기를 지원함으로써 해당하는 특징이다.

SQL쿼리를 날릴때 쿼리를 모았다가 한번에 사용되는것인데 이것이 스프링의 컨테이너와 비슷하게 EntityManager라는 클래스가 해당 쿼리에 해당하는 엔티티의 생명주기를 관리한다.

그리고 여기서 사용되는 엔티티는 EntityManager에 존재하는 동일한 엔티티를 참조함으로써 동일성이 보장되는것이다.  

Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);

member1 == member2 // 다르다

public MembergetMember(String memberId){
...
retrun new Member(..);
}

//이런 형태로 이뤄지기 때문

Member member1 = list.get(memberId);
Member member2 = list.get(memberId);

member1 == member2 //같다

Member member1 = jpa.find(memberId);
Member member2 = jpa.find(memberId);

member1 == member2 //동일한 트랜잭션에선 조회한 엔티티의 동일성을 보장

 

 

1. JPA와 상속

JPA
jpa.persitt(album)...

Mybatis
insert into Item ...
insert into Album ...

조회 (Join을 jpa가 알아서 해준다)
JPA
Album album = jpa.find(Album.class, albumId);

Mybatis
Select I.*, A.*
	from ITEM I
    JOIN ALBUM a On I.ITEM_ID = A.ITEM_ID

 

2. JPA와 연관관계

연관관계 저장

연관관계 저장
member.setTeam(team);
jpa.persist(member);

객체 그래프 탐색
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();

Member member = memberDAO.find(memberId);
member.getTeam();	//객체 그래프 탐색 가능 -> 지연로딩으로 가능하게 된다.
member.getOrder().getDelivery();

 

 

기본적인 사용법

저장 jpa.persist(member)
조회 Member member = jpa.find(memberId)
수정 member.setName("변경할 이름")
삭제 jpa.remove(member)

 

JPA의 성능최적화 기능

1차 캐시와 동일성 보장

Member member1 = jpa.find(Member.class, memberId);	//SQL이 전송
Member member2 = jpa.find(Member.class, memberId);	//캐시

member1 == member2 //같다
println(m1 == m2)	//같다

트랜잭션을 지원하는 쓰기 지연 

//insert
transaction.begin();	//트랜잭션 시작
em.persist(A);
em.persist(B);
em.persist(C);
//쿼리 대기중
transaction.commit();	//트랜잭션 종료(커밋), SQL 실행


//update,delete 등
transaction.begin();	//트랜잭션 시작
changeMember(A);
deleteMember(C);
//비즈니스 로직이 실행될때 DB 로우락이 걸리지 않는다
//커밋하는 시점에 DB로 쿼리를 전송
transaction.commit();	//트랜잭션 종료(커밋), SQL 실행

 

 

 

 

 

 

 

 

 

현재 2개의 테이블에서 서로 관계를 맺은건 detail_id 한개이다.

Join

//기본예시
SELECT *
FROM table1
INNER JOIN table2
ON table1.common_field = table2.common_field;

//Inner Join
SELECT *
FROM selectanswerlist
INNER JOIN detailsurvey
ON selectanswerlist.detail_id = detailsurvey.detail_id;

//Left Join
SELECT *
FROM selectanswerlist
LEFT JOIN detailsurvey
ON selectanswerlist.detail_id = detailsurvey.detail_id;

//Right Join
SELECT *
FROM selectanswerlist
RIGHT JOIN detailsurvey
ON selectanswerlist.detail_id = detailsurvey.detail_id;

 

inner Join

1. Join의 결과는 다음과 같으며 detail_id를 기준으로정렬되있다

2. 내부 조인은 두 테이블에서 조건이 일치하는 행만 반환

 

Right Join

Natural join

Natural join의 경우 테이블에서 이름이 같은 칼럼을 찾아 이를 기준으로 열을 정렬한다.

from에서 사용한 테이블의 칼럼이 순차적으로 나오고 이후에 join된 테이블의 칼럼이 순차적으로 나온다.

일반적인 join보다 칼럼이 중복된걸 자동으로 없애주기에 간결해진다. 

 

SELECT *
FROM table1
NATURAL JOIN table2;

select *
from selectanswerlist
natural join detailsurvey;

 

 

 

Union, Union all (합집합)

union은 두 개 이상의 SELECT 문의 결과를 결합하여 단일 결과 집합으로 보여준다.

union all의 경우 2개의 테이블중 겹치는 칼럼의 중복을 제거해준다.

//(select column_name from Table_name1) union (select column_name from Table_name2)
//(select column_name from Table_name1) union all (select column_name from Table_name2)

(select detail_id from selectanswerlist) union (select detail_id from answer);
(select detail_id from selectanswerlist) union all (select detail_id from answer);

union (좌측), union all (우측)

 

 

Intersect, Intersect all (교집합)

2개의 테이블중 겹치는 부분만 출력

intersect all의 경우 각 테이블에 값이 2개씩 있다면 2번씩 출력

//SELECT column_name FROM Table_name1 INTERSECT SELECT column_name FROM Table_name2;

select member_id from answer intersect select member_id from member;
select member_id from answer intersect all select member_id from member;

 

 

except, except all (차집합)

2개의 테이블중 서로 겹치지 않는 부분만 출력

except all의 경우 겹치지 않는 것들에서 중복된것도 출력

a = 1,2,3,3 // b = 2,5 일때

except는 1,3,5를출력

except all은 1,3,3,5를 출력

//(select column_name from Table_name1) except (select column_name from Table_name2);

(select detail_id from selectanswerlist) except (select detail_id from answer);
(select detail_id from selectanswerlist) except all (select detail_id from answer);

except (좌측), except all (우측)

 

DB 기초 초작에 관련된 포스팅을 진행합니다.

 

붉은색 -> row

주황색 -> 레코드

노란색 -> column

 

단일 테이블에 존재하는 데이터 가져오기 (전체)

// select * from Table_name;
//member 테이블의 전체 데이터 가져오기
select * from member;

//select * from Table_name where condition;
//member 테이블에서 member_id가 1 인것만 가져오기
select * from member where member_id = 1;

 

//member_id의 alise를 a로 정의,
//a와 nickname을 member테이블에서 가져오되 이름이 '가가가', id가 10 넘을것 (where에선 alise 사용불가)
select member_id as a ,member_nickname
from member
where member_nickname ='가가가' and member_id >10;

 

단일 테이블에서 몇몇 칼럼만 데이터만 가져오기

//selct column_name from table_name
//id, name순서로 출력
select member_id,member_name from member

//name, id순서로 출력
select member_name,member_id from member

 

 

실제 응용

//사칙 연산도 가능하다.
select member_id*2 ,member_nickname from member;

//as를 사용할경우 member_id를 a로 변경하여 출력
select member_id*2 as a, member_nickname from member;

 

 

요약

select는 데이터 값을 가져오는 쿼리문

*를 사용할경우 전체, 특정 값만 가져오고 싶을경우는 column_name을 입력하되 as(alies)로 이름을 변경할수 있다.

where부분은 as로 변경한 이름을 사용 불가능하다 (column_name만 인식된다)

 

Order by

어떤 기준으로 결과를 정렬할지 보여주는 명령어

//select * from Table_name by column_name desc; (내림차순)
//select * from Table_name by column_name asc; (오름차순)
//select * from Table_name by column_name1 asc, column_name2 desc; (column_name1 우선 정렬)

select * from member order by member_id desc;

 

like

일부 문자만 검색하기

//select column_name from Table_name where column_name like 조건식;
// % -> 어떤 문자와도 일치
// _ 어떤 한 문자와도 일치
// ab_cd -> ab와 cd 사이에 1글자만 존재하는것들
// ab%cd -> ab와 cd에 글자가 있는것들

select member_id, member_name from `member` where member_name like '%sd%'; (좌측)
select member_id, member_name from `member` where member_name like 'as__'; (우측)

 

 

and

where에 추가로 조건 더하기

//select column_name from Table_name where column_name=조건 and column_name2 = 조건;

SELECT * FROM member WHERE member_name LIKE 'asd%' AND member_nickname = '가가가';
SELECT * FROM member WHERE member_id > 30 AND member_nickname = '가가가';
SELECT * FROM member WHERE member_id > 3 and member_id <20 AND member_nickname = '가가가';
select * from member where member_id between 3 and 10;

 

 

avg (평균), count (갯수)

평균값을 구할땐 avg를 사용, ()안에 칼럼명 써야 정상적으로 작동

테이블에 몇개의 튜플이 존재하는지 확인 

count에선 distinct사용이 불가하다

//select avg (column_name) from Table_name;
//select count (*) fro Table_name;

SELECT AVG(member_money) as money FROM member;
select count(*) from member;

 

 

group by

여러 튜플 집합에 대해 집계함수를 적용할경우 사용

특정 열의 값에 따라 행을 그룹화하고, 각 그룹에 대해 집계 함수를 적용할 때 사용

//select avg (column_name) as a,member_name from member group by member_name;

select avg (member_money) as a,member_name from member group by member_name;

member_name | a
------------|-----
Alice       | 150  (100과 200의 평균)
Bob         | 150


//member 테이블에서 member_name과 member_id의 갯수를 가져오되
//member_id가 3보다 큰 모든 멤버와 그들이 작성한 답변의 수를 가져오는 것
select member_name, count(member_id) as member_answer_count
from member
natural join
answer
where member_id >3 group by member_name,member_id;

 

Having

집계 함수(COUNT, SUM, AVG, MAX, MIN 등)의 결과에 대한 조건을 적용할 때 사용됩니다.

WHERE 절과 비슷하지만, WHERE 절은 집계 함수를 실행하기 전의 개별 행에 대한 조건을 적용

HAVING 절은 집계 함수의 결과에 대한 조건을 적용, GROUP BY과 함께 사용

  • where은 집계 작업 전에 데이터를 줄이기 때문에, 처리할 데이터 양이 감소하고 결과적으로 쿼리의 성능이 향상
  • having은 집계 작업이 수행된 후에 결과에 대해 필터링을 진행하며  이미 집계된 결과에 대한 추가작업으 where절과는 다른 시점에 작동
//SELECT column_name FROM Table_name GROUP BY column_name HAVING 조건;

select * from member group by member_id having MAX(member_id) < 20;


	<select id="test1" resultType="noticeVO" parameterType="int">
		select * from notice
		where notice_id = #{notice_id}
	</select>
    
	<select id="test2" resultType="noticeVO" parameterType="int">
		SELECT * FROM notice GROUP BY notice_id HAVING notice_id = #{notice_id}
	</select>

성능 차이를 위하여 test1과 test2를 실행하였고  확연한 차이를 위해 해당 코드를 100번씩 반복해보았다.

그 결과 test1(where)과 test2(having)의 차이는 다음과 같았으며 단순하게 값을 가져오는건  차이가 있진 않았다.

구분 test1 test2
1 41 35
2 28 31
3 25 25
4 24 23
5 20 23
6 23 20

 

+ Recent posts