JPA란? (SpringBoot + JPA 사용법)
JPA란?
Java Persistance API. 자바의 ORM(Object-Relational Mapping) 표준 기술이다. 즉 자바의 객체와 관계형 DB를 매핑하는 기술이다.
Hibernate
Hibernate란? ORM Framework 중 하나이다. JDBC를 이용하다가 MyBatis를 이용하면 훨씬 편하고 코드가 간결하며 유지보수가 편하다. 마찬가지로 Hibernate도 MyBatis에 비해 코드가 훨씬 더 간결하며 객체지향적이다.
구글 지역별 비교 분석에서 볼 수 있듯이 한국에서는 MyBatis를 많이 사용하지만 전세계 개발자들은 Hibernate를 많이 사용한다.
장점
- 생산성
- 유지보수
단점
- 어렵다
- 성능상 문제가 있을 수 있다.
사용법
MyBatis나 기존에 자바에서 RDBMS 내의 테이블 데이터를 출력하기 위해서는 SELECT * FROM USER; 라는 query를 실행해야 했다. 하지만 JPA를 이용할 경우 user.findAll() 이라는 간단한 자바 명령어로 역할을 대신할 수 있다.
DTO 에서 객체와 DB 테이블을 매핑해주고, Repository를 만들고 나면 Controller에서 JPA에서 제공하는 몇 가지 기본적인 메소드들을 사용할 수 있다. 기본적으로는 아래 세가지를 포함한 몇가지 함수들을 제공한다.
- save()
- findAll()
- deleteAll()
만일 커스텀 기능을 사용하고 싶을 경우 Repository에 직접 선언 후 사용할 수 있다. 예를 들어 FAQRepository에 faq 번호(no)로 검색하는 쿼리문을 동작시키고 싶을 경우 아래와 같이 함수 이름을 findByNo(int no)로 선언해주기만 하면 아래 쿼리문과 똑같이 동작하는 기능을 사용할 수 있다.
select * from faq where no=#{no}
FAQController에서는 아래와 같이 @Autowired를 통해 FAQRepository를 연결시켜주고 repo.findByNo(no)를 통해 함수를 수행할 수 동작시킬 수 있다.
폴더 구조
- 크게 DTO, Controller, Repository, Service로 구성된다.
- Repository는 인터페이스로
application.properties
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://70.12.247.81:3306/ssafysnsdb?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=utf-8
spring.datasource.username=ssafy
spring.datasource.password=ssafy
spring.datasource.type=org.apache.commons.dbcp.BasicDataSource
# JPA
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.database=mysql
# 아래와 같이 mysql InnoDB 설정을 해줘야 foreign key가 작동한다. (default는 MyISAM)
spring.jpa.database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
DTO Annotation 사용법
-
DTO 위
생성자 접근 권한은 PUBLIC, PROTECTED 외 다른 것들을 지정할 수 있다. 단, PROTECTED를 설정할 경우 외부에서 해당 생성자에게 접근하는 것을 막을 수 있다.
@AllArgsConstructor //1. 모든 인자가 있는 생성자 @NoArgsConstructor(access=AccessLevel.PROTECTED) //2. 인자 없는 생성자 @ToString //3. toString() 메소드 생성 @Getter //4. @Getter, @Setter 대신 @Data로 사용할 수도 있다. @Setter @Entity //5. DB테이블과 일대일로 매칭되는 객체 단위 @Table(name="notice") //6. Notice 객체와 매핑될 테이블 이름 지정 public class Notice { //5,6을 합쳐서 @Entity(name="notice")로 사용 가능. //따로 지정해주지 않을 경우 class 명과 동일하게 지어진다. }
-
DTO 내부 - 속성 선언
@Id //테이블 primary key 지정 @GeneratedValue(strategy=GenerationType.IDENTITY) //auto_increment @Column(length=30, nullable=false, unique=true) //타입 크기, not null, unique private int no; @Column(length=50, nullable=false) private String title; @Column(length=1000, nullable=false) private String content; @ManyToOne //Foreign Key 지정 - User(id)를 참조할 경우 @JoinColumn(name="id", foreignKey=@ForeignKey(name="fk_notice_user_id")) private User user; //User타입을 가져오는 것에 주의!! //Date 타입 format 지정 @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss", timezone="Asia/Seoul") @Column(nullable=false) private Date datetime; @Column(nullable=false) private boolean deleted;
-
그 외 Annotation
3-1. 객체 위에 사용하는 Annotation
//모든 파라미터가 있는 생성자 @AllArgsConstructor //not null인 애들 빼고 모두 파라미터로 가지는 생성자 @RequiredArgsConstructor
3-2. 속성 위에 사용하는 Annotation
//exclude - 특정 컬럼을 뺄 수 있음. @ToString(exclude="컬럼명") //Not null 지정. 이걸 이용할 경우 자바상에서도 에러를 잡아준다. @NotNull //DB타입에 맞춰서 매핑해줌. @Temporal(TemporalType.DATE) private Date birth; //Boolean Type을 사용하고 싶을 경우 @Column(columnDefinition = "TINYINT", length=1) private int deleted;
전체 DTO 예제
package com.ssafysns.model.dto; import java.util.Date; //java.util.Date를 import 해야 한다. import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.ForeignKey; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.AccessLevel; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; //생성자 접근 권한 - PUBLIC, PACKAGE @NoArgsConstructor(access=AccessLevel.PROTECTED) @ToString @Data @Entity @Table(name="notice") public class Notice { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(length=30, nullable=false, unique=true) private int no; @Column(length=50, nullable=false) private String title; @Column(length=1000, nullable=false) private String content; @ManyToOne @JoinColumn(name="id", foreignKey=@ForeignKey(name="fk_notice_user_id")) private User user; @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss", timezone="Asia/Seoul") @Column(nullable=false) private Date datetime; @Column(nullable=false) private boolean deleted; }