Simplifying JDBC Operations with the `SimpleJdbc` Classes

Dev.Hammy·2024년 3월 6일

SimpleJdbcInsertSimpleJdbcCall 클래스는 JDBC 드라이버를 통해 검색할 수 있는 데이터베이스 메타데이터를 활용하여 단순화된 구성을 제공합니다. 즉, 코드에 모든 세부 정보를 제공하려는 경우 메타데이터 처리를 재정의하거나 끌 수 있지만 미리 구성할 항목이 적다는 것을 의미합니다.

Inserting Data by Using SimpleJdbcInsert

최소한의 구성 옵션을 사용하여 SimpleJdbcInsert 클래스를 살펴보는 것부터 시작합니다. 데이터 액세스 계층의 초기화 메서드에서 SimpleJdbcInsert를 인스턴스화해야 합니다. 이 예에서 초기화 메소드는 setDataSource 메소드입니다. SimpleJdbcInsert 클래스를 서브클래싱할 필요는 없습니다. 대신 withTableName 메서드를 사용하여 새 인스턴스를 만들고 테이블 이름을 설정할 수 있습니다. 이 클래스의 구성 메서드는 모든 구성 메서드를 연결할 수 있는 SimpleJdbcInsert의 인스턴스를 반환하는 fluid 스타일을 따릅니다. 다음 예에서는 하나의 구성 메소드만 사용합니다(나중에 여러 메소드의 예를 보여줍니다).

public class JdbcActorDao implements ActorDao {

	private SimpleJdbcInsert insertActor;

	public void setDataSource(DataSource dataSource) {
		this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor");
	}

	public void add(Actor actor) {
		Map<String, Object> parameters = new HashMap<>(3);
		parameters.put("id", actor.getId());
		parameters.put("first_name", actor.getFirstName());
		parameters.put("last_name", actor.getLastName());
		insertActor.execute(parameters);
	}

	// ... additional methods
}

여기서 사용된 execute 메소드는 일반 java.util.Map을 유일한 매개변수로 사용합니다. 여기서 주목해야 할 중요한 점은 Map에 사용되는 키가 데이터베이스에 정의된 테이블의 열 이름과 일치해야 한다는 것입니다. 이는 실제 insert 문을 구성하기 위해 메타데이터를 읽기 때문입니다.

Retrieving Auto-generated Keys by Using SimpleJdbcInsert

다음 예제에서는 이전 예제와 동일한 삽입을 사용하지만 id를 전달하는 대신 자동 생성 키를 검색하여 새 Actor 객체에 설정합니다. SimpleJdbcInsert를 생성할 때 테이블 이름을 지정하는 것 외에도 usingGeneratedKeyColumns 메서드를 사용하여 생성된 키 열의 이름을 지정합니다. 다음 목록은 작동 방식을 보여줍니다.

public class JdbcActorDao implements ActorDao {

	private SimpleJdbcInsert insertActor;

	public void setDataSource(DataSource dataSource) {
		this.insertActor = new SimpleJdbcInsert(dataSource)
				.withTableName("t_actor")
				.usingGeneratedKeyColumns("id");
	}

	public void add(Actor actor) {
		Map<String, Object> parameters = new HashMap<>(2);
		parameters.put("first_name", actor.getFirstName());
		parameters.put("last_name", actor.getLastName());
		Number newId = insertActor.executeAndReturnKey(parameters);
		actor.setId(newId.longValue());
	}

	// ... additional methods
}

이 두 번째 접근 방식을 사용하여 삽입을 실행할 때의 주요 차이점은 Mapid를 추가하지 않고 executeAndReturnKey 메서드를 호출한다는 것입니다. 이는 도메인 클래스에서 사용되는 숫자 유형의 인스턴스를 생성할 수 있는 java.lang.Number 객체를 반환합니다. 여기에서 특정 Java 클래스를 반환하기 위해 모든 데이터베이스에 의존할 수는 없습니다. java.lang.Number는 신뢰할 수 있는 기본 클래스입니다. 자동 생성된 열이 여러 개 있거나 생성된 값이 숫자가 아닌 경우, ExecuteAndReturnKeyHolder 메소드에서 반환된 KeyHolder를 사용할 수 있습니다.

Specifying Columns for a SimpleJdbcInsert

다음 예제와 같이 usingColumns 메서드를 사용하여 열 이름 목록을 지정하여 삽입할 열을 제한할 수 있습니다.

public class JdbcActorDao implements ActorDao {

	private SimpleJdbcInsert insertActor;

	public void setDataSource(DataSource dataSource) {
		this.insertActor = new SimpleJdbcInsert(dataSource)
				.withTableName("t_actor")
				.usingColumns("first_name", "last_name")
				.usingGeneratedKeyColumns("id");
	}

	public void add(Actor actor) {
		Map<String, Object> parameters = new HashMap<>(2);
		parameters.put("first_name", actor.getFirstName());
		parameters.put("last_name", actor.getLastName());
		Number newId = insertActor.executeAndReturnKey(parameters);
		actor.setId(newId.longValue());
	}

	// ... additional methods
}

삽입 실행은 메타데이터를 사용하여 사용할 열을 결정하는 것과 동일합니다.

Using SqlParameterSource to Provide Parameter Values

매개변수 값을 제공하기 위해 Map을 사용하는 것은 잘 작동하지만 사용하기 가장 편리한 클래스는 아닙니다. Spring은 대신 사용할 수 있는 SqlParameterSource 인터페이스의 몇 가지 구현을 제공합니다. 첫 번째는 BeanPropertySqlParameterSource로, 값이 포함된 JavaBean 호환 클래스가 있는 경우 매우 편리한 클래스입니다. 해당 getter 메서드를 사용하여 매개변수 값을 추출합니다. 다음 예에서는 BeanPropertySqlParameterSource를 사용하는 방법을 보여줍니다.

public class JdbcActorDao implements ActorDao {

	private SimpleJdbcInsert insertActor;

	public void setDataSource(DataSource dataSource) {
		this.insertActor = new SimpleJdbcInsert(dataSource)
				.withTableName("t_actor")
				.usingGeneratedKeyColumns("id");
	}

	public void add(Actor actor) {
		SqlParameterSource parameters = new BeanPropertySqlParameterSource(actor);
		Number newId = insertActor.executeAndReturnKey(parameters);
		actor.setId(newId.longValue());
	}

	// ... additional methods
}

또 다른 옵션은 Map과 유사하지만 연결(chained)될 수 있는 더 편리한 addValue 메서드를 제공하는 MapSqlParameterSource입니다. 다음 예에서는 사용 방법을 보여줍니다.

public class JdbcActorDao implements ActorDao {

	private SimpleJdbcInsert insertActor;

	public void setDataSource(DataSource dataSource) {
		this.insertActor = new SimpleJdbcInsert(dataSource)
				.withTableName("t_actor")
				.usingGeneratedKeyColumns("id");
	}

	public void add(Actor actor) {
		SqlParameterSource parameters = new MapSqlParameterSource()
				.addValue("first_name", actor.getFirstName())
				.addValue("last_name", actor.getLastName());
		Number newId = insertActor.executeAndReturnKey(parameters);
		actor.setId(newId.longValue());
	}

	// ... additional methods
}

보시다시피 구성은 동일합니다. 이러한 대체 입력 클래스를 사용하려면 실행 코드만 변경하면 됩니다.

Calling a Stored Procedure with SimpleJdbcCall

SimpleJdbcCall 클래스는 데이터베이스의 메타데이터를 사용하여 inout 매개변수의 이름을 조회하므로 이를 명시적으로 선언할 필요가 없습니다. 이를 선호하거나 Java 클래스에 대한 자동 매핑이 없는 매개변수(예: ARRAY 또는 STRUCT)가 있는 경우 매개변수를 선언할 수 있습니다. 첫 번째 예는 MySQL 데이터베이스에서 VARCHARDATE 형식의 스칼라 값만 반환하는 간단한 프로시저를 보여줍니다. 예제 프로시저는 지정된 배우 항목을 읽고 first_name, last_namebirthday_date 열을 out 매개변수 형식으로 반환합니다. 다음 목록은 첫 번째 예를 보여줍니다.

CREATE PROCEDURE read_actor (
	IN in_id INTEGER,
	OUT out_first_name VARCHAR(100),
	OUT out_last_name VARCHAR(100),
	OUT out_birth_date DATE)
BEGIN
	SELECT first_name, last_name, birth_date
	INTO out_first_name, out_last_name, out_birth_date
	FROM t_actor where id = in_id;
END;

in_id 매개변수에는 조회 중인 행위자의 id가 포함됩니다. out 매개변수는 테이블에서 읽은 데이터를 반환합니다.

