diff --git a/framework-docs/framework-docs.gradle b/framework-docs/framework-docs.gradle index 60c852fc978..579a88cd243 100644 --- a/framework-docs/framework-docs.gradle +++ b/framework-docs/framework-docs.gradle @@ -53,6 +53,7 @@ dependencies { api(project(":spring-websocket")) api("org.jetbrains.kotlin:kotlin-stdlib") + api("com.oracle.database.jdbc:ojdbc11") api("jakarta.jms:jakarta.jms-api") api("jakarta.servlet:jakarta.servlet-api") api("org.apache.commons:commons-dbcp2:2.11.0") diff --git a/framework-docs/modules/ROOT/pages/data-access/jdbc/parameter-handling.adoc b/framework-docs/modules/ROOT/pages/data-access/jdbc/parameter-handling.adoc index b0035ea9546..f7df5a755f5 100644 --- a/framework-docs/modules/ROOT/pages/data-access/jdbc/parameter-handling.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/jdbc/parameter-handling.adoc @@ -209,141 +209,26 @@ are passed in as a parameter to the stored procedure. The `SqlReturnType` interface has a single method (named `getTypeValue`) that must be implemented. This interface is used as part of the declaration of an `SqlOutParameter`. -The following example shows returning the value of an Oracle `STRUCT` object of the user +The following example shows returning the value of a `java.sql.Struct` object of the user declared type `ITEM_TYPE`: -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes",role="primary"] ----- - 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; - })); - // ... - } ----- - -Kotlin:: -+ -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] ----- - class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() { - - init { - // ... - declareParameter(SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE") { cs, colIndx, sqlType, typeName -> - val struct = cs.getObject(colIndx) as STRUCT - val attr = struct.getAttributes() - TestItem((attr[0] as Long, attr[1] as String, attr[2] as Date) - }) - // ... - } - } ----- -====== +include-code::./TestItemStoredProcedure[] You can use `SqlTypeValue` to pass the value of a Java object (such as `TestItem`) to a stored procedure. The `SqlTypeValue` interface has a single method (named `createTypeValue`) that you must implement. The active connection is passed in, and you -can use it to create database-specific objects, such as `StructDescriptor` instances -or `ArrayDescriptor` instances. The following example creates a `StructDescriptor` instance: +can use it to create database-specific objects, such as `java.sql.Struct` instances +or `java.sql.Array` instances. The following example creates a `java.sql.Struct` instance: -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes",role="primary"] ----- - 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; - } - }; ----- - -Kotlin:: -+ -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] ----- - val (id, description, expirationDate) = TestItem(123L, "A test item", - SimpleDateFormat("yyyy-M-d").parse("2010-12-31")) - - val value = object : AbstractSqlTypeValue() { - override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any { - val itemDescriptor = StructDescriptor(typeName, conn) - return STRUCT(itemDescriptor, conn, - arrayOf(id, description, java.sql.Date(expirationDate.time))) - } - } ----- -====== +include-code::./SqlTypeValueFactory[tag=struct,indent=0] You can now add this `SqlTypeValue` to the `Map` that contains the input parameters for the `execute` call of the stored procedure. Another use for the `SqlTypeValue` is passing in an array of values to an Oracle stored -procedure. Oracle has its own internal `ARRAY` class that must be used in this case, and -you can use the `SqlTypeValue` to create an instance of the Oracle `ARRAY` and populate -it with values from the Java `ARRAY`, as the following example shows: - -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes",role="primary"] ----- - 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; - } - }; ----- - -Kotlin:: -+ -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] ----- - class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() { - - init { - val ids = arrayOf(1L, 2L) - val value = object : AbstractSqlTypeValue() { - override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any { - val arrayDescriptor = ArrayDescriptor(typeName, conn) - return ARRAY(arrayDescriptor, conn, ids) - } - } - } - } ----- -====== - +procedure. Oracle has an `createOracleArray` method on `OracleConnection` that you can +access by unwrapping it. You can use the `SqlTypeValue` to create an array and populate +it with values from the Java `java.sql.Array`, as the following example shows: +include-code::./SqlTypeValueFactory[tag=oracle-array,indent=0] diff --git a/framework-docs/modules/ROOT/pages/data-access/jdbc/simple.adoc b/framework-docs/modules/ROOT/pages/data-access/jdbc/simple.adoc index 9e0c0200a17..177249aa352 100644 --- a/framework-docs/modules/ROOT/pages/data-access/jdbc/simple.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/jdbc/simple.adoc @@ -323,12 +323,12 @@ use these alternative input classes. The `SimpleJdbcCall` class uses metadata in the database to look up names of `in` and `out` parameters so that you do not have to explicitly declare them. You can -declare parameters if you prefer to do that or if you have parameters (such as `ARRAY` -or `STRUCT`) that do not have an automatic mapping to a Java class. The first example -shows a simple procedure that returns only scalar values in `VARCHAR` and `DATE` format -from a MySQL database. The example procedure reads a specified actor entry and returns -`first_name`, `last_name`, and `birth_date` columns in the form of `out` parameters. -The following listing shows the first example: +declare parameters if you prefer to do that or if you have parameters that do not +have an automatic mapping to a Java class. The first example shows a simple procedure +that returns only scalar values in `VARCHAR` and `DATE` format from a MySQL database. +The example procedure reads a specified actor entry and returns `first_name`, +`last_name`, and `birth_date` columns in the form of `out` parameters. The following +listing shows the first example: [source,sql,indent=0,subs="verbatim,quotes"] ---- diff --git a/framework-docs/src/main/java/org/springframework/docs/dataaccess/jdbc/jdbccomplextypes/SqlTypeValueFactory.java b/framework-docs/src/main/java/org/springframework/docs/dataaccess/jdbc/jdbccomplextypes/SqlTypeValueFactory.java new file mode 100644 index 00000000000..bdcfc4b2323 --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/dataaccess/jdbc/jdbccomplextypes/SqlTypeValueFactory.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2024 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 + * + * https://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.docs.dataaccess.jdbc.jdbccomplextypes; + +import java.sql.Connection; +import java.sql.SQLException; +import java.text.ParseException; +import java.text.SimpleDateFormat; + +import oracle.jdbc.driver.OracleConnection; + +import org.springframework.jdbc.core.SqlTypeValue; +import org.springframework.jdbc.core.support.AbstractSqlTypeValue; + +@SuppressWarnings("unused") +class SqlTypeValueFactory { + + void createStructSample() throws ParseException { + // tag::struct[] + 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 connection, int sqlType, String typeName) throws SQLException { + Object[] item = new Object[] { testItem.getId(), testItem.getDescription(), + new java.sql.Date(testItem.getExpirationDate().getTime()) }; + return connection.createStruct(typeName, item); + } + }; + // end::struct[] + } + + void createOracleArray() { + // tag::oracle-array[] + Long[] ids = new Long[] {1L, 2L}; + + SqlTypeValue value = new AbstractSqlTypeValue() { + protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException { + return conn.unwrap(OracleConnection.class).createOracleArray(typeName, ids); + } + }; + // end::oracle-array[] + } + +} diff --git a/framework-docs/src/main/java/org/springframework/docs/dataaccess/jdbc/jdbccomplextypes/TestItem.java b/framework-docs/src/main/java/org/springframework/docs/dataaccess/jdbc/jdbccomplextypes/TestItem.java new file mode 100644 index 00000000000..b0968b877f8 --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/dataaccess/jdbc/jdbccomplextypes/TestItem.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2024 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 + * + * https://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.docs.dataaccess.jdbc.jdbccomplextypes; + +import java.util.Date; + +class TestItem { + + private Long id; + + private String description; + + private Date expirationDate; + + public TestItem() { + } + + public TestItem(Long id, String description, Date expirationDate) { + this.id = id; + this.description = description; + this.expirationDate = expirationDate; + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getExpirationDate() { + return this.expirationDate; + } + + public void setExpirationDate(Date expirationDate) { + this.expirationDate = expirationDate; + } +} diff --git a/framework-docs/src/main/java/org/springframework/docs/dataaccess/jdbc/jdbccomplextypes/TestItemStoredProcedure.java b/framework-docs/src/main/java/org/springframework/docs/dataaccess/jdbc/jdbccomplextypes/TestItemStoredProcedure.java new file mode 100644 index 00000000000..1343dcfbaee --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/dataaccess/jdbc/jdbccomplextypes/TestItemStoredProcedure.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2024 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 + * + * https://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.docs.dataaccess.jdbc.jdbccomplextypes; + +import java.sql.CallableStatement; +import java.sql.Struct; +import java.sql.Types; + +import javax.sql.DataSource; + +import org.springframework.jdbc.core.SqlOutParameter; +import org.springframework.jdbc.object.StoredProcedure; + +@SuppressWarnings("unused") +public class TestItemStoredProcedure extends StoredProcedure { + + public TestItemStoredProcedure(DataSource dataSource) { + super(dataSource, "get_item"); + declareParameter(new SqlOutParameter("item", Types.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; + })); + // ... + } + +} \ No newline at end of file diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/dataaccess/jdbc/jdbccomplextypes/SqlTypeValueFactory.kt b/framework-docs/src/main/kotlin/org/springframework/docs/dataaccess/jdbc/jdbccomplextypes/SqlTypeValueFactory.kt new file mode 100644 index 00000000000..808f57895fe --- /dev/null +++ b/framework-docs/src/main/kotlin/org/springframework/docs/dataaccess/jdbc/jdbccomplextypes/SqlTypeValueFactory.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2024 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 + * + * https://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.docs.dataaccess.jdbc.jdbccomplextypes + +import oracle.jdbc.driver.OracleConnection +import org.springframework.jdbc.core.SqlTypeValue +import org.springframework.jdbc.core.support.AbstractSqlTypeValue +import java.sql.Connection +import java.sql.Date +import java.text.SimpleDateFormat + +@Suppress("unused") +class SqlTypeValueFactory { + + fun createStructSample(): AbstractSqlTypeValue { + // tag::struct[] + val testItem = TestItem(123L, "A test item", + SimpleDateFormat("yyyy-M-d").parse("2010-12-31")) + + val value = object : AbstractSqlTypeValue() { + override fun createTypeValue(connection: Connection, sqlType: Int, typeName: String?): Any { + val item = arrayOf(testItem.id, testItem.description, + Date(testItem.expirationDate.time)) + return connection.createStruct(typeName, item) + } + } + // end::struct[] + return value + } + + fun createOracleArray() : SqlTypeValue { + // tag::oracle-array[] + val ids = arrayOf(1L, 2L) + val value: SqlTypeValue = object : AbstractSqlTypeValue() { + override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any { + return conn.unwrap(OracleConnection::class.java).createOracleArray(typeName, ids) + } + } + // end::oracle-array[] + return value + } + +} \ No newline at end of file diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/dataaccess/jdbc/jdbccomplextypes/TestItemStoredProcedure.kt b/framework-docs/src/main/kotlin/org/springframework/docs/dataaccess/jdbc/jdbccomplextypes/TestItemStoredProcedure.kt new file mode 100644 index 00000000000..ac0c648ba6f --- /dev/null +++ b/framework-docs/src/main/kotlin/org/springframework/docs/dataaccess/jdbc/jdbccomplextypes/TestItemStoredProcedure.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2024 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 + * + * https://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.docs.dataaccess.jdbc.jdbccomplextypes + +import org.springframework.jdbc.core.SqlOutParameter +import org.springframework.jdbc.`object`.StoredProcedure +import java.sql.CallableStatement +import java.sql.Struct +import java.sql.Types +import java.util.Date +import javax.sql.DataSource + +@Suppress("unused") +class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure(dataSource, "get_item") { + init { + declareParameter(SqlOutParameter("item",Types.STRUCT,"ITEM_TYPE") { + cs: CallableStatement, colIndx: Int, _: Int, _: String? -> + val struct = cs.getObject(colIndx) as Struct + val attr = struct.attributes + val item = TestItem() + item.id = (attr[0] as Number).toLong() + item.description = attr[1] as String + item.expirationDate = attr[2] as Date + item + }) + // ... + } +} \ No newline at end of file diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index cd1824689de..94924e7d6bd 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -34,6 +34,7 @@ dependencies { api("com.google.protobuf:protobuf-java-util:4.27.1") api("com.h2database:h2:2.2.224") api("com.jayway.jsonpath:json-path:2.9.0") + api("com.oracle.database.jdbc:ojdbc11:21.9.0.0") api("com.rometools:rome:1.19.0") api("com.squareup.okhttp3:mockwebserver:3.14.9") api("com.squareup.okhttp3:okhttp:3.14.9")