컨트롤러에서 SQL을 피하기위한 전략… 또는 모델에 몇 개의 메소드를 사용해야합니까?


17

따라서 합리적으로 자주 발생하는 상황은 모델이 다음 중 하나를 시작하는 상황입니다.

  • 수많은 방법으로 몬스터로 성장

또는

  • 백만 가지 방법이 필요하지 않을 정도로 유연하도록 SQL 조각을 전달할 수 있습니다.

예를 들어 "위젯"모델이 있다고 가정합니다. 우리는 몇 가지 기본 방법으로 시작합니다.

  • get ($ id)
  • 삽입 ($ record)
  • 업데이트 ($ id, $ record)
  • 삭제 ($ id)
  • getList () // 위젯 목록을 얻습니다.

괜찮습니다.하지만보고가 필요합니다.

  • listCreatedBetween ($ start_date, $ end_date)
  • listPurchasedBetween ($ start_date, $ end_date)
  • listOfPending ()

그런 다음보고가 복잡해지기 시작합니다.

  • listPendingCreatedBetween ($ start_date, $ end_date)
  • listForCustomer ($ customer_id)
  • listPendingCreatedBetweenForCustomer ($ customer_id, $ start_date, $ end_date)

당신은 이것이 어디에서 성장하고 있는지 알 수 있습니다 ... 결국 우리는 너무 많은 특정 쿼리 요구 사항을 가지고 있으므로 톤과 톤의 메소드를 구현해야하거나 일종의 "쿼리"객체를 단일-> 쿼리 (쿼리)에 전달할 수 있습니다 $ query) 메소드 ...

... 또는 총알을 물고 다음과 같이하십시오.

  • list = MyModel-> query ( "start_date> X AND end_date <Y AND pending = 1 AND customer_id = Z")

5000 만 개의 다른보다 구체적인 방법 대신에 한 가지 방법을 사용하는 것이 매력적입니다. 그러나 때때로 컨트롤러에 기본 SQL 항목을 넣는 것은 "잘못된"느낌입니다.

이와 같은 상황을 처리하는 "올바른"방법이 있습니까? 그런 쿼리를 일반-> query () 메소드에 채우는 것이 허용되는 것처럼 보입니까?

더 나은 전략이 있습니까?


지금은 비 MVC 프로젝트 에서이 동일한 문제를 겪고 있습니다. 데이터 액세스 계층이 모든 저장 프로 시저를 추상화하고 비즈니스 논리 계층 데이터베이스를 무시해도되는지, 아니면 비즈니스 계층의 비용을 지불하면서 기본 데이터베이스에 대해 알고있는 데이터 액세스 계층이 일반적이어야합니까? 아마도 중간 솔루션은 ExecuteSP (string spName, params object [] parameters)와 같은 것을 갖고 비즈니스 계층에서 읽을 수 있도록 구성 파일에 모든 SP 이름을 포함시키는 것입니다. 그래도 이것에 대한 좋은 대답은 없습니다.
Greg Jackson

답변:


10

Martin Fowler의 Enterprise Application Architecture 패턴은 Query Object의 사용을 포함하여 많은 ORM 관련 패턴을 설명합니다.

쿼리 개체를 사용하면 각 쿼리의 논리를 개별적으로 관리 및 유지 관리되는 전략 개체로 분리하여 단일 책임 원칙을 따를 수 있습니다. 컨트롤러는 사용을 직접 관리하거나 보조 컨트롤러 또는 도우미 개체에 위임 할 수 있습니다.

당신은 그들 중 많은 것이 있습니까? 확실히. 일부는 일반 쿼리로 그룹화 할 수 있습니까? 다시 그렇습니다.

종속성 주입을 사용하여 메타 데이터에서 개체를 만들 수 있습니까? 그것이 대부분의 ORM 도구가하는 일입니다.


4

올바른 방법은 없습니다. 많은 사람들이 ORM을 사용하여 모든 복잡성을 제거합니다. 고급 ORM 중 일부는 코드 표현식을 복잡한 SQL 문으로 변환합니다. ORM의 단점도 있지만 많은 응용 프로그램의 경우 이점이 비용보다 중요합니다.

대규모 데이터 세트로 작업하지 않는 경우 가장 간단한 방법은 전체 테이블을 메모리로 선택하고 코드로 필터링하는 것입니다.

//pseudocode
List<Person> people = Sql.GetList<Person>("select * from people");
List<Person> over21 = people.Where(x => x.Age >= 21);

내부보고 응용 프로그램의 경우이 방법이 좋습니다. 데이터 집합이 실제로 큰 경우 테이블에 적절한 인덱스뿐만 아니라 많은 사용자 지정 방법이 필요합니다.


1
+ 1 "올바른 방법은 없습니다"
ozz

1
불행히도, 데이터 세트 외부에서 필터링하는 것은 실제로 우리가 사용하는 가장 작은 데이터 세트에서도 옵션이 아닙니다. 너무 느립니다. :-( 그래도 다른 사람들이 내 같은 문제에 부딪힌다는 것을 들었습니다. :-)
Keith Palmer Jr.

