不能将Integer类型的数据直接赋值给UserBasic类型的属性
java.sql.SQLException: Cannot set author: incompatible types, cannot convert java.lang.Integer to com.wtk.qqzone.pojo.UserBasic Query: select id,title,content,topicDate,author from t_topic where author=?; Parameters: [1]
从数据库里面查询表t_topic的数据,然后映射到Topic类。数据库中的t_topic表中的author字段存储的是t_user_basic表的id,即为int类型,但是java中的Topic类的author属性的类型是自定义的UserBasic类型,所以在映射的时候会导致抛出Interger不能直接赋值给UserBasic类型的错误
下图就是抛出异常的位置,value是Integer类型,而set方法的firstParam的类型是UserBasic,所以在判断的时候类型不一致就进入了else语句里面执行了throw
看到一个帖子,说是把setAuthor方法的参数由UserBasic类型改为Integer类型,然后在setAuthor方法里面自己new一个UserBabic对象,然后调用对象的setId方法,设置UserBasic实例的对象的id,然后再把这个对象赋值给author属性值。
看似很完美的方案,改了之后也没有抛出异常了,但是映射出的对象里面的author为null,和预想的不一样,是因为dbutils里面会获取映射类里每个属性对应的get、set方法,更改set方法的参数类型之后,dbutils里就获取不到这个属性对应的set方法,然后根据查询结果逐一对映射对象属性赋值的时候,遇到某个属性的set方法为空的,直接跳过这个属性的调用set方法赋值了,就会出现自定义属性的属性值为null的情况。
错误改法
没修改参数类型前,可以识别到set方法
修改参数类型之后,识别不到set方法
导致自定义属性在映射后为null
既然修改set方法的参数类型会导致dbutils获取不到属性对应的set方法,就会跳过这个属性的赋值。不修改set方法参数类型,就会抛出赋值异常。这两种方法都行不通,那我们就只能自己实现映射细节来替代dbutils的映射了,俗话说走别人的路让别人无路可走,这样就不会被抛出异常了。
自己实现org.apache.commons.dbutils.RowProcessor这个接口,然后在实例ResultSetHandler的的时候,把实现RowProcessor接口的类的对象放在第二个参数传过去,然后查询到数据之后,在映射的时候就会调用我们实现的方法来映射。
为每个映射过程中遇到属性为自定义类型的类添加一个单独参数的构造器。Topic类中的author是UserBasic类型,就在UserBasic类型里面添加一个单独参数的构造器,这个参数的类型要和从数据库中查出的对应字段的类型一致,后面就会调用这个构造器并且把查询到的字段数据作为参数。
public class UserBasic {
private int id;
public UserBasic(Integer id) {
this.id = id;
}
........
在resource目录下新建一个myType.xml文件,没有resources目录或者目录不是这个图标的,新建文件夹 ----> 填写文件夹名称 resources ----> 右键 ----> mark Directory as ----> resource Root
myType.xml文件文件里面的内容填写规则:
<myTypes>
<myType class="com.wtk.qqzone.pojo.Topic">
<constructor parameterType="java.lang.Integer"/>
myType>
<myType class="com.wtk.qqzone.pojo.UserBasic">
<constructor parameterType="java.lang.Integer"/>
myType>
myTypes>
创建一个类MyRowProcessor,实现RowProcessor接口。这里的实现代码我写的基本每一行都有注释,想学习的小伙伴可以阅读一下。
主要的思想就在从查询结果集里面获取查询的字段名,然后再根据字段名获取要反射类的属性,再获取这个属性的类型,判断这个类型是否是自定义的类型
,自定义类型的话需要写在xml文件里面,MyRowProcessor被实例时,自动加载xml文件的内容。然后如果是自定义类型,就先调用这个自定义类型的一个参数的构造器,其中这个构造器的参数类型要和查询结果的类型一致,使用反射调用这个构造器,将查询值作为参数传进去,把获取的实例对象赋值给当前映射对的对应属性。不是自定义类型属性的话就直接赋值。
package com.wtk.myssm.basedao;
import org.apache.commons.dbutils.RowProcessor;
import org.apache.commons.dbutils.handlers.MapHandler;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MyRowProcessor extends MapHandler implements RowProcessor {
//存储自定义类型的名称和类
private Map<String, Class> myTypeMap = new HashMap<>();
//存储自定义类型的名称和一个参数构造器的参数类型
private Map<String, Class> constructorParameterTypeMap = new HashMap<>();
//从xml文件中读取自定义类型的名称,并存到MyTypeNameList
public MyRowProcessor() {
InputStream is = getClass().getClassLoader().getResourceAsStream("myType.xml");
if (is == null) {
throw new RuntimeException("没有读取到myType.xml这个文件");
}
try {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document document = documentBuilder.parse(is);
//获取xml文件的所有 标签
NodeList nodeList = document.getElementsByTagName("myType");
for (int i = 0; i < nodeList.getLength(); i++) {
//获取每一个结点
Node item = nodeList.item(i);
//判断这个结点的类型是不是元素结点
if (item.getNodeType() == Node.ELEMENT_NODE) {
//转一下类型,才有更多的方法来调用
Element element = (Element) item;
//读取 标签的class属性值
String myTypeClassName = element.getAttribute("class");
//根据属性值获取对应的类
Class<?> myType = Class.forName(myTypeClassName);
//存储名称和类
myTypeMap.put(myTypeClassName, myType);
//获取一个参数的构造器的参数类型
NodeList nodeList2 = element.getElementsByTagName("constructor");
for (int k = 0; k < nodeList2.getLength(); k++) {
Node item1 = nodeList2.item(k);
if (item1.getNodeType() == Node.ELEMENT_NODE) {
Element element1 = (Element) item1;
String parameterTypeStr = element1.getAttribute("parameterType");
Class<?> parameterType = Class.forName(parameterTypeStr);
//存储
constructorParameterTypeMap.put(myTypeClassName, parameterType);
}
}
}
}
} catch (ParserConfigurationException e) {
throw new RuntimeException(e);
} catch (SAXException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException("自定义类不能获取到");
}
}
/**
* 将查询结果集对象转成一个object数组
*
* @param resultSet
* @return
* @throws SQLException
*/
@Override
public Object[] toArray(ResultSet resultSet) throws SQLException {
//获取元数据
ResultSetMetaData metaData = resultSet.getMetaData();
//获取查询的列数
int columnCount = metaData.getColumnCount();
//定义相同长度的数组,用来存储查询到的值
Object[] objects = new Object[columnCount];
for (int i = 0; i < columnCount; i++) {
//从查询结果中查出数据,并存到数组中
objects[i] = resultSet.getObject(i + 1);
}
//返回查询结果的object数组
return objects;
}
/**
* 传入结果集对象,返回一个bean对象
*
* @param resultSet
* @param clazz
* @param
* @return
* @throws SQLException
*/
@Override
public <T> T toBean(ResultSet resultSet, Class<? extends T> clazz) throws SQLException {
//获取结果集的元数据
ResultSetMetaData metaData = resultSet.getMetaData();
//获取查询结果的字段数
int columnCount = metaData.getColumnCount();
//将查询结果转数组,这里调用上面实现的方法
Object[] objects = this.toArray(resultSet);
try {
//创建bean对象
T bean = clazz.newInstance();
//将结果集的值赋值给bean对象的属性
for (int i = 0; i < columnCount; i++) {
//获取本次要处理的查询字段名称(查询时用别名就返回别名)
String columnLabel = metaData.getColumnLabel(i + 1);
//获取这个字段的值
Object columnValue = objects[i];
//找到这个查询字段名称对应的bean对象中的属性
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
//获取这个字段的类型名称
String fieldTypeName = field.getType().getName();
//判断这个类型是否是自定义类型
Class type = myTypeMap.get(fieldTypeName);
if (type != null) {
//属性类型是自定义类
//找到自定义对应的一个参数的构造器方法,然后调用
// 查找的规则:如果字段名是author,类型是User
// 对应构造器方法:public User(Integer id){...};
Class parameterType = constructorParameterTypeMap.get(fieldTypeName);
Constructor constructor = type.getDeclaredConstructor(parameterType);
Object newInstance = constructor.newInstance(columnValue);
//设置这个自定义属性的值
field.set(bean, newInstance);
} else {
//不是自定义类,直接设置
field.set(bean, columnValue);
}
}
return bean;
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
/**
* @param resultSet
* @param aClass
* @param
* @return
* @throws SQLException
*/
@Override
public <T> List<T> toBeanList(ResultSet resultSet, Class<? extends T> aClass) throws SQLException {
List<T> beanList = new ArrayList<>();
while (resultSet.next()) {
//这里调用上面重写的方法就可以了
T t = toBean(resultSet, aClass);
//添加到列表里面
beanList.add(t);
}
return beanList;
}
/**
* @param resultSet
* @return
* @throws SQLException
*/
@Override
public Map<String, Object> toMap(ResultSet resultSet) throws SQLException {
//继承MapHandler就是为了调用MapHandler类中的方法
return super.handle(resultSet);
}
}
在实例化ResultSetHandler的时候,把MyMyRowProcessor的实例作为第二个参数传进去
这里映射出来的结果已经正常了,查询得到的结果中author只是一个Integer类型的id,所以映射后得到一个UserBasic的对象,这个对象的id属性值为1,这个才是我们想要的结果