반응형

나는 작심 30일 해.. 할수있어..

# 이번목표 (~ 5월 31일)

  • SQL 첫걸음 읽기
  • SQL 레벨업 읽기
  • php & laravel 익힐겸 프로젝트 해보기 (아이디어는 구상 완료)
  • 코틀린 인 액션 읽기 (6월로 넘어갈 수 있음)
  • 코틀린 고급 강의 듣기
더보기

SQL 첫걸음 : ~ 5월 15일

SQL 레벨업 : ~ 5월 18일

php & laravel 프로젝트 : 5월 15일 + 그 이후 꾸준히 ~ 5월 24일까지 / 매주 일요일 3시간씩 할애

코틀린 인 액션 읽기 : ~ 5월 31일

코틀린 고급 강의 : 코틀린 인 액션과 주제가 일치할때

반응형
반응형

laravel 강의는 예전것들이 많이 올해 새로 나온 laravel11 버전에 맞지 않는 경우가 종종 있다. 대부분의 경우 방식은 달라도 자동으로 import 해주거나 하여 개발자가 수작업 할 일이 없지만 middleware 적용에서 에러가 발생했다.

 

[에러 상황]

middleware 에 관한 공부를 하던 중 web.php 에서 route group 에 한번에 middleware를 적용할 수 있음을 배웠다.

 

Route::resource('articles', ArticleController::class)->middleware('auth')->except(['index', 'show']);

 

또한 except 를 통해 middleware 처리를 제외할 조건도 확인하였다.

다음으로는 개별 controller 에서 middleware를 처리해 주는 방법을 학습하였다.

Controller가 실행될때 자동으로 적용되게 하기 위해 생성자로 지정해 주었다.

class ArticleController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth')->except(['index', 'show']);
    }

 

하지만 middleware 부분에 warning이 떴고 메소드를 추가하라는 선택지가 나왔다.

 

laravel에 설정되어 있는 middleware 메소드를 알아서 찾았어야 하는데 해당 메소드를 Class 내에서 찾고, 이것이 없다는 것이다.

 

실제 import를 하려고 하면 해당 메소드를 구현하거나, 이름을 변경하거나의 선택지가 있다. Add @method 해봤지만 동작하지 않았고, 아래와 같은 화면이 노출되었다.

이 외에도 강의와 다르게 라라벨 11버전에서는 몇가지 다른 점이 있었다.

[1] Laravel11 버전의 업데이트 내용

버전 문제일수 있다 생각하여 검색해 보니 실제 Laravel11 버전에서는 middleware 관련하여 몇가지 변경사항이 있었다.

https://rezakhademix.medium.com/laravel-11-no-http-kernel-no-casts-no-console-kernel-721c62adb6ef

 

Laravel 11: No Http Kernel, No $casts, No Console Kernel!

Laravel 11 is scheduled for release in Q1 2024, bringing various improvements and new features to the framework.

rezakhademix.medium.com

 

1. app/http/Middleware 폴더 삭제

더이상 해당 폴더가 지원되지 않는다고 한다. 대신 app.php 파일에 middleware 설정이 작성되어 있다고 한다.

 

2. Kernel.xml 파일 삭제

해당 파일도 더이상 존재하지 않는다고 한다. 해당 파일에서 기존에 수정하던 것은 app.php 파일에서 작성하라고 한다.

 

[에러 해결]

구글링한 결과 다행히 답을 찾을 수 있었다! 검색은 화면에 노출된 에러메세지로 검색하였다.

검색 : Call to undefined method Controller::middleware()

 

어떤 깃헙 repo의 pr 내역에서 해결법을 찾을 수 있었다.

https://github.com/laravel/framework/issues/50566

 

Call to undefined method ::middleware() when calling authorizeResource() · Issue #50566 · laravel/framework

Laravel Version 11 PHP Version 8.3 Database Driver & Version No response Description Calling authorizeResource() in a resource controller throws a "Call to undefined method ::middleware()" exceptio...