호기심에서 @KeithPalmer, 테이블이 얼마나 큽니까?
dan

더 많지 않은 경우 수십만 행. 데이터베이스 외부에서 허용 가능한 성능으로 필터링하기에는 너무 많으며, 특히 데이터베이스가 응용 프로그램과 동일한 시스템에없는 분산 아키텍처에서는 필터링되지 않습니다.
Keith Palmer Jr.

"올바른 방법은 없습니다"는 -1입니다. 몇 가지 올바른 방법이 있습니다. OP가 수행 할 때 기능을 추가 할 때 메소드 수를 두 배로 늘리는 것은 확장 할 수없는 방법이며 여기에 제안 된 대안은 쿼리 기능 수가 아니라 데이터베이스 크기와 관련하여 동일하게 확장 할 수 없습니다. 확장 가능한 접근 방식이 존재하며 다른 답변을 참조하십시오.
Theodore Murdock

4

일부 ORM을 사용하면 기본 방법에서 시작하여 복잡한 쿼리를 구성 할 수 있습니다. 예를 들어

old_purchases = (Purchase.objects
    .filter(date__lt=date.today(),type=Purchase.PRESENT).
    .excude(status=Purchase.REJECTED)
    .order_by('customer'))

Django ORM 에서 완벽하게 유효한 쿼리입니다 .

Purchase.objects내부 상태가 쿼리에 대한 정보를 나타내는 쿼리 빌더 (이 경우 )가 있습니다. 같은 방법 get, filter, exclude, order_by유효하고 업데이트 된 상태로 새 쿼리 빌더를 반환합니다. 이러한 객체는 반복 가능한 인터페이스를 구현하므로 반복 할 때 쿼리가 수행되고 지금까지 쿼리 결과가 생성됩니다. 이 예제는 Django에서 가져온 것이지만 다른 많은 ORM에서 동일한 구조를 볼 수 있습니다.


old_purchases = Purchases.query ( "date> date.today () AND type = Purchase.PRESENT AND status! = Purchase.REJECTED")와 비교하여 이것이 어떤 이점이 있는지 알 수 없습니다. SQL AND 및 OR을 메소드 AND 및 OR로 만들어서 복잡성을 줄이거 나 추상화하지 않고 AND와 OR의 표시를 변경하는 것입니까?
Keith Palmer Jr.

4
실제로는 아닙니다. SQL을 추상화하면 많은 이점을 얻을 수 있습니다. 첫째, 주사를 피하십시오. 그런 다음 ORM이이를 처리하므로 약간 다른 버전의 SQL 언어에 대해 걱정할 필요없이 기본 데이터베이스를 변경할 수 있습니다. 많은 경우, 통지없이 NoSQL 백엔드를 넣을 수도 있습니다. 셋째, 이러한 쿼리 작성기는 다른 것과 같이 전달할 수있는 개체입니다. 이는 모델이 쿼리의 절반을 구성 할 수 있음을 의미하며 (예 : 가장 일반적인 경우에 몇 가지 방법이있을 수 있음) 컨트롤러에서 쿼리를 처리하도록 조정할 수 있습니다.
Andrea

2
... 가장 구체적인 경우. 전형적인 예는 Django에서 모델의 기본 순서를 정의하는 것입니다. 달리 지정하지 않는 한 모든 쿼리 결과는 해당 순서를 따릅니다. 넷째, 성능상의 이유로 데이터를 비정규 화해야하는 경우 모든 쿼리를 다시 작성하지 않고 ORM 만 조정하면됩니다.
Andrea

+1 언급 한 것과 같은 동적 쿼리 언어 및 LINQ의 경우.
Evan Plaice

2

세 번째 접근 방식이 있습니다.

귀하의 특정 예는 필요한 기능의 수가 증가함에 따라 필요한 방법의 수가 기하 급수적으로 증가하는 것을 보여줍니다. 우리는 고급 쿼리를 제공하고 모든 쿼리 기능을 결합하는 기능을 원합니다 ... 방법을 추가하여이를 수행하는 경우 기본 쿼리, 하나의 선택적 기능을 추가하면 2, 2를 추가하면 4, 3을 추가하면 8, n 기능을 추가하면 2 ^ n

그것은 3 ~ 4 가지의 특징을 넘어서는 유지 될 수 없으며, 방법들 사이에 거의 복사-붙여 넣기 된 밀접하게 관련된 많은 코드의 악취가 있습니다.

매개 변수를 보유 할 데이터 오브젝트를 추가하여이를 피하고 제공된 (또는 제공되지 않은) 매개 변수 세트를 기반으로 쿼리를 빌드하는 단일 메소드를 가질 수 있습니다. 이 경우 날짜 범위와 같은 새로운 기능을 추가하는 것은 데이터 객체에 날짜 범위에 대한 세터 및 게터를 추가 한 다음 매개 변수화 된 쿼리가 작성되는 코드를 추가하는 것만 큼 간단합니다.

if (dataObject.getStartDate() != null) {
    query += " AND (date BETWEEN ? AND ?) "
}

