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

相关推荐
麦兜*2 天前
MongoDB Atlas 云数据库实战:从零搭建全球多节点集群
java·数据库·spring boot·mongodb·spring·spring cloud
麦兜*2 天前
MongoDB 在物联网(IoT)中的应用:海量时序数据处理方案
java·数据库·spring boot·物联网·mongodb·spring
青衫客362 天前
Spring异步编程- 浅谈 Reactor 核心操作符
java·spring·响应式编程
Cyan_RA92 天前
SpringMVC @RequestMapping的使用演示和细节 详解
java·开发语言·后端·spring·mvc·ssm·springmvc
wuxuanok3 天前
SpringBoot -原理篇
java·spring boot·spring
若鱼19193 天前
spring-kafka消费异常处理
spring·kafka
送秋三十五3 天前
spring源码分析————ListableBeanFactory
java·后端·spring
一又四分之一.3 天前
spring、springboot、springCloud
spring boot·spring·spring cloud
float_六七3 天前
Spring事务注解@Transactional核心机制详解
java·后端·spring
Java水解3 天前
从 “Hello AI” 到企业级应用:Spring AI 如何重塑 Java 生态的 AI 开发
后端·spring