github.com

To be able to use authorizeResource() in Laravel 11, your base controller class in app\Http\Controllers\Controller.php should extend \Illuminate\Routing\Controller (because that's where the middleware() method is), just like the Controller.php file found in a fresh Laravel 10 installation.

 

Laravel11 에서는 app\Http\Controllers\Controller.php 파일에 작성된 클래스가 상속받아야 한다는 것이다. 상속 대상 클래스가 middleware 메소드를 구현하고 있다고 한다. 그래서 해당 파일을 아래와 같이 작성하였다.

<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Auth\Access\AuthorizesRequests;

abstract class Controller extends \Illuminate\Routing\Controller
{
    use AuthorizesRequests;
}

 

이후 기존에 작성한 ArticleController로 돌아가보니 middleware 메소드를 사용하는 부분의 warning 라인이 없어지고 command+클릭으로 이동하니 AuthorizesRequesets 파일로 이동했다!

 

그리고 기능도 정상적으로 동작하였다!

 

[테스트로 기능확인]

middleware를 적용하고 기존에 작성한 테스트코드를 실행해보니 테스트가 실패하였다.

 

인증관련해서 404가 나올줄알았지만 302인 이유는 실제 화면에서는 미인증 유저가 해당 화면에 접근할 경우 로그인 화면으로 "리다이렉트" 시키기 때문이다! 따라서 리다이렉트 코드인 302가 반환되었다. 

 

해당 테스트코드는 미인증 회원의 접근이 잘 막아지는지로 코드를 수정하고, 로그인한 회원의 테스트코드를 작성하였다.

/**
 * @test
 */
public function 로그인한_사용자는_글쓰기_화면을___있다(): void
{
    $user = User::factory()->create();
    $this->actingAs($user)
        ->get(route('articles.create'))
        ->assertStatus(200)
        ->assertSee('글쓰기');
}

테스트 성공!

반응형
반응형

[기능설명]

articles 라는 테이블에 body값과 userId값을 받아 새로운 document 를 생성한다.

 

[1] 순수 PHP

//1. pdo 객체 만들고
$host=config('database.connections.mysql.host');
$dbname=config('database.connections.mysql.database');
$username=config('database.connections.mysql.username');
$password=config('database.connections.mysql.password');
$conn = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
//2. 쿼리준비
$stmt = $conn->prepare("INSERT INTO articles (body, user_id) VALUES (:body, :userId)");
//3. 쿼리 값을 설정
$user_id=
$stmt -> bindValue(':body', $input['body']);
$stmt->bindValue(':userId', Auth::id());
//4. 실행
$stmt->execute();

 

[2] DB 파사드 사용

먼서 DB 패키지 사용을 위해 import 해준다.

use Illuminate\Support\Facades\DB;

 

// 2) DB 파사드를 이용하는 방법 : Laravel 을 사용해 DB 사용하기
DB::insert ("INSERT INTO articles (body, user_id) VALUES (:body, :userId)", ['body' => $input['body'], 'userId' => Auth::id()]);

 

DB 뒤의 명령어는 상황에 따라 다른데 아래와 같다

- statement : insert, selectl, update, delete가 아닌 경우에 + index를 만든다거나 할때 사용한다
- CRUD : insert, select, update, delete 메소드 사용

 

[3] 쿼리 빌더 사용

1) insert 하기

// 3) 쿼리 빌더를 사용하는 방법
DB::table('articles') ->insert([
    'body' => $input['body'],
    'user_id' => Auth::id()
]);

 

위 방법대로 쿼리 빌더를 사용하면 1번이나 2번과 달리 query 문을 작성하지 않아도 된다는 점이 있다.

 

2) read 하기

> sail tinker
> DB::table('articles')->where('id',9)->first();
------------------------------------------------------
= {#5289
    +"id": 2,
    +"body": "<EB><91><90><EB><B2><88><EC><A7><B8> <EA><B8><80>~~",
    +"user_id": 1,
    +"created_at": "2024-05-02 05:40:39",
    +"updated_at": "2024-05-02 05:40:39",
  }

