package com.spring.jpastudy.chap01.entity;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import javax.persistence.*;
import java.time.LocalDateTime;
@Getter @ToString
@EqualsAndHashCode(of = "id")
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name= "tbl_product") // 테이블 명 지정 가능
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // auto_increment
// Oracle = SEQUENCE. Mariadb, MySql = IDENTITY
@Column(name= "prod_id")
private Long id; // PK
@Column(name= "prod_nm", length = 30, nullable = false) // VARCHAR(30) NOT NULL
private String name; // 상품명
@Column(name= "price")
private int price; // 상품 가격
@CreationTimestamp // INSERT시에 자동으로 서버시간 저장
@Column(updatable = false) // 수정 불가
private LocalDateTime createdAt; // 상품 등록시간
@UpdateTimestamp // UPDATE문 실행시 자동으로 시간이 저장
private LocalDateTime updatedAt; // 상품 수정시간
// 데이터베이스에는 저장 안하고 클래스 내부에서만 사용할 필드
@Transient
private String nickName;
@Column(nullable = false)
@Enumerated(EnumType.STRING) // EnumType.ORDINAL로 하면 int 타입으로 됨
private Category category; // 상품 카테고리
public enum Category {
FOOD, FASHION, ELECTRONIC
}
// 컬럼 기본값 설정 (defualt value)
@PrePersist
public void prePersist() {
if (this.price == 0) {
this.price = 10000;
}
if (this.category == null) {
this.category = Category.FOOD;
}
}
}
save(S entity)
, findById(ID id)
, findAll()
,deleteById(ID id)
,delete(S entity)
, count()
package com.spring.jpastudy.chap01.repository;
import com.spring.jpastudy.chap01.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;
// JpaRepository를 상속한 후 첫 번째 제너릭엔 Entity 클래스 타입
// 두 번째 제너릭엔 PK 타입
// CRUD가 이미 구현 // Entity, Pk의 타입을 명시
public interface ProductRepository extends JpaRepository<Product, Long> {
}
-> 추가적인 쿼리는 직접 작성가능, 예를들어 이름을 통해 user를 조회하고싶다면
List<User> findByName(String name);
사용가능
package com.spring.jpastudy.chap01.repository;
import com.spring.jpastudy.chap01.entity.Product;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
import static com.spring.jpastudy.chap01.entity.Product.Category.*;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
@Rollback
class ProductRepositoryTest {
@Autowired
ProductRepository productRepository;
@BeforeEach
void insertBeforeTest() {
Product p1 = Product.builder()
.name("아이폰")
.category(ELECTRONIC)
.price(2000000)
.build();
Product p2 = Product.builder()
.name("탕수육")
.category(FOOD)
.price(20000)
.build();
Product p3 = Product.builder()
.name("구두")
.category(FASHION)
.price(300000)
.build();
Product p4 = Product.builder()
.name("주먹밥")
.category(FOOD)
.price(1500)
.build();
productRepository.save(p1);
productRepository.save(p2);
productRepository.save(p3);
productRepository.save(p4);
}
@Test
@DisplayName("상품을 데이터베이스에 저장한다")
void saveTest() {
//given
Product product = Product.builder()
.name("떡볶이")
// .price(90000)
// .category(Product.Category.FASHION)
.build();
//when
// insert후 저장된 데이터의 객체를 반환
Product saved = productRepository.save(product);
//then
assertNotNull(saved);
}
@Test
@DisplayName("1번 상품을 삭제한다")
void deleteTest() {
//given
Long id = 1L;
//when
productRepository.deleteById(id);
//then
Product foundProduct = productRepository.findById(id)
.orElse(null);
assertNull(foundProduct);
}
@Test
@DisplayName("3번 상품을 단일조회하면 그 상품명이 구두이다.")
void findOneTest() {
//given
Long id = 3L;
//when
Product foundProduct = productRepository.findById(id).orElse(null);
//then
assertEquals("구두", foundProduct.getName());
System.out.println("foundProduct = " + foundProduct);
}
@Test
@DisplayName("상품을 전체조회하면 상품의 총 개수가 4개이다.")
void findAllTest() {
//given
//when
List<Product> productList = productRepository.findAll();
//then
System.out.println("\n\n\n");
productList.forEach(System.out::println);
System.out.println("\n\n\n");
assertEquals(4, productList.size());
}
@Test
@DisplayName("2번 상품의 이름과 카테고리를 수정한다")
void modifyTest() {
//given
Long id = 2L;
String newName = "청소기";
Product.Category newCategory = ELECTRONIC;
//when
/*
jpa에서는 수정메서드를 따로 제공하지 않습니다.
단일 조회를 수행한 후 setter를 통해 값을 변경하고
다시 save를하면 INSERT대신에 UPDATE문이 나갑니다.
*/
Product product = productRepository.findById(id).orElse(null);
product.setName(newName);
product.setCategory(newCategory);
Product saved = productRepository.save(product);
//then
assertEquals(newName, saved.getName());
}
}
ex) 3b160fb6-e4d9-4987-b737-3f853aff7300
@GeneratedValue(generator = "uid")
package com.spring.jpastudy.chap02.entity;
import lombok.*;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
@Setter
@Getter
@ToString
@EqualsAndHashCode(of = "id")
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "tbl_student")
public class Student {
// 랜덤문자로 PK지정
@Id
@Column(name = "stu_id")
@GeneratedValue(generator = "uid")
@GenericGenerator(strategy = "uuid", name = "uid") // universal unique id
private String id;
@Column(name = "stu_name", nullable = false)
private String name;
private String city;
private String major;
https://docs.spring.io/spring-data/jpa/reference/repositories/query-methods-details.html4
package com.spring.jpastudy.chap02.repository;
public interface StudentRepository extends JpaRepository<Student, String> {
// 쿼리메서드: 메서드의 이름에 특별한 규칙을 적용하면
// SQL이 규칙에 맞게 생성됨.
// findBy + 필드명, findBy는 규칙(고정)
List<Student> findByName(String name);
// 규칙에 맞게 WHERE 조건 추가한 메서드
List<Student> findByCityAndMajor(String city, String Major);
// Containing은 '포함'의 의미
// WHERE major like '%major%'
List<Student> findByMajorContaining(String Major);
// WHERE major like 'major%'
List<Student> findByMajorStartingWith(String Major);
// WHERE major like '%major'
List<Student> findByMajorEndingWith(String Major);
// WHERE age <= ?
List<Student> findByAgeLessThanEqual(int age);
}
@Test
@DisplayName("이름이 춘식이인 학생의 모든 정보를 조회한다")
void findByNameTest() {
//given
String name = "춘식이";
//when
List<Student> students = studentRepository.findByName(name);
//then
assertEquals(1, students.size());
System.out.println("students = " + students);
}
@Test
@DisplayName("도시 이름과 전공으로 학생을 조회")
void findByCityAndMajorTest() {
//given
String city = "제주도";
String major = "화학공학";
//when
List<Student> foundStu = studentRepository.findByCityAndMajor(city, major);
//then
System.out.println("foundStu = " + foundStu);
}
@Test
@DisplayName("전공이 공학으로 끝나는 학생들 조회")
void findByMajorContainingTest() {
//given
String majorContaining = "공학";
//when
List<Student> students = studentRepository.findByMajorContaining(majorContaining);
//then
students.forEach(System.out::println);
}
// 순수한 SQL
// native sql 사용하기 (메서드 명은 막 지어도 됨. 규칙에 해당되지 않음)
// 파라미터 작명 내맘대로. @Param과 통일만 시켜준다 nativeQuery값은 필수
@Query(value = "SELECT * FROM tbl_student WHERE stu_name = :snm OR city = :city", nativeQuery = true)
List<Student> getStudentByNameOrCity(@Param("snm") String name, @Param("city") String city);
// 다른 방법. 파라미터가 ?1 부터 자동으로 바인딩 된다.
@Query(value = "SELECT * FROM tbl_student WHERE stu_name = ?1 OR city = ?2", nativeQuery = true)
List<Student> getStudentByNameOrCity2(String name, String city);
@Test
@DisplayName("도시 또는 이름으로 학생을 조회")
void nativeSQLTest() {
//given
String name = "춘식이";
String city = "제주도";
//when
List<Student> student = studentRepository.getStudentByNameOrCity(name, city);
//then
student.forEach(System.out::println);
}