Spring JDBC
Spring Security OAuth2 ํ์ต์ ์ํ ์ํ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค๋ฉด์ ์ฌ์ฉํ๊ฒ๋ ๊ฐ ๋ชจ๋์์ ํ์ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง๋ฅผ ์ ์ฉํ๊ธฐ ์ํด์ Spring JDBC๋ฅผ ์ฌ์ฉํ ๋ถ๋ถ์ ๋ํด์ ์ ๋ฆฌํด๋ณด๊ณ ์ ํฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ๋ฐ์ดํฐ ์ก์ธ์ค์ ๋ํด์๋ Mybatis ๋๋ JPA ์ด๋ผ๋ ๊ธฐ์ ์ ๋์ ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ํ ๋ฐ ์คํ๋ง JDBC ๋ง์ผ๋ก๋ ์ถฉ๋ถํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ก์ธ์ค๊ฐ ๊ฐ๋ฅํ๋ฉฐ Spring Session ์ด๋ Spring Security ์์๋ JDBC ๊ธฐ๋ฐ์ผ๋ก ๊ด๋ จ ๊ธฐ๋ฅ์ ์ ๊ณตํ๊ณ ์์ต๋๋ค.
Data Access with JDBC
Spring JDBC๋ ๋ค์ํ ๋ฐฉ์์ผ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ํ ์ก์ธ์ค ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ฉฐ ์คํ๋ง ์ธ์ ์ด๋ ์คํ๋ง ์ํ๋ฆฌํฐ์ ํจ๊ป JDBC ๊ธฐ๋ฐ์ผ๋ก ๊ด๋ จ๋ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํด์๋ ๋ฐ๋์ Spring JDBC๊ฐ ํฌํจ๋์ด์ผ ํฉ๋๋ค. ์๋ง๋ ๋๋ถ๋ถ์ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ํ ์ ๊ทผ์ด ํ์์ ์ด๋ฏ๋ก ๋ค์๊ณผ ๊ฐ์ JDBC ๋ชจ๋์ ๋ฐ๋์ ํฌํจํ๊ณ ์์ ๊ฒ ์ ๋๋ค.
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
JdbcTemplate
์คํ๋ง ๋ถํธ์์๋ JdbcTemplateAutoConfiguration๋ฅผ ํตํด์ JdbcTemplate์ NamedParameterJdbcTemplate๋ฅผ ์๋์ผ๋ก ๋น์ผ๋ก ๊ตฌ์ฑํ๋ ๊ฒ์ ํ์ธํ ์ ์๋๋ฐ์. JdbcTemplate ๋ฟ๋ง ์๋๋ผ NamedParameterJdbcTemplate๋ฅผ ํจ๊ป ๊ตฌ์ฑํ๋ ์ด์ ๋ Spring Data JDBC์ ๊ฐ์ ๋ชจ๋์์ ๋ด๋ถ์ ์ผ๋ก ์ฌ์ฉํ๋๋ก ๋์ด์๊ธฐ ๋๋ฌธ์ด๋ผ๊ณ ์๊ฐ๋ฉ๋๋ค.
JdbcUserDetailsManager
์คํ๋ง ์ํ๋ฆฌํฐ์์ JdbcUserDetailsManager๋ JDBC ๊ธฐ๋ฐ์ ์ฌ์ฉ์ ์ธ์ฆ ๊ตฌํ์ ์ํด์ JdbcDaoSupport๋ฅผ ํ์ฅํ๋ฉฐ ๋ด๋ถ์ ์ผ๋ก JdbcTemplate๊ณผ RowMapper๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ผ๋ก ์์ฑ๋์ด ์์ต๋๋ค.
JdbcIndexedSessionRepository
์คํ๋ง ์ธ์ ์์์ JdbcHttpSessionConfiguration๋ JdbcTemplate๋ฅผ ํตํด์ JdbcIndexedSessionRepository๋ฅผ ๋น์ผ๋ก ๋ฑ๋กํ๊ฒ ๋ฉ๋๋ค. JdbcIndexedSessionRepository๋ ๋ด๋ถ์ ์ผ๋ก JdbcOperations๋ฅผ ์ฌ์ฉํ์ฌ SQL๋ฅผ ์ํํ๋๋ฐ ์ธ์ ์ ๋ํ ์ ํธ๋ฆฌ๋ทฐํธ๋ฅผ ์ ์ฅํ ๋ JDBC Batch Operations๋ฅผ ํ์ฉํ๋ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค.
JdbcTemplate๋ JdbcOperatrions ๊ตฌํ์ฒด์ ๋๋ค.
Stored Function with JDBC
ํ์ฌ ์กฐ์ง์์๋ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๋๋ Mybatis ๋๋ JPA๋ฅผ ๋์ ํ์ง ์๊ณ ์คํ ์ด๋ ํจ์(ํ๋ก์์ ์ ๋น์ทํ)๋ฅผ ์์ฑํด๋๊ณ ์คํ๋ง JDBC๋ฅผ ํตํด์ ํธ์ถํ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํ๊ณ ์์ต๋๋ค. ๋ ๊ฑฐ์ ์์คํ ์ ๊ฒฝํํ์ง ์์๊ฑฐ๋ Mybatis ๋๋ JPA๋ผ๋ ๊ธฐ์ ๋ง์ ์ ํ ๊ฐ๋ฐ์๋ค์ ๊ถ๊ธํ ์ ์๋ ๋ถ๋ถ์ด๊ธฐ๋ ํ ๊ฒ ๊ฐ์ต๋๋ค. ์ฐ์ ์๋์ ๊ฐ์ ํจ์๊ฐ PostgreSQL ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์๋์ด์๋ค๊ณ ๊ฐ์ ํ๊ฒ ์ต๋๋ค.
CREATE OR REPLACE FUNCTION users$find_by_username(v_username VARCHAR) RETURNS REFCURSOR AS
$$
DECLARE
rtn_cursor REFCURSOR := 'rtn_cursor';
BEGIN
OPEN rtn_cursor FOR
SELECT username, password, enabled from users where username = v_username;
RETURN rtn_cursor;
END;
$$ LANGUAGE plpgsql;
BEGIN;
select users$find_by_username('user');
FETCH ALL IN "rtn_cursor";
END;
StoredProcedure
์คํ๋ง JDBC์ GenericStoredProcedure๋ RDBMS์์ ์ง์ํ๋ ์คํ ์ด๋ ํ๋ก์์ ๋ฅผ ํธ์ถํ ์ ์๋๋ก ๊ตฌํ๋ ํด๋์ค์ ๋๋ค. ์๋์ ๊ฐ์ด ์คํ ์ด๋ ํจ์๋ช ์ ์ง์ ํ์ฌ ํ๋ผ๋ฏธํฐ์ ํจ๊ป ์ ๋ฌํ๋ฉด ํ๋ก์์ ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.
@DisplayName("Call stored function using GenericStoredProcedure")
@Test
void testCallFunctionWithStoredProcedure() {
String functionName = "users$find_by_username";
jdbcTemplate.setResultsMapCaseInsensitive(true);
GenericStoredProcedure storedProcedure = new GenericStoredProcedure();
storedProcedure.setJdbcTemplate(jdbcTemplate);
storedProcedure.setFunction(true);
storedProcedure.setSql(functionName);
storedProcedure.declareParameter(new SqlOutParameter("rtn_cursor", Types.REF_CURSOR, new ColumnMapRowMapper()));
storedProcedure.declareParameter(new SqlParameter("v_username", Types.VARCHAR));
Map<String, Object> inParams = new HashMap<>();
inParams.put("v_username", "user");
Map<String, Object> results = storedProcedure.execute(inParams);
if (results.containsKey("rtn_cursor")) {
Assertions.assertDoesNotThrow(() -> {
List<Object> cursors = (List<Object>) results.get("rtn_cursor");
for (Object cursor : cursors) {
log.info("{}", cursor);
}
});
}
}
SimpleJdbcXXXX
SimpleJdbcInsert์ SimpleJdbcCall์ JdbcTemplate๋ฅผ ์ฌ์ฉํ์ฌ ๋ช๊ฐ์ง ์ํฉ์ ๋ํด ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ํ๋ฒ์ ๋ง์ ์์ฑ ์์ ์ด ํ์ํ ๊ฒฝ์ฐ์ SimpleJdbcInsert๋ฅผ ์ฌ์ฉํ ์ ์๊ณ ์คํ ์ด๋ ํ๋ก์์ (Stored Procedure) ๋๋ ์คํ ์ด๋ ํจ์(Stored Function)๋ฅผ ํธ์ถํ๊ณ ์ ํ๋ ๊ฒฝ์ฐ์๋ SimpleJdbcCall์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
@DisplayName("Call stored function using SimpleJdbcCall")
@Test
void testCallFunctionWithSimpleJdbcCall() {
String functionName = "users$find_by_username";
MapSqlParameterSource sqlParameterSource = new MapSqlParameterSource();
sqlParameterSource.addValue("v_username", "user");
Map<String, Object> result = new SimpleJdbcCall(jdbcTemplate)
.withFunctionName(functionName)
.withoutProcedureColumnMetaDataAccess()
.declareParameters(new SqlParameter("v_username", Types.VARCHAR))
.returningResultSet("rtn_cursor", new ColumnMapRowMapper())
.execute(sqlParameterSource);
log.info("{}", result.get("rtn_cursor"));
}
GenericStoredProcedure์ ๋น๊ตํด์ ์กฐ๊ธ์ ์ฝ๋๊ฐ ๊ฐ๊ฒฐํจ์ ํ์ธํ ์ ์์ต๋๋ค.
JdbcTemplate
์คํ๋ง JDBC์์ ์ ๊ณตํ๋ ํด๋์ค๊ฐ ์๋๋๋ผ๋ ์คํ ์ด๋ ํ๋ก์์ ๋ฅผ ํธ์ถํ ์ ์์ต๋๋ค. JdbcTemplate์์ ์ปค๋ฅ์ ์ ๊ฐ์ ธ์จ ํ prepareCall์ ์ฌ์ฉํด์ ์ง์ ํธ์ถํ ๊ฒฐ๊ณผ๋ฅผ RowMapper๋ก ๋ณํํ ์ ์์ต๋๋ค.
@DisplayName("Call stored function using connection")
@Test
void testCallFunctionWithConnection() {
String functionName = "users$find_by_username";
Assertions.assertDoesNotThrow(() -> {
try (Connection connection = jdbcTemplate.getDataSource().getConnection();
CallableStatement statement = connection.prepareCall(String.format("{call %s(?)}", functionName))) {
statement.setString(1, "user");
ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) {
PgResultSet pgResultSet = (PgResultSet) resultSet.getObject(1);
RowMapperResultSetExtractor<Map<String, Object>> extractor = new RowMapperResultSetExtractor<>(new ColumnMapRowMapper());
log.info("{}", extractor.extractData(pgResultSet));
}
}
});
}
DataClassRowMapper ๋๋ BeanPropertyRowMapper๋ฅผ ์ฌ์ฉํด์ ๋ ๋ฒ์ฉ์ ์ธ ์ฝ๋๋ฅผ ์์ฑํ ์๋ ์์ต๋๋ค.
์ ์ฅ ํ๋ก์์ ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๋ํ ์ฅ์ ๋ ์กด์ฌํ๊ธฐ ๋๋ฌธ์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ ์ฅ ํ๋ก์์ ๋ฅผ ํธ์ถํ ์ ์๋ ๋ฐฉ๋ฒ์ ์๊ณ ์๋ ๊ฒ๋ ์ค์ํฉ๋๋ค. Spring JDBC์ ๋ํด์ ๋ค๋ฃจ๊ธฐ ๋๋ฌธ์ ์๊ฐํ์ง๋ ์์์ง๋ง JPA ๊ธฐ์ ์คํ์์๋ NamedStoredProcedureQuery์ ๊ฐ์ด ํ๋ก์์ ๋ฅผ ํธ์ถํ ์ ์๋๋ก ์ง์ํ๊ณ ์์ต๋๋ค.