Spring

JPQL연습_미니게시판

wintertreey 2024. 8. 1. 20:02

프로젝트 셋팅

 

 


엔티티 생성하기

package pack.model;

import java.sql.Timestamp;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Data;

@Data
@Entity
@Table(name = "springboard")
public class Board {
	@Id
	private int num;
	
	private String author, title, content;
	private Timestamp bwrite;
	private int readcnt;
}

 

JPA로 작업할 것이기에 JPArepository extends 를 실시한다.

package pack.model;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.web.bind.annotation.RequestParam;

public interface DataRepository extends JpaRepository<Board, Integer>{
	
	//JPQL
	//검색용
	@Query("select b from Board as b where b.author like %?1%") //포함된. 이름별검색
	List<Board> searchLike(String searchValue);
	
	@Query("select b from Board b where b.title like %:searchValue%") //제목별검색
	List<Board> searchLike2(@Param("searchValue") String searchValue);
		
	//추가할때 가장 큰 번호 얻기
	@Query("select max(b.num) from Board b")
	int maxNum();
	
	//상세보기 할때 조회수 증가
	@Modifying(clearAutomatically = true) //이게 refresh역할. 1차캐시를 비워주는 설정. 영속성 컨텍스트에 있는 쿼리를 초기화함.
	@Query(value = "update Board as bo set bo.readcnt=bo.readcnt+1 where bo.num=?1")
	void updateReadcnt(int num);
	
	
	
}

 

@RequestParam

HTTP 요청 파라미터를 컨트롤러 메소드의 파라미터로 매핑


@Param

Spring Data JPA에서 사용 - 메소드의 파라미터에 사용하여 동적 쿼리의 매개 변수로 사용

 

내부적으로 JPA는 벌크연산을 함(update,delete,insert).

영속성 컨텍스트에 있는 Board자료와 db에 있는 Board 값이 다를 수 있다. 
벌크 연산 수행 후 영속성 컨텍스트에 있는 쿼리를 refresh(clear)해야한다.

 

벌크연산이란?

 

해당 포스팅인 엔티티매니저로 처리하는 것을 보여줌. 실습에선 어노테이션을 이용할 예정.

 

벌크 연산을 사용할 경우 주의사항

※ 벌크 연산은 영속성 컨텍스트를 무시하고 DB에 직접 쿼리 한다는 점을 주의해야 한다.

즉, DB에 반영된 변경이 영속성 컨텍스트에는 반영되지 않는다는 말이다.

만약 salary 데이터를 조회 후 벌크 연산을 수행 한다면,
영속성 컨텍스트에 있는 salary와 DB에 있는 salary의 값이 다를 수 있다.
(영속성 컨텍스트와 DB 간에 데이터 차이 발생)

 

해결 방법

  1. em.refresh() 사용
    • 벌크 연산 수행 직후 정확한 salary 엔티티를 사용해야 한다면, em.refresh(salary)를 사용하여 DB에서 salary를 다시 조회한다.
  2. 벌크 연산 먼저 실행
    • 벌크 연산 먼저 실행하고 조회하면 된다. 가장 실용적인 해결책이며, JPA와 JDBC를 함께 사용할 때도 유용하다.
  3. 벌크 연산 수행 후 영속성 컨텍스트 초기화
    • 영속성 컨텍스트에 남아 있는 엔티티를 제거하는 방법이다.

 

 

 

쿼리문을 작성했다면, 

해당 쿼리문을 호출하는 메소드들을 만들자.

package pack.model;

import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import pack.controller.BoardBean;

@Repository
public class BoardDao {
	private Logger logger = LoggerFactory.getLogger(this.getClass());

	@Autowired
	private DataRepository dataRepository;

	public List<Board> list(){ //전체자료읽기
		List<Board> list = dataRepository.findAll();
		logger.info("list.size: "+list.size());
		
		return list;
	}
	public List<Board> search(BoardBean bean){ //검색자료읽기
		List<Board> slist = null;

		if(bean.getSearchName().equals("author")) {// 작성자 검색 
			slist= dataRepository.searchLike(bean.getSearchValue());
		}else { //아니면 작성제목검색
			slist= dataRepository.searchLike2(bean.getSearchValue());  
		}
		
		return slist;
	}
	
	@Transactional  
	public String insertData(BoardBean bean) {
		try {
			//새글 입력시 가장 큰번호 얻어 +1
			int max = dataRepository.maxNum();
			
			Board dto = new Board();
			dto.setNum(max+1);
			dto.setAuthor(bean.getAuthor());
			dto.setTitle(bean.getTitle());
			dto.setContent(bean.getContent());
			dto.setBwrite(Timestamp.valueOf(LocalDateTime.now()));
			dto.setReadcnt(0);
			
			dataRepository.save(dto);
			
			return "success";
		} catch (Exception e) {
			return "입력 오류: " + e.getMessage();
		}
	
	}
	@Transactional
	public Board detail(int num) {
		//조회수 증가
		dataRepository.updateReadcnt(num);
		
		// Optional<T> 클래스를 사용해 NPE를 방지할 수 있도록 도와준다.
		//Repository에서 findById()의 반환값은 Optional 타입
		Optional<Board> board = dataRepository.findById(num); //null일수도있을것. 
		logger.info("board :: {}", board.get());
		
		if(board.isPresent()) {
			return board.get();// board이면 안됨. optional해놧기 때문(?)에 board.get()으로 값을 뽑아야한다.
		} else {
			return new Board();
		}
		
	}
	