... 그리고 매개 변수가 쿼리에 추가되는 위치 :

if (dataObject.getStartDate() != null) {
    preparedStatement.setTime(dataObject.getStartDate());
    preparedStatement.setTime(dataObject.getEndDate());
}

이 방법을 사용하면 매개 변수가없는 임의 쿼리를 허용하지 않고 기능이 추가 될 때 선형 코드 증가가 가능합니다.


0

일반적인 합의는 MVC 모델에서 가능한 한 많은 데이터 액세스를 유지하는 것입니다. 다른 디자인 원칙 중 하나는 더 일반적인 쿼리 (모델과 직접 관련이없는 쿼리)를 다른 모델에서도 사용할 수있는 더 높은 추상 수준으로 옮기는 것입니다. (RoR에는 프레임 워크라는 것이 있습니다.) 고려해야 할 또 다른 사항이 있으며 이는 코드의 유지 가능성입니다. 프로젝트가 성장함에 따라 컨트롤러에서 데이터 액세스가 가능하면이를 추적하기가 점점 더 어려워 질 것입니다 (현재 거대한 프로젝트 에서이 문제에 직면하고 있습니다). 테이블에서 퀘스트를 끝낼 수 있습니다. (이것은 또한 코드 재사용으로 이어질 수 있으며 결과적으로 유익합니다)


1
당신이 말하는 것의 예 ...?
Keith Palmer Jr.

0

서비스 계층 인터페이스에는 많은 메소드가있을 수 있지만 데이터베이스 호출에는 하나만있을 수 있습니다.

데이터베이스에는 4 가지 주요 작업이 있습니다

  • 끼워 넣다
  • 최신 정보
  • 지우다
  • 질문

다른 선택적 방법은 기본 DB 작업에 해당하지 않는 일부 데이터베이스 작업을 실행하는 것입니다. Execute라고 부르겠습니다.

삽입과 업데이트는 저장이라는 하나의 작업으로 결합 될 수 있습니다.

많은 메소드가 쿼리입니다. 따라서 대부분의 즉각적인 요구를 충족시키는 일반 인터페이스를 만들 수 있습니다. 다음은 샘플 일반 인터페이스입니다.

 public interface IDALService
    {
        DataTransferObject<T> Save<T>(DataTransferObject<T> Dto) where T : IPOCO;
        DataTransferObject<T> Search<T>(DataTransferObject<T> Dto) where T: IPOCO;
        DataTransferObject<T> Delete<T>(DataTransferObject<T> Dto) where T : IPOCO;
        DataTransferObject<T> Execute<T>(DataTransferObject<T> Dto) where T : IPOCO;
    }

데이터 전송 개체는 일반적이며 모든 필터, 매개 변수, 정렬 등이 포함됩니다. 데이터 계층은이를 분석하고 추출하고 저장 프로 시저, 매개 변수화 된 sql, linq 등을 통해 데이터베이스에 작업을 설정해야합니다. 따라서 SQL은 계층간에 전달되지 않습니다. 이것은 일반적으로 ORM의 기능이지만 롤백하고 매핑 할 수 있습니다.

따라서 귀하의 경우 위젯이 있습니다. 위젯은 IPOCO 인터페이스를 구현합니다.

따라서 서비스 계층 모델에서 getList().

getList변형 을 처리하기 위해 매핑 레이어가 필요합니다.

Search<Widget>(DataTransferObject<Widget> Dto)

그 반대. 다른 사람들이 언급했듯이, 때로는 ORM을 통해 수행되지만 궁극적으로 테이블 수가 많은 경우 특히 많은 상용구 유형 코드로 끝납니다. ORM은 마술처럼 매개 변수화 된 SQL을 작성하여 데이터베이스에 대해 실행합니다. 데이터 계층 자체에서 자체 롤링을 수행하는 경우 SP, linq 등을 설정하려면 맵퍼가 필요합니다 (기본적으로 데이터베이스로 이동하는 SQL).

앞에서 언급했듯이 DTO는 컴포지션으로 구성된 개체입니다. 아마도 그 안에 포함 된 개체 중 하나는 QueryParameters라는 개체 일 것입니다. 이들은 쿼리에 의해 설정되고 사용되는 쿼리에 대한 모든 매개 변수입니다. 다른 객체는 쿼리, 업데이트, 내선에서 반환 된 객체의 목록입니다. 이것이 페이로드입니다. 이 경우 페이로드는 위젯 목록입니다.

기본 전략은 다음과 같습니다.

  • 서비스 계층 호출
  • 일종의 리포지토리 / 매핑을 사용하여 서비스 계층 호출을 데이터베이스로 변환
  • 데이터베이스 호출

귀하의 경우 모델에는 많은 메소드가있을 수 있다고 생각하지만 최적의 데이터베이스 호출은 일반적입니다. 여전히 많은 상용구 매핑 코드 (특히 SP 포함) 또는 매개 변수화 된 SQL을 동적으로 생성하는 마법의 ORM 코드가 생깁니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.