쿼리빌더를 사용해 데이터를 조회하면 standard class 로 조회되는걸 확인 할 수 있다.

 

3) Data converting : Article class

standard class로 받은 결과를 Article 클래스로 변환할 수 있다.

// 3-1) 쿼리빌더로 데이터 조회 후 Article class로 변환하기
$res = DB::table('articles')->where('id',2)->first();
$article = new Article;
$article->body=$res -> body;
$article->user_id = $res->user_id;

[4] Eloquent ORM 사용하기

Laravel 사용에서 아주아주 많이 이용된다는 Eloquent ORM을 사용해보자!

먼저 이를 사용하기 위해선 model을 생성해 주어야 한다.

1) model Class 생성하기

article collection에 저장할 데이터 클래스를 생성해준다.

이때 기존에 laravel의 blade를 사용하면서 인증 관련 테이블이 자동으로 생성되어 이미 User 라는 모델이 있는걸 확인 할 수 있다. 

 

sail artisan make:model Article

위의 명령어로 해당 경로에 Article 이라는 모델 파일이 생성된걸 확인할 수 있다.

이때 주의해야 할 것은 테이블 명의 단수형으로 모델을 생성해야 laravel이 인식할수 있다는것 !!

2) insert 하기

그리고 나서 요청값으로부터 body와 user_id를 가져와서 저장해 주는 로직을 작성한다

//4) Eloquent ORM 사용하기
$article = new Article;
$article->body = $input['body'];
$article->user_id = Auth::id();
$article->save();

 

위의 1~3번과 Eloquent ORM을 사용했을때의 차이점은 여러가지가 있지만 일단 date 값이 자동으로 생성된다는 것이다.

 

 

위의 insert 쿼리를 리팩토링 하여 class생성과 query문 작성을 동시에 할 수 있다.

 

3) read 하기

sail tinker 를 통해 shell 에서 조회하고 있었는데 오류가 발생했다.

Article class 를 찾지 못한다는것... 그래서 경로 전체를 입력해 주었다.

> App\Models\Article::find(2);
= App\Models\Article {#5621
    id: 2,
    body: "<EB><91><90><EB><B2><88><EC><A7><B8> <EA><B8><80>~~",
    user_id: 1,
    created_at: "2024-05-02 05:40:39",
    updated_at: "2024-05-02 05:40:39",
  }

Article 클래스를 입력했을때 자동으로 App\Models 경로로부터 import 할수 있으면 편할거같다...

standard class로 조회되던 쿼리빌더 방식과 달리 Eloquent ORM을 사용하면 Article 클래스로 조회되는걸 확인 할 수 있다.

 

4) create 리팩토링 하기 + 에러 발생 🔥

쿼리 빌더를 사용해 create 구문을 리팩토링 한 것처럼 Eloquent ORM으로 데이터를 조회할때 class 주입과 query 문 작성을 한번에 할 수 있다.

> 리팩토링 쿼리문

// 4-2) Eloquent ORM 사용 2번째 : class 생성과 query 문 작성 동시에 진행하기
Article::create([
    'body' => $input['body'],
    'user_id' => Auth::id()
]);

 

이때 create에 어떤 모듈을 import 해줄껀지 뜨는데 이부분 관련해서는 강의에서 언급이 없어서 일단 @method 어노테이션을 넣는걸로 하였다.

 

> @method

해당 어노테이션을 추가하니 App\Models\Article 의 class 위에 다음과 같은 코드가 추가되었다.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

/**
 * @method static create(array $array)
 */
class Article extends Model
{
    use HasFactory;
}

 

그러고 나서 데이서 생성을 확인해 보았는데 에러가 발생했다.

> fillable property  : 대량 할당 오류

 

