From c33ad15b281530f204fef4add2a66b70566d1565 Mon Sep 17 00:00:00 2001 From: Mario Arias Date: Sun, 15 Jan 2017 22:51:04 +0000 Subject: [PATCH] Add Kotlin JDBC extensions Issue: SPR-15158 --- build.gradle | 2 + .../jdbc/core/JdbcOperationsExtensions.kt | 175 ++++++++++++++++ .../MapSqlParameterSourceExtensions.kt | 59 ++++++ .../core/JdbcOperationsExtensionsTests.kt | 189 ++++++++++++++++++ .../MapSqlParameterSourceExtensionsTests.kt | 51 +++++ 5 files changed, 476 insertions(+) create mode 100644 spring-jdbc/src/main/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensions.kt create mode 100644 spring-jdbc/src/main/kotlin/org/springframework/jdbc/core/namedparam/MapSqlParameterSourceExtensions.kt create mode 100644 spring-jdbc/src/test/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensionsTests.kt create mode 100644 spring-jdbc/src/test/kotlin/org/springframework/jdbc/core/namedparam/MapSqlParameterSourceExtensionsTests.kt diff --git a/build.gradle b/build.gradle index 7d6f22b8f2..e2e0cfab28 100644 --- a/build.gradle +++ b/build.gradle @@ -678,6 +678,8 @@ project("spring-jdbc") { optional("com.h2database:h2:1.4.193") optional("org.apache.derby:derby:10.13.1.1") optional("org.apache.derby:derbyclient:10.13.1.1") + optional("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}") + optional("org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}") } } diff --git a/spring-jdbc/src/main/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensions.kt b/spring-jdbc/src/main/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensions.kt new file mode 100644 index 0000000000..a3b27cca8d --- /dev/null +++ b/spring-jdbc/src/main/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensions.kt @@ -0,0 +1,175 @@ +/* + * Copyright 2002-2017 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.jdbc.core + +import java.sql.ResultSet +import kotlin.reflect.KClass + + +/** + * Extension for [JdbcOperations.queryForObject] providing a [KClass] based variant + * + * @author Mario Arias + * @since 5.0 + */ +fun JdbcOperations.queryForObject(sql: String, elementType: KClass): T = queryForObject(sql, elementType.java) + +/** + * Extension for [JdbcOperations.queryForObject] providing a `queryForObject("...")` variant + * + * @author Mario Arias + * @since 5.0 + */ +inline fun JdbcOperations.queryForObject(sql: String): T = queryForObject(sql, T::class.java) + +/** + * Extensions for [JdbcOperations.queryForObject] providing a RowMapper-like function variant: `queryForObject("...", arg1, argN){ rs, i -> }`. + * + * @author Mario Arias + * @since 5.0 + */ +fun JdbcOperations.queryForObject(sql: String, vararg args: Any, function: (ResultSet, Int) -> T): T = + queryForObject(sql, RowMapper { resultSet, i -> function(resultSet, i) }, *args) + +/** + * Extension for [JdbcOperations.queryForObject] providing a [KClass] based variant + * + * @author Mario Arias + * @since 5.0 + */ +fun JdbcOperations.queryForObject(sql: String, args: Array, argTypes: IntArray, requiredType: KClass): T = + queryForObject(sql, args, argTypes, requiredType.java) + +/** + * Extension for [JdbcOperations.queryForObject] providing a `queryForObject("...", arrayOf(arg1, argN), intArray(type1, typeN))` variant + * + * @author Mario Arias + * @since 5.0 + */ +inline fun JdbcOperations.queryForObject(sql: String, args: Array, argTypes: IntArray): T = + queryForObject(sql, args, argTypes, T::class.java) + +/** + * Extension for [JdbcOperations.queryForObject] providing a [KClass] based variant + * + * @author Mario Arias + * @since 5.0 + */ +fun JdbcOperations.queryForObject(sql: String, args: Array, requiredType: KClass): T = + queryForObject(sql, args, requiredType.java) + +/** + * Extension for [JdbcOperations.queryForObject] providing a `queryForObject("...", arrayOf(arg1, argN))` variant + * + * @author Mario Arias + * @since 5.0 + */ +inline fun JdbcOperations.queryForObject(sql: String, args: Array): T = + queryForObject(sql, args, T::class.java) + +/** + * Extension for [JdbcOperations.queryForObject] providing a [KClass] based variant + * + * @author Mario Arias + * @since 5.0 + */ +fun JdbcOperations.queryForObject(sql: String, requiredType: KClass, vararg args: Any): T = + queryForObject(sql, requiredType.java, *args) + + +/** + * Extension for [JdbcOperations.queryForList] providing a [KClass] based variant. + * + * @author Mario Arias + * @since 5.0 + */ +fun JdbcOperations.queryForList(sql: String, elementType: KClass): List = + queryForList(sql, elementType.java) + +/** + * Extension for [JdbcOperations.queryForList] providing a `queryForList("...")` variant. + * + * @author Mario Arias + * @since 5.0 + */ +inline fun JdbcOperations.queryForList(sql: String): List = + queryForList(sql, T::class.java) + +/** + * Extension for [JdbcOperations.queryForList] providing a [KClass] based variant. + * + * @author Mario Arias + * @since 5.0 + */ +fun JdbcOperations.queryForList(sql: String, args: Array, argTypes: IntArray, elementType: KClass): List = + queryForList(sql, args, argTypes, elementType.java) + +/** + * Extension for [JdbcOperations.queryForList] providing a `queryForList("...", arrayOf(arg1, argN), intArray(type1, typeN))` variant + * + * @author Mario Arias + * @since 5.0 + */ +inline fun JdbcOperations.queryForList(sql: String, args: Array, argTypes: IntArray): List = + queryForList(sql, args, argTypes, T::class.java) + +/** + * Extension for [JdbcOperations.queryForList] providing a [KClass] based variant. + * + * @author Mario Arias + * @since 5.0 + */ +fun JdbcOperations.queryForList(sql: String, args: Array, elementType: KClass): List = + queryForList(sql, args, elementType.java) + +/** + * Extension for [JdbcOperations.queryForList] providing a `queryForList("...", arrayOf(arg1, argN))` variant + * + * @author Mario Arias + * @since 5.0 + */ +inline fun JdbcOperations.queryForList(sql: String, args: Array): List = + queryForList(sql, args, T::class.java) + + +/** + * Extension for [JdbcOperations.query] providing a ResultSetExtractor-like function variant: `query("...", arg1, argN){ rs -> }` + * + * @author Mario Arias + * @since 5.0 + */ +inline fun JdbcOperations.query(sql: String, vararg args: Any, crossinline function: (ResultSet) -> T): T = + query(sql, ResultSetExtractor { function(it) }, *args) + +/** + * Extension for [JdbcOperations.query] providing a RowCallbackHandler-like function variant: `query("...", arg1, argN){ rs -> }` + * + * @author Mario Arias + * @since 5.0 + */ +fun JdbcOperations.query(sql: String, vararg args: Any, function: (ResultSet) -> Unit): Unit = + query(sql, RowCallbackHandler { function(it) }, *args) + +/** + * Extensions for [JdbcOperations.query] providing a RowMapper-like function variant: `query("...", arg1, argN){ rs, i -> }`. + * + * @author Mario Arias + * @since 5.0 + */ +fun JdbcOperations.query(sql: String, vararg args: Any, function: (ResultSet, Int) -> T): List = + query(sql, RowMapper { rs, i -> function(rs, i) }, *args) + diff --git a/spring-jdbc/src/main/kotlin/org/springframework/jdbc/core/namedparam/MapSqlParameterSourceExtensions.kt b/spring-jdbc/src/main/kotlin/org/springframework/jdbc/core/namedparam/MapSqlParameterSourceExtensions.kt new file mode 100644 index 0000000000..b2e0e452d1 --- /dev/null +++ b/spring-jdbc/src/main/kotlin/org/springframework/jdbc/core/namedparam/MapSqlParameterSourceExtensions.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2017 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.jdbc.core.namedparam + +/** + * Extension for [MapSqlParameterSource.addValue] providing Array like setter + * + * ```kotlin + * source["age"] = 3 + * ``` + * @author Mario Arias + * @since 5.0 + * + */ +operator fun MapSqlParameterSource.set(paramName: String, value: Any) { + this.addValue(paramName, value) +} + +/** + * Extension for [MapSqlParameterSource.addValue] providing Array like setter + * + * ```kotlin + * source["age", JDBCType.INTEGER.vendorTypeNumber] = 3 + * ``` + * @author Mario Arias + * @since 5.0 + * + */ +operator fun MapSqlParameterSource.set(paramName: String, sqlType: Int, value: Any) { + this.addValue(paramName, value, sqlType) +} + +/** + * Extension for [MapSqlParameterSource.addValue] providing Array like setter + * + * ```kotlin + * source["age", JDBCType.INTEGER.vendorTypeNumber, "INT"] = 3 + * ``` + * @author Mario Arias + * @since 5.0 + * + */ +operator fun MapSqlParameterSource.set(paramName: String, sqlType: Int, typeName: String, value: Any) { + this.addValue(paramName, value, sqlType, typeName) +} \ No newline at end of file diff --git a/spring-jdbc/src/test/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensionsTests.kt b/spring-jdbc/src/test/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensionsTests.kt new file mode 100644 index 0000000000..9b470c4aa4 --- /dev/null +++ b/spring-jdbc/src/test/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensionsTests.kt @@ -0,0 +1,189 @@ +/* + * Copyright 2002-2017 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.jdbc.core + + +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException +import org.mockito.ArgumentMatchers.anyString +import org.mockito.BDDMockito.given +import org.mockito.Mockito.mock +import java.sql.* +import javax.sql.DataSource + +/** + * Mock object based tests for JdbcOperationsExtension + */ +class JdbcOperationsExtensionsTests { + + @Rule + @JvmField + val thrown = ExpectedException.none()!! + + lateinit private var connection: Connection + lateinit private var dataSource: DataSource + lateinit private var preparedStatement: PreparedStatement + lateinit private var statement: Statement + lateinit private var resultSet: ResultSet + lateinit private var template: JdbcTemplate + lateinit private var callableStatement: CallableStatement + lateinit private var resultSetMetaData: ResultSetMetaData + + @Before + fun setup() { + connection = mock(Connection::class.java) + dataSource = mock(DataSource::class.java) + preparedStatement = mock(PreparedStatement::class.java) + statement = mock(Statement::class.java) + resultSet = mock(ResultSet::class.java) + callableStatement = mock(CallableStatement::class.java) + resultSetMetaData = mock(ResultSetMetaData::class.java) + template = JdbcTemplate(dataSource) + given(dataSource.connection).willReturn(connection) + given(connection.prepareStatement(anyString())).willReturn(preparedStatement) + given(preparedStatement.executeQuery()).willReturn(resultSet) + given(preparedStatement.executeQuery(anyString())).willReturn(resultSet) + given(preparedStatement.connection).willReturn(connection) + given(statement.connection).willReturn(connection) + given(statement.executeQuery(anyString())).willReturn(resultSet) + given(connection.prepareCall(anyString())).willReturn(callableStatement) + given(connection.createStatement()).willReturn(statement) + given(callableStatement.resultSet).willReturn(resultSet) + given(resultSetMetaData.columnCount).willReturn(1) + given(resultSetMetaData.getColumnName(1)).willReturn("age") + given(resultSet.metaData).willReturn(resultSetMetaData) + given(resultSet.next()).willReturn(true, false) + given(resultSet.getInt(1)).willReturn(22) + } + + + @Test + fun `queryForObject with KClass`() { + val i = template.queryForObject("select age from customer where id = 3", Int::class) + assertEquals(22, i) + } + + @Test + fun `queryForObject with reified type`() { + val i: Int = template.queryForObject("select age from customer where id = 3") + assertEquals(22, i) + } + + @Test + fun `queryForObject with RowMapper-like function`() { + val i = template.queryForObject("select age from customer where id = ?", 3) { rs, i -> + rs.getInt(1) + } + assertEquals(22, i) + } + + @Test + fun `queryForObject with argTypes`() { + val i = template.queryForObject("select age from customer where id = ?", arrayOf(3), + intArrayOf(JDBCType.INTEGER.vendorTypeNumber), Int::class) + assertEquals(22, i) + } + + @Test + fun `queryForObject with reified type and argTypes`() { + val i: Int = template.queryForObject("select age from customer where id = ?", arrayOf(3), + intArrayOf(JDBCType.INTEGER.vendorTypeNumber)) + assertEquals(22, i) + } + + @Test + fun `queryForObject with args`() { + val i = template.queryForObject("select age from customer where id = ?", arrayOf(3), Int::class) + assertEquals(22, i) + } + + @Test + fun `queryForObject with reified type and args`() { + val i: Int = template.queryForObject("select age from customer where id = ?", arrayOf(3)) + assertEquals(22, i) + } + + @Test + fun `queryForObject with varargs`() { + val i = template.queryForObject("select age from customer where id = ?", Int::class, 3) + assertEquals(22, i) + } + + @Test + fun `queryForList with KClass`() { + val i = template.queryForList(sql = "select age from customer where id = 3", elementType = Int::class) + assertEquals(22, i.first()) + } + + @Test + fun `queryForList with reified type`() { + val i = template.queryForList("select age from customer where id = 3") + assertEquals(22, i.first()) + } + + @Test + fun `queryForList with argTypes`() { + val i = template.queryForList(sql = "select age from customer where id = ?", args = arrayOf(3), + argTypes = intArrayOf(JDBCType.INTEGER.vendorTypeNumber), elementType = Int::class) + assertEquals(22, i.first()) + } + + @Test + fun `queryForList with reified type and argTypes`() { + val i = template.queryForList("select age from customer where id = ?", arrayOf(3), intArrayOf(JDBCType.INTEGER.vendorTypeNumber)) + assertEquals(22, i.first()) + } + + @Test + fun `queryForList with args`() { + val i = template.queryForList(sql = "select age from customer where id = ?", args = arrayOf(3), elementType = Int::class) + assertEquals(22, i.first()) + } + + @Test + fun `queryForList with reified type and args`() { + val i = template.queryForList("select age from customer where id = ?", arrayOf(3)) + assertEquals(22, i.first()) + } + + @Test + fun `query with ResultSetExtractor-like function`() { + val i = template.query("select age from customer where id = ?", 3) { rs -> + rs.next() + rs.getInt(1) + } + assertEquals(22, i) + } + + @Test + fun `query with RowCallbackHandler-like function`() { + template.query("select age from customer where id = ?", 3) { rs -> + assertEquals(22, rs.getInt(1)) + } + } + + @Test + fun `query with RowMapper-like function`() { + val i = template.query("select age from customer where id = ?", 3) { rs, i -> + rs.getInt(1) + } + assertEquals(22, i.first()) + } +} \ No newline at end of file diff --git a/spring-jdbc/src/test/kotlin/org/springframework/jdbc/core/namedparam/MapSqlParameterSourceExtensionsTests.kt b/spring-jdbc/src/test/kotlin/org/springframework/jdbc/core/namedparam/MapSqlParameterSourceExtensionsTests.kt new file mode 100644 index 0000000000..5ab31a25f9 --- /dev/null +++ b/spring-jdbc/src/test/kotlin/org/springframework/jdbc/core/namedparam/MapSqlParameterSourceExtensionsTests.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2017 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.jdbc.core.namedparam + +import org.junit.Assert.assertEquals +import org.junit.Test +import java.sql.JDBCType + +/** + * @author Mario Arias + */ +class MapSqlParameterSourceExtensionsTests { + + @Test + fun `setter with value`() { + val source = MapSqlParameterSource() + source["foo"] = 2 + assertEquals(2, source.getValue("foo")) + } + + @Test + fun `setter with value and type`() { + val source = MapSqlParameterSource() + source["foo", JDBCType.INTEGER.vendorTypeNumber] = 2 + assertEquals(2, source.getValue("foo")) + assertEquals(JDBCType.INTEGER.vendorTypeNumber, source.getSqlType("foo")) + } + + @Test + fun `setter with value, type and type name`() { + val source = MapSqlParameterSource() + source["foo", JDBCType.INTEGER.vendorTypeNumber, "INT"] = 2 + assertEquals(2, source.getValue("foo")) + assertEquals(JDBCType.INTEGER.vendorTypeNumber, source.getSqlType("foo")) + assertEquals("INT", source.getTypeName("foo")) + } +} \ No newline at end of file