Common Problems with Parameter and Data Value Handling

Dev.Hammy·2024년 3월 6일

매개변수 및 데이터 값과 관련된 일반적인 문제는 Spring Framework의 JDBC 지원이 제공하는 다양한 접근 방식에 존재합니다. 이 섹션에서는 이를 해결하는 방법을 다룹니다.

Providing SQL Type Information for Parameters

일반적으로 Spring은 전달된 매개변수의 타입에 따라 매개변수의 SQL 타입을 결정합니다. 매개변수 값을 설정할 때 사용할 SQL 유형을 명시적으로 제공하는 것이 가능합니다. NULL 값을 올바르게 설정하는 데 필요한 경우가 있습니다.

여러 가지 방법으로 SQL 타입 정보를 제공할 수 있습니다.

  • JdbcTemplate의 많은 업데이트 및 쿼리 메소드는 int 배열 형식의 추가 매개변수를 사용합니다. 이 배열은 java.sql.Types 클래스의 상수 값을 사용하여 해당 매개변수의 SQL 타입을 나타내는 데 사용됩니다. 각 매개변수에 대해 하나의 항목(entry)을 제공하십시오.

  • SqlParameterValue 클래스를 사용하여 이 추가 정보가 필요한 매개 변수 값을 래핑할 수 있습니다. 이렇게 하려면 각 값에 대해 새 인스턴스를 만들고 생성자에 SQL 타입과 매개변수 값을 전달합니다. 숫자 값에 대한 optional scale 매개변수를 제공할 수도 있습니다.

  • 명명된 매개 변수와 함께 작동하는 메서드의 경우 SqlParameterSource 클래스, BeanPropertySqlParameterSource 또는 MapSqlParameterSource를 사용할 수 있습니다. 두 가지 모두 명명된 매개변수 값에 대해 SQL 유형을 등록하는 방법을 가지고 있습니다.

Handling BLOB and CLOB objects

이미지, 기타 바이너리 데이터, 대량의 텍스트를 데이터베이스에 저장할 수 있습니다. 이러한 대형 객체를 바이너리 데이터의 경우 BLOB(Binary Large OBject), 문자 데이터의 경우 CLOB(Character Large OBject)라고 합니다. Spring에서는 JdbcTemplate을 직접 사용하거나 RDBMS 객체 및 SimpleJdbc 클래스에서 제공하는 더 높은 추상화를 사용하여 이러한 대형 객체를 처리할 수 있습니다. 이러한 접근 방식은 모두 LOB(Large OBject) 데이터의 실제 관리를 위해 LobHandler 인터페이스 구현을 사용합니다. LobHandler는 삽입할 새 LOB 객체를 생성하는 데 사용되는 getLobCreator 메서드를 통해 LobCreator 클래스에 대한 액세스를 제공합니다.

LobCreatorLobHandler는 LOB 입력 및 출력에 대해 다음 지원을 제공합니다.

  • BLOB

    • byte[]: getBlobAsBytessetBlobAsBytes
    • InputStream: getBlobAsBinaryStreamsetBlobAsBinaryStream
  • CLOB

    • String : getClobAsStringsetClobAsString
    • InputStream: getClobAsAsciiStreamsetClobAsAsciiStream
    • Reader : getClobAsCharacterStreamsetClobAsCharacterStream

다음 예에서는 BLOB를 만들고 삽입하는 방법을 보여줍니다. 나중에 데이터베이스에서 다시 읽는 방법을 보여줍니다.

이 예제에서는 JdbcTemplateAbstractLobCreatingPreparedStatementCallback 구현을 사용합니다. 이는 setValues라는 하나의 메소드를 구현합니다. 이 메소드는 SQL 삽입 문에서 LOB 열의 값을 설정하는 데 사용하는 LobCreator를 제공합니다.

이 예에서는 이미 DefaultLobHandler의 인스턴스로 설정된 lobHandler 변수가 있다고 가정합니다. 일반적으로 종속성 주입을 통해 이 값을 설정합니다.

다음 예에서는 BLOB를 만들고 삽입하는 방법을 보여줍니다.

