Spring中的JdbcTemplate的使用

在最近的一个工作中,为了简单方便我就是用了Spring自带的JdbcTemplate来访问数据库,我以为之前自己很熟练的掌握,后来才发现我太天真了,踩了很多坑。

基本方法

JdbcTemplate自带很多方法可以执行SQL语句,以下我主要列举,比较常用的方法

复制代码
//执行SQL,返回一个对象
@Override
public <T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args)
		 throws DataAccessException {
	List<T> results = query(sql, args, 
			new RowMapperResultSetExtractor<T>(rowMapper, 1));
	return DataAccessUtils.requiredSingleResult(results);
}

//同上,不过多要传入返回值的对象的Class
@Override
public <T> T queryForObject(String sql, Class<T> requiredType)
		 throws DataAccessException {
	return queryForObject(sql, getSingleColumnRowMapper(requiredType));
}

//执行SQL,返回一个Map对象
@Override
public Map<String, Object> queryForMap(String sql, Object... args)
		 throws DataAccessException {
	return queryForObject(sql, args, getColumnMapRowMapper());
}

//执行SQL,返回一个List对象
@Override
public <T> List<T> query(String sql, Object[] args, RowMapper<T> rowMapper) 
		throws DataAccessException {
	return query(sql, args, new RowMapperResultSetExtractor<T>(rowMapper));
}

//执行SQL,返回一个List对象
@Override
@Override
public <T> List<T> queryForList(String sql, Class<T> 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<Map<String, Object>> queryForList(String sql, Object... args) 
		throws DataAccessException {
	return query(sql, args, getColumnMapRowMapper());
}

注意点

至少返回一个对象

复制代码
@Override
public <T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args)
		 throws DataAccessException {
	List<T> results = query(sql, args, 
			new RowMapperResultSetExtractor<T>(rowMapper, 1));
	return DataAccessUtils.requiredSingleResult(results);
}

public RowMapperResultSetExtractor(RowMapper<T> 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

所以如果不确定是否有信息,就使用queryqueryForList来避免错误。返回单对象一般会有这样的限制,如果自己不确定可以使用该方法的时候看一下源码,设置了1就代表必须要有一个值。

返回指定对象

JdbcTemplate里面这样的方法,就是可以传入了一个对象的Class值,就可以返回该对象的值,但是需要注意,它只支持基础类型

复制代码
//例如,它支持以下的写法
public Integer getCourseCount(String sql){
	return (Integer) jdbcTemplate.queryForObject(sql,java.lang.Integer.class);
}

通过源代码发现Class<T> requiredType这个参数支持以下类型,源代码就是从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<T>,就如以下的案例

复制代码
public List<Course> getCourseList(String sql){
	return jdbcTemplate.query(sql,new RowMapper<Course>(){
		@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<Map<String,Object>>

复制代码
@Override
public List<MyScoreDto> 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<Map<String,Object>> list = jdbcTemplate.queryForList(sql);
  if(list!=null && list.size()>0){
    List<MyScoreDto> ls = new ArrayList<MyScoreDto>();
    for(int i=0;i<list.size();i++){
      MyScoreDto c = new MyScoreDto();

        try {
          BeanUtils.populate(c, list.get(i));
        } catch (IllegalAccessException | InvocationTargetException e) {
          e.printStackTrace();
        }

      ls.add(c);
    }
    return ls;
  }

  return null;
}

使用了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<T> implements RowMapper<T> {

	Class<T> cls;

	public MyRowMapper(Class<T> 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<T> implements RowMapper<T> {

	Class<T> cls;

	public MyRowMapper(Class<T> cls) {
		this.cls = cls;
	}

	@Override
	public T mapRow(ResultSet rs, int rowNum) {
		try {

			T obj = cls.newInstance();
			
			//这个只能获取当前类的共有私有字段
			//Field[] fields = cls.getDeclaredFields();
			List<Field> 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网站的反射信息

相关推荐
Micro麦可乐6 小时前
最新Spring Security实战教程(十八)安全日志与审计:关键操作追踪与风险预警
java·spring boot·后端·安全·spring·安全审计
星垣矩阵架构师7 小时前
六.架构设计之存储高性能——缓存
java·spring·缓存
chanalbert7 小时前
SpringBoot设计基石:约定优于配置与模块化架构
spring boot·spring·spring cloud
江小北8 小时前
今天去面试了,遇到一个面试题,spring单例bean是线程安全的吗?
java·后端·spring
考虑考虑1 天前
feign异常处理
spring boot·后端·spring
可丷乐1 天前
学习笔记-spring core
spring
知其然亦知其所以然1 天前
Spring AI 入门实战:我用七个关键词,彻底搞懂了它的核心概念!
java·后端·spring
RexTechie1 天前
Spring Cloud 原生中间件
spring·spring cloud·中间件
chanalbert1 天前
Spring Boot诞生背景:从Spring的困境到设计破局
java·spring boot·spring
huisheng_qaq1 天前
【Spring源码核心篇-08】spring中配置类底层原理和源码实现
java·spring·spring源码·spring配置类解析·spring注解与实现