JPAとQueryDSL
緒論
前回は
JPA
の基本と内部構造を理解し,QueryDSL
について一時的に言及した.QueryDSL
は、複雑なクエリーを簡単に変更し、Java
とオブジェクトを使用してクエリー文を記述させることができるため、非常に有用である.QueryDSL
の設定と必要性を見て、簡単に使用しましょう.QueryDSLの設定
まず、国内の
Java
教育ブログを参考にした有名なjojoduの文章を設置する.最初はspringイニシャルRiserページからMySQL
、JPA
"の数字のコピーのみをインポートし、その後は上記の文章に従うように設定します.もともとはMaven
が管理していたので、上記はGradle
なので、Gradle
でシャベルを作りました.最終的なグレープロファイルを以下に示します.
// 스프링 부트 프로젝트 기본 설정 플러그인
plugins {
id 'org.springframework.boot' version '2.4.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
// 상위 버전의 그레이들은 어노테이션 프로세서를 사용한다고 한다.
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
// 어느 패키지 관리 사이트에 올라가 있는 패키지를 사용할 것인지 선택한다. 이외에도 Jcenter라는 곳을 사용할
// 수도 있다.
repositories {
mavenCentral()
}
apply plugin: "io.spring.dependency-management"
dependencies {
// querydsl 패키지
compile("com.querydsl:querydsl-core")
compile("com.querydsl:querydsl-jpa")
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
// querydsl JPAAnnotationProcessor을 사용
annotationProcessor("jakarta.persistence:jakarta.persistence-api")
annotationProcessor("jakarta.annotation:jakarta.annotation-api")
// 스프링 이니셜라이저에서 가져온 기본 세팅
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// JPA, MySQL 패키지
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'mysql:mysql-connector-java'
}
// QueryDSL은 QueryDSL의 사용을 위한 엔티티를 새로 만드는데, 그 엔티티를 저장할 경로
def generated='src/main/generated'
sourceSets {
main.java.srcDirs += [ generated ]
}
tasks.withType(JavaCompile) {
options.annotationProcessorGeneratedSourcesDirectory = file(generated)
}
clean.doLast {
file(generated).deleteDir()
}
test {
useJUnitPlatform()
}
さらに調べてみると、どんどんアップグレードしていくGreyに追いつけない可能性があるので、IntelliJバージョンやGreyバージョン、パッケージのバージョンに注目する必要があります.生成されたSpringBootプロジェクトに
config
というパッケージを作成し、QuerydslConfig
というクラスを作成します.ちなみに、すべてのコードはここです。で確認できます.そして、下記のように記入します.
package com.example.querydsl_prac.config;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@Configuration
public class QuerydslConfig {
@PersistenceContext
private EntityManager em;
@Bean
public JPAQueryFactory jpaQueryFactory(){
return new JPAQueryFactory(em);
}
}
QueryDSL
クエリに使用されるJPAQueryFactory
は、上記のコードによって空に登録される.次に、エンティティとrepositoryをそれぞれ作成します.すなわち,コードはすべて羽状バニラ上に置かれ,コピーして貼り付けたり,自分で練習エンティティを作成したりすることができる.
簡単に説明すると、
User
とItem
User
がショッピングかごにItem
を入れ、CartItem
とUser
を@ManyToOne
に一方向にマッピングしている.QueryDSLが必要
テストコードの作成を開始し、
Item
の必要性を理解します.まず、@OneToOne
を使用して、データベースにアクセスするオブジェクトを直接生成します.
@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class QuerydslPracApplicationTests {
@Autowired
private UserRepository userRepository;
@Autowired
private ItemRepository itemRepository;
@Autowired
private CartItemRepository cartItemRepository;
@BeforeAll
public void setUp() {
User user1 = new User("pepper", 27);
User user2 = new User("hansol", 25);
User user3 = new User("tan", 1);
Item item1 = new Item("shoes", 100);
Item item2 = new Item("jacket", 150);
Item item3 = new Item("pants", 80);
CartItem cartItem1 = new CartItem(3, item1, user1);
CartItem cartItem2 = new CartItem(1, item2, user1);
CartItem cartItem3 = new CartItem(5, item3, user1);
userRepository.saveAll(List.of(user1, user2, user3));
itemRepository.saveAll(List.of(item1, item2, item3));
cartItemRepository.saveAll(List.of(cartItem1, cartItem2, cartItem3));
}
}
ここでは、データベースにどれだけのユーザーが存在するか、どれだけのプロジェクトがあるかを決定するために、コードを簡単に作成するとします. @Test
@DisplayName("check there are 3 users")
public void check_there_are_3_users() {
List<User> users = userRepository.findAll();
Assertions.assertEquals(3, users.size());
}
@Test
@DisplayName("check there are 3 items")
public void check_there_are_3_items() {
List<Item> items = itemRepository.findAll();
Assertions.assertEquals(3, items.size());
}
3人のプレイヤーと3つのアイテムを保存し、もちろんテストに合格した.では、非常に複雑な状況はどうなるのでしょうか. @Test
@DisplayName("get cartitems by username")
public void get_cartitems_by_username() {
String username = "pepper";
List<CartItem> cartItems = cartItemRepository.findByUserName(username);
Assertions.assertEquals(3, cartItems.size());
}
プレイヤー名義でショッピングバスケットのアイテムを取得します.QueryDSL
の方法は存在しないので、@BeforeEach
には次のような方法が作成される. List<CartItem> findByUserName(String userName);
findByUserName
は非常に頭が良く、上記のように詳しく書き方ができず、インタフェースだけでやってもよく理解できます.しかし、すべての買い物かごに10歳以上のプレイヤーのものしか持ち込まれなかったらどうなるのでしょうか.練習しているうちにそのように例を挙げてみたいと思うのはつまらない例です
まず10歳以上のプレイヤーを集め、
CartItemRepository
を持ってきて、JPA
とuser_id
の中の外来鍵Nested Query
を使って比較します.まず、テストコードを作成します.
@Test
@DisplayName("cartitems that user's age is over 10 is 3")
public void cart_items_that_user_s_age_is_over_10_is_3() {
Integer ageOver = 10;
List<CartItem> cartItems = cartItemRepository.findByAgeOver(ageOver);
Assertions.assertEquals(3, cartItems.size());
}
こう書きます.カートには3つのアイテムがあり、10歳以上のCartItem
があり、3つの結果が必要です.次に、user_id
に複雑なクエリー"pepper"
を作成します. @Query(value = "SELECT * FROM cartitem WHERE cartitem.user_id IN (SELECT user.id FROM user WHERE user.age > 10)", nativeQuery = true)
List<CartItem> findByAgeOver(@Param("ageOver") Integer ageOver);
上記のコードをrepositoryに追加します.ちなみに、findByAgeOver
と書きたかったのですが、文法はいつも間違っていて、本機照会オプションがあることを知っていたので、そのままCartItemRepository
と書きました.この過程で,
jpql
をなぜ使用するのかを理解した.テストをすれば、結果は自然によくなります.しかし、この方法は少し問題があります.
まず、クエリ文は文字列で直接記述されます.
MySQL
の利点は、これらのクエリ文を簡単にすることができ、コンパイル時にエラーをキャプチャし、QueryDSL
構文を文字列に直接書くことができ、JPA
の利点の80%を低下させることに相当する.また、
jpql
の文法自体も問題です.もちろん、勉強すればいいのですが、一般的なSQL文法とは少し違って混同される可能性があります.QueryDSLを使用してコードを改善
まず
JPA
を作ります.IntellieJを基準に右側の階調設定でjpql
を押し、初期階調設定ファイルで指定したフォルダにgradle
を生成します.これらのクラスは、私たちが作成したエンティティが
build
構文にコンパイルされた結果です.参考までに、これらのファイルはグレーの構築中に生成され、ハブにアップロードされたアイテムであれば
Q 클래스
で除外できます.次に、以下に示すように、Repositoryパッケージ内に
QueryDSL
および.gitignore
が生成される.public interface CartItemRepositoryCustom {
List<CartItem> q_findByAgeOver(Integer ageOver);
}
CartItemRepositoryCustom
とカスタムメソッドを上記のインタフェースで接続します.区別と比較のために、CartItemRepositoryImpl
の方法を適用する前に、JPARepository
がランダムに貼り付けられた.public class CartItemRepositoryImpl implements CartItemRepositoryCustom{
@Autowired
private JPAQueryFactory query;
@Override
public List<CartItem> q_findByAgeOver(Integer ageOver) {
//SELECT * FROM cartitem WHERE cartitem.user_id IN (SELECT user.id FROM user WHERE user.age > 10)
return query.selectFrom(cartItem).where(cartItem.user.age.gt(ageOver)).fetch();
}
}
参考までにQueryDSL
を導入する場合import static com.example.querydsl_prac.entity.QCartItem.cartItem;
上記の方法で取得すべきです.そして非常に直感的なq_
コードを書きます.cartItem
以降よく見ると、オブジェクトを介してクエリーを生成し続けることができます.参照として、QueryDSL
は.where
を意味する.最後に真の
gt
からgreater than
に継承される.@Repository
public interface CartItemRepository extends JpaRepository<CartItem, Long>, CartItemRepositoryCustom {
List<CartItem> findByUserName(String userName);
@Query(value = "SELECT * FROM cartitem WHERE cartitem.user_id IN (SELECT user.id FROM user WHERE user.age > 10)", nativeQuery = true)
List<CartItem> findByAgeOver(@Param("ageOver") Integer ageOver);
@Override
List<CartItem> q_findByAgeOver(Integer ageOver);
}
次に、次のテストコードを追加します. @Test
@DisplayName("querydsl cartitems that user's age is over 10 is 3")
public void querydsl_cart_items_that_user_s_age_is_over_10_is_3() {
Integer ageOver = 10;
List<CartItem> cartItems = cartItemRepository.q_findByAgeOver(ageOver);
Assertions.assertEquals(3, cartItems.size());
}
もちろん結果は同じで、すべてのテストも合格を確認できます.n/a.結論
個人的には、1つのプロジェクトでは、クエリが少し複雑でも
CartItemRepository
でクエリを記述し、コードも汚いし、CartItemRepositoryCustom
の構文のため、よくエラーが発生します.ただし、jpql
を使用すれば、コードも直感的であり、エラーが発生してもコンパイル中に訂正することができる.複雑なクエリー文を記述するプロジェクトはまだ行われていませんが、現在の業界では、十分複雑なクエリーを記述し続ける可能性があるので、簡単に勉強しました.この文章もjpql
の文法よりも複雑な設定方法と必要性について書いたので、文法の内容は特に議論されていません.Reference
この問題について(JPAとQueryDSL), 我々は、より多くの情報をここで見つけました https://velog.io/@peppermint100/JPA와-QueryDSLテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol