구문 빌더

Request For Translation

This section is outdated in the Korean version. Please refer to the English manual. Any help with the Korean translation will be really welcome.

SelectBuilder

자바 개발자에게 가장 끔찍한 일중 하나는 자바 코드에서 내장 SQL 을 처리하는 것이다. SQL 은 항상 동적으로 생성되기 때문에 그렇다. 반면에 파일이나 저장 프로시저를 외부에 둘수도 있다. 이미 그렇게 하고 있다면, MyBatis 는 XML 매핑에서 동적으로 SQL 을 생성하기 위한 강력한 대답이 될 것이다. 어쨌든 때때로 자바코드에서 SQL 구문을 문자열을 만들어야 할 필요가 종종 있다. 이 경우, MyBatis 는 좀더 쉽게 만들도록 해준다. 이 기능은 + 기호, 따옴표, 개행문자 그리고 콤마와 AND 등의 포맷팅에 관련된 작업을 줄여준다. 자바 코드에서 SQL 코드를 동적으로 생성하는 것은 정말 끔찍하다.

MyBatis 3 은 이 문제는 다루기 위해 다른 컨셉을 소개한다. 단계별로 SQL 구문을 만들도록 메서드를 호출하는 클래스의 인스턴스를 생성 할 수 있다. 하지만 그 후 SQL 은 SQL 이라기 보다는 자바에 가까운 형태로 끝나게 된다. 대신 조금 다른 것을 시도할 것이다.

SelectBuilder 의 비밀

SelectBuilder 클래스는 마법스럽지는 않다. 어떻게 작동하는지 모른다면 좋은 선택이라고 보기에도 어렵다. 그래서 바로 살펴보자. SelectBuilder 는 깔끔한 문법이 가능하도록 Static Import 와 ThreadLocal 변수의 조합을 사용한다. 생성하는 메서드는 다음과 같다.

public String selectBlogsSql() {
  BEGIN(); // Clears ThreadLocal variable
  SELECT("*");
  FROM("BLOG");
  return SQL();
}

정적으로 빌드하는 매우 간단한 예제이다. 그래서 좀더 복잡한 예제를 보자.

private String selectPersonSql() {
  BEGIN(); // Clears ThreadLocal variable
  SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
  SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");
  FROM("PERSON P");
  FROM("ACCOUNT A");
  INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");
  INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");
  WHERE("P.ID = A.ID");
  WHERE("P.FIRST_NAME like ?");
  OR();
  WHERE("P.LAST_NAME like ?");
  GROUP_BY("P.ID");
  HAVING("P.LAST_NAME like ?");
  OR();
  HAVING("P.FIRST_NAME like ?");
  ORDER_BY("P.ID");
  ORDER_BY("P.FULL_NAME");
  return SQL();
}

위 SQL 이 빌드되면 다음의 문자열과 거의 같을 것이다. 예를 들면

"SELECT P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME, "
"P.LAST_NAME,P.CREATED_ON, P.UPDATED_ON " +
"FROM PERSON P, ACCOUNT A " +
"INNER JOIN DEPARTMENT D on D.ID = P.DEPARTMENT_ID " +
"INNER JOIN COMPANY C on D.COMPANY_ID = C.ID " +
"WHERE (P.ID = A.ID AND P.FIRST_NAME like ?) " +
"OR (P.LAST_NAME like ?) " +
"GROUP BY P.ID " +
"HAVING (P.LAST_NAME like ?) " +
"OR (P.FIRST_NAME like ?) " +
"ORDER BY P.ID, P.FULL_NAME";

이러한 문법을 선호한다면, 이 기능을 사용해도 좋다. 아마도 다소 에러를 야기할 수도 있다. 각 라인마다 끝에 공백을 추가하는 것을 잊지 말아야 한다. 지금부터 이 문법을 선호한다면, 다음 예제는 자바 문자열 처리보다 좀더 간단할 것이다.

private String selectPersonLike(Person p){
  BEGIN(); // Clears ThreadLocal variable
  SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME");
  FROM("PERSON P");
  if (p.id != null) {
  WHERE("P.ID like #{id}");
  }
  if (p.firstName != null) {
  WHERE("P.FIRST_NAME like #{firstName}");
  }
  if (p.lastName != null) {
  WHERE("P.LAST_NAME like #{lastName}");
  }
  ORDER_BY("P.LAST_NAME");
  return SQL();
}

이 예제에서 다소 특별한건 무엇인가? 자 좀더 자세히 보자. “AND” 키워드가 중복되는 것에 대해서는 그다지 걱정하지 않아도 된다. “WHERE” 와 “AND” 간의 선택하는 것도 그렇다. 예를 들어 위 구문은 PERSON 의 모든 레코드를 리턴하는 쿼리를 생성할 것이다. 파라미터로 ID 또는 firstName, lastName 등 3 개의 조합을 가진다. SelectBuilder 는 “WHEN” 가 어디서 필요한지 “AND”가 어디서 사용되는지 이해하는데 주의해야 한다. 무엇보다도 이러한 메서드의 호출 순서에 영향을 받지 않는다.(단 OR() 메서드는 예외)

아마도 두개의 메서드가 먼저 보일 것이다. 두개의 메서드는 BEGIN() 과 SQL()이다. 먼저 SelectBuilder 의 모든 메서드는 BEGIN()을 호출해서 시작하고 SQL()을 호출해서 끝난다. 물론 로직 중간에 생성될 SQL 을 추출하는 메서드를 호출할 수도 있으나 SQL 을 생성하는 대부분의 상황에서는 BEGIN()으로 시작하고 SQL()로 끝난다. BEGIN() 메서드는 ThreadLocal 변수를 초기화하고 SQL()메서드는 BEGIN() 이후 호출에 따라 SQL 구문을 만든다. BEGIN()은 RESET()과 동의어이다.

위 예제처럼 SelectBuilder 를 사용하기 위해, static import 할 필요가 있다.

import static org.apache.ibatis.jdbc.SelectBuilder.*;

한번 import 하고나면, SelectBuilder 메서드 모두 사용할 수 있다. 실제 사용가능한 모든 메서드들이다.

방법

설명

BEGIN() / RESET() 이 메서드들은 SelectBuilder 클래스의 ThreadLocal 상태를 초기화하고 새로운 구문을 추가하기 위해 준비한다. BEGIN() 메서드는 새로운 구문을 추가할때 호출하는 것이 가장 좋다. RESET() 메서드는 어떠한 이유로 인해 실행중간에 구문을 초기화할때 호출하면 된다.
SELECT(String) SELECT 절을 시작하거나 추가한다. 한번 이상 호출될 수 있고,파라미터는 SELECT 절에 추가될 것이다. 파라미터들은 칼럼과 별칭의 목록이고 각각의 값은 콤마로 구분된다.
SELECT_DISTINCT(String) SELECT 절을 시작하거나 추가한다. 생성된 쿼리에 “DISTINCT”키워드 만을 추가한다. 한번 이상 호출될 수 있고, 파라미터들은 SELECT 절에 추가될 것이다. 파라미터들은 칼럼과 별칭의 목록이고 각각의 값은 콤마로 구분된다.
FROM(String) FROM 절을 시작하거나 추가한다. 한번 이상 호출될 수 있고, 파라미터는 FROM 절에 추가될 것이다. 파라미터는 테이블 명과 별칭이다.
  • JOIN(String)
  • INNER_JOIN(String)
  • LEFT_OUTER_JOIN(String)
  • RIGHT_OUTER_JOIN(String)
메서드를 호출할때 적절한 타입으로 JOIN 절을 추가한다. 파라미터는 칼럼과 조인하고자 하는 조건으로 구성된 표준 조인을 포함할 수 있다.
WHERE(String) WHERE 절의 조건을 추가한다. AND 를 자동으로 추가하고 여러번 호출될 수 있다. 여러차례 AND 로 새로운 조건을 만든다. OR 로 분기하고자 한다면 OR()메서드를 사용하면 된다.
OR() WHERE 절의 조건을 OR 로 분리한다. 한번 이상 호출될 수 있으나 잘못 호출하면 SQL 에 에러가 발생할 수 있다.
AND() WHERE 절의 조건을 AND 로 분리한다. 한번 이상 호출될 수 있으나 잘못 호출하면 SQL 에 에러가 발생할 수 있다. WHERE 과 HAVING 모두 조건에 AND 를 자동으로 붙여준다. 흔히 사용되지는 않을 것이며,반드시 필요한 경우에 추가로 사용하면 된다..
GROUP_BY(String) GROUP BY 절을 추가한다. 콤마를 자동으로 추가하고 여러차례 호출될수 있다. 매번 콤마를 추가해서 새로운 조건을 만든다.
HAVING(String) HAVING 절의 조건을 추가한다. AND 를 자동으로 추가하고 여러차례 호출될 수 있다. 매번 AND 를 추가해서 새로운 조건을 추가한다. OR 로 분기하고자 한다면 OR()메서드를 사용하면 된다.
ORDER_BY(String) ORDER BY 절을 추가한다. 콤마를 자동으로 추가하고 여러차례 호출될 수 있다. 매번 콤마를 추가해서 새로운 조건을 만든다.
SQL() 생성되는 SQL 을 리턴하고 SelectBuilder 상태를 리셋(BEGIN()이나 RESET()가 호출된 것처럼)한다. 게다가 이 메서드는 한번만 호출될 수 있다.

SqlBuilder

SelectBuilder 와 유사하게, MyBatis 는 일반적인 SqlBuilder 를 가지고 있다. SelectBuilder 가 가진 모든 메서드를 포함할 뿐 아니라, insert, update 그리고 delete 를 만드는 메서드도 가지고 있다. 이 클래스는 SelectProvider , DeleteProvider, InsertProvider, 또는 UpdateProvider 에서 SQL 문자열을 만들 때 유용하다. 위 예제에서 SqlBuilder 를 사용하기 위해, 다음처럼 static 으로 import 할 필요가

위 예제에서 SqlBuilder 를 사용하기 위해, 다음처럼 static 으로 import 할 필요가 있다.

import static org.apache.ibatis.jdbc.SqlBuilder.*;

SqlBuilder 는 SelectBuilder 의 모든 메서드를 가지고 있으며 몇가지 추가로 메서드를 더 가지고 있다.

방법 설명
DELETE_FROM(String) delete 구문을 시작하고 실제 삭제하고자 하는 테이블을 명시한다. 대개는 WHERE 구문이 뒤에 붙는다.
INSERT_INTO(String) insert 구문을 시작하고 실제 입력하고자 하는 테이블을 명시한다. 대개는 하나 이상의 VALUES() 호출을 뒤따른다.
SET(String) update 구문에서 “set” 에 관련된 목록을 추가한다.
UPDATE(String) update 구문을 시작하고 실제 수정하고자 하는 테이블을 명시한다. 하나 이상의 SET() 호출이 뛰따르고 대개는 WHERE() 호출도 사용한다.
VALUES(String, String) insert 구문에 추가된다. 첫번째 파라미터는 칼럼(column(s))이고 두번째 파라미터는 값(value(s))이다.

이건 몇가지 예제이다.

public String deletePersonSql() {
  BEGIN(); // Clears ThreadLocal variable
  DELETE_FROM("PERSON");
  WHERE("ID = ${id}");
  return SQL();
}

public String insertPersonSql() {
  BEGIN(); // Clears ThreadLocal variable
  INSERT_INTO("PERSON");
  VALUES("ID, FIRST_NAME", "${id}, ${firstName}");
  VALUES("LAST_NAME", "${lastName}");
  return SQL();
}

public String updatePersonSql() {
  BEGIN(); // Clears ThreadLocal variable
  UPDATE("PERSON");
  SET("FIRST_NAME = ${firstName}");
  WHERE("ID = ${id}");
  return SQL();
}