Skip to content

Commit

Permalink
Merge pull request #1548 from dirk-olmes/ArrayTypeHandlerImprovements
Browse files Browse the repository at this point in the history
Improve ArrayTypeHandler
  • Loading branch information
harawata committed May 27, 2019
2 parents 002a525 + 9e85477 commit f7ce783
Show file tree
Hide file tree
Showing 8 changed files with 363 additions and 2 deletions.
68 changes: 66 additions & 2 deletions src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java
Expand Up @@ -15,24 +15,88 @@
*/
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.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<Object> {

private static final ConcurrentHashMap<Class<?>, String> STANDARD_MAPPING;
static {
STANDARD_MAPPING = new ConcurrentHashMap<>();
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
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
ps.setArray(i, (Array) parameter);
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 {
if (!parameter.getClass().isArray()) {
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);
ps.setArray(i, array);
array.free();
}
}

protected String resolveTypeName(Class<?> type) {
return STANDARD_MAPPING.getOrDefault(type, JdbcType.JAVA_OBJECT.name());
}

@Override
Expand Down
@@ -0,0 +1,85 @@
/**
* 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.
* 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 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")) {
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);
}
}
}
@@ -0,0 +1,23 @@
--
-- 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.
-- 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
);
@@ -0,0 +1,28 @@
/**
* 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.
* 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();
}
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
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.
-->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="org.apache.ibatis.submitted.array_type_handler.Mapper">

<insert id="insert"
parameterType="org.apache.ibatis.submitted.array_type_handler.User">
insert into users
(id, name, nicknames)
values
(#{id}, #{name}, #{nicknames,typeHandler=org.apache.ibatis.type.ArrayTypeHandler})
</insert>

<select id="getUserCount" resultType="int">
select count(*) from users
</select>

<select id="getNicknameCount" resultType="int">
select cardinality(nicknames) from users where id = 1
</select>

</mapper>
@@ -0,0 +1,47 @@
/**
* 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.
* 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;
}
}
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
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.
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.
-->
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="UNPOOLED">
<property name="driver" value="org.hsqldb.jdbcDriver" />
<property name="url" value="jdbc:hsqldb:mem:arrayrtypehandler" />
<property name="username" value="sa" />
</dataSource>
</environment>
</environments>

<mappers>
<mapper class="org.apache.ibatis.submitted.array_type_handler.Mapper" />
</mappers>

</configuration>

0 comments on commit f7ce783

Please sign in to comment.