Java에서 SQL 문자열을 작성하는 가장 깨끗한 방법


107

데이터베이스 조작 (업데이트, 삭제, 삽입, 선택 등)을 수행하기 위해 SQL 문자열을 만들고 싶습니다-기껏해야 읽을 수없는 수백만 개의 "+"와 따옴표를 사용하는 끔찍한 문자열 연결 방법 대신-거기 더 나은 방법이어야합니다.

MessageFormat 사용을 생각했지만 합리적인 작업을 할 것이라고 생각하지만 사용자 메시지에 사용되어야하지만 Java SQL 라이브러리의 SQL 유형 작업에 더 잘 맞는 것이 있어야한다고 생각합니다.

Groovy가 좋을까요?

답변:


76

먼저 준비된 명령문에서 쿼리 매개 변수 사용을 고려하십시오.

PreparedStatement stm = c.prepareStatement("UPDATE user_table SET name=? WHERE id=?");
stm.setString(1, "the name");
stm.setInt(2, 345);
stm.executeUpdate();

수행 할 수있는 다른 작업은 모든 쿼리를 속성 파일에 보관하는 것입니다. 예를 들어 query.properties 파일에서 위의 쿼리를 배치 할 수 있습니다.

update_query=UPDATE user_table SET name=? WHERE id=?

그런 다음 간단한 유틸리티 클래스의 도움으로 :

public class Queries {

    private static final String propFileName = "queries.properties";
    private static Properties props;

    public static Properties getQueries() throws SQLException {
        InputStream is = 
            Queries.class.getResourceAsStream("/" + propFileName);
        if (is == null){
            throw new SQLException("Unable to load property file: " + propFileName);
        }
        //singleton
        if(props == null){
            props = new Properties();
            try {
                props.load(is);
            } catch (IOException e) {
                throw new SQLException("Unable to load property file: " + propFileName + "\n" + e.getMessage());
            }           
        }
        return props;
    }

    public static String getQuery(String query) throws SQLException{
        return getQueries().getProperty(query);
    }

}

다음과 같이 쿼리를 사용할 수 있습니다.

PreparedStatement stm = c.prepareStatement(Queries.getQuery("update_query"));

이것은 다소 간단한 해결책이지만 잘 작동합니다.


1
:이처럼 깨끗하고 SQL 빌더를 사용하는 것을 선호 mentabean.soliveirajr.com
TraderJoeChicago

2
필요하지 않을 때 인스턴스화하지 않도록 명령문 InputStream내부에 넣는 것이 좋습니다 if (props == null).
SyntaxRules

64

임의의 SQL의 경우 jOOQ를 사용 하십시오 . jOOQ 현재 지원 SELECT, INSERT, UPDATE, DELETE, TRUNCATE,와 MERGE. 다음과 같이 SQL을 생성 할 수 있습니다.

String sql1 = DSL.using(SQLDialect.MYSQL)  
                 .select(A, B, C)
                 .from(MY_TABLE)
                 .where(A.equal(5))
                 .and(B.greaterThan(8))
                 .getSQL();

String sql2 = DSL.using(SQLDialect.MYSQL)  
                 .insertInto(MY_TABLE)
                 .values(A, 1)
                 .values(B, 2)
                 .getSQL();

String sql3 = DSL.using(SQLDialect.MYSQL)  
                 .update(MY_TABLE)
                 .set(A, 1)
                 .set(B, 2)
                 .where(C.greaterThan(5))
                 .getSQL();

SQL 문자열을 얻는 대신 jOOQ를 사용하여 실행할 수도 있습니다. 보다

http://www.jooq.org

(면책 조항 : 나는 jOOQ 뒤에있는 회사에서 일합니다)


dbms가 "5", "8"등에 대해 다른 값으로 미리 명령문을 구문 분석하도록 할 수 없기 때문에 많은 경우에 이것이 좋지 않은 솔루션이 아닐까요? jooq로 실행하면 해결할 수 있을까요?
Vegard

@Vegard : jOOQ 가 SQL 출력에서 ​​바인드 값을 렌더링 하는 방법을 완전히 제어 할 수 있습니다 : jooq.org/doc/3.1/manual/sql-building/bind-values . 즉, 렌더링 "?"할지 또는 바인딩 값을 인라인할지 여부를 선택할 수 있습니다.
Lukas Eder 2013 년

예, 그러나 SQL을 작성하는 깨끗한 방법과 관련하여 JOOQ를 사용하여 실행하지 않으면 내 눈에 약간 지저분한 코드가 될 것입니다. 이 예제에서는 A를 1로, B를 2로 설정했지만 JOOQ로 실행하지 않는 경우 실행할 때 한 번 더 수행해야합니다.
Vegard

1
@Vegard : jOOQ API에 변수를 전달하고 SQL 문을 다시 빌드하는 것을 방해하는 것은 없습니다. 또한 jooq.org/javadoc/latest/org/jooq/Query.html#getBindValues ​​()를 사용하여 순서대로 바인드 값을 추출 하거나 jooq.org/javadoc/latest/org/jooq를 사용하여 이름별로 명명 된 바인드 값을 추출 할 수 있습니다. /Query.html#getParams () . 내 대답에는 매우 단순한 예가 포함되어 있습니다 ... 그래도 이것이 귀하의 우려에 응답하는지 확실하지 않습니까?
Lukas Eder 2013 년

2
비용이 많이 드는 솔루션입니다.
Sorter

15

고려해야 할 한 가지 기술은 SQLJ 입니다. 즉, Java에 SQL 문을 직접 포함하는 방법입니다. 간단한 예로 TestQueries.sqlj라는 파일에 다음이있을 수 있습니다.

public class TestQueries
{
    public String getUsername(int id)
    {
        String username;
        #sql
        {
            select username into :username
            from users
            where pkey = :id
        };
        return username;
    }
}

.sqlj 파일을 가져와 순수 Java로 변환하는 추가 사전 컴파일 단계가 있습니다. 즉, 다음으로 구분 된 특수 블록을 찾습니다.

#sql
{
    ...
}

JDBC 호출로 변환합니다. SQLJ를 사용하면 몇 가지 주요 이점이 있습니다.

  • JDBC 계층을 완전히 추상화합니다. 프로그래머는 Java와 SQL 만 생각하면됩니다.
  • 번역기는 컴파일 타임에 데이터베이스에 대한 구문 등의 쿼리를 확인하도록 만들 수 있습니다.
  • ":"접두사를 사용하여 쿼리에서 Java 변수를 직접 바인딩하는 기능

대부분의 주요 데이터베이스 공급 업체를위한 변환기 구현이 있으므로 필요한 모든 것을 쉽게 찾을 수 있습니다.


이것은 wikipedia에 따라 현재 구식입니다.
Zeus

1
작성 당시 (2016 년 1 월) SQLJ는 참조 없이 Wikipedia 에서 "오래된"것으로 언급되었습니다. 공식적으로 버려 졌습니까? 그렇다면이 답변 상단에 경고를 표시하겠습니다.
Ashley Mercer

NB이 기술은 예를 들어 최신 버전의 Oracle 12c 에서 여전히 지원됩니다 . 가장 현대적인 표준은 아니지만 여전히 작동하고 다른 시스템에서는 사용할 수없는 몇 가지 이점 (예 : DB에 대한 쿼리의 컴파일 타임 검증)이 있습니다.
Ashley Mercer

12

나는 당신이 Squiggle 과 같은 것을 추구 하고 있는지 궁금합니다 . 또한 매우 유용한 것은 jDBI 입니다. 그래도 쿼리에는 도움이되지 않습니다.


9

나는 Spring JDBC를 살펴볼 것이다 . 프로그래밍 방식으로 SQL을 실행해야 할 때마다 사용합니다. 예:

int countOfActorsNamedJoe
    = jdbcTemplate.queryForInt("select count(0) from t_actors where first_name = ?", new Object[]{"Joe"});

모든 종류의 SQL 실행, 특히 쿼리에 정말 좋습니다. 전체 ORM의 복잡성을 추가하지 않고도 결과 집합을 개체에 매핑하는 데 도움이됩니다.


실제 실행 된 SQL 쿼리를 어떻게 얻을 수 있습니까? 나는 그것을 기록하고 싶다.
kodmanyagha

5

저는 Spring의 명명 된 JDBC 매개 변수를 사용하는 경향이 있으므로 "select * from blah where colX = ': someValue'"와 같은 표준 문자열을 작성할 수 있습니다. 꽤 읽기 쉬운 것 같아요.

대안은 별도의 .sql 파일에 문자열을 제공하고 유틸리티 메서드를 사용하여 내용을 읽는 것입니다.

오, Squill도 살펴볼 가치가 있습니다 : https://squill.dev.java.net/docs/tutorial.html


나는 당신이 BeanPropertySqlParameterSource를 사용하고 있다는 것을 의미한다고 가정합니까? 나는 당신에게 거의 동의합니다. 방금 언급 한 클래스는 엄격하게 빈을 사용할 때 멋지지만 그렇지 않으면 사용자 지정 ParameterizedRowMapper를 사용하여 개체를 생성하는 것이 좋습니다.
Esko

좀 빠지는. 명명 된 JDBC 매개 변수와 함께 모든 SqlParameterSource를 사용할 수 있습니다. 콩 품종보다는 MapSqlParameterSource를 사용하는 데 적합했습니다. 어느 쪽이든 좋은 솔루션입니다. 그러나 RowMappers는 결과 집합을 개체로 변환하는 SQL 퍼즐의 다른 측면을 다룹니다.
GaryF

4

Hibernate와 같은 ORM 사용에 대한 권장 사항을 두 번째로 설명합니다. 그러나 확실히 작동하지 않는 상황이 있기 때문에이 기회를 통해 내가 작성하는 데 도움이 된 몇 가지 내용을 소개 하겠습니다 . SqlBuilder 는 "빌더"스타일을 사용하여 SQL 문을 동적으로 빌드하기위한 자바 라이브러리입니다. 상당히 강력하고 유연합니다.


4

저는 임시보고 목적을 위해 매우 동적 인 SQL 문을 구성해야하는 Java 서블릿 애플리케이션을 개발하고 있습니다. 앱의 기본 기능은 명명 된 HTTP 요청 매개 변수를 사전 코딩 된 쿼리에 제공하고 형식이 잘 지정된 출력 테이블을 생성하는 것입니다. Spring MVC와 의존성 주입 프레임 워크를 사용하여 모든 SQL 쿼리를 XML 파일에 저장하고 테이블 형식화 정보와 함께보고 애플리케이션에로드했습니다. 결국보고 요구 사항은 기존 매개 변수 매핑 프레임 워크의 기능보다 더 복잡 해져서 직접 작성해야했습니다. 이것은 개발 과정에서 흥미로운 작업이었으며 내가 찾을 수있는 다른 어떤 것보다 훨씬 강력한 매개 변수 매핑을위한 프레임 워크를 생성했습니다.

새 매개 변수 매핑은 다음과 같습니다.

select app.name as "App", 
       ${optional(" app.owner as "Owner", "):showOwner}
       sv.name as "Server", sum(act.trans_ct) as "Trans"
  from activity_records act, servers sv, applications app
 where act.server_id = sv.id
   and act.app_id = app.id
   and sv.id = ${integer(0,50):serverId}
   and app.id in ${integerList(50):appId}
 group by app.name, ${optional(" app.owner, "):showOwner} sv.name
 order by app.name, sv.name