SimpleJdbcInsert를 선언하는 것과 유사한 방식으로 SimpleJdbcCall을 선언할 수 있습니다. 데이터 액세스 계층의 초기화 메서드에서 클래스를 인스턴스화하고 구성해야 합니다. StoredProcedure 클래스와 비교하면 하위 클래스를 생성할 필요가 없으며 데이터베이스 메타데이터에서 조회할 수 있는 매개 변수를 선언할 필요도 없습니다. 다음 SimpleJdbcCall 구성 예에서는 이전 저장된 프로시저를 사용합니다(DataSource 외에 유일한 구성 옵션은 저장된 프로시저의 이름입니다).

public class JdbcActorDao implements ActorDao {

	private SimpleJdbcCall procReadActor;

	public void setDataSource(DataSource dataSource) {
		this.procReadActor = new SimpleJdbcCall(dataSource)
				.withProcedureName("read_actor");
	}

	public Actor readActor(Long id) {
		SqlParameterSource in = new MapSqlParameterSource()
				.addValue("in_id", id);
		Map out = procReadActor.execute(in);
		Actor actor = new Actor();
		actor.setId(id);
		actor.setFirstName((String) out.get("out_first_name"));
		actor.setLastName((String) out.get("out_last_name"));
		actor.setBirthDate((Date) out.get("out_birth_date"));
		return actor;
	}

	// ... additional methods
}

호출 실행을 위해 작성하는 코드에는 IN 매개 변수가 포함된 SqlParameterSource를 만드는 작업이 포함됩니다. 입력 값에 제공된 이름은 저장된 프로시저에 선언된 매개변수 이름과 일치해야 합니다. 메타데이터를 사용하여 저장된 프로시저에서 데이터베이스 개체를 참조하는 방법을 결정하므로 대소문자(case)가 일치할 필요는 없습니다. 저장된 프로시저의 소스에 지정된 내용이 반드시 데이터베이스에 저장되는 방식은 아닙니다. 일부 데이터베이스는 이름을 모두 대문자로 변환하는 반면 다른 데이터베이스는 소문자를 사용하거나 지정된 대로 대소문자를 사용합니다.

execute 메소드는 IN 매개변수를 취하고 저장된 프로시저에 지정된 이름으로 입력된 모든 out 매개변수를 포함하는 Map을 반환합니다. 이 경우에는 out_first_name, out_last_name, out_birth_date입니다.

execute 메소드의 마지막 부분은 검색된 데이터를 반환하는 데 사용할 Actor 인스턴스를 생성합니다. 여기서도 저장된 프로시저에 선언된 out 매개변수의 이름을 사용하는 것이 중요합니다. 또한 결과 맵에 저장된 out 매개변수 이름의 대소문자는 데이터베이스에 있는 out 매개변수 이름의 대소문자와 일치하며 이는 데이터베이스마다 다를 수 있습니다. 코드의 이식성(portable)을 높이려면 대소문자를 구분하지 않는(case-insensitive) 조회를 수행하거나 Spring에 LinkedCaseInsensitiveMap을 사용하도록 지시해야 합니다. 후자를 수행하려면 자신만의 JdbcTemplate을 만들고 setResultsMapCaseInsensitive 속성을 true로 설정하면 됩니다. 그런 다음 이 사용자 정의된 JdbcTemplate 인스턴스를 SimpleJdbcCall의 생성자에 전달할 수 있습니다. 다음 예에서는 이 구성을 보여줍니다.

public class JdbcActorDao implements ActorDao {

	private SimpleJdbcCall procReadActor;

	public void setDataSource(DataSource dataSource) {
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
		jdbcTemplate.setResultsMapCaseInsensitive(true);
		this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
				.withProcedureName("read_actor");
	}

	// ... additional methods
}

이 작업을 수행하면 반환된 out 매개변수의 이름에 사용되는 대소문자 충돌을 피할 수 있습니다.

Explicitly Declaring Parameters to Use for a SimpleJdbcCall

이 장 앞부분에서 매개변수가 메타데이터에서 추론되는(deduced from) 방법을 설명했지만 원하는 경우 매개변수를 명시적으로 선언할 수 있습니다. 가변 개수의 SqlParameter 개체를 입력으로 사용하는 declareParameters 메서드를 사용하여 SimpleJdbcCall을 만들고 구성하면 그렇게 할 수 있습니다. SqlParameter를 정의하는 방법에 대한 자세한 내용은 다음 섹션을 참조하세요.

