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网站的反射信息

相关推荐
之歆5 小时前
Spring AI入门到实战到原理源码-MCP
java·人工智能·spring
Mr__Miss5 小时前
保持redis和数据库一致性(双写一致性)
数据库·redis·spring
daidaidaiyu6 小时前
Spring IOC 源码学习 一文学习完整的加载流程
java·spring
Knight_AL7 小时前
Spring 事务传播行为 + 事务失效原因 + 传播行为为什么不用其他模式
数据库·sql·spring
五阿哥永琪7 小时前
Spring中的定时任务怎么用?
java·后端·spring
TaiKuLaHa7 小时前
Spring Bean的生命周期
java·后端·spring
刀法如飞8 小时前
开箱即用的 DDD(领域驱动设计)工程脚手架,基于 Spring Boot 4.0.1 和 Java 21
java·spring boot·mysql·spring·设计模式·intellij-idea
瑶山9 小时前
Spring Cloud微服务搭建一、Nacos配置和服务注册
spring·spring cloud·微服务·nacos
Knight_AL9 小时前
Spring 事务管理:为什么内部方法调用事务不生效以及如何解决
java·后端·spring