결과 프레임 워크의 장점은 적절한 유형 검사 및 제한 검사를 통해 HTTP 요청 매개 변수를 쿼리로 직접 처리 할 수 ​​있다는 것입니다. 입력 유효성 검사에 추가 매핑이 필요하지 않습니다. 위의 예제 쿼리에서 serverId 라는 매개 변수 가 정수로 캐스트 될 수 있고 0-50 범위에 있는지 확인합니다. 매개 변수 appId 는 길이 제한이 50 인 정수 배열로 처리됩니다. showOwner이 존재하고 "true"로 설정하면 따옴표 안의 SQL 비트가 선택적 필드 매핑에 대해 생성 된 쿼리에 추가됩니다. 필드 추가 매개 변수 매핑이있는 SQL의 선택적 세그먼트를 포함하여 여러 매개 변수 유형 매핑을 사용할 수 있습니다. 개발자가 생각할 수있는 것처럼 복잡한 쿼리 매핑을 허용합니다. 보고서 구성에 주어진 쿼리가 PreparedStatement를 통해 최종 매핑을 가질 것인지 또는 단순히 미리 빌드 된 쿼리로 실행되는지를 결정하는 컨트롤도 있습니다.

샘플 Http 요청 값의 경우 :

showOwner: true
serverId: 20
appId: 1,2,3,5,7,11,13

다음 SQL을 생성합니다.

select app.name as "App", 
       app.owner as "Owner", 
       sv.name as "Server", sum(act.trans_ct) as "Trans"
  from activity_records act, servers sv, applications app
 where act.server_id = sv.id
   and act.app_id = app.id
   and sv.id = 20
   and app.id in (1,2,3,5,7,11,13)
 group by app.name,  app.owner,  sv.name
 order by app.name, sv.name

저는 Spring이나 Hibernate 또는 그 프레임 워크 중 하나가 유형을 확인하고 배열 및 기타 이러한 기능과 같은 복잡한 데이터 유형을 허용하는보다 강력한 매핑 메커니즘을 제공해야한다고 생각합니다. 내 목적으로 만 엔진을 작성했지만 일반 릴리스에서는 읽지 못합니다. 현재 Oracle 쿼리에서만 작동하며 모든 코드는 대기업에 속합니다. 언젠가는 내 아이디어를 가지고 새로운 오픈 소스 프레임 워크를 구축 할 수 있지만, 기존의 거물 중 한 명이 도전을 맡길 바랍니다.


3

모든 SQL을 수동으로 생성하려는 이유는 무엇입니까? Hibernate와 같은 ORM을 보았습니까? 프로젝트에 따라 필요한 작업의 95 % 이상을 수행하고 원시 SQL보다 더 깨끗한 방식으로 수행하며 마지막 성능을 얻으려면 다음을 생성 할 수 있습니다. 직접 조정해야하는 SQL 쿼리.


3

MyBatis ( www.mybatis.org )를 살펴볼 수도 있습니다 . Java 코드 외부에서 SQL 문을 작성하는 데 도움이되며 SQL 결과를 Java 객체에 매핑합니다.


3

Google은 기본적으로 기본 SQLite 데이터베이스에 대한 추상화 계층 인 Android 앱용 SQL 작성하는 매우 깔끔한 방법을 제공하는 Room Persitence 라이브러리라는 라이브러리 를 제공합니다 . Bellow는 공식 웹 사이트의 짧은 코드 스 니펫입니다.

@Dao
public interface UserDao {
    @Query("SELECT * FROM user")
    List<User> getAll();

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    List<User> loadAllByIds(int[] userIds);

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND "
           + "last_name LIKE :last LIMIT 1")
    User findByName(String first, String last);

    @Insert
    void insertAll(User... users);

    @Delete
    void delete(User user);
}

라이브러리의 공식 문서에는 더 많은 예제와 더 나은 문서가 있습니다.

Java ORM 인 MentaBean이라는 것도 있습니다. 그것은 좋은 기능을 가지고 있으며 SQL을 작성하는 매우 간단한 방법 인 것 같습니다.


