From bb1db0243f0bce3a2ad48e3ffd6cf6370e4f2fa8 Mon Sep 17 00:00:00 2001 From: Dirk Olmes Date: Wed, 22 May 2019 09:50:17 +0200 Subject: [PATCH 1/6] Improve ArrayTypeHandler ArrayTypeHandler unwraps the contents of the java.sql.Array when fetching data. Upon insertion it expects an instance of java.sql.Array as the parameter. This changeset improves ArrayTypeHandler so that it accepts an Object[] parameter for insert, constructing the java.sql.Array object if necessary. This aproach relies on the jdbcType of the column - it must reflect the array element's type. For setting NULL values on ARRAY columns I had to refactor BaseTypeHandler a bit - but I'm quite sure that this won't break anything. --- .../apache/ibatis/type/ArrayTypeHandler.java | 16 +++- .../apache/ibatis/type/BaseTypeHandler.java | 6 +- .../array_type_handler/BaseTest.java | 84 +++++++++++++++++++ .../submitted/array_type_handler/CreateDB.sql | 23 +++++ .../submitted/array_type_handler/Mapper.java | 29 +++++++ .../submitted/array_type_handler/Mapper.xml | 41 +++++++++ .../submitted/array_type_handler/User.java | 46 ++++++++++ .../array_type_handler/mybatis-config.xml | 40 +++++++++ .../ibatis/type/ArrayTypeHandlerTest.java | 7 ++ 9 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 src/test/java/org/apache/ibatis/submitted/array_type_handler/BaseTest.java create mode 100644 src/test/java/org/apache/ibatis/submitted/array_type_handler/CreateDB.sql create mode 100644 src/test/java/org/apache/ibatis/submitted/array_type_handler/Mapper.java create mode 100644 src/test/java/org/apache/ibatis/submitted/array_type_handler/Mapper.xml create mode 100644 src/test/java/org/apache/ibatis/submitted/array_type_handler/User.java create mode 100644 src/test/java/org/apache/ibatis/submitted/array_type_handler/mybatis-config.xml diff --git a/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java b/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java index 9b1ae64e782..2df4bbd86ce 100644 --- a/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java +++ b/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java @@ -20,6 +20,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Types; /** * @author Clinton Begin @@ -30,9 +31,22 @@ public ArrayTypeHandler() { super(); } + @Override + protected void setNullParameter(PreparedStatement ps, int i, JdbcType jdbcType) throws SQLException { + ps.setNull(i, Types.ARRAY); + } + @Override public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException { - ps.setArray(i, (Array) parameter); + Array array = null; + if (parameter instanceof Array) { + array = (Array)parameter; + } + else { + Object[] values = (Object[])parameter; + array = ps.getConnection().createArrayOf(jdbcType.name(), values); + } + ps.setArray(i, array); } @Override diff --git a/src/main/java/org/apache/ibatis/type/BaseTypeHandler.java b/src/main/java/org/apache/ibatis/type/BaseTypeHandler.java index 5b3bc8dbde3..393907f4971 100644 --- a/src/main/java/org/apache/ibatis/type/BaseTypeHandler.java +++ b/src/main/java/org/apache/ibatis/type/BaseTypeHandler.java @@ -58,7 +58,7 @@ public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbc throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters."); } try { - ps.setNull(i, jdbcType.TYPE_CODE); + setNullParameter(ps, i, jdbcType); } catch (SQLException e) { throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " @@ -75,6 +75,10 @@ public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbc } } + protected void setNullParameter(PreparedStatement ps, int i, JdbcType jdbcType) throws SQLException { + ps.setNull(i, jdbcType.TYPE_CODE); + } + @Override public T getResult(ResultSet rs, String columnName) throws SQLException { try { diff --git a/src/test/java/org/apache/ibatis/submitted/array_type_handler/BaseTest.java b/src/test/java/org/apache/ibatis/submitted/array_type_handler/BaseTest.java new file mode 100644 index 00000000000..dca665c8797 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/array_type_handler/BaseTest.java @@ -0,0 +1,84 @@ +/** + * Copyright 2019 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.apache.ibatis.submitted.array_type_handler; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.io.Reader; + +import org.apache.ibatis.BaseDataTest; +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class BaseTest { + + private SqlSessionFactory sqlSessionFactory; + + @BeforeEach + public void setUp() throws Exception { + try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/array_type_handler/mybatis-config.xml")) { + sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); + } + + BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), + "org/apache/ibatis/submitted/array_type_handler/CreateDB.sql"); + } + + @Test + public void shouldInsertArrayValue() throws Exception { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + User user = new User(); + user.setId(1); + user.setName("User 1"); + user.setNicknames(new String[] { "User", "one" }); + + Mapper mapper = sqlSession.getMapper(Mapper.class); + mapper.insert(user); + sqlSession.commit(); + + int usersInDatabase = mapper.getUserCount(); + assertEquals(1, usersInDatabase); + + Integer nicknameCount = mapper.getNicknameCount(); + assertEquals(2, nicknameCount); + } + } + + @Test + public void shouldInsertNullValue() throws Exception { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + User user = new User(); + user.setId(1); + user.setName("User 1"); + // note how the user does not have nicknames + + Mapper mapper = sqlSession.getMapper(Mapper.class); + mapper.insert(user); + sqlSession.commit(); + + int usersInDatabase = mapper.getUserCount(); + assertEquals(1, usersInDatabase); + + Integer nicknameCount = mapper.getNicknameCount(); + assertNull(nicknameCount); + } + } +} diff --git a/src/test/java/org/apache/ibatis/submitted/array_type_handler/CreateDB.sql b/src/test/java/org/apache/ibatis/submitted/array_type_handler/CreateDB.sql new file mode 100644 index 00000000000..b0c3aaf63d5 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/array_type_handler/CreateDB.sql @@ -0,0 +1,23 @@ +-- +-- Copyright 2019 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. +-- + +drop table users if exists; + +create table users ( + id int, + name varchar(20), + nicknames varchar(20) array +); diff --git a/src/test/java/org/apache/ibatis/submitted/array_type_handler/Mapper.java b/src/test/java/org/apache/ibatis/submitted/array_type_handler/Mapper.java new file mode 100644 index 00000000000..838021af9f8 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/array_type_handler/Mapper.java @@ -0,0 +1,29 @@ +/** + * Copyright 2019 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.apache.ibatis.submitted.array_type_handler; + +public interface Mapper { + + void insert(User user); + + int getUserCount(); + + /** + * HSQL returns NULL when asked for the cardinality of an array column + * with NULL value :-( + */ + Integer getNicknameCount(); +} diff --git a/src/test/java/org/apache/ibatis/submitted/array_type_handler/Mapper.xml b/src/test/java/org/apache/ibatis/submitted/array_type_handler/Mapper.xml new file mode 100644 index 00000000000..46a523b2520 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/array_type_handler/Mapper.xml @@ -0,0 +1,41 @@ + + + + + + + + insert into users + (id, name, nicknames) + values + (#{id}, #{name}, #{nicknames,jdbcType=VARCHAR,typeHandler=org.apache.ibatis.type.ArrayTypeHandler}) + + + + + + + diff --git a/src/test/java/org/apache/ibatis/submitted/array_type_handler/User.java b/src/test/java/org/apache/ibatis/submitted/array_type_handler/User.java new file mode 100644 index 00000000000..14779c54aca --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/array_type_handler/User.java @@ -0,0 +1,46 @@ +/** + * Copyright 2019 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.apache.ibatis.submitted.array_type_handler; + +public class User { + + private Integer id; + private String name; + private String[] nicknames; + + public Integer getId() { + return id; + } + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String[] getNicknames() { + return nicknames; + } + + public void setNicknames(String[] nicknames) { + this.nicknames = nicknames; + } +} diff --git a/src/test/java/org/apache/ibatis/submitted/array_type_handler/mybatis-config.xml b/src/test/java/org/apache/ibatis/submitted/array_type_handler/mybatis-config.xml new file mode 100644 index 00000000000..71dcc1387b2 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/array_type_handler/mybatis-config.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java b/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java index 13e2919fad3..bb9ccd90451 100644 --- a/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java +++ b/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java @@ -19,6 +19,7 @@ import org.mockito.Mock; import java.sql.Array; +import java.sql.Types; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -38,6 +39,12 @@ public void shouldSetParameter() throws Exception { TYPE_HANDLER.setParameter(ps, 1, mockArray, null); verify(ps).setArray(1, mockArray); } + + @Test + public void shouldSetNullParameter() throws Exception { + TYPE_HANDLER.setParameter(ps, 1, null, JdbcType.ARRAY); + verify(ps).setNull(1, Types.ARRAY); + } @Override @Test From 8e8e5565c47e8cf45e0634e82354049f7769a783 Mon Sep 17 00:00:00 2001 From: Dirk Olmes Date: Thu, 23 May 2019 03:32:32 +0200 Subject: [PATCH 2/6] Check that the parameter is actually a java array --- .../java/org/apache/ibatis/type/ArrayTypeHandler.java | 3 +++ .../java/org/apache/ibatis/type/ArrayTypeHandlerTest.java | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java b/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java index 2df4bbd86ce..1550e84177f 100644 --- a/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java +++ b/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java @@ -43,6 +43,9 @@ public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, J array = (Array)parameter; } else { + if (!parameter.getClass().isArray()) { + throw new TypeException("ArrayType Handler requires SQL array or java array parameter and does not support type " + parameter.getClass()); + } Object[] values = (Object[])parameter; array = ps.getConnection().createArrayOf(jdbcType.name(), values); } diff --git a/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java b/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java index bb9ccd90451..fa295549704 100644 --- a/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java +++ b/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -46,6 +47,13 @@ public void shouldSetNullParameter() throws Exception { verify(ps).setNull(1, Types.ARRAY); } + @Test + public void shouldFailForNonArrayParameter() { + assertThrows(TypeException.class, () -> { + TYPE_HANDLER.setParameter(ps, 1, "unsupported parameter type", null); + }); + } + @Override @Test public void shouldGetResultFromResultSetByName() throws Exception { From ebe6457ff028b2a8308dbdaa31c08424b2651e90 Mon Sep 17 00:00:00 2001 From: Dirk Olmes Date: Fri, 24 May 2019 07:42:17 +0200 Subject: [PATCH 3/6] Free the array after it has been set to the prepared statement --- src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java | 1 + src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java b/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java index 1550e84177f..8e0a07cf725 100644 --- a/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java +++ b/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java @@ -50,6 +50,7 @@ public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, J array = ps.getConnection().createArrayOf(jdbcType.name(), values); } ps.setArray(i, array); + array.free(); } @Override diff --git a/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java b/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java index fa295549704..e59dd414c29 100644 --- a/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java +++ b/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java @@ -39,6 +39,7 @@ class ArrayTypeHandlerTest extends BaseTypeHandlerTest { public void shouldSetParameter() throws Exception { TYPE_HANDLER.setParameter(ps, 1, mockArray, null); verify(ps).setArray(1, mockArray); + verify(mockArray).free(); } @Test From 6d5db132706e0ca95e75ede1e2cc56c441b03879 Mon Sep 17 00:00:00 2001 From: Dirk Olmes Date: Mon, 27 May 2019 07:44:50 +0200 Subject: [PATCH 4/6] Only free() the Array if it was created by us. We leave the management of user supplied Arrays to the user. --- .../java/org/apache/ibatis/type/ArrayTypeHandler.java | 8 +++++--- .../java/org/apache/ibatis/type/ArrayTypeHandlerTest.java | 3 +-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java b/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java index 8e0a07cf725..448d604b290 100644 --- a/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java +++ b/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java @@ -40,7 +40,9 @@ protected void setNullParameter(PreparedStatement ps, int i, JdbcType jdbcType) public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException { Array array = null; if (parameter instanceof Array) { - array = (Array)parameter; + // it's the user's responsibility to properly free() the Array instance + ps.setArray(i, (Array)parameter); + } else { if (!parameter.getClass().isArray()) { @@ -48,9 +50,9 @@ public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, J } Object[] values = (Object[])parameter; array = ps.getConnection().createArrayOf(jdbcType.name(), values); + ps.setArray(i, array); + array.free(); } - ps.setArray(i, array); - array.free(); } @Override diff --git a/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java b/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java index e59dd414c29..415c9180374 100644 --- a/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java +++ b/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java @@ -39,9 +39,8 @@ class ArrayTypeHandlerTest extends BaseTypeHandlerTest { public void shouldSetParameter() throws Exception { TYPE_HANDLER.setParameter(ps, 1, mockArray, null); verify(ps).setArray(1, mockArray); - verify(mockArray).free(); } - + @Test public void shouldSetNullParameter() throws Exception { TYPE_HANDLER.setParameter(ps, 1, null, JdbcType.ARRAY); From 38120820510631e9b42ab4e236c9ba39f9629ded Mon Sep 17 00:00:00 2001 From: Dirk Olmes Date: Mon, 27 May 2019 08:22:13 +0200 Subject: [PATCH 5/6] Introduce a type mapping Detect the type of the Array from the component type of the Object[] that's passed in as parameter. Only regular Java types are mapped - for mapping interface types like java.sql.Clob etc. users would have to override the resolveTypeName() method. --- .../apache/ibatis/type/ArrayTypeHandler.java | 56 +++++++++++++++++-- .../ibatis/type/ArrayTypeHandlerTest.java | 18 ++++++ 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java b/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java index 448d604b290..0c827e2238b 100644 --- a/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java +++ b/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java @@ -15,18 +15,63 @@ */ package org.apache.ibatis.type; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URL; import java.sql.Array; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; import java.sql.Types; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.util.Calendar; +import java.util.concurrent.ConcurrentHashMap; /** * @author Clinton Begin */ public class ArrayTypeHandler extends BaseTypeHandler { + private static final ConcurrentHashMap, String> STANDARD_MAPPING; + static { + STANDARD_MAPPING = new ConcurrentHashMap<>(); + STANDARD_MAPPING.put(BigDecimal.class, "NUMERIC"); + STANDARD_MAPPING.put(BigInteger.class, "BIGINT"); + STANDARD_MAPPING.put(boolean.class, "BOOLEAN"); + STANDARD_MAPPING.put(Boolean.class, "BOOLEAN"); + STANDARD_MAPPING.put(byte[].class, "VARBINARY"); + STANDARD_MAPPING.put(byte.class, "TINYINT"); + STANDARD_MAPPING.put(Byte.class, "TINYINT"); + STANDARD_MAPPING.put(Calendar.class, "TIMESTAMP"); + STANDARD_MAPPING.put(java.sql.Date.class, "DATE"); + STANDARD_MAPPING.put(java.util.Date.class, "TIMESTAMP"); + STANDARD_MAPPING.put(double.class, "DOUBLE"); + STANDARD_MAPPING.put(Double.class, "DOUBLE"); + STANDARD_MAPPING.put(float.class, "REAL"); + STANDARD_MAPPING.put(Float.class, "REAL"); + STANDARD_MAPPING.put(int.class, "INTEGER"); + STANDARD_MAPPING.put(Integer.class, "INTEGER"); + STANDARD_MAPPING.put(LocalDate.class, "DATE"); + STANDARD_MAPPING.put(LocalDateTime.class, "TIMESTAMP"); + STANDARD_MAPPING.put(LocalTime.class, "TIME"); + STANDARD_MAPPING.put(long.class, "BIGINT"); + STANDARD_MAPPING.put(Long.class, "BIGINT"); + STANDARD_MAPPING.put(OffsetDateTime.class, "TIMESTAMP_WITH_TIMEZONE"); + STANDARD_MAPPING.put(OffsetTime.class, "TIME_WITH_TIMEZONE"); + STANDARD_MAPPING.put(Short.class, "SMALLINT"); + STANDARD_MAPPING.put(String.class, "VARCHAR"); + STANDARD_MAPPING.put(Time.class, "TIME"); + STANDARD_MAPPING.put(Timestamp.class, "TIMESTAMP"); + STANDARD_MAPPING.put(URL.class, "DATALINK"); + }; + public ArrayTypeHandler() { super(); } @@ -38,23 +83,26 @@ protected void setNullParameter(PreparedStatement ps, int i, JdbcType jdbcType) @Override public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException { - Array array = null; if (parameter instanceof Array) { // it's the user's responsibility to properly free() the Array instance ps.setArray(i, (Array)parameter); - } else { if (!parameter.getClass().isArray()) { throw new TypeException("ArrayType Handler requires SQL array or java array parameter and does not support type " + parameter.getClass()); } - Object[] values = (Object[])parameter; - array = ps.getConnection().createArrayOf(jdbcType.name(), values); + Class componentType = parameter.getClass().getComponentType(); + String arrayTypeName = resolveTypeName(componentType); + Array array = ps.getConnection().createArrayOf(arrayTypeName, (Object[])parameter); ps.setArray(i, array); array.free(); } } + protected String resolveTypeName(Class type) { + return STANDARD_MAPPING.getOrDefault(type, "JAVA_OBJECT"); + } + @Override public Object getNullableResult(ResultSet rs, String columnName) throws SQLException { return extractArray(rs.getArray(columnName)); diff --git a/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java b/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java index 415c9180374..f8d590490d0 100644 --- a/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java +++ b/src/test/java/org/apache/ibatis/type/ArrayTypeHandlerTest.java @@ -18,12 +18,17 @@ import org.junit.jupiter.api.Test; import org.mockito.Mock; + import java.sql.Array; +import java.sql.Connection; import java.sql.Types; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -40,6 +45,19 @@ public void shouldSetParameter() throws Exception { TYPE_HANDLER.setParameter(ps, 1, mockArray, null); verify(ps).setArray(1, mockArray); } + + @Test + public void shouldSetStringArrayParameter() throws Exception { + Connection connection = mock(Connection.class); + when(ps.getConnection()).thenReturn(connection); + + Array array = mock(Array.class); + when(connection.createArrayOf(anyString(), any(String[].class))).thenReturn(array); + + TYPE_HANDLER.setParameter(ps, 1, new String[] { "Hello World" }, JdbcType.ARRAY); + verify(ps).setArray(1, array); + verify(array).free(); + } @Test public void shouldSetNullParameter() throws Exception { From 9e85477fec98eca1b97d9014d6b440466d241486 Mon Sep 17 00:00:00 2001 From: Iwao AVE! Date: Tue, 28 May 2019 01:08:09 +0900 Subject: [PATCH 6/6] Minor adjustments --- .../apache/ibatis/type/ArrayTypeHandler.java | 84 +++++++++---------- .../apache/ibatis/type/BaseTypeHandler.java | 6 +- ...aseTest.java => ArrayTypeHandlerTest.java} | 19 +++-- .../submitted/array_type_handler/CreateDB.sql | 2 +- .../submitted/array_type_handler/Mapper.java | 7 +- .../submitted/array_type_handler/Mapper.xml | 4 +- .../submitted/array_type_handler/User.java | 13 +-- .../array_type_handler/mybatis-config.xml | 2 +- 8 files changed, 65 insertions(+), 72 deletions(-) rename src/test/java/org/apache/ibatis/submitted/array_type_handler/{BaseTest.java => ArrayTypeHandlerTest.java} (91%) diff --git a/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java b/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java index 0c827e2238b..cd211877b40 100644 --- a/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java +++ b/src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java @@ -25,7 +25,6 @@ import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; -import java.sql.Types; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -42,67 +41,64 @@ public class ArrayTypeHandler extends BaseTypeHandler { private static final ConcurrentHashMap, String> STANDARD_MAPPING; static { STANDARD_MAPPING = new ConcurrentHashMap<>(); - STANDARD_MAPPING.put(BigDecimal.class, "NUMERIC"); - STANDARD_MAPPING.put(BigInteger.class, "BIGINT"); - STANDARD_MAPPING.put(boolean.class, "BOOLEAN"); - STANDARD_MAPPING.put(Boolean.class, "BOOLEAN"); - STANDARD_MAPPING.put(byte[].class, "VARBINARY"); - STANDARD_MAPPING.put(byte.class, "TINYINT"); - STANDARD_MAPPING.put(Byte.class, "TINYINT"); - STANDARD_MAPPING.put(Calendar.class, "TIMESTAMP"); - STANDARD_MAPPING.put(java.sql.Date.class, "DATE"); - STANDARD_MAPPING.put(java.util.Date.class, "TIMESTAMP"); - STANDARD_MAPPING.put(double.class, "DOUBLE"); - STANDARD_MAPPING.put(Double.class, "DOUBLE"); - STANDARD_MAPPING.put(float.class, "REAL"); - STANDARD_MAPPING.put(Float.class, "REAL"); - STANDARD_MAPPING.put(int.class, "INTEGER"); - STANDARD_MAPPING.put(Integer.class, "INTEGER"); - STANDARD_MAPPING.put(LocalDate.class, "DATE"); - STANDARD_MAPPING.put(LocalDateTime.class, "TIMESTAMP"); - STANDARD_MAPPING.put(LocalTime.class, "TIME"); - STANDARD_MAPPING.put(long.class, "BIGINT"); - STANDARD_MAPPING.put(Long.class, "BIGINT"); - STANDARD_MAPPING.put(OffsetDateTime.class, "TIMESTAMP_WITH_TIMEZONE"); - STANDARD_MAPPING.put(OffsetTime.class, "TIME_WITH_TIMEZONE"); - STANDARD_MAPPING.put(Short.class, "SMALLINT"); - STANDARD_MAPPING.put(String.class, "VARCHAR"); - STANDARD_MAPPING.put(Time.class, "TIME"); - STANDARD_MAPPING.put(Timestamp.class, "TIMESTAMP"); - STANDARD_MAPPING.put(URL.class, "DATALINK"); - }; - + STANDARD_MAPPING.put(BigDecimal.class, JdbcType.NUMERIC.name()); + STANDARD_MAPPING.put(BigInteger.class, JdbcType.BIGINT.name()); + STANDARD_MAPPING.put(boolean.class, JdbcType.BOOLEAN.name()); + STANDARD_MAPPING.put(Boolean.class, JdbcType.BOOLEAN.name()); + STANDARD_MAPPING.put(byte[].class, JdbcType.VARBINARY.name()); + STANDARD_MAPPING.put(byte.class, JdbcType.TINYINT.name()); + STANDARD_MAPPING.put(Byte.class, JdbcType.TINYINT.name()); + STANDARD_MAPPING.put(Calendar.class, JdbcType.TIMESTAMP.name()); + STANDARD_MAPPING.put(java.sql.Date.class, JdbcType.DATE.name()); + STANDARD_MAPPING.put(java.util.Date.class, JdbcType.TIMESTAMP.name()); + STANDARD_MAPPING.put(double.class, JdbcType.DOUBLE.name()); + STANDARD_MAPPING.put(Double.class, JdbcType.DOUBLE.name()); + STANDARD_MAPPING.put(float.class, JdbcType.REAL.name()); + STANDARD_MAPPING.put(Float.class, JdbcType.REAL.name()); + STANDARD_MAPPING.put(int.class, JdbcType.INTEGER.name()); + STANDARD_MAPPING.put(Integer.class, JdbcType.INTEGER.name()); + STANDARD_MAPPING.put(LocalDate.class, JdbcType.DATE.name()); + STANDARD_MAPPING.put(LocalDateTime.class, JdbcType.TIMESTAMP.name()); + STANDARD_MAPPING.put(LocalTime.class, JdbcType.TIME.name()); + STANDARD_MAPPING.put(long.class, JdbcType.BIGINT.name()); + STANDARD_MAPPING.put(Long.class, JdbcType.BIGINT.name()); + STANDARD_MAPPING.put(OffsetDateTime.class, JdbcType.TIMESTAMP_WITH_TIMEZONE.name()); + STANDARD_MAPPING.put(OffsetTime.class, JdbcType.TIME_WITH_TIMEZONE.name()); + STANDARD_MAPPING.put(Short.class, JdbcType.SMALLINT.name()); + STANDARD_MAPPING.put(String.class, JdbcType.VARCHAR.name()); + STANDARD_MAPPING.put(Time.class, JdbcType.TIME.name()); + STANDARD_MAPPING.put(Timestamp.class, JdbcType.TIMESTAMP.name()); + STANDARD_MAPPING.put(URL.class, JdbcType.DATALINK.name()); + } + public ArrayTypeHandler() { super(); } @Override - protected void setNullParameter(PreparedStatement ps, int i, JdbcType jdbcType) throws SQLException { - ps.setNull(i, Types.ARRAY); - } - - @Override - public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException { + public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) + throws SQLException { if (parameter instanceof Array) { // it's the user's responsibility to properly free() the Array instance - ps.setArray(i, (Array)parameter); - } - else { + ps.setArray(i, (Array) parameter); + } else { if (!parameter.getClass().isArray()) { - throw new TypeException("ArrayType Handler requires SQL array or java array parameter and does not support type " + parameter.getClass()); + throw new TypeException( + "ArrayType Handler requires SQL array or java array parameter and does not support type " + + parameter.getClass()); } Class componentType = parameter.getClass().getComponentType(); String arrayTypeName = resolveTypeName(componentType); - Array array = ps.getConnection().createArrayOf(arrayTypeName, (Object[])parameter); + Array array = ps.getConnection().createArrayOf(arrayTypeName, (Object[]) parameter); ps.setArray(i, array); array.free(); } } protected String resolveTypeName(Class type) { - return STANDARD_MAPPING.getOrDefault(type, "JAVA_OBJECT"); + return STANDARD_MAPPING.getOrDefault(type, JdbcType.JAVA_OBJECT.name()); } - + @Override public Object getNullableResult(ResultSet rs, String columnName) throws SQLException { return extractArray(rs.getArray(columnName)); diff --git a/src/main/java/org/apache/ibatis/type/BaseTypeHandler.java b/src/main/java/org/apache/ibatis/type/BaseTypeHandler.java index 393907f4971..5b3bc8dbde3 100644 --- a/src/main/java/org/apache/ibatis/type/BaseTypeHandler.java +++ b/src/main/java/org/apache/ibatis/type/BaseTypeHandler.java @@ -58,7 +58,7 @@ public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbc throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters."); } try { - setNullParameter(ps, i, jdbcType); + ps.setNull(i, jdbcType.TYPE_CODE); } catch (SQLException e) { throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " @@ -75,10 +75,6 @@ public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbc } } - protected void setNullParameter(PreparedStatement ps, int i, JdbcType jdbcType) throws SQLException { - ps.setNull(i, jdbcType.TYPE_CODE); - } - @Override public T getResult(ResultSet rs, String columnName) throws SQLException { try { diff --git a/src/test/java/org/apache/ibatis/submitted/array_type_handler/BaseTest.java b/src/test/java/org/apache/ibatis/submitted/array_type_handler/ArrayTypeHandlerTest.java similarity index 91% rename from src/test/java/org/apache/ibatis/submitted/array_type_handler/BaseTest.java rename to src/test/java/org/apache/ibatis/submitted/array_type_handler/ArrayTypeHandlerTest.java index dca665c8797..6df13f529db 100644 --- a/src/test/java/org/apache/ibatis/submitted/array_type_handler/BaseTest.java +++ b/src/test/java/org/apache/ibatis/submitted/array_type_handler/ArrayTypeHandlerTest.java @@ -1,5 +1,5 @@ /** - * Copyright 2019 the original author or authors. + * Copyright 2009-2019 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. @@ -28,20 +28,21 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class BaseTest { +public class ArrayTypeHandlerTest { private SqlSessionFactory sqlSessionFactory; @BeforeEach public void setUp() throws Exception { - try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/array_type_handler/mybatis-config.xml")) { + try (Reader reader = Resources + .getResourceAsReader("org/apache/ibatis/submitted/array_type_handler/mybatis-config.xml")) { sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); } - + BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), "org/apache/ibatis/submitted/array_type_handler/CreateDB.sql"); } - + @Test public void shouldInsertArrayValue() throws Exception { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { @@ -49,19 +50,19 @@ public void shouldInsertArrayValue() throws Exception { user.setId(1); user.setName("User 1"); user.setNicknames(new String[] { "User", "one" }); - + Mapper mapper = sqlSession.getMapper(Mapper.class); mapper.insert(user); sqlSession.commit(); int usersInDatabase = mapper.getUserCount(); assertEquals(1, usersInDatabase); - + Integer nicknameCount = mapper.getNicknameCount(); assertEquals(2, nicknameCount); } } - + @Test public void shouldInsertNullValue() throws Exception { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { @@ -69,7 +70,7 @@ public void shouldInsertNullValue() throws Exception { user.setId(1); user.setName("User 1"); // note how the user does not have nicknames - + Mapper mapper = sqlSession.getMapper(Mapper.class); mapper.insert(user); sqlSession.commit(); diff --git a/src/test/java/org/apache/ibatis/submitted/array_type_handler/CreateDB.sql b/src/test/java/org/apache/ibatis/submitted/array_type_handler/CreateDB.sql index b0c3aaf63d5..d674a0dfa99 100644 --- a/src/test/java/org/apache/ibatis/submitted/array_type_handler/CreateDB.sql +++ b/src/test/java/org/apache/ibatis/submitted/array_type_handler/CreateDB.sql @@ -1,5 +1,5 @@ -- --- Copyright 2019 the original author or authors. +-- Copyright 2009-2019 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. diff --git a/src/test/java/org/apache/ibatis/submitted/array_type_handler/Mapper.java b/src/test/java/org/apache/ibatis/submitted/array_type_handler/Mapper.java index 838021af9f8..2d382539d37 100644 --- a/src/test/java/org/apache/ibatis/submitted/array_type_handler/Mapper.java +++ b/src/test/java/org/apache/ibatis/submitted/array_type_handler/Mapper.java @@ -1,5 +1,5 @@ /** - * Copyright 2019 the original author or authors. + * Copyright 2009-2019 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. @@ -21,9 +21,8 @@ public interface Mapper { int getUserCount(); - /** - * HSQL returns NULL when asked for the cardinality of an array column - * with NULL value :-( + /** + * HSQL returns NULL when asked for the cardinality of an array column with NULL value :-( */ Integer getNicknameCount(); } diff --git a/src/test/java/org/apache/ibatis/submitted/array_type_handler/Mapper.xml b/src/test/java/org/apache/ibatis/submitted/array_type_handler/Mapper.xml index 46a523b2520..2f808ebebf9 100644 --- a/src/test/java/org/apache/ibatis/submitted/array_type_handler/Mapper.xml +++ b/src/test/java/org/apache/ibatis/submitted/array_type_handler/Mapper.xml @@ -1,7 +1,7 @@