fillable property 가 눈에 들어온다. 구글링을 해보니 Article 클래스에 담을 필드들을 무언가 처리해 주어야 한다는것 같았다.

 답변 : https://stackoverflow.com/questions/53793841/add-title-to-fillable-property-to-allow-mass-assignment-on-app-post

 

Add [title] to fillable property to allow mass assignment on [App\Post]

While inserting data in Mysql I have encountered the following error: "Add [title] to the fillable property to allow mass assignment on [App\Post]." Here is my code: $post = Post::create([ '

stackoverflow.com

 

해당 답변들을 참고하여 App/Models/Atricle 에 한줄을 추가해 주었다.

/**
 * @method static create(array $array)
 */
class Article extends Model
{
    use HasFactory;
    protected $fillable = ['body','user_id'];
}

 

그랬더니 정상적으로 데이터가 insert 된걸 확인할 수 있었다!

 

--------------------------------------------------------

[에러해결1] fillable porptery : 대량 할당 취약점

해당 개념에 대해서는 laravel 공식문서에도 설명되어 있다.

https://laravel.com/docs/11.x/eloquent#mass-assignment

 

Laravel - The PHP Framework For Web Artisans

Laravel is a PHP web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing you to create without sweating the small things.

laravel.com

 

요약하자면 의도치 않은 필드 수정을 막기 위한 조치라고 할수 있는데, 우리가 의도한 필드의 수정을 허용하기 위해서 Atricle 모델에 별도의 처리가 필요하다.

1) fillable : white list

해당 설정으로 사용을 허용하는 필드를 정의할 수 있다. 위 해결법에서 사용한 방법이다

protected $fillable = ['body','user_id'];

 

2) guarded : black list

해당 설정으로 선언된 필드 외의 모든 필드를 허용하는 방법이다.

protected $guarded = ['id'];

 

둘다 작성해줄 필요는 없고 둘중 하나만 설정해 주면된다.

반응형
반응형

강의를 따라하며 실습을 진행하다가 강사님의 화면에서 IDE를 이용해 db ui를 확인하는 장면을 보았다.

관련해서 언급이 없어서 삽질을 좀 하고... 어찌저찌 연결하긴 했는데 관련 내용 검색을 했을때 정확한 내용을 찾지 못해 내 해결법을 공유한다.

 

[1] database 아이콘 좌측 네비게이션 바로 이동

 

지금 필요한 것은 IDE의 네비게이션 바에 database가 추가되는 것이다.

참고로 나는 좌측 네비게이션 바에 두었지만 좌측하단 혹은 우측에 두어도 상관은 없는듯!!

 

먼저 처음부터 database 아이콘이 보인다면 상관없겠지만 처음에 보이지 않는다면 다음을 따라한다.

 

1) 우측 메뉴툴바에서 View > Tool Windows확인

2) database 클릭

 

그러면 짜잔~ 하고 나타난다. 이제 db를 연결해 줄 차례이다!

 

[2] Laravel 프로젝트 내 databse.app 확인

Laravel은 앱을 생성하면 config나 다른 기타 설정 파일들이 함께 들어오더라! 약간 Django 처럼~~??

먼저 db 연결을 위해 어떤 정보가 있는지 확인해자

1) 연결할 DB종류 선택

나의 프로젝트는 mysql을 사용중이다! 따라서 Data Source > MySQL 을 선택해 준다.

 

2) 필요한 정보 확인

 

Host UserName Password Database URL Port 정보가 필요하다. 모두 database.php 파일에서 확인가능하다 ㅎㅎ

 

3) Config > database.php 확인

해당 파일을들어가면 아래와 같은 코드가 작성되어 있다.

.env 파일에서 값을 찾고 없을 경우 default값을 참조하도록 되어있는데 이때 env 파일에 있는 값도 있지만 없는 값도 있어서 잘 확인해 주어야 한다.!

 

해당 정보들에서 위에 입력에 필요한 정보들을 확인해 둔다.

[3] database 연결하기

이제 연결할 차례이다 참고로 이때 docker를 실행중에 있어야 한다!

