일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 자바편
- 티스토리 쿠키 삭제
- network configuration
- iterator
- ㅒ
- 자료구조와 함께 배우는 알고리즘 입문
- 서버설정
- GIT
- baeldung
- 코드로배우는스프링웹프로젝트
- 데비안
- 코드로배우는스프링부트웹프로젝트
- resttemplate
- 리눅스
- 처음 만나는 AI 수학 with Python
- 처음 만나는 AI수학 with Python
- 스프링 시큐리티
- 페이징
- Kernighan의 C언어 프로그래밍
- /etc/network/interfaces
- 스프링부트핵심가이드
- 네트워크 설정
- d
- 자료구조와함께배우는알고리즘입문
- 이터레이터
- 구멍가게코딩단
- 친절한SQL튜닝
- 목록처리
- 알파회계
- 선형대수
- Today
- Total
bright jazz music
guestbook : 03. 자동 일자/시간 처리(등록/수정 등) +Querydsl (1) 본문
guestbook : 03. 자동 일자/시간 처리(등록/수정 등) +Querydsl (1)
bright jazz music 2022. 6. 29. 21:192022.06.26 - [JAVA/Spring] - guestbook: 01. 프로젝트 생성, gradle설정, DB연결, 구동 확인
2022.06.28 - [JAVA/Spring] - guestbook : 02. 기본 화면 레이아웃 구성
이번 포스팅 요약 1. 기본구조 생성 2. 자동 일자/시간 처리를 적용할 엔티티 생성(여기서는 BaseEntity.java) 3. Application클래스에서 AuditingListener 활성화 (GuestbookApplication.java에 @EnableJpaAuditing 어노테이션 추가) 4. AuditingListener를 적용한 엔티티 클래스(BaseEntity.java)를 상속할 엔티티 클래스(Guestbook.java) 생성 5. JPA를 이용할 Repository 생성 6. build.gradle 설정 추가 7. build.gradle Task 실행(compileQuerydsl): Q도메인 생성작업 8. Qdomain 생성 확인 (Q도메인은 build 패키지에 자동으로 생성된다.) 9. Repository에서 QueryPredicateExcutor 인터페이스 상속 10. 테스트 |
엔티티 관련 작업 중 데이터의 등록 시간과 수정 시간처럼 자동으로 추가되고 변경되어야 하는 칼럼이 존재한다.
이를 매번 프로그램 내에서 처리하지 않고 어노테이션을 이용해 설정할 수 있다.
1. 기본구조
2. 자동 일자/시간 처리를 적용할 엔티티 생성(여기서는 BaseEntity.java)
//BaseEntity.java
package com.example.guestbook.entity;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
@MappedSuperclass
@EntityListeners(value = {AuditingEntityListener.class})
@Getter
public class BaseEntity {
@CreatedDate
@Column(name = "regDate", updatable = false)
private LocalDateTime regDate;
@LastModifiedDate
@Column(name="moddate")
private LocalDateTime modDate;
}
/*
* @MappedSuperclass:
* 이 어노테이션이 적용되면 적용되면 테이블로 생성되지 않는다.
* 실제 테이블은 BaseEntity클래스를 상속한 엔티티의 클래스로 DB 테이블이 생성된다.
*
* @CreatedDate:
* JPA에서 엔티티의 생성 시간을 처리한다.
*
* @LastModifiedDate:
* 최종시간을 자동으로 처리하는 용도로 사용된다.
* 칼럼의 속성으로는 insertable, updatable이 있다. 여기서는 updatable=false로 지정했기 때문에
* 해당 엔티티 객체를 DB에 반영할 때 regDate 칼럼은 변경되지 않는다. 생성될 때만 기록되야 하니 당연하다.
*
*
*
*/
- @MappedSuperclass
- @EntityListener(value = {AuditingEntityListener.class})
- @CreatedDate
- @LastModifiedDate
* JPA와 MyBatis의 데이터 접근 방식 차이 그리고 AuditingListener의 역할JPA는 JPA만의 고유한 메모리 공간인 Persistence Context를 이요해서 엔티티 객체들을 관리한다. 기존의 MyBatis보다 한 단계가 더 있는 방식이다. 마이바티스를 이용하는 경우 SQL을 위해 전달되는 객체는 SQL처리가 끝난 뒤의 처리가 상관 없는 객체들이다. 그러나 JPA에서 사용하는 엔티티 객체들은 영속 컨텍스트(persistence context)에서 관리된다. 이 객체들이 변경되면 결과적으로 DB에 이를 반영하는 방식이다. 마이바티스에서는 마이바티스로 보내지는 객체, 마이바티스에서 처리되는 객체, DB에서 가져오는 객체가 전부 달라도 무관하다. 그러나 JPA에서는 해당 엔티티 객체가 영속 컨텍스트에 유지된다. 그리고 필요할 때 재사용한다. 따라서 이러한 엔티티 객체에는 변화가 발생하는 것을 감지하는 Listener가 있어야 한다. AuditingListener가 바로 JPA 내에서 엔티티 객체가 생성/변경되는 것을 감지하는 역할을 한다. 바로 이 AuditingLiistener를 통해 regDate, modDate에 적합한 값이 지정된다. AuditingEntityListener를 활성화시키기 위해서는 프로젝트에 @EnableJpaAuditing 설정을 추가해야 한다. 프로젝트 생성시 생성되는 Application클래스(여기서는 GuestbookApplication.java)에 설정을 추가한다. |
3. Application클래스에서 AuditingListener 활성화 (GuestbookApplication.java에 @EnableJpaAuditing 어노테이션 추가)
package com.example.guestbook;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@SpringBootApplication
@EnableJpaAuditing // <== AuditingEntityListener 활성화를 위해 추가
public class GuestbookApplication {
public static void main(String[] args) {
SpringApplication.run(GuestbookApplication.class, args);
}
}
4. AuditingListener를 적용한 엔티티 클래스(BaseEntity.java)를 상속할 엔티티 클래스(Guestbook.java) 생성
//guestbook.java
package com.example.guestbook.entity;
import lombok.*;
import javax.persistence.*;
@Entity // * 엔티티로 선언하지 않으면 Qdomain이 생성되지 않는다.
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Guestbook extends BaseEntity{ // <== BaseEntity 클래스 상속
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long gno;
@Column(length = 100, nullable = false)
private String title;
@Column(length = 1500, nullable = false)
private String content;
@Column(length = 50, nullable = false)
private String writer;
}
프로젝트 시작하면 테이블이 하이버네이트를 통해 SQL로 만들어짐.
5. JPA를 이용할 Repository 생성
Repository 패키지 안에 아래의 GuestbookRepository 인터페이스 생성. JpaRepository 상속
//GuestbookRepository.java (인터페이스 생성)
package com.example.guestbook.repository;
import com.example.guestbook.entity.Guestbook;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
public interface GuestbookRepository extends JpaRepository<Guestbook, Long>{ //JpaRepository상속
//추후 Qdomain이 생성되고 나면 QuerydslPredicateExcutor<Guestbook>을 상속할 것임.
}
여기까진 JPA리포지토리 기본. 동적쿼리 처리를 위해서는 추후 여기에 Querydsl을 추가해 줘야 함.
Querydsl (http://querydsl.com/): JPA에서 동적으로 쿼리를 생성해서 처리할 수 있는 기술.JPA는 QueryMethod와 @Query만 이용해서는 고정된 형태의 검색조건만 생성할 수 있다. 복잡한 조건에서는 사용이 힘들다. 이럴 때는 동적으로 쿼리를 생성하는 것이 편리한데, 이 때 Querydsl을 이용한다. Querydsl을 이용해서 복잡한 검색조건 뿐만 아니라 조인, 서브쿼리 등의 기능도 구현 가능하다. Qdomain: Querydsl을 이용하면 코드 내부에서 적합한 쿼리를 생성할 수 있다. 그러나 이 때 작성된 엔티티를 그대로 이용하지는 않는다. Qdomain을 사용해야만 한다.Qdomain을 사용하기 위해서는 Querydsl 라이브러리를 이용해서 엔티티 클래스를 Qdomain으로 변환해야 한다. 이를 위해 build.gradle에 추가적인 설정이 필요하다.
|
6. build.gradle 설정 추가
//build.gradle
// 1. querydsl 버전정보 추가####
buildscript {
ext {
queryDslVersion = "5.0.0"
}
}
plugins {
id 'org.springframework.boot' version '2.7.1'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
id 'war'
id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10' // <== 2. querydsl 플러그인 추가####
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation('org.springframework.boot:spring-boot-starter-test') {//괄호 넣어줌
//아래에 mariadb관련 jdbc 드라이버 추가. jpa 관련 설정 추가, thymeleaf에서 쓸 java8 날짜 라이브러리 추가
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
// compile, runtime, testCompile, testRuntime 은 Gradle 4.10 (2018.8.27) 이래로
// deprecate 되었다. 그리고 Gradle 7.0 (2021.4.9) 부터 삭제되었다.
// 삭제된 네 명령은 각각 implementation, runtimeOnly, testImplementation, testRuntimeOnly 으로 대체되었다.
// compile group: 'org.mariadb.jdbc', name: 'mariadb-java-client'
// compile group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-java8time'
// 아래와 같이 써준다.
implementation('org.thymeleaf.extras:thymeleaf-extras-java8time')
implementation('org.mariadb.jdbc:mariadb-java-client')
implementation('org.thymeleaf.extras:thymeleaf-extras-java8time')
// ' : ' 콜론을 띄어 쓰며 오류남.
// implementation 'com.querydsl:querydsl-jpa'
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}" // <== 3. querydsl 의존성 추가####
implementation "com.querydsl:querydsl-apt:${queryDslVersion}"
}
tasks.named('test') {
useJUnitPlatform()
}
// 4. querydsl 추가 시작####
// querydsl에서 사용할 경로 설정
def querydslDir = "$buildDir/generated/querydsl"
// JPA 사용 여부와 사용할 경로를 설정
querydsl {
jpa = true
querydslSourcesDir = querydslDir
}
// build 시 사용할 sourceSet 추가
sourceSets{
main.java.srcDir querydslDir
}
// querydsl 컴파일시 사용할 옵션 설정
compileQuerydsl {
options.annotationProcessorPath = configurations.querydsl
//build.gradle을 갱신하면 여기가 실행가능한 task가 됨.
}
// querydsl 이 compileClassPath 를 상속하도록 설정
configurations {
compileOnly {
extendsFrom annotationProcessor
}
querydsl.extendsFrom compileClasspath
}
//querydsl 추가 끝####
7. build.gradle Task 실행(compileQuerydsl): Q도메인 생성작업
//build.gradle의 아래와 같은 코드 부분
// querydsl 컴파일시 사용할 옵션 설정
compileQuerydsl {
options.annotationProcessorPath = configurations.querydsl
//build.gradle을 갱신하면 여기가 실행가능한 task가 됨.
}
화살표 시작 버튼 누르기
아래와 같은 오류발생 시:
Unable to load class 'com.mysema.codegen.model.Type'.
This is an unexpected error. Please file a bug containing the idea.log file.
그래들 버전과 관련 있을 수 있음. 위 build.gradle에서 추가한 1, 2, 3, 4에 적합한 코드가 들어갔는지 확인할 것.
8. Qdomain 생성 확인 (Q도메인은 build 패키지에 자동으로 생성된다.)
//QBaseEntity.java
package com.example.guestbook.entity;
import static com.querydsl.core.types.PathMetadataFactory.*;
import com.querydsl.core.types.dsl.*;
import com.querydsl.core.types.PathMetadata;
import javax.annotation.processing.Generated;
import com.querydsl.core.types.Path;
/**
* QBaseEntity is a Querydsl query type for BaseEntity
*/
@Generated("com.querydsl.codegen.DefaultSupertypeSerializer")
public class QBaseEntity extends EntityPathBase<BaseEntity> {
private static final long serialVersionUID = 1713494209L;
public static final QBaseEntity baseEntity = new QBaseEntity("baseEntity");
public final DateTimePath<java.time.LocalDateTime> modDate = createDateTime("modDate", java.time.LocalDateTime.class);
public final DateTimePath<java.time.LocalDateTime> regDate = createDateTime("regDate", java.time.LocalDateTime.class);
public QBaseEntity(String variable) {
super(BaseEntity.class, forVariable(variable));
}
public QBaseEntity(Path<? extends BaseEntity> path) {
super(path.getType(), path.getMetadata());
}
public QBaseEntity(PathMetadata metadata) {
super(BaseEntity.class, metadata);
}
}
//QGuestbook.java
package com.example.guestbook.entity;
import static com.querydsl.core.types.PathMetadataFactory.*;
import com.querydsl.core.types.dsl.*;
import com.querydsl.core.types.PathMetadata;
import javax.annotation.processing.Generated;
import com.querydsl.core.types.Path;
/**
* QGuestbook is a Querydsl query type for Guestbook
*/
@Generated("com.querydsl.codegen.DefaultEntitySerializer")
public class QGuestbook extends EntityPathBase<Guestbook> {
private static final long serialVersionUID = -2039934860L;
public static final QGuestbook guestbook = new QGuestbook("guestbook");
public final QBaseEntity _super = new QBaseEntity(this);
public final StringPath content = createString("content");
public final NumberPath<Long> gno = createNumber("gno", Long.class);
//inherited
public final DateTimePath<java.time.LocalDateTime> modDate = _super.modDate;
//inherited
public final DateTimePath<java.time.LocalDateTime> regDate = _super.regDate;
public final StringPath title = createString("title");
public final StringPath writer = createString("writer");
public QGuestbook(String variable) {
super(Guestbook.class, forVariable(variable));
}
public QGuestbook(Path<? extends Guestbook> path) {
super(path.getType(), path.getMetadata());
}
public QGuestbook(PathMetadata metadata) {
super(Guestbook.class, metadata);
}
}
자동으로 Qdomain이 생성된다는 사실은 Q로 시작되는 클래스들을 개발자가 직접 건드리지 않는다는 뜻이다. 개발자는 gradle의 compileQuerydsl과 같은 task를 통해서 자동으로 Qdomain이 생성되는 방법만을 사용한다.
9. Repository에서 QueryPredicateExcutor 인터페이스 상속
Querydsl을 이용하게 되면 Repository(여기서는 GuestbookRepository) 인터페이스 역시 QuerydslPredicateExcutor라는 인터페이스를 추가로 상속해야 한다.
//GuestbookRepository.java
package com.example.guestbook.repository;
import com.example.guestbook.entity.Guestbook;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
public interface GuestbookRepository extends JpaRepository<Guestbook, Long> // <== JpaRepository
, QuerydslPredicateExecutor<Guestbook>{ // <== 추가 상속
}
테스트 준비가 완료됨
10. 테스트
테스트 시작에 앞서 test폴더 내에repository 패키지를 생성하고 GuestbookRepositoryTest클래스 추가
//GuestbookRepositoryTests.java
package com.example.guestbook.repository;
import com.example.guestbook.entity.Guestbook;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.stream.IntStream;
@SpringBootTest
public class GuestbookRepositoryTests {
@Autowired
private GuestbookRepository guestbookRepository; //의존성 주입
@Test
public void insertDummies(){
//테스트 데이터 300개 입력
IntStream.rangeClosed(1, 300).forEach(i -> {
Guestbook guestbook = Guestbook.builder()
.title("Title...." + i)
.content("Content..." + i)
.writer("user" + (i %10))
.build();
System.out.println(guestbookRepository.save(guestbook));
});
}
}
위의 테스트 코드 실행
콘솔창 >
DB > guestbook 테이블이 생성되었고, 거기에 300개의 데이터가 입력되었다.
요약
1. 기본구조 생성
2. 자동 일자/시간 처리를 적용할 엔티티 생성(여기서는 BaseEntity.java)
3. Application클래스에서 AuditingListener 활성화 (GuestbookApplication.java에 @EnableJpaAuditing 어노테이션 추가)
4. AuditingListener를 적용한 엔티티 클래스(BaseEntity.java)를 상속할 엔티티 클래스(Guestbook.java) 생성
5. JPA를 이용할 Repository 생성
6. build.gradle 설정 추가
7. build.gradle Task 실행(compileQuerydsl): Q도메인 생성작업
8. Qdomain 생성 확인 (Q도메인은 build 패키지에 자동으로 생성된다.)
9. Repository에서 QueryPredicateExcutor 인터페이스 상속
10. 테스트
'Framework > Spring' 카테고리의 다른 글
guestbook : 04. querydsl 테스트 (0) | 2022.07.01 |
---|---|
guestbook : 03. 자동 일자/시간 처리(등록/수정 등) +Querydsl (2) (0) | 2022.06.30 |
guestbook : 02. 기본 화면 레이아웃 구성 (0) | 2022.06.28 |
Thymeleaf 타임리프 기본 + Simple Sidebar (0) | 2022.06.28 |
JpaRepository + @Query, nativeQuery = true (0) | 2022.06.23 |