처음 프로젝트 실행 시 데이터베이스와 관련된 설정이 없기때문에 오류 발생
사용하고자하는 데이터베이스 시스템(mariaDB사용)을 연동시켜주기 위해 application properties에
spring.datasource.driver-class name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://localhost:3307/webdbboot
spring.datasource.username=webuser
spring.datasource.password=webuser
으로 설정하고나면 다시 jpa open-in-view에서 오류났다고 함
spring.jpa.open-in-view=false 으로 설정을 추가해 줌
기본적으로 Spring Boot JPA에서는 "Open EntityManager in View" 패턴이 활성화되어 있습니다. 이 패턴은 웹 요청이 끝날 때까지
EntityManager를 유지하여 뷰 렌더링 단계에서도 지연로딩된 엔티티에 접근할 수 있게 해줍니다. 그러나 이 패턴은 장기 실행 트랜잭션과 지연로딩으로 인한 성능 문제를 일으킬 수 있음
또한 스프링 부트에서는 자동으로 의존성을 주입해주지만 Lombok의 경우 build.gradle에 Test 라이브러리를 넣어줘야한다.
Board 클래스의 설정(엔티티 객체를 생성하기 위한 엔티티 클래스 정의)
엔티티 클래스는 반드시 @Entity가 존재하고 해당 엔티티 객체의 구분을 위한 @Id가 필요함
@Entity
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long bno;
private String title;
private String content;
private String writer;
}
spring.jpa.hibernate.ddl-auto=create 를 했을 경우 실행하면 아래와 같이 테이블이 생성된 것에 대해 콘솔에 출력됨(create는 기존에 테이블이 있는 경우에도 삭제하고 재생성하므로 주의할 것)

서버를 구동해서 ORM으로 매핑된 SQL 실행 → 테이블 생성
bno bigint not null auto_increment →
Board 클래스의 키생성전략(key generate strategy)
@GeneratedValue(strategy = GenerationType.*IDENTITY*)로 데이터베이스에서 알아서 결정하는 방식을 이용
키 생성 전략의 종류
@MappedSuperClass를 이용한 공통 속성 처리
Board라는 Entity 클래스를 지정한 후 부모가 될 BaseEntity 클래스를 생성(persistance의 성격을 가짐) 부모가 자식을 감시한다라고 생각
@MappedSuperclass : 데이터베이스의 거의 모든 테이블에는 데이터가 추가된 시간이나 수정된 시간등이 칼럼으로 작성, 이를 쉽게 처리하고자 공통으로 사용되는 칼럼들을 지정하고 해당 클래스를 상속 Auditing(감사) 기능을 구현할 때 사용되며 해당 필드에 엔티티가 생성된 일시가 자동으로 저장
@EntityListeners``(value = { AuditingEntityListener.class}): 콜백 리스너 클래스를 지정, JPA 엔티티 리스너는 엔티티 유지 및 업데이트에 대한 감사 정보를 캡쳐, AuditingEntityListener를 적용하면 엔티티가 데이터베이스에 추가되거나 변경될 때 자동으로 시간 값을 지정
AuditingEntityListener를 활성화 시키기 위해서는 @SpringBootApplication 다음에 @EnableJpaAuditing을 추가해 주어야 함.

