在最近的一个工作中,为了简单方便我就是用了Spring自带的JdbcTemplate来访问数据库,我以为之前自己很熟练的掌握,后来才发现我太天真了,踩了很多坑。
JdbcTemplate自带很多方法可以执行SQL语句,以下我主要列举,比较常用的方法
//执行SQL,返回一个对象
@Override
public T queryForObject(String sql, RowMapper rowMapper, Object... args)
throws DataAccessException {
List results = query(sql, args,
new RowMapperResultSetExtractor(rowMapper, 1));
return DataAccessUtils.requiredSingleResult(results);
}
//同上,不过多要传入返回值的对象的Class
@Override
public T queryForObject(String sql, Class requiredType)
throws DataAccessException {
return queryForObject(sql, getSingleColumnRowMapper(requiredType));
}
//执行SQL,返回一个Map对象
@Override
public Map queryForMap(String sql, Object... args)
throws DataAccessException {
return queryForObject(sql, args, getColumnMapRowMapper());
}
//执行SQL,返回一个List对象
@Override
public List query(String sql, Object[] args, RowMapper rowMapper)
throws DataAccessException {
return query(sql, args, new RowMapperResultSetExtractor(rowMapper));
}
//执行SQL,返回一个List对象
@Override
@Override
public List queryForList(String sql, Class elementType, Object... args)
throws DataAccessException {
return query(sql, args, getSingleColumnRowMapper(elementType));
}
//执行一条SQL,主要用于更新、新增数据
@Override
public int update(String sql, Object... args) throws DataAccessException {
return update(sql, newArgPreparedStatementSetter(args));
}
//执行SQL,返回一个List对象,List里是Map对象
@Override
public List
@Override
public T queryForObject(String sql, RowMapper rowMapper, Object... args)
throws DataAccessException {
List results = query(sql, args,
new RowMapperResultSetExtractor(rowMapper, 1));
return DataAccessUtils.requiredSingleResult(results);
}
public RowMapperResultSetExtractor(RowMapper rowMapper, int rowsExpected) {
Assert.notNull(rowMapper, "RowMapper is required");
this.rowMapper = rowMapper;
this.rowsExpected = rowsExpected;
}
以上代码就是可以知道,必须返回一个对象,不能返回null,如果从数据库查找发现没有一条信息吻合就会报错,报以下的错误
org.springframework.dao.IncorrectResultSizeDataAccessException: Incorrect result size: expected 1, actual 0
所以如果不确定是否有信息,就使用query
、queryForList
来避免错误。返回单对象一般会有这样的限制,如果自己不确定可以使用该方法的时候看一下源码,设置了1就代表必须要有一个值。
JdbcTemplate
里面这样的方法,就是可以传入了一个对象的Class
值,就可以返回该对象的值,但是需要注意,它只支持基础类型
//例如,它支持以下的写法
public Integer getCourseCount(String sql){
return (Integer) jdbcTemplate.queryForObject(sql,java.lang.Integer.class);
}
通过源代码发现Class
这个参数支持以下类型,源代码就是从primitiveWrapperTypeMap
查找是否是一下类型:
primitiveWrapperTypeMap.put(Boolean.class, boolean.class);
primitiveWrapperTypeMap.put(Byte.class, byte.class);
primitiveWrapperTypeMap.put(Character.class, char.class);
primitiveWrapperTypeMap.put(Double.class, double.class);
primitiveWrapperTypeMap.put(Float.class, float.class);
primitiveWrapperTypeMap.put(Integer.class, int.class);
primitiveWrapperTypeMap.put(Long.class, long.class);
primitiveWrapperTypeMap.put(Short.class, short.class);
如果需要返回自定义对象就需要另外的方法:
如果返回List
,就如以下的案例
public List getCourseList(String sql){
return jdbcTemplate.query(sql,new RowMapper(){
@Override
public Course mapRow(ResultSet rs, int rowNum) throws SQLException {
Integer id=rs.getInt("id");
String coursename=rs.getString("coursename");
//把数据封装到对象里
Course course=new Course();
course.setId(id);
course.setCoursename(coursename.trim());
return course;
}
});
}
或者使用List
@Override
public List listMyScore(Integer studentId) {
String sql = "select g.score,c.className from grade g"
+ " left join teacher t on t.id=g.teacherId"
+ " left join student s on s.id=g.studentId"
+ " left join classname c on c.id=t.classNameId"
+ " where s.id="+studentId;
List
使用了org.apache.commons.beanutils.BeanUtils
把Map对象转换为自定义对象。
后来为了方便起见我还自己写了一个RowMapper
,来简化操作
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import org.springframework.jdbc.core.RowMapper;
import com.lsb.exam.utils.StringUtils;
public class MyRowMapper implements RowMapper {
Class cls;
public MyRowMapper(Class cls) {
this.cls = cls;
}
@Override
public T mapRow(ResultSet rs, int rowNum) {
try {
Field[] fields = cls.getDeclaredFields();
T obj = cls.newInstance();
// 获取所有的属性
for (Field field : fields) {
field.setAccessible(true);
if (field.getGenericType().toString().equals("class java.lang.Integer")) {
Method m = obj.getClass().getDeclaredMethod(
"set" + StringUtils.firstChar2UpperCase(field.getName()), java.lang.Integer.class);
m.invoke(obj, rs.getInt(field.getName()));
}
if (field.getGenericType().toString().equals("class java.lang.String")) {
Method m = obj.getClass().getDeclaredMethod(
"set" + StringUtils.firstChar2UpperCase(field.getName()), java.lang.String.class);
m.invoke(obj, rs.getString(field.getName()));
}
if (field.getGenericType().toString().equals("class java.util.Date")) {
Method m = obj.getClass().getDeclaredMethod(
"set" + StringUtils.firstChar2UpperCase(field.getName()), java.util.Date.class);
m.invoke(obj, rs.getDate(field.getName()));
}
}
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
上面有一个错误,就是如果对象继承了父类,就无法将值注入到父类的的属性中,因为cls.getDeclaredFields()
无法获取父类的属性,所以我又改了一种方法
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.jdbc.core.RowMapper;
import com.lsb.exam.utils.StringUtils;
public class MyRowMapper implements RowMapper {
Class cls;
public MyRowMapper(Class cls) {
this.cls = cls;
}
@Override
public T mapRow(ResultSet rs, int rowNum) {
try {
T obj = cls.newInstance();
//这个只能获取当前类的共有私有字段
//Field[] fields = cls.getDeclaredFields();
List list = new ArrayList<>();
Class> c = cls;
//循环获取
while(c != null){
list.addAll(Arrays.asList(c.getDeclaredFields()));
c = c.getSuperclass();
}
// 获取所有的属性
for (Field field : list) {
field.setAccessible(true);
if (field.getGenericType().toString().equals("class java.lang.Integer")) {
Method m = obj.getClass().getMethod(
"set" + StringUtils.firstChar2UpperCase(field.getName()), java.lang.Integer.class);
m.invoke(obj, rs.getInt(field.getName()));
}
if (field.getGenericType().toString().equals("class java.lang.String")) {
Method m = obj.getClass().getMethod(
"set" + StringUtils.firstChar2UpperCase(field.getName()), java.lang.String.class);
m.invoke(obj, rs.getString(field.getName()));
}
if (field.getGenericType().toString().equals("class java.util.Date")) {
Method m = obj.getClass().getMethod(
"set" + StringUtils.firstChar2UpperCase(field.getName()), java.util.Date.class);
m.invoke(obj, rs.getDate(field.getName()));
}
}
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
其实过程中,有一个反射的知识很重要,关于方法的介绍:
1、获取class
方法 | 描述 |
---|---|
object.getClass() | 获取这个实例所属的class对象 |
T.class | 通过类型获取所属class对象 |
Class.forName() | 通过类路径获取class对象 |
Class.getSuperclass() | 获取父类的class对象 |
Class.getClasses() | 获取类内所有的公开的类,接口,枚举成员,以及它继承的成员(特指类) |
Class.getDeclaredClasses() | 通过类内显示声明的类,接口 |
Class.getEnclosingClass() | 获取闭包类 |
2、获取属性
方法 | 描述 |
---|---|
getDeclaredField(String name) | 获取指定字段(公有,私有),不包括父类字段 |
getField(String name) | 获取指定字段(公有),包括父类字段 |
getDelaredFields() | 获取所有类内显示声明的字段(公有,私有),不包括父类字段 |
getFields() | 获取所有字段(公有),包括父类字段 |
3、获取方法
方法 | 描述 |
---|---|
getDeclaredMethod(String name, Class> … paramType) | 获取指定方法(公有,私有),不包括父类方法 |
getMethod(String name, Class> … paramType) | 获取指定方法(公有),包括父类方法 |
getDeclaredMethods() | 获取所有声明方法(公有,私有),不包括父类方法 |
getMethods() | 获取所有方法(公有),包括父类方法 |
上面的信息可以访问Oracle网站的反射信息。