당으로 방 문서 : Room provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite. 따라서 RDBMS 용 일반 ORM 라이브러리가 아닙니다. 주로 Android 앱용입니다.
RafiAlhamd

2

XML 파일을 읽습니다.

XML 파일에서 읽을 수 있습니다. 유지 관리 및 작업이 쉽습니다. 표준 STaX, DOM, SAX 파서는 Java에서 몇 줄의 코드로 만들 수 있습니다.

속성으로 더 많은 작업 수행

SQL로 더 많은 작업을 수행하는 데 도움이되도록 태그에 속성이있는 일부 의미 정보를 가질 수 있습니다. 메서드 이름이나 쿼리 유형 또는 코딩을 줄이는 데 도움이되는 모든 것이 될 수 있습니다.

유지

xml을 jar 외부에 넣고 쉽게 유지할 수 있습니다. 특성 파일과 동일한 이점.

변환

XML은 확장 가능하며 다른 형식으로 쉽게 변환 할 수 있습니다.

사용 사례

Metamug는 xml을 사용하여 sql로 REST 리소스 파일을 구성합니다.


원하는 경우 yaml 또는 json을 사용할 수 있습니다. 일반 속성 파일에 저장하는 것보다 낫습니다
Sorter

문제는 SQL을 구축하는 방법입니다. SQL을 구축하기 위해 XML, Parser, Validation 등을 사용해야한다면 부담이 큽니다. SQL을 구축하기 위해 XML과 관련된 대부분의 초기 시도는 Annotation을 선호합니다. 허용 대답 하여 표트르 Kochański는 것입니다 간단하고 우아한 및 지점 - 문제 및 유지 보수를 해결합니다. 참고 : 다른 언어로 더 나은 SQL을 유지하는 다른 방법은 없습니다.
RafiAlhamd

I don't see a reason to make use of XML. 편집 할 수 없어 이전 댓글을 삭제 했습니다.
RafiAlhamd

1

SQL 문자열을 속성 파일에 넣은 다음이를 읽으면 SQL 문자열을 일반 텍스트 파일에 보관할 수 있습니다.

그것은 SQL 유형 문제를 해결하지 못하지만 적어도 TOAD 또는 sqlplus에서 복사 및 붙여 넣기를 훨씬 쉽게 만듭니다.


0

여러 줄로 나누는 PreparedStatements의 긴 SQL 문자열 (텍스트 파일에 쉽게 제공하고 리소스로로드 할 수 있음)을 제외하고 문자열 연결을 어떻게 얻습니까?

SQL 문자열을 직접 작성하지 않습니까? 그것은 프로그래밍에서 가장 큰 금지입니다. PreparedStatements를 사용하고 데이터를 매개 변수로 제공하십시오. SQL 주입의 가능성을 크게 줄입니다.


그러나 웹 페이지를 공개하지 않는다면 SQL Injection이 관련 문제입니까?
Vidar

4
SQL 주입은 의도적으로나 우연히 발생할 수 있기 때문에 항상 관련이 있습니다.
sleske

1
@Vidar- 지금 은 웹 페이지를 대중 에게 공개하지 않을 수도 있지만, "항상"내부에있는 코드조차도 한 단계 더 내려 가면 외부에 노출되는 경우가 많습니다. 그리고 ... 나중에 문제에 대한 전체 코드베이스를 감사하는 것보다 그것을 바로 처음 라운드를 할 더 빠르고 더 안전한이다
의 Andrzej 도일

4
PreparedStatement조차도 문자열에서 만들어야합니다.
Stewart

예,하지만 안전한 PreparedStatement를 빌드하는 한 문자열에서 PreparedStatement를 빌드하는 것이 안전합니다. 당신은 그것들을 생성하기 위해 PreparedStatementBuilder 클래스를 작성해야 할 것입니다.
JeeBee
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.