스프링/6. 스프링 DB-2

26. JPA 적용

sdafdq 2023. 10. 11. 07:43

아무래도 가장 중요한 부분은 객체와 테이블을 매핑하는 거다.

 

JPA가 제공하는 다양한 애노테이션을 사용하여 매핑하면 된다.

 

@Data
@Entity
//@Table(name="item")
public class Item {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name="item_name", length = 10)
    private String itemName;
    private Integer price;
    private Integer quantity;

    public Item() {
    }

    public Item(String itemName, Integer price, Integer quantity) {
        this.itemName = itemName;
        this.price = price;
        this.quantity = quantity;
    }
}

매핑 끝.

@Entity 라고 해야 JPA가 아, 이거 DB와 연동되는 객체구나 인식함. JPA가 사용하는 객체라는 뜻

@Id를 해야 이게 pk라고 인지함.

@GeneratedValue(strategy=GenerationType.IDENTITY) 해야 

이게 DB에서 직접 값을 넣어주는  IDENTITY 전략인구나 앎. (IDENTITY = MySQL에서는 auto increment )

 

@Column(name="item_name")

DB의 컬럼명. 그리고 저렇게 길이도 넣을 수 있음.

컬럼명과 필드명이 같으면 생략 가능.

item_name도 사실 스네이크 <-> 카멜 자동변환 해 줘서 안써도 되긴 함

@Table도 DB의 어떤 테이블인지 넣을 수 있는데, 마찬가지로 테이블명과 클래스 명이 같으면 생략 가능. 보통 클래스명과 DB의 테이블명을 같게 할듯.

 

또한, 이렇게 해 놓으면 이걸 기반으로 테이블도 만들 수 있다.

length=10은 그 때 컬럼의 길이값으로 사용

 

참고로 public 또는 protected 기본 생성자를 꼭 넣어야 된다.

 

 

그리고, JPA의 모든 데이터 변경은 트랜잭션 안에서 이루어 진다.

 

그래서 꼭 트랜잭셔널을 붙여줘야 한다.

 

 

이제 본격적으로 리포지토리 작성.

@Slf4j
@Repository
@Transactional
public class JpaItemRepository implements ItemRepository {
    private final EntityManager em;
    public JpaItemRepository(EntityManager em) {
        this.em = em;
    }

    @Override
    public Item save(Item item) {
        em.persist(item);
        return item;
    }

    @Override
    public void update(Long itemId, ItemUpdateDto updateParam) {
        Item findedItem = em.find(Item.class, itemId);
        findedItem.setItemName(updateParam.getItemName());
        findedItem.setPrice(updateParam.getPrice());
        findedItem.setQuantity(updateParam.getQuantity());
    }

    @Override
    public Optional<Item> findById(Long id) {
        Item item = em.find(Item.class, id);
        return Optional.ofNullable(item);
    }

    @Override
    public List<Item> findAll(ItemSearchCond cond) {
        String jpql = "select i from Item i";
        List<Item> result = em.createQuery(jpql, Item.class)
                .getResultList();
        return result;
    }
}

먼저 트랜잭셔널 붙여준다. JPA 자체가 트랜잭셔널과 함께 사용되기 때문에 거의 붙여준다고 보면 된다. 조회는 모르겠는데, 데이터 변경은 꼭 트랜잭션 붙여줘야 한다. 근데 사실, 여기서 트랜잭션 붙여주기 보다는, 비즈니스 로직이 있는 서비스 계층에서 트랜잭션을 걸어주는 게 맞다. (무조건 이라는 것은 없긴 함. 서비스 없으면 리포지토리에 걸기도 함.)

EntityManager는 스프링이 자동으로 생성해서 빈에 등록해주는거라 주입받을 수 있다. 쟤도 DataSource를 주입받아서 생성되는건데, DataSource또한 스프링이 자동으로 빈에 등록해 주는 것 이므로 알아서 주입받고 빈으로 등록된다.

 

