spring.datasource.url=jdbc:h2:tcp://localhost/~/testspring.datasource.driver-class-name=org.h2.Driver# 스프링부트 2.4부터는 꼭 추가해줘야 오류가 발생하지 않는다.spring.datasource.username=sa# jpa 설정 추가# JPA가 생성하는 SQL을 호출한다.spring.jpa.show-sql=true# JPA는 테이블을 자동으로 생성하는 기능을 제공한다.# none으로 설정하면 해당 기능을 끈다.# create를 사용하면 Entity 정보를 바탕으로 테이블을 직접 생성한다.spring.jpa.hibernate.ddl-auto=none
JPA를 사용하려면 일단 Entity를 매핑해야 한다. jpa는 인터페이스고 구현체로 hibernate가 있다. 따라서 hibernate 라이브러리가 필요하다.
JPA는 ORM(Object Relational Mapping) 즉, 객체와 데이터베이스 테이블을 매핑해주는 것이다.
// 테이블을 매핑해주는 애너테이션 = JPA가 관리하는 Entity라는 표시 @EntitypublicclassMember {// PK 매핑 @Id// 아이디를 디비에서 자동으로 만들어주는 전략을 설정한다. @GeneratedValue(strategy =GenerationType.IDENTITY)privateLong id;privateString name;publicLonggetId() {return id; }publicvoidsetId(Long id) {this.id= id; }publicStringgetName() {return name; }publicvoidsetName(String name) {this.name= name; }}
publicclassJpaMemberRepositoryimplementsMemberRepository {// JPA는 Entity Manager로 동작한다. // jpa 라이브러리를 추가해놓기만 하면 스프링 부트가 자동으로 EntityManager를 생성해준다.// 우리는 만들어진 것을 주입만 받으면 된다.// 이전에 datasource로 했던 것(DB 연결 등)을 EntityManager가 다 관리한다.privatefinalEntityManager em;publicJpaMemberRepository(EntityManager em) {this.em= em; }publicMembersave(Member member) {em.persist(member);return member; }publicOptional<Member> findById(Long id) {Member member =em.find(Member.class, id);returnOptional.ofNullable(member); }publicOptional<Member> findByName(String name) {// name으로 찾아야 할 때는 JPQL이라는 객체지향 쿼리를 사용해야 한다.// Member라는 테이블이 아닌 Entity를 대상으로 쿼리를 날린다.// m은 객체 자체를 select 하는 것이다.List<Member> result = em.createQuery("select m from Member m where m.name = :name ",Member.class).setParameter("name", name).getResultList();returnresult.stream().findAny(); }publicList<Member> findAll() {returnem.createQuery("select m from Member m",Member.class).getResultList(); }}
// 데이터를 저장하고 변경할 때는 항상 서비스 쪽에 @Transactional 애너테이션이 있어야 한다.// 스프링은 해당 클래스의 메서드를 실행할 때 트랜잭션을 시작한다.// 메서드가 정상 종료되면 트랜잭션을 커밋하고 런타임 예외가 발생하면 롤백한다.// JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행해야 한다.@TransactionalpublicclassMemberService {}
@ConfigurationpublicclassSpringConfig {privatefinalEntityManager em;publicSpringConfig(EntityManager em) {this.em= em; } @BeanpublicMemberServicememberService() {returnnewMemberService(memberRepository()); } @BeanpublicMemberRepositorymemberRepository() {// return new MemoryMemberRepository();// return new JdbcMemberRepository(dataSource);// return new JdbcTemplateMemberRepository(dataSource);returnnewJpaMemberRepository(em); }}
스프링 데이터 JPA
repository에 구현 클래스 없이 인터페이스만으로 개발을 완료할 수 있다. 기본 CRUD 기능도 모두 제공한다. 스프링 부트와 JPA 위에 스프링 데이터 JPA라는 프레임워크를 더하면 핵심 비즈니스 개발에 집중할 수 있다.
스프링 데이터 JPA는 JPA를 편리하게 사용하도록 도와주는 기술이다. 따라서 JPA를 먼저 학습한 후에 스프링 데이터 JPA를 학습해야 한다.
// Entity와 Id 타입을 맞춰 JpaRepository를 상속한 인터페이스를 만들어야 한다. // 그럼 스프링 데이터 JPA가 SpringDataJpaMemberRepository 빈을 자동으로 만들어준다.// 우리가 만들었던 MemberRepository도 상속한다.publicinterfaceSpringDataJpaMemberRepositoryextendsJpaRepository<Member,Long>,MemberRepository {// 이렇게만 선언해주면 구현할 필요없이 쓸 수 있다.Optional<Member> findByName(String name);}
@ConfigurationpublicclassSpringConfig {// 스프링 데이터 JPA가 만들어 놓은 구현체를privatefinalMemberRepository memberRepository;// 주입받는다.publicSpringConfig(MemberRepository memberRepository) {this.memberRepository= memberRepository; }// MemberService에 의존 관계를 세팅해준다. @BeanpublicMemberServicememberService() {returnnewMemberService(memberRepository); }}
스프링 데이터 JPA 기본 기능
인터페이스를 통해 기본적인 CRUD와 페이징 기능을 쓸 수 있다. 하지만 name, email처럼 비즈니스 로직에 따라 다양해지는 내용은 인터페이스가 공통으로 제공할 수가 없다. 그래서 스프링 데이터 JPA는 findByName(), findByEmail()처럼 규칙에 따라 작성하면 JPQL로 select m from MEmber m where m.name = ?이라는 쿼리를 날려준다.
실무에서는 JPA와 스프링 데이터 JPA를 기본으로 사용하고, 복잡한 동적 쿼리는 QueryDsl이라는 라이브러리를 사용한다. QueryDsl을 사용하면 쿼리를 자바 코드로 안전하게 작성할 수 있다. 이 조합으로 해결하기 어려운 쿼리는 JPA가 제공하는 네이티브 쿼리를 사용하거나 JdbcTemplate을 사용하면 된다.