final File blobIn = new File("spring2004.jpg");
final InputStream blobIs = new FileInputStream(blobIn);
final File clobIn = new File("large.txt");
final InputStream clobIs = new FileInputStream(clobIn);
final InputStreamReader clobReader = new InputStreamReader(clobIs);

jdbcTemplate.execute(
	"INSERT INTO lob_table (id, a_clob, a_blob) VALUES (?, ?, ?)",
	new AbstractLobCreatingPreparedStatementCallback(lobHandler) { // (1)
		protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException {
			ps.setLong(1, 1L);
			lobCreator.setClobAsCharacterStream(ps, 2, clobReader, (int)clobIn.length()); // (2)
			lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, (int)blobIn.length()); // (3)
		}
	}
);

blobIs.close();
clobReader.close();

(1) (이 예에서는) 일반 DefaultLobHandlerlobHandler를 전달합니다.
(2) setClobAsCharacterStream 메소드를 사용하여 CLOB의 컨텐츠를 전달합니다.
(3) setBlobAsBinaryStream 메소드를 사용하여 BLOB의 내용을 전달합니다.

Note
DefaultLobHandler.getLobCreator()에서 반환된 LobCreator에서 setBlobAsBinaryStream, setClobAsAsciiStream 또는 setClobAsCharacterStream 메서드를 호출하는 경우 선택적으로 contentLength 인수에 음수 값을 지정할 수 있습니다. 지정된 컨텐츠 길이가 음수인 경우 DefaultLobHandler는 길이 매개변수 없이 set-stream 메소드의 JDBC 4.0 변형을 사용합니다. 그렇지 않으면 지정된 길이를 드라이버에 전달합니다.

콘텐츠 길이를 제공하지 않고 LOB 스트리밍을 지원하는지 확인하려면 사용하는 JDBC 드라이버에 대한 설명서를 참조하세요.

이제 데이터베이스에서 LOB 데이터를 읽을 차례입니다. 이번에도 동일한 인스턴스 변수 lobHandlerDefaultLobHandler에 대한 참조가 있는 JdbcTemplate을 사용합니다. 다음 예에서는 그 방법을 보여줍니다.

List<Map<String, Object>> l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table",
	new RowMapper<Map<String, Object>>() {
		public Map<String, Object> mapRow(ResultSet rs, int i) throws SQLException {
			Map<String, Object> results = new HashMap<String, Object>();
			String clobText = lobHandler.getClobAsString(rs, "a_clob");  // (1)
			results.put("CLOB", clobText);
			byte[] blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob");  // (2)
			results.put("BLOB", blobBytes);
			return results;
		}
	});

(1) getClobAsString 메소드를 사용하여 CLOB의 컨텐츠를 검색합니다.
(2) getBlobAsBytes 메소드를 사용하여 BLOB의 컨텐츠를 검색합니다.

Passing in Lists of Values for IN Clause

SQL 표준에서는 변수 값 목록을 포함하는 표현식을 기반으로 행을 선택할 수 있습니다. 일반적인 예는 select * from T_ACTOR where id in (1, 2, 3) 입니다. 이 변수 목록은 JDBC 표준에 의해 준비된 명령문에 대해 직접 지원되지 않습니다. 가변 개수의 자리 표시자를 선언할 수 없습니다. 원하는 수의 자리 표시자를 준비한 다양한 변형이 필요하거나 필요한 자리 표시자 수를 알고 나서 SQL 문자열을 동적으로 생성해야 합니다. NamedParameterJdbcTemplate에서 제공되는 명명된 매개변수 지원은 후자의 접근 방식을 취합니다. 간단한 값의 java.util.List(또는 Iterable)로 값을 전달할 수 있습니다. 이 목록은 실제 SQL 문에 필요한 자리 표시자를 삽입하고 statement 실행 중에 값을 전달하는 데 사용됩니다.

