본 글은 인프런의 김영한님 강의 자바 ORM 표준 JPA 프로그래밍 - 기본편
을 수강하며 기록한 필기 내용을 정리한 글입니다.
-> 인프런
-> 자바 ORM 표준 JPA 프로그래밍 - 기본편 강의
⇒ SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어를 제공함.
→ SQL 문법과 유사함. SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 지원함
⭐ JPQL은 엔티티 객체를 대상으로 쿼리를 작성함 ⭐
⭐ SQL은 DB 테이블을 대상으로 쿼리를 작성함 ⭐
String jpql = "select m from Member m where m.username like '%kim%'";
List<Member> result = em.createQuery(jpql, Member.class).getResultList();
System.out.println(result);
em.createQuery()
메서드를 활용함.'%kim%'
부분에 변수가 들어가야함 CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> query = cb.createQuery(Member.class);
Root<Member> m = query.from(Member.class);
CriteriaQuery<Member> cq = query.select(m).where(cb.equal(m.get("username"), "kim"));
List<Member> result = em.createQuery(cq).getResultList();
→ QueryDSL 활용 예
List<Member> result = em.createNativeQuery("select * from member m where m.username like '%kim%'").getResultList();
em.createNativeQuery()
메서드를 활용하여 파라미터로 SQL을 전달하면 해당 SQL이 DB에 그대로 보내진다.em.createQuery()
, em.createNativeQuery()
)을 활용하기 때문에 해당 쿼리들을 전송하기 전에 자동으로 flush()가 된다.select m from Member as m where m.age > 18
Member
: Member 엔티티를 의미함. 테이블이 아님에 유의.Member as m
, Member m
+Group By, Having, Order By 다 지원됨
TypedQuery<Member> typedQuery = em.createQuery("select m from Member m where m.age > 18", Member.class);
TypedQuery<String> typedQuery2 = em.createQuery("select m.username from Member m where m.age > 18", String.class);
Query query = em.createQuery("select m.username, m.age from Member m where m.age > 18");
getResultList()
TypedQuery<Member> typedQuery = em.createQuery("select m from Member m where m.age > 18", Member.class);
List<Member> members = typedQuery.getResultList();
getResultList()
메서드의 경우, 결과가 없으면 빈 리스트를 반환해준다. → NullPointerException 걱정 안해도 된다.getSingleResult()
: 결과가 정확히 하나만 나와야함TypedQuery<Member> typedQuery = em.createQuery("select m from Member m where m.age > 18", Member.class);
Member member = typedQuery.getSingleResult();
결과가 올바르지 않을 때(결과가 없거나, 둘 이상이면) 예외를 발생한다.
결과 없으면 : javax.persistence.NoResultException
결과 둘 이상이면 : javax.persistence.NonUniqueResultException
→ 따라서 해당 메서드를 쓸 때는 try-catch 로 처리해줄 필요가 있다.
Vue.js처럼 :
로 바인딩해준다.
String usernameParam = "UserName1";
TypedQuery<Member> bindingQuery = em.createQuery("select m from Member m where m.username = :username", Member.class);
bindingQuery.setParameter("username", usernameParam);
PreparedStatements 에서 setInt, setString 했던 것 처럼 위치를 기준으로 설정한다.
String usernameParam = "UserName1";
TypedQuery<Member> bindingQuery = em.createQuery("select m from Member m where m.username = ?1", Member.class);
bindingQuery.setParameter(1, usernameParam);
⇒ 하지만 웬만하면 이름 기준으로 하는게 좋음. 더 직관적이다.
SELECT m FROM Member m
: 엔티티SELECT [m.team](http://m.team) FROM Member m
: 엔티티 프로젝션select t from Member m join [m.team](http://m.team) t
SELECT m.address FROM Member m
: Address 라는 임베디드 값 타입을 Member 엔티티에서 갖고 있으므로 임베디드 타입으로 설정된다.SELECT m.username, m.age FROM Member m
: 스칼라 타입 프로젝션⇒ 엔티티 프로젝션의 경우, 결과로 반환된 엔티티 객체들은 영속성 컨텍스트에 의해 관리된다.
SELECT m.username, m.age FROM Member m
이렇게 여러 타입의 값들이 반환될 경우, 각 값은 다음 방식들로 가져올 수 있다.List resultList = em.createQuery("select m.username, m.age from Member m")
.getResultList();
Object o = resultList.get(0);
Object[] result = (Object[])o;
System.out.println("username : " + result[0]);
System.out.println("age : " + result[1]);
위 방식을 다음과 같이 List의 Generic에 Object[]를 지정함으로써 간략히 할 수도 있다.
List<Object[]> resultList = em.createQuery("select m.username, m.age from Member m")
.getResultList();
Object[] result = resultList.get(0);
System.out.println("username : " + result[0]);
System.out.println("age : " + result[1]);
하지만 본 방식들은 불편하다.
DTO 파일을 따로 생성해서 활용하는 방식이다.
<MemberDTO 생성>
package jpql;
public class MemberDTO {
private String username;
private int age;
public MemberDTO(String username, int age) {
this.username = username;
this.age = age;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
다음과 같이 활용한다.
List<MemberDTO> result = em.createQuery("select new jpql.MemberDTO(m.username, m.age) from Member m", MemberDTO.class)
.getResultList();
MemberDTO memberDTO = result.get(0);
System.out.println("username : " + memberDTO.getUsername());
System.out.println("age : " + memberDTO.getAge());
위 코드를 보면 new jpql.MemberDTO(m.username, m.age)
부분에서 MemberDTO 객체를 생성해서 반환하는 것이다.
이에 따라 적절한 생성자가 꼭 필요하다.
new 이후에는 패키지 명을 전부 다 써줘야 한다.
setFirstResult(int startPosition)
: 조회 시작 위치 (0부터 시작)setMaxResult(int maxResult)
: 조회할 데이터 수 for (int i = 0; i < 100; i++) {
Member member = new Member();
member.setUsername("member" + i);
member.setAge(i);
em.persist(member);
}
em.flush();
em.clear();
List<Member> result = em.createQuery("select m from Member m order by m.age desc", Member.class)
.setFirstResult(2)
.setMaxResults(10)
.getResultList();
for (Member m : result) {
System.out.println(m);
}
tx.commit();
em.createQuery("select m from Member m where m.type=:userType")
.setParameter("userType", MemberType.Admin)
.getResultList();
자식 엔티티들 중 특정 타입의 엔티티에 대한 데이터만 조회하고 싶을 경우
em.createQuery("select i from Item i where type(i) = Book", Item.class)
.getResultList();
→ 상속관계 DB에서 부모 테이블의 DTYPE 컬럼에 where 절이 설정됨
public class MyH2Dialect extends H2Dialect {
public MyH2Dialect() {
registerFunction("group_concat", new StandardSQLFunction("group_concat", StandardBasicTypes.STRING));
}
}
→ H2 DB 방언을 상속받아 사용자 정의 함수 group_concat
을 등록한 것.
hibernate.dialect
를 상속받은 MyH2Dialect
로 설정해주면 된다.<property name="hibernate.dialect" value="dialect.MyH2Dialect" />
SELECT FUNCTION('group_concat', m.username) FROM Member m
→ FUNCTION() 을 쓰면 됨