[Note]
사용하는 데이터베이스가 Spring 지원 데이터베이스가 아닌 경우 명시적인 선언이 필요합니다. 현재 Spring은 Apache Derby, DB2, MySQL, Microsoft SQL Server, Oracle 및 Sybase 데이터베이스에 대한 저장된 프로시저 호출의 메타데이터 조회를 지원합니다. 또한 MySQL, Microsoft SQL Server 및 Oracle에 대한 저장된 함수의 메타데이터 조회도 지원합니다.

매개변수 중 하나, 일부 또는 전부를 명시적으로 선언하도록 선택(opt to)할 수 있습니다. 매개변수 메타데이터는 매개변수를 명시적으로 선언하지 않은 경우에도 계속 사용됩니다. 잠재적 매개변수에 대한 메타데이터 조회의 모든 처리를 우회하고 선언된 매개변수만 사용하려면 선언의 일부로 withoutProcedureColumnMetaDataAccess 메소드를 호출할 수 있습니다. 데이터베이스 함수에 대해 두 개 이상의 서로 다른 호출 시그니처가 선언되어 있다고 가정합니다. 이 경우 useInParameterNames를 호출하여 주어진 시그니처에 포함할 IN 매개변수 이름 목록을 지정합니다.

다음 예에서는 완전히 선언된 프로시저 호출을 보여주고 이전 예의 정보를 사용합니다.

public class JdbcActorDao implements ActorDao {

	private SimpleJdbcCall procReadActor;

	public void setDataSource(DataSource dataSource) {
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
		jdbcTemplate.setResultsMapCaseInsensitive(true);
		this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
				.withProcedureName("read_actor")
				.withoutProcedureColumnMetaDataAccess()
				.useInParameterNames("in_id")
				.declareParameters(
						new SqlParameter("in_id", Types.NUMERIC),
						new SqlOutParameter("out_first_name", Types.VARCHAR),
						new SqlOutParameter("out_last_name", Types.VARCHAR),
						new SqlOutParameter("out_birth_date", Types.DATE)
				);
	}

	// ... additional methods
}

두 예제의 실행 및 최종 결과는 동일합니다. 두 번째 예에서는 메타데이터에 의존하지 않고 모든 세부 정보를 명시적으로 지정합니다

How to Define SqlParameters

SimpleJdbc 클래스 및 RDBMS 작업 클래스(JDBC 작업을 Java 개체로 모델링하기에서 다룸)에 대한 매개 변수를 정의하려면 SqlParameter 또는 그것의 하위 클래스 중 하나를 사용할 수 있습니다. 이렇게 하려면 일반적으로 생성자에서 매개변수 이름과 SQL 타입을 지정합니다. SQL 타입은 java.sql.Types 상수를 사용하여 지정됩니다. 이 장의 앞부분에서 다음과 유사한 선언을 보았습니다.

new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),

SqlParameter가 있는 첫 번째 줄은 IN 매개 변수를 선언합니다. SqlQuery 및 해당 하위 클래스를 사용하여 저장된 프로시저 호출과 쿼리 모두에 IN 매개 변수를 사용할 수 있습니다(SqlQuery 이해하기에서 다룹니다).

두 번째 줄(SqlOutParameter 포함)은 저장된 프로시저 호출에 사용할 out 매개 변수를 선언합니다. InOut 매개변수(프로시저에 IN 값을 제공하고 값도 반환하는 매개변수)에 대한 SqlInOutParameter도 있습니다.

[Note]
SqlParameterSqlInOutParameter로 선언된 매개 변수만 입력 값을 제공하는 데 사용됩니다. 이는 이전 버전과의 호환성을 위해 SqlOutParameter로 선언된 매개 변수에 입력 값을 제공할 수 있는 StoredProcedure 클래스와 다릅니다.

IN 매개변수의 경우, 이름 및 SQL 타입 외에도 숫자 데이터의 소수점 이하 자릿수 또는 사용자 정의 데이터베이스 타입의 타입 이름을 지정할 수 있습니다. out 매개변수의 경우 REF 커서에서 반환된 행 매핑을 처리하기 위해 RowMapper를 제공할 수 있습니다. 또 다른 옵션은 반환 값의 사용자 정의 처리를 정의할 수 있는 기회를 제공하는 SqlReturnType을 지정하는 것입니다.

Calling a Stored Function by Using SimpleJdbcCall