[Note]
많은 값을 전달할 때는 주의하세요. JDBC 표준은 IN 표현식 목록에 100개가 넘는 값을 사용할 수 있다고 보장하지 않습니다. 다양한 데이터베이스가 이 숫자를 초과하지만 일반적으로 허용되는 값 수에 대한 엄격한 제한이 있습니다. 예를 들어 Oracle의 한도는 1000입니다.

값 목록의 기본 값 외에도 객체 배열의 java.util.List를 생성할 수 있습니다. 이 목록은 in 절에 대해 정의되는 여러 표현식을 지원할 수 있습니다(예: select * from T_ACTOR where (id, last_name) in ((1, 'Johnson'), (2, 'Harrop'))). 물론 이를 위해서는 데이터베이스가 이 구문을 지원해야 합니다.

Handling Complex Types for Stored Procedure Calls

저장된 프로시저를 호출할 때 데이터베이스에 특정한 복합 유형을 사용할 수 있는 경우가 있습니다. 이러한 유형을 수용하기 위해 Spring은 저장된 프로시저 호출에서 반환될 때 이를 처리하기 위한 SqlReturnType을 제공하고 저장된 프로시저에 매개변수로 전달될 때 SqlTypeValue를 제공합니다.

SqlReturnType 인터페이스에는 구현해야 하는 단일 메서드(getTypeValue라는 이름)가 있습니다. 이 인터페이스는 SqlOutParameter 선언의 일부로 사용됩니다. 다음 예에서는 사용자 선언 타입 ITEM_TYPE의 Oracle STRUCT 객체 값을 반환하는 방법을 보여줍니다.

public class TestItemStoredProcedure extends StoredProcedure {

	public TestItemStoredProcedure(DataSource dataSource) {
		// ...
		declareParameter(new SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE",
			(CallableStatement cs, int colIndx, int sqlType, String typeName) -> {
				STRUCT struct = (STRUCT) cs.getObject(colIndx);
				Object[] attr = struct.getAttributes();
				TestItem item = new TestItem();
				item.setId(((Number) attr[0]).longValue());
				item.setDescription((String) attr[1]);
				item.setExpirationDate((java.util.Date) attr[2]);
				return item;
			}));
		// ...
	}

SqlTypeValue를 사용하여 Java 개체(예: TestItem)의 값을 저장된 프로시저에 전달할 수 있습니다. SqlTypeValue 인터페이스에는 구현해야 하는 단일 메서드(createTypeValue라는 이름)가 있습니다. 활성 연결이 전달되며 이를 사용하여 StructDescriptor 인스턴스 또는 ArrayDescriptor 인스턴스와 같은 데이터베이스별 개체를 생성할 수 있습니다. 다음 예제에서는 StructDescriptor 인스턴스를 만듭니다.

final TestItem testItem = new TestItem(123L, "A test item",
		new SimpleDateFormat("yyyy-M-d").parse("2010-12-31"));

SqlTypeValue value = new AbstractSqlTypeValue() {
	protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
		StructDescriptor itemDescriptor = new StructDescriptor(typeName, conn);
		Struct item = new STRUCT(itemDescriptor, conn,
		new Object[] {
			testItem.getId(),
			testItem.getDescription(),
			new java.sql.Date(testItem.getExpirationDate().getTime())
		});
		return item;
	}
};

이제 저장된 프로시저의 execute 호출에 대한 입력 매개변수가 포함된 Map에 이 SqlTypeValue를 추가할 수 있습니다.

SqlTypeValue의 또 다른 용도는 Oracle 저장된 프로시저에 값 배열을 전달하는 것입니다. Oracle에는 이 경우 사용해야 하는 자체 내부 ARRAY 클래스가 있으며, 다음 예제와 같이 SqlTypeValue를 사용하여 Oracle ARRAY의 인스턴스를 생성하고 Java ARRAY의 값으로 채울 수 있습니다.

final Long[] ids = new Long[] {1L, 2L};

SqlTypeValue value = new AbstractSqlTypeValue() {
	protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
		ArrayDescriptor arrayDescriptor = new ArrayDescriptor(typeName, conn);
		ARRAY idArray = new ARRAY(arrayDescriptor, conn, ids);
		return idArray;
	}
};

0개의 댓글