前言
本文的創作來源于朋友在自學mybatis遇到的問題,問題如文章標題所示Cannot determine value type from string 'xxx',他在網上搜索出來的答案基本上都是加上一個無參構造器,就可以解決問題,他的疑問點在于他物體沒有使用無參構造器,而使用了有參構造器,有的查詢方法不會報錯,有的查詢方法卻報錯了,下面將演示他出現的這種場景的示例,
注: mybatis的搭建程序忽略,僅演示案例,案例代碼取自朋友
示例
1、entity
public class Student {
private int id;
private String name;
private String email;
private int age;
public Student(String aa,int bb){
System.out.println("===============執行student的有引數構造方法 aa = "+aa+" bb = "+bb+"================");
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
", age=" + age +
'}';
}
}
2、dao
public interface StudentDao {
Student getStudentById(int id);
List<Student> getStudents(@Param("myname") String name, @Param("myage") int age);
List<Student> getStudentByObj(Student student);
}
3、mapper.xml
<mapper namespace="com.academy.dao.StudentDao">
<select id="getStudentById" resultType="com.academy.domain.Student">
select id, name, email, age from student where id = #{sid}
</select>
<select id="getStudents" resultType="com.academy.domain.Student">
select id, name, email, age from student where name = #{myname} or age = #{myage}
</select>
<select id="getStudentByObj" resultType="com.academy.domain.Student">
select id, name, email, age from student where name = #{name} or age = #{age}
</select>
</mapper>
4、單元測驗
@Test
public void testgetStudentById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Student student = dao.getStudentById(1034);
sqlSession.close();
System.out.println(student);
}
@Test
public void testgetStudents(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
List<Student> students = dao.getStudents("張三", 22);
sqlSession.close();
students.forEach(student -> System.out.println(student));
}
5、運行單元測驗
從截圖看出,當物體沒有使用無參構造器時,出現朋友所說的有一些方法成功,一些方法報錯,報錯資訊為
Cannot determine value type from string 'xxx'
采用網上介紹的方法,給物體加上無參構造器,如下:
public class Student {
private int id;
private String name;
private String email;
private int age;
public Student(){
}
public Student(String aa,int bb){
System.out.println("===============執行student的有引數構造方法 aa = "+aa+" bb = "+bb+"================");
}
再次運行單元測驗
加上無參構造器,確實不報錯,那我們是否就可以因為這樣,就得出mybatis執行必須得加上無參構造器的結論呢?
我們再把物體的無參構造器去掉,如下
public class Student {
private int id;
private String name;
private String email;
private int age;
public Student(String aa,int bb){
System.out.println("===============執行student的有引數構造方法 aa = "+aa+" bb = "+bb+"================");
}
同時把mapper.xml修改為如下
<mapper namespace="com.academy.dao.StudentDao">
<select id="getStudentById" resultType="com.academy.domain.Student">
select id, name, email, age from student where id = #{sid}
</select>
<select id="getStudents" resultType="com.academy.domain.Student">
select name, age from student where name = #{myname} or age = #{myage}
</select>
<select id="getStudentByObj" resultType="com.academy.domain.Student">
select id, name, email, age from student where name = #{name} or age = #{age}
</select>
然后再次運行單元測驗
從截圖可以看出,mybatis加了有參構造器并不影響執行,只是有參構造器要成功運行的條件是
-
mapper.xml中查詢的資料庫欄位屬性的型別要和有參構造器的欄位型別一一匹配
-
其次查詢欄位的個數要和有參構造器個數一樣
比如該示例的有參構造器為string int,則xml中select陳述句的欄位型別也得是varchar和int
解密Cannot determine value type from string 'xxx'例外
一開始我們看到這個例外,我們可能會先去檢查物體欄位和資料庫欄位是不是一樣,首先這個思路是沒問題,一旦發現不是這個問題,我們可以轉換一下思路,先預設一下可能出現這種問題場景,比如有沒有可能是mybatis在執行資料庫欄位到物體欄位型別映射的程序中出現轉換錯誤,其次解決例外的終極大招就是帶著問題去跟蹤原始碼,
我們跟蹤原始碼可以發現`
org.apache.ibatis.executor.resultset.DefaultResultSetHandler
這個類有個方法createResultObject
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
throws SQLException {
final Class<?> resultType = resultMap.getType();
final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
if (hasTypeHandlerForResultObject(rsw, resultType)) {
return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
} else if (!constructorMappings.isEmpty()) {
return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
return objectFactory.create(resultType);
} else if (shouldApplyAutomaticMappings(resultMap, false)) {
return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
}
throw new ExecutorException("Do not know how to create an instance of " + resultType);
}
這個方法是根據結果集回傳值的型別創建出相應的bean欄位物件
1、當物體使用無參構造器時
mybatis會呼叫createResultObject方法中
objectFactory.create(resultType)
其核心代碼片段如下
private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
try {
Constructor<T> constructor;
if (constructorArgTypes == null || constructorArgs == null) {
constructor = type.getDeclaredConstructor();
try {
return constructor.newInstance();
} catch (IllegalAccessException e) {
if (Reflector.canControlMemberAccessible()) {
constructor.setAccessible(true);
return constructor.newInstance();
} else {
throw e;
}
}
}
constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[0]));
try {
return constructor.newInstance(constructorArgs.toArray(new Object[0]));
} catch (IllegalAccessException e) {
if (Reflector.canControlMemberAccessible()) {
constructor.setAccessible(true);
return constructor.newInstance(constructorArgs.toArray(new Object[0]));
} else {
throw e;
}
}
} catch (Exception e) {
String argTypes = Optional.ofNullable(constructorArgTypes).orElseGet(Collections::emptyList)
.stream().map(Class::getSimpleName).collect(Collectors.joining(","));
String argValues = Optional.ofNullable(constructorArgs).orElseGet(Collections::emptyList)
.stream().map(String::valueOf).collect(Collectors.joining(","));
throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
}
}
使用無參構造器創建物件
2、當物體使用有參構造引數
mybatis會呼叫createResultObject方法中
createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
其核心代碼片段如下
private Object createUsingConstructor(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor) throws SQLException {
boolean foundValues = false;
for (int i = 0; i < constructor.getParameterTypes().length; i++) {
Class<?> parameterType = constructor.getParameterTypes()[i];
String columnName = rsw.getColumnNames().get(i);
TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
Object value = https://www.cnblogs.com/linyb-geek/p/typeHandler.getResult(rsw.getResultSet(), columnName);
constructorArgTypes.add(parameterType);
constructorArgs.add(value);
foundValues = value != null || foundValues;
}
return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
}
這個代碼片段里面有個TypeHandler,這個是mybatis的型別處理器,用來處理 JavaType 與 JdbcType 之間的轉換,
由代碼我們看出,當物體使用有參建構式時,會遍歷有參構造引數個數,根據有參構造引數下標查找相應的資料庫欄位名稱,根據有參構造欄位型別以及資料庫欄位名稱找型別處理器,然后使用TypeHandler來處理JavaType 與 JdbcType 之間的轉換,當轉換例外,就會報
Cannot determine value type from string 'xxx'
總結
解決Cannot determine value type from string 'xxx'的方法有2種
-
物體加無參構造引數
-
mapper.xml中查詢的資料庫欄位屬性的型別要和有參構造器的欄位型別一一匹配;查詢欄位的個數要和有參構造器個數一樣
最后當出現例外時,帶著問題去跟蹤原始碼,有時候會比利用搜索引擎更容易得到答案
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/152383.html
標籤:Java
下一篇:java SE基礎