프로시저 이름이 아닌 함수 이름을 제공한다는 점을 제외하면 저장된 프로시저를 호출하는 것과 거의 동일한 방식으로 저장된 함수를 호출할 수 있습니다. withFunctionName 메서드를 구성의 일부로 사용하여 함수 호출을 나타내면, 함수 호출에 해당하는 문자열이 생성됩니다. 특수 호출(executeFunction)은 함수를 실행하는 데 사용되며 함수 반환 값을 지정된 타입의 개체로 반환합니다. 즉, 결과 맵에서 반환 값을 검색할 필요가 없습니다. out 매개변수가 하나만 있는 저장된 프로시저에도 비슷한 편의 메서드(executeObject)를 사용할 수 있습니다. 다음 예제(MySQL용)는 actor의 전체 이름을 반환하는 get_actor_name이라는 저장된 함수를 기반으로 합니다.

CREATE FUNCTION get_actor_name (in_id INTEGER)
RETURNS VARCHAR(200) READS SQL DATA
BEGIN
	DECLARE out_name VARCHAR(200);
	SELECT concat(first_name, ' ', last_name)
		INTO out_name
		FROM t_actor where id = in_id;
	RETURN out_name;
END;

이 함수를 호출하기 위해 다음 예제와 같이 초기화 메서드에서 SimpleJdbcCall을 다시 만듭니다.

public class JdbcActorDao implements ActorDao {

	private SimpleJdbcCall funcGetActorName;

	public void setDataSource(DataSource dataSource) {
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
		jdbcTemplate.setResultsMapCaseInsensitive(true);
		this.funcGetActorName = new SimpleJdbcCall(jdbcTemplate)
				.withFunctionName("get_actor_name");
	}

	public String getActorName(Long id) {
		SqlParameterSource in = new MapSqlParameterSource()
				.addValue("in_id", id);
		String name = funcGetActorName.executeFunction(String.class, in);
		return name;
	}

	// ... additional methods
}

사용된 executionFunction 메소드는 함수 호출의 반환 값이 포함된 String을 반환합니다.

Returning a ResultSet or REF Cursor from a SimpleJdbcCall

결과 세트(result set)를 반환하는 저장된 프로시저나 함수를 호출하는 것은 약간 까다롭습니다. 일부 데이터베이스는 JDBC 결과 처리 중에 결과 세트를 반환하는 반면, 다른 데이터베이스는 특정 유형의 명시적으로 등록된 out 매개변수를 요구합니다. 두 접근 방식 모두 결과 집합(result set)을 반복(loop)하고 반환된 행을 처리하려면 추가 처리가 필요합니다. SimpleJdbcCall을 사용하면 returningResultSet 메소드를 사용하고 특정 매개변수에 사용할 RowMapper 구현을 선언할 수 있습니다. 결과 처리 중에 결과 집합(result set)이 반환되면 정의된 이름이 없으므로 반환된 결과는 RowMapper 구현을 선언한 순서와 일치해야 합니다. 지정된 이름은 execute 문에서 반환된 결과 맵(result map)에 처리된 결과 목록을 저장하는 데 계속 사용됩니다.

다음 예(MySQL용)에서는 IN 매개변수를 사용하지 않고 t_actor 테이블의 모든 행을 반환하는 저장된 프로시저를 사용합니다.

CREATE PROCEDURE read_all_actors()
BEGIN
 SELECT a.id, a.first_name, a.last_name, a.birth_date FROM t_actor a;
END;

이 프로시저를 호출하려면 RowMapper를 선언하면 됩니다. 매핑하려는 클래스는 JavaBean 규칙을 따르므로 매핑할 필수 클래스를 newInstance 메서드에 전달하여 생성된 BeanPropertyRowMapper를 사용할 수 있습니다. 다음 예에서는 그 방법을 보여줍니다.

public class JdbcActorDao implements ActorDao {

	private SimpleJdbcCall procReadAllActors;

	public void setDataSource(DataSource dataSource) {
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
		jdbcTemplate.setResultsMapCaseInsensitive(true);
		this.procReadAllActors = new SimpleJdbcCall(jdbcTemplate)
				.withProcedureName("read_all_actors")
				.returningResultSet("actors",
				BeanPropertyRowMapper.newInstance(Actor.class));
	}

	public List getActorsList() {
		Map m = procReadAllActors.execute(new HashMap<String, Object>(0));
		return (List) m.get("actors");
	}

	// ... additional methods
}

execute 호출은 매개변수를 사용하지 않기 때문에 빈 Map을 전달합니다. 그런 다음 행위자 목록이 결과 맵에서 검색되어 호출자에게 반환됩니다.

0개의 댓글