기존 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 실행
'DB' 카테고리의 다른 글
[데이터베이스]복수의 테이블의 데이터 결합하기 (0) | 2024.02.14 |
---|---|
[데이터베이스]데이터 가져오기 - 1 (0) | 2024.02.14 |