Host 가 localhost 이기 때문~

 

참고로 정보를 입력한 뒤 아래의 Test Connection 을 누르면 Failed 뜬다.. 그치만 연결 된다 ㅋㅋㅋ

 

아까도 말했듯이 docker가 실행중이 아니라면 DB 연결이 실패한다!!! 아래와 같은 에러메세지가 뜨고 db 안은 텅 비어있다.

따라서 docker를 꼭 실행해 주도록 한다. 이때 Laravel 프로젝트로 실행하므로 나는 아래와 같은 명령어로 도커를 background에서 실행한다.

verdor/bin/sail up -d

 

맨날 vendor/bin/sail 입력하는게 귀찮아서 alias 로 등록했다 ㅋㅋ

// alias 등록하기
> vi ~/.zshrc

alias sail="vendor/bin/sail"

// 변경된 사항 적용하기
> source ~/.zshrc

 

그럼 아까 실행했던 명령어를 아래와 같이 입력할 수 있다.

 

Docker Desktop 켜주고.. 실행하기... ㅎㅎ...

프로젝트 하나 실행하는데 터미널을 무쟈게 많이 띄우긴 곤란하니 docker는 백그라운드에서 실행! 그리고 프론트 쪽 소스도 적용하기 위해서 npm run dev도 실행해 준다.

 

이렇게 실행하고 나면 아까 연결해 두었던 db를 새로고침!

데이터양마다 다른진 모르겠지만.. 1초도 안걸린다!

Refresh 를 하고 나면 그동안 프로젝트로 생성한 table과 그 안의 document들을 확인할 수 있다!

 

참고로 처음에 db를 연결할때 database 를 Laravel 로 하였기 때문에 해당 db가 연결된 것이다.

 

끝~~!!

반응형
반응형

코틀린 강의를 들으면서 한가지 기능에 대해 가장 쉽고, 빠르고, 코틀린만의 문법규칙을 사용하지 않고 작성하여 기능을 확인한 뒤 점진적으로 리팩토링 하며 코틀린 문법에 익숙해지려 한다.

 

강의를 들으면서 3단계에 걸쳐 동일한 기능을 리팩토링 하였고 그 과정을 기억해보려 한다.

[기능] 기능 설명

카테고리가 있는 책들의 현재 대출 현황에 대한 갯수를 보여준다.

{
    "COMPUTER": 1,
    "SCIENCE": 2,
    "SOCIATY": 5
}

카테고리 종류는 더 많지만 대출중인 유효 갯수가 있는 카테고리만 반환한다.

> bookRepository

현재 도서관이 보유중이 책 목록을 조회할 수 있다.

 

> BookStatResponse

카테고리별 대출중인 책의 갯수를 담는 DTO 이다.

 

[1] for loop 로 조회하기

- serivce

@Transactional(readOnly = true)
fun getBookStatistics_v1(): List<BookStatResponse> {
    val results = mutableListOf<BookStatResponse>()
    val books = bookRepository.findAll()
    for (book in books) {
        val targetDto = results.firstOrNull { dto -> book.type == dto.type }
        if (targetDto == null) {
            results.add(BookStatResponse(book.type, 1))
        } else {
            targetDto.plusOne()
        }
    }
    return results
}

 

- BookStatResponse

data class BookStatResponse(
        val type: BookType,
        var count: Int,
) {
    fun plusOne() {
        count++
    }
}

 

> 문제점

- 코드가 너무 길다

- BookStatResponse의 count가 가변 필드로 되어 의도치 않은 수정이 일어날 수 있다.

 

[2] ?. 와 ?: 사용하기

- service

@Transactional(readOnly = true)
fun getBookStatistics_v2(): List<BookStatResponse> {
    val results = mutableListOf<BookStatResponse>()
    val books = bookRepository.findAll()
    for (book in books) {
        results.firstOrNull { dto -> book.type == dto.type }?.plusOne()
                ?: results.add(BookStatResponse(book.type, 1))
    }
    return results
}

 

