JPA는 Java Persistent API의 약자이고 자바 진영에서 ORM(object-relational mapping) 기술 표준으로 사용되는 인터페이스이다. JPA를 구현하는 기술로 대표적인 오픈소스는 Hibernate가 있다. 레거시 프로젝트에서는 많이 사용하지 않으나 해외에서나 아니면 서비스업 회사나 스타트업 회사에서는 많이 사용한다고 한다. JPA, Mybatis의 통계적인 사용 비율이 3:7이라고는 말이 전해져 내려오나 실제로 그런지는 잘 모르겠다.
- ORM(Object Relational Mapping)
- Entity 객체외 Database 테이블을 서로 매핑해서, SQL 쿼리가 아닌 Java 메소드를 사용해서 데이터를 조작한다.
- DB의 데이터 구조를 자바의 객체 구조로 취급한다.
프로젝트 셋팅
STS4)
New > New Spring Starter Project
- Name : boot-jpa
- Type : Maven
- Java Version : 11
- Packaging : Jar
- Group : com.test
- Aritifact : boot-jpa
- Package : com.test.jpa
Dependency 추가
- Spring Web
- Lombok
- Mabatis Framework
- Oracle Driver
- Spring boot Devtools
- Thymeleaf
- Spring JPA
DB 셋팅(Oracle 11g)
create table Item (
name varchar2(30) primary key,
price number not null,
color varchar2(30) not null,
owner varchar2(30) null,
orderdate date default sysdate not null
);
insert into Item (name, price, color, owner, orderdate) values ('키보드', 100000, 'black', '홍길동', sysdate - 1);
insert into Item (name, price, color, owner, orderdate) values ('무접점 고급 키보드', 120000, 'white', null, sysdate - 1.5);
insert into Item (name, price, color, owner, orderdate) values ('기계식 키보드', 150000, 'black', '홍길동', sysdate - 2);
insert into Item (name, price, color, owner, orderdate) values ('멤브레인 키보드', 80000, 'white', '홍길동', sysdate - 2.5);
insert into Item (name, price, color, owner, orderdate) values ('마우스', 50000, 'silver', '홍길동', sysdate - 3);
insert into Item (name, price, color, owner, orderdate) values ('버티컬 마우스', 90000, 'silver', '아무개', sysdate - 3.5);
insert into Item (name, price, color, owner, orderdate) values ('게이밍 마우스', 120000, 'black', '아무개', sysdate - 4);
insert into Item (name, price, color, owner, orderdate) values ('고급 볼 마우스', 95000, 'yellow', '아무개', sysdate - 3);
insert into Item (name, price, color, owner, orderdate) values ('노트북', 1100000, 'white', '아무개', sysdate - 4.5);
insert into Item (name, price, color, owner, orderdate) values ('노트북 가방', 120000, 'blue', null, sysdate - 5);
insert into Item (name, price, color, owner, orderdate) values ('노트북 받침대', 95000, 'yellow', '하하하', sysdate - 5.5);
insert into Item (name, price, color, owner, orderdate) values ('노트북 파우치', 95000, 'yellow', '하하하', sysdate - 5);
select * from Item;
commit;
application.properties
# Server port
server.port=8092
# Oracle settings
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@localhost:1521/xe
spring.datasource.username=hr
spring.datasource.password=java1234
# 히카리cp가 있어서 이건 필요가 없다. >>> 이게 commons dbcp이다.
# 아래것이 히카리CP이다.
# Hikari CP settings
spring.datasource.hikari.connection-timeout=60000
spring.datasource.hikari.maximum-pool-size=5
spring.datasource.hikari.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.hikari.jdbc-url=jdbc:oracle:thin:@localhost:1521:xe
spring.datasource.hikari.username=hr
spring.datasource.hikari.password=java1234
# logging > 이게 log4j관련이다.
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n
logging.level.org.hibernate.SQL=debug
logging.level.org.hibernate.type.descriptor.sql=trace
logging.level.=error
#Thymeleaf
spring.thymeleaf.cache=false
# JPA
spring.jpa.database=oracle
spring.jpa.generate-ddl=false
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=true
# jpa가 자동으로 만들어주는 쿼리를 보여주라 ~ !
# DB마다 자기들만의 구문이 있다. 이걸 넣어줘야 한다.
spring.jpa.database-platform=org.hibernate.dialect.Oracle10gDialect
프로젝트 파일 구조
src/main/java
com.test.controller
> ItemController
com.test.domain
> Item.java
com.test.jpa
> BootJpaApplication.java
com.test.repository
> ItemRepository.java
src/main/resources
templates
> item
> result.html
- BootJpaApplication.java
- controller 부분은 ComponentScan
- Entity는 EntityScan
- EnableJpaRepositories에 Repository 정의
@SpringBootApplication
@ComponentScan(basePackages = "com.test.controller")
@EntityScan(basePackages ="com.test.domain")
@EnableJpaRepositories(basePackages="com.test.repository")
public class BootJpaApplication {
public static void main(String[] args) {
SpringApplication.run(BootJpaApplication.class, args);
}
}
- com.test.repository > ItemRepository.java
- JpaRepository<Item, String>
- 첫번째는 제네릭에 사용하려는 객체, 두번째는 PK 타입의 자료형을 쓴다.
- @Repository라는 어노테이션이 없어도 IoC된다. 이유는 JpaRepository를 상속했기 때문에 가능하다.
public interface ItemRepository extends JpaRepository<Item, String>{
}
- domain 패키지에는 엔터티를 정의한다.
- com.test.domain > Item.java
- domain에 정의된 엔터티를 가져다가 사용하는 DAO역할을 하는 것이 필요하다. 이것을 repository 패키지에서 클래스를 정의한다.
- com.test.repository > ItemRepository.java(I)
- ItemRepository는 JpaRepository<Item, String>를 상속받아서 정의 한다.
- JpaRepository의 내부에 정의된 메서드들은 findAll, findAllById, saveAndFlush, deleteAllInBatch 등등 이상한 것들이 있다. findAll만 주로 많이 쓰는 듯하다.
- ItemRepository에 뭔가의 약속된 형식의 메서드명을 지어서 사용한다. Overriding은 아니지만 JPA에서 이 뭔가의 규칙에 맞게 처리를 해준다.
- findBy○○○
- findBy○○○Null
- findAllByOrderBy○○○
- findAllByOrderBy○○○Desc
- findBy○○○In
- 아래 예제 진행하다가 JpaRepository 자체가 상속 및 인터페이스 구조라는 것을 확인하고 검색한 이미지
- CrudRepository
- existsById, findById, save, saveAll, findAll, count, deleteById, delete, deleteAll 등의 메서드를 가지고 있다.
- 이것을 상속받아서 내가 구현하는 Repository에서 별다른 메서드 정의없이 사용할 수 있다.
- 단, 이런데에 해당하지 않는 Column에 따른 재정의가 필요한 메서드 정의시에는 정의를 해야 하는 것이다.
- item/result.html
<!DOCTYPE html>
<html xmlns:th="https://thymeleaf.org">
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Spring Boot<small>JPA</small></h1>
<div th:text="${bool}"></div>
<div th:text="${count}"></div>
<div class="message" title="결과">
<!-- 다른애 도움없이 얘 혼자 쓸 수 있다. -->
<th:block th:if="${result != null}">
<div th:text="${result != null}"></div>
<div th:text="${result.name}"></div>
<div th:text="${#numbers.formatInteger(result.price, 0, 'COMMA')}"></div>
<div th:text="${result.color}"></div>
<div th:text="${result.owner}"></div>
<div th:text="${result.orderdate}"></div>
</th:block>
</div>
<hr>
<table class="vertical" th:if="${list != null}">
<tr>
<th>이름</th>
<th>가격</th>
<th>색상</th>
<th>소유자</th>
<th>주문날짜</th>
</tr>
<tr th:each="item : ${list}">
<td th:text="${item.name}"></td>
<td th:text="${#numbers.formatInteger(item.price, 0, 'COMMA')}"></td>
<td th:text="${item.color}"></td>
<td th:text="${item.owner}"></td>
<td th:text="${item.orderdate}"></td>
</tr>
</table>
</body>
</html>
- com.test.domain > Item.java
- lombok Builder 패턴 사용
- @Entity > 테이블 정의 >>> 객체화
- @NoArgsConstructor, @AllArgsConstructor 생성자 정의
- @Id
- Primary Key 역할을 하는 칼럼에 붙인다.
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Item {
// 엔티티가 참조하는 테이블의 컬럼 > 멤버 정의
// PK는 꼭 붙여야 한다.
@Id
private String name;
private int price;
private String color;
private String owner;
@Column(insertable=false, updatable=false) // 얘를 업데이트하는 대상에서 뺴버린다.
private String orderdate; // 이 컬럼은 insert를 안해도 된다. default가 있다.
}
- ItemController.java 클래스 내부
- @EnableJpaRepositories를 BootJpaApplication에 정의됨에 따라 스캔이 가능해짐
- 이걸 사용하는 쪽(Controller나 Service로직)에서 @Autowired 하여 쓴다.
- JPA는 쿼리들을 메서드화 시켰다.
- insert(DB) > (JPA) > save(Method)
- <S extends T> S save(S entity)
- m1 > 객체 setter로 정의 > 레코드 Insert : [C]RUD
@Autowired
private ItemRepository itemRepo;
@GetMapping("/item/m1")
public String m1(Model model) {
System.out.println("m1");
//[C]RUD
// - 레코드 추가하기
Item item = new Item();
item.setName("잉크젯 프린터");
item.setPrice(250000);
item.setColor("yellow");
item.setOwner("홍길동");
// save > (JPA) > insert
Item result = itemRepo.save(item);
// Hibernate: insert into item (color, orderdate, owner, price, name) values (?, ?, ?, ?, ?)
// orderdate date default sysdate not null > db엔 이렇게 되어 있다.
model.addAttribute("result", result);
return "item/result";
}
- m2 > 객체 Builder 패턴으로 정의 > 레코드 Insert : [C]RUD
- Builder 패턴 사용으로 인한 객체 값 셋팅은 기존 생성자 방식보다 가독성이 좋다.
@GetMapping("/item/m2")
public String m2(Model model) {
System.out.println("m2");
//[C]RUD
// - 레코드 추가하기
//빌더 패턴(Builder Pattern)
// 생성자 패턴, 빌더 패턴
// - OOP에서 겍체를 생성하는 패턴 중 하나
Item item2 = new Item(null, 0, null, null, null);
// 얘는 멤버를 자유롷게 넣을 수 있어서 필요없으면 뺴고 안넣어도 된다. 가독성있다.
Item item = Item.builder()
.name("레이저 프린터")
.price(300000)
.color("black")
.owner("아무개")
.build();
// save > (JPA) > insert
Item result = itemRepo.save(item);
// Hibernate: insert into item (color, orderdate, owner, price, name) values (?, ?, ?, ?, ?)
// orderdate date default sysdate not null > db엔 이렇게 되어 있다.
model.addAttribute("result", result);
return "item/result";
}
- m3 > 단일 레코드 읽기 > findById("뭘로?") : C[R]UD
- Optional<T> findById(ID id); 리턴형이 Optional이라는 것 주의!
- .get()으로 값을 꺼낸다는 것
- itemRepo.getById("마우스") : 이것도 되지만 Deprecated 된 메서드이다. > 비권장
- 이건 T getById(ID id);
- Optional<T> findById(ID id); 리턴형이 Optional이라는 것 주의!
@GetMapping("/item/m3")
public String m3(Model model) {
//C[R]UD
// - 단일 레코드 읽기
// select item0_.name as name1_0_0_, item0_.color as color2_0_0_, item0_.orderdate as orderdate3_0_0_, item0_.owner as owner4_0_0_, item0_.price as price5_0_0_ from item item0_ where item0_.name=?
// Hibernate: select item0_.name as name1_0_0_, item0_.color as color2_0_0_, item0_.orderdate as orderdate3_0
// 내부적으로 쿼리로 동작한다. 메소드가 더이상 안쓰인다.
//Item item = itemRepo.getById("마우스"); // 얘는 더이상 안쓰임
//model.addAttribute("result", item);
Optional<Item> item = itemRepo.findById("프린터"); // ★ 얘는 가장 많이 쓰인다. 레코드 찾을 때
System.out.println(item.isPresent());
model.addAttribute("result", item.get());
return "item/result";
}
- m4 > isPresent() : C[R]UD 검색한 결과가 존재하는지 확인하는데 사용할 수 있다.
- Optional의 isPresent()
@GetMapping("/item/m4")
public String m4(Model model) {
Optional<Item> item = itemRepo.findById("프린터");
if(item.isPresent()) {
item.get().setPrice(240000);
Item result = itemRepo.save(item.get());
model.addAttribute("result", result);
}
return "item/result";
}
- m5 > delete(엔터티) > : CRU[D] 해당 레코드를 찾고 그것을 인자로 넣어서 삭제를 한다.
- Optional로 찾으니까 get()으로 꺼내서 사용
@GetMapping("/item/m5")
public String m5(Model model) {
//CRU[D]
Optional<Item> item = itemRepo.findById("노트북");
itemRepo.delete(item.get());
return "item/result";
}
- m6 > 다중 레코드 조회 > findAll() > C[R]UD
- findAll()은 JpaRepository에서 정의된 메서드로 이건 ItemRepository에 따로 정의할 필요가 없다.
@GetMapping("/item/m6")
public String m6(Model model) {
//Query Methods
//Repository에 구현된 메서드가 쿼리 메서드다.
// 다중 레코드 조회
List<Item> list = itemRepo.findAll();
model.addAttribute("list", list);
return "item/result";
}
- m7 > 존재유무 확인 > existById(뭐로?) : C[R]UD
@GetMapping("/item/m7")
public String m7(Model model) {
// 존재 유무 확인
boolean bool = itemRepo.existsById("마우스");
model.addAttribute("bool", bool);
return "item/result";
}
- m8 > count() > 이거나 existById() 이런것들은 CrudRepository 에 정의된 것을 사용한 것이다.
@GetMapping("/item/m8")
public String m8(Model model) {
// 카운트
long count = itemRepo.count();
model.addAttribute("count", count);
return "item/result";
}
이제부터는 사용자 정의 findBy○○○ 메서드
여기서부터는 다음 글에 이어서 진행
소스코드는 https://github.com/ngotic/SpringBootBasic/tree/main/boot-jpa
'JPA' 카테고리의 다른 글
스프링 부트 + JPA(2) (0) | 2023.07.19 |
---|