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์ ๊ฐ์ด ํ๋ก์์ ๋ฅผ ํธ์ถํ ์ ์๋๋ก ์ง์ํ๊ณ ์์ต๋๋ค.