> 문제점

- call chain이 길어져 디버깅이 쉽지 않다.

- BookStatResponse의 count가 가변 필드로 되어 의도치 않은 수정이 일어날 수 있다.

 

[3] group by 사용하기

- service

@Transactional(readOnly = true)
fun getBookStatistics(): List<BookStatResponse> {
    return bookRepository.findAll() //List<Book>
            .groupBy{ book -> book.type }  // Map<BookType, List<Book>>
            .map { (type, books) -> BookStatResponse(type, books.size)} // List<BookStatResponse>
}

 

- BookStatResponse

data class BookStatResponse(
        val type: BookType,
        val count: Int,
)

 

> 코드 설명

1) findAll() 을 사용해 도서관에 등록되어 있는 모든 책을 조회한다 List<Book>

2) groupBy 를 사용해 카테고리 별 Map 으로 만들어준다 Map<BookType, List<Book>>

3) map 을 사용해 BookStatResponse DTO에 카테고리별 갯수를 담아준다 List<BookStatResponse

 

> 해결한 것

- BookStatResponse data class의 count 필드가 불변 필드로 되어 의도치 않은 수정을 막을 수 있다.

- 코드가 간결해 졌다.

 

> 문제점

도서관에 등록된 책의 전체 목록을 조회하여 그 갯수를 세는 과정이므로 db부하 등을 걱정해야 한다.

(전체 데이터 쿼리 => 메모리 로딩 + grouping)

 

[4] JPQL + Spring data JPA 활용하기 (예제 2개)

1] (위 예제와 이어지는 예제) 예제 1 : Book 기능

bookRepository에 커스텀 query function을 작성한다.

//jpql
@Query("select new com.group.libraryapp.dto.book.response.BookStatResponse(b.type, count(b.id)) " +
        "from Book b group by b.type")
fun getStats(): List<BookStatResponse>

 

위 메소드를 사용하면 service에서는 매우 간결해진다.

@Transactional(readOnly = true)
fun getBookStatistics(): List<BookStatResponse> = bookRepository.getStats()

 

해당 쿼리를 통해 조회하면 h2 query 출력은 아래와 같이 나온다

select
    book0_.type as col_0_0_,
    count(book0_.id) as col_1_0_ 
from
    books book0_ 
group by
    book0_.type

 

> 해결한 것

groupBy 쿼리를 사용하여 db로부터 전체 데이터를 메모리에 로드하지 않고 필요한 정보만을 가져오게 되었다.

-> index를 사용해 튜닝 (성능 최적화) 할수있는 여지가 잇다.

> 문제점

- query 구문에서 외부 객체를 사용하기 위해서는 절대 경로를 전부 입력해 주어야 한다...

- 오타와 띄어쓰기를 매우 주의해야 한다..

 

2] 예제 2 : User 기능

유저와 대출히스토리 table이 각각 있는 db구조에서 유저와 해당 유저의 전체 대출 히스토리 기록을 한번에 조회하는 기능이 있다. 이때 대출기록이 없는 유저일지라도 유저목록으로는 반환되야 한다. 

- service

@Transactional(readOnly = true)
fun getUserLoanHistories(): List<UserLoanHistoryResponse> {
    return userRepository.findAllWithHistories().map(UserLoanHistoryResponse::of)
}

 

- repository

@Query("select distinct u from User u left join fetch u.userLoanHistories")
fun findAllWithHistories(): List<User>

 

> 문제점

- 오타 문제

- 조건이 추가된다면 또 다른 jpql 문을 작성해야 한다.

- 어떤것을 참조하는지 타고 들어가 봐야 안다.

 

[5] Querydsl (예제 2개)

0] 코프링에 querydsl 적용하기

1) gradle 추가

먼저 코프링 환경에서 querydsl을 사용하기 위해서 gradle 에 추가해주어야 한다.