MemberDAO에서 객체를 저장하고 싶을 때 개발자는 JPA에 Member 객체를 넘긴다.
1.Member 엔티티를 분석한다.
2. insert sql을 생성한다.
3. JDBC API를 사용하여 SQL을 DB에 날린다.
JpaRepository의 save()를 통해 이루어짐JpaRepository는 Spring Data JPA에서 제공하는 기능 중 하나로서 JPQL을 활용하여 데이터베이스와 상호 작용합니다. 개발자는 간편한 CRUD 작업을 위해 JpaRepository를 사용하면서, 필요에 따라 JPQL을 직접 작성하여 더 복잡한 쿼리를 수행할 수 있습니다.@SpringBootTest
@Log4j2
public class BoardRepositoryTests {
@Autowired
private BoardRepository boardRepository;
@Test
public void testInsert() {
IntStream.rangeClosed(1,100).forEach(i -> {
Board board = Board.builder()
.title("title..." +i)
.content("content..." + i)
.writer("user"+ (i % 10))
.build();
Board result = boardRepository.save(board);
log.info("BNO: " + result.getBno());
});
}


개발자는 member의 pk값을 JPA에 넘긴다.
findById()의 리턴 타입은 Optional<T>이다.@Test
public void testSelect() {
Long bno = 100L;
//findById의 테스트에러가 뜰 경우 엔티티 클래스의 기본 생성자를 추가했는지 확인할 것
//(@AllArgsConstructor
//@NoArgsConstructor
//@ToString)
Optional<Board> result = boardRepository.findById(bno); //findById의 리턴타입이 Optional
Board board = result.orElseThrow();
log.info(board);
}


Optional이 비어있을 때 예외를 발생시킴)Optional은 값이 존재하는지 여부를 확인하고, 값이 존재할 경우 해당 값을 반환하고, 값이 비어있을 경우에는 대체값을 제공하거나 예외를 발생시키는 등의 처리를 할 때 사용findById() 실행

save() 실행

update()실행

moddate가 수정된 것을 확인

@Test
public void testDelete() {
Long bno = 101L;
boardRepository.deleteById(bno);
}


JPA를 이용하는 것은 엄밀하게 얘기하면 영속 컨텍스트와 데이터베이스를 동기화해서 관리한다는 의미

따라서 영속 컨텍스트에 대해 해당 엔티티 객체가 존재해야만 하므로 select로 엔티티 객체를 영속 컨텍스트에 저장해서 이를 수정/삭제한 후에 이후 메서드가 실행되는 것

