JPAとQueryDSL


緒論


前回はJPAの基本と内部構造を理解し,QueryDSLについて一時的に言及した.QueryDSLは、複雑なクエリーを簡単に変更し、Javaとオブジェクトを使用してクエリー文を記述させることができるため、非常に有用である.QueryDSLの設定と必要性を見て、簡単に使用しましょう.

QueryDSLの設定


まず、国内のJava教育ブログを参考にした有名なjojoduの文章を設置する.最初はspringイニシャルRiserページからMySQLJPA"の数字のコピーのみをインポートし、その後は上記の文章に従うように設定します.もともとは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をそれぞれ作成します.すなわち,コードはすべて羽状バニラ上に置かれ,コピーして貼り付けたり,自分で練習エンティティを作成したりすることができる.
簡単に説明すると、UserItemUserがショッピングかごにItemを入れ、CartItemUser@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を持ってきて、JPAuser_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の文法よりも複雑な設定方法と必要性について書いたので、文法の内容は特に議論されていません.