// plugin
id "org.jetbrains.kotlin.kapt" version "1.6.21"

// dependecy
implementation("com.querydsl:querydsl-jpa:5.0.0")
kapt("com.querydsl:querydsl-apt:5.0.0:jpa")
kapt("org.springframework.boot:spring-boot-configuration-processor")

참고로 환경은 스프링 부트 3버전 / intelliJ 23.1버전 / kotlin 1.16 버전 이다.

 

설정 변경후 빌드를 하면 build > generated > source > kapt > main > .....{프로젝트 루트} ..... > domain 에 User와 Book이 아닌 QUser QBook 처럼 앞에 Q가 붙은 클래스들이 생성된걸 볼 수 있다.

 

2) 스프링 빈 등록

 

그리고 나서 QuerydslConfig 파일을 만들어 Component로 등록하고 JPAQueryFactory를 Bean으로 생성한다.

import com.querydsl.jpa.impl.JPAQueryFactory
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import javax.persistence.EntityManager

@Configuration
class QuerydslConfig(
        private val em: EntityManager
) {

    @Bean
    fun querydsl(): JPAQueryFactory {
        return JPAQueryFactory(em)

    }
}

 

다음으로 querydsl을 사용할 interface와 구현체 클래스를 작성한다. 이는 기존의 repository 는 jpql + jpa를 사용하기 때문에 분리하기 위함이다.

 

3) querydsl 인터페이스, 구현체 생성

 

- interface : UserRepositoryCustom

interface UserRepositoryCustom { ... }

 

- class : UserRepositoryCustomImpl

class UserRepositoryCustomImpl(
        private val queryFactory: JPAQueryFactory
) : UserRepositoryCustom{ ... }

 

 

4) 기존 repository interface에 방금만든 것 상속

그리고 기존의 userRepository interface에서 Custom interface를 상속받도록 한다. 기존에 상속받고 있던 JPARepository는 그대로.

interface UserRepository : JpaRepository<User, Long>, UserRepositoryCustom{ ... }

 

1] 예제 2 : User 기능 => interface로 만들기

querydsl을 활용해 repository를 작성한다.

- UserRepositoryCustom

interface UserRepositoryCustom {
    fun findWithHistories(): List<User>
}

 

- UserRepositoryCustomImpl

import com.group.libraryapp.domain.user.QUser.user
import com.group.libraryapp.domain.user.loanhistory.QUserLoanHistory.userLoanHistory
import com.querydsl.jpa.impl.JPAQueryFactory

class UserRepositoryCustomImpl(
        private val queryFactory: JPAQueryFactory
) : UserRepositoryCustom{
    override fun findWithHistories(): List<User> {
        // querydsl 로 작성
        return queryFactory.select(user).distinct()
                .from(user)
                .leftJoin(userLoanHistory).on(userLoanHistory.user.id.eq(user.id)).fetchJoin()
                .fetch()
    }
}

 

의존성 주입을 위해 생성자에 JPAQueryFactory를 적어주어야 한다. query 구문에서는 기존 Entity class가 아닌 Q클래스를 사용한다.

 

2] (위 예제와 이어지는 예제) 예제 1 : Book 기능 => class로 만들기

BookQuerydslRepository 라는 클래스를 정의한다.

@Component
class BookQuerydslRepository(
        private val queryFactory: JPAQueryFactory,
) {
    fun getStats(): List<BookStatResponse> {
        return queryFactory.select(Projections.constructor(
                BookStatResponse::class.java,
                book.type,
                book.id.count()
        ))
                .from(book)
                .groupBy(book.type)
                .fetch()
    }
}

> Projections.constructor

주어진 DTO의 생성자를 호출한다. (첫번째로 나오는 클래스의 생성자)

그 뒤 인자들은 생성을 위한 초기값으로 사용된다.

select type, count(book.id) from book;

 

위 쿼리문을 해석하면 아래와 같다.

select type, count(book.id)
from book
group by type;

 

반응형

+ Recent posts