이 게시글은 인프런 김영한님의'자바 ORM 표준 JPA 프로그래밍 - 기본편'을 보며 공부하고 작성한 게시글임을 밝힙니다.
강의 내용 외에도 공부한 내용과 본인생각이 함께 있습니다
JPA 구동방식은 우선 Persistence라는 클래스가 있어야한다.
이 Persistence가 META-INF/persistence.xml에서 설정 정보를 조회한 후 EntityManageFactory 클래스를 생성한다.
그리고 이 EntityManageFactory는 EntityManager를 필요할 때마다 생성해서 돌리면된다.
우선 Persistence를 사용하자.
Persistence.createEntityManagerFactory();
을 작성하면 아래와 같이 파라미터로 persistenceUnitName을 넘기라는 안내가 뜬다.
(생성된것은 EntityManagerFactory타입의 변수로 받아야한다.)
이 unitName에는 META-INF/persistence.xml에 작성해둔 persistence-unit name="hello"
의 값을 적는다.
나는 "hello"이다.
EntityManagerFactory를 만들었으면, 데이터베이스 연결도되고 웬만한것은 다 된다.
기본적인 메인 코드는 다음과 같다.
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
//code(데이터 저장, 조회 등 실제 코드 작성)
em.close();
//app이 완전히 끝나면 emf를 닫아줘야한다.
emf.close();
}
EntityManagerFactory는 애플리케이션 로딩시점에 딱 하나만 만들어놔야한다.
-> 데이터베이스가 하나씩 묶여서 돌아가는 것
그리고 실제 디비에 저장하거나 그런 디비 커넥션 얻어서 쿼리 날리고 종료되는 한 일관적인 단위(트랜잭션)를 할 때마다 EntityManager를 꼭 만들어줘야한다.
-> ex) 고객 요청이올때마다 작업한다면 각 요청마다 EntityManager를 만들어줘야한다.
이제 데이터들을 직접 생성한 후 적용해보자
인터넷 localhost:8082
에 접속해서 H2를 사용할 수 있다. 데이터들을 생성할 수 있다.
여기서 중요한게 있다!
아래 쿼리문을 실행해서 테이블을 생성하자.
CREATE TABLE MEMBER (
ID BIGINT NOT NULL,
NAME VARCHAR(255),
PRIMARY KEY (ID)
)
db에 테이블을 생성했으면 다음 코드를 작성하자.
테이블의 컬럼과 동일한 구조의 클래스를 먼저 만든다.
main - java - hellojpa아래에 Member클래스를 선언한다.
코드는 다음과 같다.
package hellojpa;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class Member {
@Id
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
id, name 필드값을 선언한 이유는 db에도 그렇게 있기때문이다.
@Entity
@Id
그 다음 main-java-hellojpa -JpaMain 파일의 main메소드를 작성해보자.
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
Member member = new Member();
member.setId(1L);
member.setName("HelloA");
em.persist(member); //저장
em.close();
emf.close();
}
}
위처럼만 작성하면 에러가난다. 왜일까?
에러가 발생했다.
의도한 에러가 아니긴하지만, 그래도 살펴보자.
Database may be already in use: ~~
https://www.inflearn.com/questions/211951/database-may-be-already-in-use-null-에러-발생-ㅠㅠ
포트가 중복으로 사용되고있는것같다.
위 링크를 참고해서 jdbc:h2:tcp://localhost/~/test
로 h2연결 및 설정파일 코드의 url을 수정했다.
에러는 해결했지만, persist()를 썼음에도 쿼리가 안날라가는걸보니 뭔가 실행되고있는것같지않다.
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager(); //db 커넥션 하나 받았다고 생각
EntityTransaction tx = em.getTransaction();
tx.begin(); //db trasaction 시작
//code(데이터 저장, 조회 등 실제 코드 작성)
Member member = new Member();
member.setId(1L);
member.setName("HelloA");
em.persist(member);
tx.commit();
em.close();
//전체 app이 완전히 끝나면 EntityManagerFactory를 닫아줘야한다.
emf.close();
}
}
JPA는 트랜잭션이 엄청 중요하다!
데이터를 변경하는 모든 작업은 꼭 트랜잭션 안에서 해야한다!
마지막에 commit을 꼭 해줘야 반영된다.
EntityManager를 닫는게 굉장히 중요하다.
EntityManager는 내부적으로 db connection을 물고 동작한다.그렇기때문에 사용을 다 하고나면 꼭 닫아줘야한다.
실행하면 아래와 같이 결과가 뜬다.
(아래 결과말고도 많이 뜨는데 신경쓰지 말자)
실행 후 db로 가보면 데이터가 잘 들어온것을 볼 수 있다.
여기서 의문이 들 것이다.
어 이상하다? 나는 멤버가 어느 테이블에 저장되라고 한 적 없는데?
사실 관례를 따른것이다.
만약에 테이블 이름이 member가 아니라 user로 되어있다면 Member 파일에서 @Table를 사용해 설정해주면된다.
import javax.persistence.Table;
@Entity
@Table(name = "USER") //실제 db의 table명과 매치!
public class Member {
마찬가지로 코드의 필드명과 db의 컬럼명이 맞지않으면 @Column 을 사용해서 설정할 수 있다.
@Column(name = "username") //실제 db의 컬럼명과 일치!
private String name;
이렇게 annotation에 필요한 맵핑을 다 하면된다.
여기서 JpaMain의 코드가 조금 이상한 부분이 있다!
저렇게 작성하고 중간에 문제가 생기면 .close()
부분이 호출이 안된다.
안좋은 코드이다.
아래는 정석 코드이다.
try-catch를 이용
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager(); //db 커넥션 하나 받았다고 생각
EntityTransaction tx = em.getTransaction();
tx.begin(); //db trasaction 시작
try {
//code(데이터 저장, 조회 등 실제 코드 작성)
Member member = new Member();
member.setId(1L);
member.setName("HelloA");
em.persist(member);
tx.commit();
}catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
//전체 app이 완전히 끝나면 emf를 닫아줘야한다.
emf.close();
}
}
em.close();
가 매우중요!실제 spring에서 사용하면 위처럼 코드가 많지는 않다. (다 해주기때문,, persist()정도만 호출해주면 됨)
emf.close();
EntityManager를 자바 컬렉션처럼 이해하면된다.
내 객체를 대신 저장해주는 것.
그래서 아래코드로 id가 1L에 해당하는 멤버를 찾을 수 있다.
Member findMember = em.find(Member.class, 1L);
Member.class에서 PK인 id값이 1L인 멤버를 찾아오는 코드
try {
//code(데이터 저장, 조회 등 실제 코드 작성)
Member findMember = em.find(Member.class, 1L); //조회
em.remove(findMember);// 삭제
tx.commit();
}
try {
//code(데이터 저장, 조회 등 실제 코드 작성)
Member findMember = em.find(Member.class, 1L); //조회
findMember.setName("HelloJPA");
//em.persist(findMember); //수정 후 안해도됨
tx.commit();
}
수정 후 .persist()로 저장해주지않아도된다.
지바 컬렉션 다루는것처럼 다루도록 설계되어서 그렇다.
어떻게 되는걸까?
JPA를 통해서 Entity를 가져오면 이 객체는 이제 JPA를 통해 관리됨
그리고 트랜잭션 커밋 시점에 JPA가 변경여부를 다 체크해서, 변경되었으면 트랜잭션 커밋직전에 update쿼리 만들어서 날림
update 쿼리가 나간다.
rdb는 데이터변경자체를 트랜잭션안에서 실행하도록 다 설계가 되어있다.
-> Q : 나는 jpa에서 트랜잭션 안걸어도 디비 변경되던데?
-> A : 우리가 트랜잭션 안걸어도 db는 단건 쿼리가 올때마다 내부적으로 다 처리하는 것.
+단순한 조회시 find()를 이용하는 것 말고, 특정 조건이 걸린 다양한 데이터를 한 번에 가져오려면 어떻게 해야할까?
-> JPQL사용!!
객체를 대상으로하는 객체지향 쿼리
EntityManager의 createQuery("쿼리", 타입)를 사용해 파라미터에 쿼리를 작성해서 넣어준다.
(SQL에서 아용하는 기본적인 쿼리는 다 됨)
//JPQL로 다양한 데이터 한 번에 조회
List<Member> result = em.createQuery("select m from Member as m", Member.class)
.getResultList();
for (Member member : result) {
System.out.println("member.getName() = " + member.getName());
}
JPA입장에서는 테이블을 대상으로 코드를 절대 짜지 않음.
(테이블에서 가져오면 JPA의 사상이 깨짐)
멤버 객체 대상으로 쿼리를 날린다고 생각
"select m from Member as m" -> 멤버 객체 다 가져와!( select m : Member 엔티티 선택한 것) : 대상이 객체임
결과를 보면, select에서 Member의 필드를 모두 가져옴
어떤 이점이 있을까?
List<Member> result = em.createQuery("select m from Member as m", Member.class)
.setFirstResult(5) //5번부터
.setMaxResults(8) //8개 가져와
.getResultList();