원래 JPA 설정하려면 EntityManagerFactory, JpaTransactionManager, DataSource 등 다양한 걸 설정해 줘야 함. 하지만 스프링부트가 다 알아서 해 줌. 스프링 부트의 JpaBaseConfiguration에서 자동설정 해 줌. 원래는 다른 거 였다가 JPA 라이브러리 들어오면 저걸로 설정함.

 

JPA의 모든 동작은 저 EntityManager를 통해 이루어 진다.

 

save. 쉽다. EM.persist(객체) 하면 등록된다.  persist는 영구히 등록한다. 뭐 그런 뜻이다.

 

update.

먼저 em으로 부터 찾은 다음에, 정말 값을 set 하듯 넣어주면 된다.

그럼 그 후 우리가 뭐 안해줘도 알아서 쿼리가 날라간다.

이게 비밀이 뭐냐면,

저 findedItem의 초기값을 가지고 있다가,(캡쳐해뒀다고 많이 표현한다.)

commit되기 직전에(트랜잭셔널에 의해)

바뀐 값을 보고 쿼리를 만들어서 날린다.

그 다음 커밋한다.

 

그 다음 find

그냥 EM.find(반환될객체타입, pk)

pk기준으로 조회할 때는 find를 쓴다.

 

Optional은 DB관련 보다는 Null일수 있는 객체를 좀 안전하게? 다루도록.

 

findAll

이 부분이 좀 까다로울 수 있다.

먼저 조건없이 그냥 가져오는 것.

이거는 JPQL이라고 SQL이랑 비슷한 건데, 뭔가 객체 관련으로 추상화 한 것이다. 결국 SQL로 바뀌긴 한다.

select i from Item i

조회한다. i를.

근데 이 i가 뭐냐,

뒤에 from Item i 해서 Item에다가 별칭 붙인거다. 뭐 from Item it 이렇게 하면 select it 이렇게 해야 한다.

참고로 Item 이거 대문자로 해야 한다. 왜냐하면 이거는 DB의 테이블을 가리키는 것이 아니라 객체를 가리키는 거라고 생각해야 한다. 우리가 Item 클래스로 만들었으니까.

 

jpql 문법은 객체를 대상으로 한다고 생각해야 한다.

 

 

얘도 약간 단점이 동적쿼리에 약하다.

 

이제 제대로 조건을 넣어서 가져오는 것..

@Override
public List<Item> findAll(ItemSearchCond cond) {
    String jpql = "select i from Item i";

    String itemName = cond.getItemName();
    Integer maxPrice = cond.getMaxPrice();
    List<Object> param = new ArrayList<>();
    boolean andFlag = false;

    if(StringUtils.hasText(itemName)){
        jpql += "i.itemName like concat('%', :itemName, '%')";
        param.add(itemName);
        andFlag = true;
    }

    if(maxPrice != null){
        if(andFlag){
            jpql += " and";
        }
        jpql += " i.price <= :maxPrice";
        param.add(maxPrice);
    }
    log.info("jpql={}",jpql);

    TypedQuery<Item> query = em.createQuery(jpql, Item.class);
    if(StringUtils.hasText(itemName)){
        query.setParameter("itemName", itemName);
    }
    if(maxPrice != null){
        query.setParameter("maxPrice",maxPrice);
    }
    return query.getResultList();
}

어.. 일단 코드 하는대로 짜기는 했는데 왜 param을 사용했는지 모르겠다.

솔직히 한번은 제대로 짜보고 싶은데, 넘어가신다..

 

이거는 Querydsl 이라는 기술을 통해 해결한다고 한다.

그래서 보통 JPA + Querydsl + JdbcTemplate 일 듯?

쿼리를 직접 날려야 하는 경우도 있어서.

 

 

'스프링 > 6. 스프링 DB-2' 카테고리의 다른 글

28. 스프링 데이터 JPA  (0) 2023.10.11
27. JPA 예외  (0) 2023.10.11
25. ORM -JPA  (0) 2023.10.10
24. ORM  (0) 2023.10.10
23. JPA  (0) 2023.10.10