Skip to content

Commit

Permalink
Merge pull request #3327 from luozongle01/fixIndexOutOfBoundsExceptio…
Browse files Browse the repository at this point in the history
…n20241226

Throw helpful error instead of IndexOutOfBoundsException when there are not enough columns for constructor auto-mapping
  • Loading branch information
harawata authored Dec 27, 2024
2 parents 3e54e62 + f6c28a6 commit f7e6d98
Show file tree
Hide file tree
Showing 10 changed files with 531 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,13 @@ private Object applyConstructorAutomapping(ResultSetWrapper rsw, ResultMap resul
private boolean applyColumnOrderBasedConstructorAutomapping(ResultSetWrapper rsw, List<Class<?>> constructorArgTypes,
List<Object> constructorArgs, Constructor<?> constructor, boolean foundValues) throws SQLException {
Class<?>[] parameterTypes = constructor.getParameterTypes();

if (parameterTypes.length > rsw.getClassNames().size()) {
throw new ExecutorException(MessageFormat.format(
"Constructor auto-mapping of ''{0}'' failed. The constructor takes ''{1}'' arguments, but there are only ''{2}'' columns in the result set.",
constructor, parameterTypes.length, rsw.getClassNames().size()));
}

for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
String columnName = rsw.getColumnNames().get(i);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright 2009-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.apache.ibatis.submitted.column_order_based_constructor_automapping;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.Reader;
import java.text.MessageFormat;
import java.util.List;

import org.apache.ibatis.BaseDataTest;
import org.apache.ibatis.exceptions.PersistenceException;
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.BeforeAll;
import org.junit.jupiter.api.Test;

class ColumnOrderBasedConstructorAutomappingTest {

private static SqlSessionFactory sqlSessionFactory;

@BeforeAll
static void setUp() throws Exception {
// create an SqlSessionFactory
try (Reader reader = Resources.getResourceAsReader(
"org/apache/ibatis/submitted/column_order_based_constructor_automapping/mybatis-config.xml")) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
sqlSessionFactory.getConfiguration().setArgNameBasedConstructorAutoMapping(false);
}

// populate in-memory database
BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
"org/apache/ibatis/submitted/column_order_based_constructor_automapping/CreateDB.sql");
}

@Test
void shouldHandleNoArgsConstructor() {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
Mapper mapper = sqlSession.getMapper(Mapper.class);
List<UserNoArgsConstructor> userList = mapper.finaAllByNoArgsConstructor();

assertEquals(2, userList.size());
UserNoArgsConstructor user1 = userList.get(0);
UserNoArgsConstructor user2 = userList.get(1);

assertEquals(1, user1.getId());
assertEquals("Tom", user1.getName());
assertEquals(7, user1.getAge());
assertNull(user1.getEmail());

assertEquals(2, user2.getId());
assertEquals("Cat", user2.getName());
assertEquals(3, user2.getAge());
assertNull(user2.getEmail());
}
}

@Test
void shouldHandleConstructorEqualsResultSet() {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
Mapper mapper = sqlSession.getMapper(Mapper.class);
List<UserConstructorEqualsResultSet> userList = mapper.finaAllByConstructorEqualsResultSet();

assertEquals(2, userList.size());
UserConstructorEqualsResultSet user1 = userList.get(0);
UserConstructorEqualsResultSet user2 = userList.get(1);

assertEquals(1, user1.getId());
assertEquals("Tom", user1.getName());
assertEquals(7, user1.getAge());
assertNull(user1.getEmail());

assertEquals(2, user2.getId());
assertEquals("Cat", user2.getName());
assertEquals(3, user2.getAge());
assertNull(user2.getEmail());
}
}

@Test
void shouldHandleConstructorLessThanResultSet() {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
Mapper mapper = sqlSession.getMapper(Mapper.class);
List<UserConstructorLessThanResultSet> userList = mapper.finaAllByConstructorLessThanResultSet();

assertEquals(2, userList.size());
UserConstructorLessThanResultSet user1 = userList.get(0);
UserConstructorLessThanResultSet user2 = userList.get(1);

assertEquals(1, user1.getId());
assertEquals("Tom", user1.getName());
assertEquals(7, user1.getAge());
assertNull(user1.getEmail());

assertEquals(2, user2.getId());
assertEquals("Cat", user2.getName());
assertEquals(3, user2.getAge());
assertNull(user2.getEmail());
}
}

@Test
void shouldNotHandleConstructorGreaterThanResultSet() {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
Mapper mapper = sqlSession.getMapper(Mapper.class);

PersistenceException persistenceException = assertThrows(PersistenceException.class,
mapper::finaAllByConstructorGreaterThanResultSet);
assertNotNull(persistenceException);
String message = persistenceException.getMessage();
assertNotNull(message);
assertTrue(message.contains(MessageFormat.format(
"Constructor auto-mapping of ''{0}'' failed. The constructor takes ''{1}'' arguments, but there are only ''{2}'' columns in the result set.",
UserConstructorGreaterThanResultSet.class.getConstructors()[0], 4, 3)));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2009-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.apache.ibatis.submitted.column_order_based_constructor_automapping;

import java.util.List;

public interface Mapper {

List<UserNoArgsConstructor> finaAllByNoArgsConstructor();

List<UserConstructorEqualsResultSet> finaAllByConstructorEqualsResultSet();

List<UserConstructorLessThanResultSet> finaAllByConstructorLessThanResultSet();

List<UserConstructorGreaterThanResultSet> finaAllByConstructorGreaterThanResultSet();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2009-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.apache.ibatis.submitted.column_order_based_constructor_automapping;

public class UserConstructorEqualsResultSet {

private Integer id;
private String name;
private Integer age;
private String email;

public UserConstructorEqualsResultSet(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}

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 Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2009-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.apache.ibatis.submitted.column_order_based_constructor_automapping;

public class UserConstructorGreaterThanResultSet {

private Integer id;
private String name;
private Integer age;
private String email;

public UserConstructorGreaterThanResultSet(Integer id, String name, Integer age, String email) {
this.id = id;
this.name = name;
this.age = age;
this.email = email;
}

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 Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2009-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.apache.ibatis.submitted.column_order_based_constructor_automapping;

public class UserConstructorLessThanResultSet {

private Integer id;
private String name;
private Integer age;
private String email;

public UserConstructorLessThanResultSet(Integer id, String name) {
this.id = id;
this.name = name;
}

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 Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}
}
Loading

0 comments on commit f7e6d98

Please sign in to comment.