	@Transactional
	public String updateData(BoardBean bean) {
		try {
			//조회수조작을 원한다면..
			Optional<Board> board = dataRepository.findById(bean.getNum()); 
			Board imsi = board.get();
			/*
			Board dto = new Board();
			dto.setNum(bean.getNum()); //이미 등록된 num이므로 수정
			dto.setAuthor(bean.getAuthor());
			dto.setTitle(bean.getTitle());
			dto.setContent(bean.getContent());
			dto.setBwrite(Timestamp.valueOf(LocalDateTime.now()));
			dto.setReadcnt(imsi.getReadcnt());
			
			dataRepository.save(dto);
			*/
			//save 안 쓰는 방법.
			imsi.setAuthor(bean.getAuthor());
			imsi.setTitle(bean.getTitle());
			imsi.setContent(bean.getContent());
			
			// 
			//imsi.setBwrite(Timestamp.valueOf(LocalDateTime.now())); 이걸 쓰면 수정시간으로 리셋됨.
			
			return "success";
		} catch (Exception e) {
			return "수정 오류: " + e.getMessage();
		}
	}
	
	@Transactional
	public String deleteData(int num) {
		try {
			dataRepository.deleteById(num);
			
			return "success";
		} catch (Exception e) {
			return "삭제 오류: " + e.getMessage();
		}
	}
	
	
	
}

 

하나의 메소드내에서 update, delete 가 연속적 혹은 함께 이뤄질 경우 

@Transactional 어노테이션을 쓴다. 

 

 

 이 메서드가 트랜잭션 내에서 실행됨을 보장해준다.

트랜잭션이 시작되고 메서드 실행이 완료되면 트랜잭션이 커밋되거나 롤백됨.
동일한 트랜잭션 내에서 동일한 엔티티를 여러 번 조회해도 1차 캐시로 인해 같은 객체 인스턴스를 반환한다.

 

프록시 객체는 해당메소드가 처리될때 commit or rollback 수행
CheckdException 또는 예외가 없는 경우 commit 수행
UncheckedException가 발생하면 Rollback

 

 

 

java optional

 

Optional이란?

JAVA의 영원한 숙적인 NullPointerException을 방지해주는. 즉, null인 값을 참조해도 NullPointerException이 발생하지 않도록 값을 래퍼로 감싸주는 타입.

흔하게 볼 수 있는 곳은 Spring Data JPA를 사용할 때 Repository에서 findById()의 반환값은 Optional 타입

 

 

 

컨트롤러와 bean은 깃허브 sprweb18jpa_miniboard 를 참고하자.

https://github.com/yoonah0327/java_source.git

 

GitHub - yoonah0327/java_source

Contribute to yoonah0327/java_source development by creating an account on GitHub.

github.com

 

 

 

해당문제에 대한 소스코드는 sprweb18jpa_ex를 확인하자. 


https://cafe.daum.net/flowlife/HrhB/93

 

-parameters 컴파일 옵션은 리플렉션(reflection) API를 사용

tasks.withType(JavaCompile){ options.compileArgs '-parameters' }는 Gradle 빌드 스크립트에서 사용되는 코드로, 자바 컴파일 작업에 대해 특정 컴파일 옵션을 설정하는 데 사용된다. 이 코드의 목적과 동작 방식

cafe.daum.net

https://dev-coco.tistory.com/169

 

[JPA] 벌크 연산(Bulk Operation)이란?

시작하기 앞서, DB에 Member라는 테이블이 있고, salary라는 컬럼이 존재하며 이는 연봉을 나타낸다고 가정해보겠다. 연봉 3000만원 미만의 Member의 salary를 10% 만큼 인상한다면 다음과 같은 SQL문을 짤

dev-coco.tistory.com

https://cafe.daum.net/flowlife/HrhB/91

 

1차 캐시로 인한 동일 객체 인스턴스 반환 확인

* 동일한 트랜잭션 내에서 같은 엔티티를 조회할 때 JPA가 제공하는 1차 캐시(엔티티 매니저 컨텍스트)로 인해 동일한 객체 인스턴스를 반환하는지 확인하는 방법 Member m1 = repo.fi

cafe.daum.net

https://mangkyu.tistory.com/70

 

[Java] Optional이란? Optional 개념 및 사용법 - (1/2)

이번에는 Java8부터 지원하는 Optional 클래스에 대해 알아보도록 하겠습니다. 1. Optional이란? Optional 개념 및 사용법 [ NPE(NullPointerException) 이란? ] 개발을 할 때 가장 많이 발생하는 예외 중 하나가 바

mangkyu.tistory.com

 

https://velog.io/@kjgi73k/JAVA-Optional%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90

 

[JAVA Optional] Optional에 대해 알아보자

JAVA8에서 추가된 Optional

velog.io