JPA는 ORM을 위한 자바 EE 표준이며 Spring-Data-JPA는 JPA를 쉽게 사용하기 위해 스프링에서 제공하고 있는 프레임워크이다.
추상화 정도는 Spring-Data-JPA -> Hibernate -> JPA이다.
Hibernate를 쓰는 것과 Spring Data JPA를 쓰는 것 사이에는 큰 차이가 없지만
PageRequest.of()라는 기능을 이용PageRequest.of(페이지번호,사이즈) : 페이지번호는 0부터
PageRequest.of(페이지번호,사이즈,sort) : 정렬조건추가
PageRequest.of(페이지번호,사이즈,sort,Direction,속성…) : 정렬 방향과 여러 속성 지정
@Test
public void testSearch1() {
//2 page order by bno desc
Pageable pageable = PageRequest.of(1,10, Sort.by("bno").descending());
boardRepository.search1(pageable);
}
Pageable을 사용하는 경우 메소드의 리턴 타입으로 Page<T>를 선택하는 것이 관례@Test
public void testSearch1() {
//2 page order by bno desc
Pageable pageable = PageRequest.of(1,10, Sort.by("bno").descending());
boardRepository.search1(pageable);
}
@Test
public void testPagingEX(){
Pageable pageable = PageRequest.of(0,10); //페이지번호는 0부터 10까지
//return Type pageagble
log.info("페이지관련 객체 " + pageable);
log.info("첫번째 페이지 " + pageable.first() );
log.info("다음 페이지 " + pageable.next() );
log.info("페이지 번호 가져오기 " + pageable.getPageNumber() );
log.info("10페이지 가져오기 " + pageable.withPage(10) );
}
다양한 조건의 쿼리와 목록 기능 구현시 JPQL 이용
@Query 어노테이션의 value로 작성하는 문자열을 JPQL이라고 함
SQL과 유사하게 JPA에서 사용하는 쿼리 언어(query language)
JPA는 데이터베이스에 독립적으로 개발이 가능하므로 특정한 데이터베이스에서만 동작하는 SQL 대신 JPA에 맞게 사용하는 JPQL을 이용하는 것
스프링 JPQ에서는 복잡한 JPQL을 메소드로 대신 처리 -> 쿼리 메소드(메소드의 이름으로 필요한 쿼리를 만들어주는 기능)
find + 엔티티 이름(생략가능) + By + 변수명
Querydsl(domain specific language)
특정 도메인(산업,분야)에 맞춘 쿼리
데이터베이스를 이용할 때 JPA나 JPQL을 이용하는 경우
JPQL (Java Persistence Query Language):
EntityManager를 통해 사용되며, JPA에서 제공하는 createQuery 메서드를 사용하여 작성된 JPQL 쿼리를 실행근본 원인은 JPQL이 정적으로 고정되기 때문
이를 해결해주는 방식으로 Querydsl을 사용
JPQL은 Java Persistence Query Language의 약자로, Java에서 JPA(Java Persistence API)를 사용하여 데이터베이스와 상호 작용하기 위해 사용되는 쿼리 언어입니다.
JPQL은 엔터티 객체를 대상으로 쿼리를 작성하며, SQL과는 다르게 데이터베이스의 특정 구현에 의존하지 않습니다. 대신, 엔터티 객체를 기반으로 쿼리를 작성하고 실행할 수 있어 객체 지향적인 접근이 가능합니다.
반면에 JPQLQuery는 Querydsl에서 제공하는 인터페이스로, Querydsl을 사용하여 동적이고 타입 안전한 쿼리를 작성하기 위한 도구입니다.
Querydsl은 JPQL의 확장된 형태로 생각할 수 있습니다. Querydsl을 사용하면 자바 코드로 쿼리를 작성할 수 있으며, 타입 안전성을 제공하면서도 동적인 쿼리 작성이 가능합니다.
간단히 말해, JPQL은 JPA에서 사용하는 쿼리 언어이고, JPQLQuery는 Querydsl에서 제공하는 도구로서 타입 안전하고 동적인 쿼리 작성을 위한 인터페이스입니다.
Querydsl은 JPQL을 기반으로 하되, 좀 더 강력하고 유연한 쿼리 작성을 지원합니다.
Querydsl은 JPA의 구현체인 Hibernate 프레임워크가 사용하는 HQL(Hibernate Query Language)을 동적으로 생성할 수 있는 프레임워크지만 JPA를 지원
자바코드를 이용하기 때문에 타입의 안정성을 유지한 상태에서 원하는 쿼리 작성 가능
Querydsl을 이용하기 위해서는 Q도메인이라는 존재가 필요함
Q도메인은 Querydsl의 설정을 통해서 기존의 엔티티 클래스를 Querydsl에서 사용하기 위해 별도의 코드로 생성하는 클래스
build.gradle의 설정 변경
buildscript {
ext {
queryDslVersion = "5.0.0"
}
}
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
annotationProcessor(
"javax.persistence:javax.persistence-api",
"javax.annotation:javax.annotation-api",
"com.querydsl:querydsl-apt:${queryDslVersion}:jpa")
}
sourceSets {
main {
java {
srcDirs = ["$projectDir/src/main/java", "$projectDir/build/generated"]
}
}
Querydsl을 기존 코드에 연동하기 위해서는 다음과 같은 과정을 따른다
Querydsl의 목적은 타입 기반으로 코드를 이용해서 JPQL 쿼리를 생성하고 실행하는 것 이때 코드를 만드는 대신 클래스가 Q도메인 클래스이다
public class BoardSearchImpl extends QuerydslRepositorySupport implements BoardSearch {
//어떤 타입이든 올 수 있다
public BoardSearchImpl(Class<?> domainClass) {
super(domainClass);
}
@Override
public Page<Board> search1(Pageable pageable) {
//QBoard객체를 생성해서 Querydsl 작성
QBoard board = QBoard.board;
//쿼리를 생성하는 부분
JPQLQuery<Board> query = from(board);
//쿼리 검색 조건
query.where(board.title.contains("1"));
//JPQLQuery 실행
List<Board> list = query.fetch();
//JPQLQuery count쿼리 실행
long count = query.fetchCount();
return null;
//테스트용이기때문에 리턴값은 null
}
}
테스트코드
void testSearch1() {
Pageable pageable = PageRequest.of(1,10,Sort.by("bno").descending());
boardRepository.search1(pageable);
}
}
//에러나는 경우 QBoard나 QBaseEntity쪽에서 객체가 불러와지지 않았을 수 있으므로
//새로고침이나 build clean 적용해보기
다양한 검색 조건을 Querydsl을 이용해서 원하는 JPQL을 생성하고 실행
검색의 경우 ‘제목(t),내용(c),작성자(w)’의 조합을 통해 이루어진다고 가정
SQL문으로 작성할 경우(제목이나 내용에 키워드가 존재하며 bno가 0보다 큰 데이터→검색조건)
select * from board where
(title like concat('%','1','%') or
content like concat('%','1','%'))
order by bno desc;
//title 또는 content에 1을 포함한 문자열이 있는지 찾는 쿼리문
//concat-> 문자열을 결합하는 명령어
//'%1%' -> 와일드카드를 사용해서 1이라는 숫자가 어느 곳에 위치하던 1이 포함된 문자열을 찾는다
BooleanBuilder
where 조건에 and와 or의 우선 순위는 기본적으로 and > or라서 or 조건은 ‘()’로 묶어준다
Querydsl을 이용할때 ‘()’가 필요한 상황에서 BooleanBuilder를 이용해서 작성
QBoard board = QBoard.board;
JPQLQuery<Board> query = from(board);
BooleanBuilder booleanBuilder = new BooleanBuilder(); //
booleanBuilder.or(board.title.contains("11")); // title like ...
booleanBuilder.or(board.content.contains("11")); // content like ....
query.where(booleanBuilder);
query.where(board.bno.gt(0L)); //gt-> bno 필드의 값이 0보다 큰지를 확인
types → 여러 조건의 조합이 가능하도록 처리하는 메소드를 BoardSearch에 정의
Page<Board> searchAll(String[] types, String keyword, Pageable pageable);
BoardSearchImpl 에서 반복문과 제어문을 이용한 처리
@Override
public Page<Board> searchAll(String[] types, String keyword, Pageable pageable) {
QBoard board = QBoard.board;
JPQLQuery<Board> query = from(board); //Querydsl을 사용하기 위해 Qboard라는 동적 쿼리용 엔티티 생성
if( (types != null && types.length > 0) && keyword != null ){ //검색 조건과 키워드가 있다면
BooleanBuilder booleanBuilder = new BooleanBuilder(); // (
for(String type: types){
switch (type){ //어느 필드에서 검색할지 결정
case "t":
booleanBuilder.or(board.title.contains(keyword));
break;
case "c":
booleanBuilder.or(board.content.contains(keyword));
break;
case "w":
booleanBuilder.or(board.writer.contains(keyword));
break;
}
}//end for
query.where(booleanBuilder);
}//end if
//bno > 0
query.where(board.bno.gt(0L));
//paging 현재 Querydsl을 가져와 페이징 처리
this.getQuerydsl().applyPagination(pageable, query);
List<Board> list = query.fetch();
long count = query.fetchCount();
return null;
}
페이징 처리의 최종 결과는 Page<T> 타입을 반환하는 것이므로 Querydsl에서는 이를 직접 처리해야하는 불편함이 있다. 따라서 Spring Data JPA에서는 이를 처리하기위해 PageImpl이라는 클래스를 제공해서 3개의 파라미터로 Page<T>를 생성
return new PageImpl<>(list, pageable, count);