JPA(Java Persistence API) - 자바 진영의 ORM 기술 표준
JPA의 특징 | JPA가 개발자를 대신하여 적합한 SQL을 생성하여 DB에 전달하고, 객체를 자동으로 Mapping 해주기에 SQL을 직접 작성하지 않음 |
ex. Hibernate(JPA를 구현한 대표적인 오픈소스) | |
JPA의 장점 | DBMS에 대한 종속성이 줄어들어 생산성이 뛰어나고 유지보수가 용이하다 |
JPA의 단점 | JPA의 장점을 살려 잘 사용하기 위해서는 학습 비용이 높고, 복잡한 쿼리를 사용할 때 불리하다. 잘못 사용할 경우, SQL을 직접 사용하는 것보다 성능저하 이슈가 발생할 수 있다. |
JPA를 실제로 사용하는 방법
1. build.gradle 파일의 dependencies에 JPA 관련 항목을 추가한다. |
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'com.mysql:mysql-connector-j' // Database가 MySQL임을 알려줌 |
2. JPA를 구현하기 위한 기초적인 application.properties 정보를 설정한다. |
spring.datasource.driver-class-name=cohttp://m.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/milkway?useUnicode=yes&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Seoul spring.datasource.username=root spring.datasource.password=1234 spring.database=milkway spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl |
3. 데이터베이스 설정에 맞는 Entity 클래스를 실제로 작성한다. |
@Entity
@Table(name = "Inqurie")
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Builder
public class InquireEntity
{
@Id
@Column(name = "inquireId")
private String inquireId;
@Column(name = "address", nullable = false)
private String address;
@Column(name = "phoneNumber", nullable = false)
private String phoneNumber;
@Column(name = "inquire", nullable = false)
private String inquire;
}
데이터베이스 테이블과 매핑되는 JAVA 클래스로 데이터베이스에 쓰일 여러 Entity 간의 관계를 설정하는 것 |
@Entity를 이용해 해당 클래스가 Entity 임을 알려주고, JPA에 정의된 필드를 바탕으로 데이터베이스에 테이블을 제작할 수 있다. |
다른 말로는 도메인(domain) 모델이라고도 함. |
Enttiy 관련 Annotation |
@Entity : 해당 클래스를 엔터티로 인식할 수 있도록 하며, 데이터베이스 테이블과 매핑됨 |
@Table : 엔터티와 매핑할 데이터베이스 테이블 지정 |
@Id : primary key 지정 |
@GeneratedValue : primary key의 생성 전략 |
GenerationType.IDENTITY: 고유한 번호를 자동으로 생성함 |
GenerationType.AUTO : 데이터베이스 설정에 따라 적절한 전락을 자동으로 선택함 |
@Colum : 엔터티 필드와 매핑할 테이블의 칼럼 지정 |
@PrePersist : 엔터티가 데이터베이스에 저장되기 전에 필요한 초기화 작업을 수행함 |
4. 데이터베이스 설정에 맞는 DTO 객체를 작성한다. |
@Getter
@Builder
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class InquireDTO
{
private String inquireId;
private String address;
private String phoneNumber;
private String inquire;
}
@Builder를 사용할 때에는 생성자 관련 어노테이션이 반드시 필요하다. |
5. 설정한 Entity 정보를 이용해 테이블 데이터에 접근(CRUD)할 수 있도록 하는 Repository를 설계한다. |
@Repository
public interface InqurieRepository extends JpaRepository<InquireEntity, String>
//<>는 Entity와 PK의 타입이 들어감, PK의 타입의 경우 Wapper 클래스로 기입할 것
{
InquireEntity findByInquireId(String InquireId);
boolean existsByInquireId(String InquireId);
void deleteByInquireId(String InqurieId);
}
JPA Repository 인터페이스의 함수명의 경우, JPA 홈페이지에 존재하는 설정 메뉴얼에 따라서 지어야 한다. |
대소문자 차이나 오탈자 등을 허용하지 않으니, 설정 후 다시 한번 꼼꼼하게 확인할 것. |
save() : 데이터 저장, findAll() : 전체 데이터 조회, findById() : ID 조회는 인터페이스에 자동적으로 포함되는 함수임 |
6. 설정한 Entity, Repository를 이용해서 관련 설정을 실제 수행하는 Service 클래스를 작성한다. |
@Service
public class InquireService
{
@Autowired
InqurieRepository inqurieRepository;
public InquireEntity Insert(InquireEntity inquireEntity)
{
boolean bool = inqurieRepository.existsByInquireId(inquireEntity.getInquireId());
if(bool)
{
throw new FindFailedException("해당 inquireId를 가진 정보는 이미 존재해요.");
}
else
{
InquireEntity inquireEntity1 = inqurieRepository.save(inquireEntity);
if (inquireEntity1 != null) {
return inquireEntity1;
} else {
throw new InsertFailedException("날짜 가능 문의 등록이 실패하였습니다.");
}
}
}
}
본 코드는 객체 주입을 통해 Repository interface를 실행하는 함수를 주입 받아, 사용하는 Service 클래스의 예제이다. |
7. 마지막으로 URL에 따라 정해진 명령을 수행하도록 Service 객체에 따른 URL을 Controller 클래스에 매핑한다. |
@RestController
@RequestMapping("/inqurie")
public class InqurieController
{
@Autowired
InquireService inquireService;
ResponseDTO<InquireDTO> responseDTO = new ResponseDTO<>();
LoginSuccess loginSuccess = new LoginSuccess();
@PostMapping("/Insert")
public ResponseEntity<?> Insert(@RequestBody InquireDTO inquireDTO)
{
try
{
InquireEntity inquireEntity1 = ConvertToEntity(inquireDTO);
InquireEntity inquireEntity2 = inquireService.Insert(inquireEntity1);
if (inquireEntity2 != null)
{
InquireDTO inquireDTO1 = ConvertToDTO(inquireEntity2);
return ResponseEntity.ok().body(responseDTO.Response("success",
"데이터 추가에 성공했습니다.", Collections.singletonList(inquireDTO1)));
}
else
{
throw new InsertFailedException("inquire 저장 실패, 정보를 가져올 수 없어요");
}
}
catch (Exception e)
{
return ResponseEntity.badRequest()
.body(responseDTO.Response("error",e.getMessage()));
}
}
8. 이후 설계한 기능이 정상적으로 동작하는지 PostMan이나 Swagger를 이용해서 Test를 수행한다. |