Spring之JdbcTemplate使用
一:JdbcTemplate概述及入门
“Don‘t Reinvent the Wheel” , 这是一句很经典的话,出自Spring官方,翻译过来就是说 “不要重复发明轮子” 。由此我们可以猜测,JdbcTemplate的存在使我们开发人员可以摒弃JDBC的原始开发模式,使我们不必重复性的写JDBC原生代码。所以说Spring为了开发的效率,顺带着写了一套JdbcTemplate的模板工具类,它对原始的JDBC有着一个封装,通过模板设计模式帮我们消除了冗余的代码;有经验的朋友们应该会很清楚的知道dbutils工具类,它的封装和JdbcTemplate封装有着相似之处,都是为了简化JDBC开发的方便。
Tips:大家凡是在Spring中看到xxxTemplate,就是说明被封装了一个模板类
1:JdbcTemplate类支持的回调类
(一):预编译语句及存储过程创建回调:用于根据JdbcTemplate提供的连接创建相应的语句 ①:PreparedStatementCreator: 通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的PreparedStatement; ②:CallableStatementCreator: 通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的CallableStatement; (二):预编译语句设值回调:用于给预编译语句相应参数设值 ①:PreparedStatementSetter: 通过回调获取JdbcTemplate提供的PreparedStatement,由用户来对相应的预编译语句相应参数设值; ②:BatchPreparedStatementSetter: 类似于PreparedStatementSetter,但用于批处理,需要指定批处理大小; (三):自定义功能回调:提供给用户一个扩展点,用户可以在指定类型的扩展点执行任何数量需要的操作 ①:ConnectionCallback: 通过回调获取JdbcTemplate提供的Connection,用户可在该Connection执行任何数量的操作; ②:StatementCallback: 通过回调获取JdbcTemplate提供的Statement,用户可以在该Statement执行任何数量的操作; ③:PreparedStatementCallback: 通过回调获取JdbcTemplate提供的PreparedStatement,用户可以在该PreparedStatement执行任何数量的操作; ④:CallableStatementCallback: 通过回调获取JdbcTemplate提供的CallableStatement,用户可以在该CallableStatement执行任何数量的操作; (四):结果集处理回调:通过回调处理ResultSet或将ResultSet转换为需要的形式 ①:RowMapper: 用于将结果集每行数据转换为需要的类型,用户需实现方法mapRow(ResultSet rs, int rowNum)来完成将每行数据转换为相应的类型。 ②:RowCallbackHandler: 用于处理ResultSet的每一行结果,用户需实现方法processRow(ResultSet rs)来完成处理,在该回调方法中无需执行rs.next(),
该操作由JdbcTemplate来执行,用户只需按行获取数据然后处理即可。 ③:ResultSetExtractor: 用于结果集数据提取,用户需实现方法extractData(ResultSet rs)来处理结果集,用户必须处理整个结果集;
2:搭建一个最简单的JdbcTemplate
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<dependencies> <!--Spring核心包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.6.RELEASE</version> </dependency> <!--Spring的操作数据库坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.6.RELEASE</version> </dependency> <!--Spring测试坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.6.RELEASE</version> </dependency> <!--Mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.32</version> </dependency> <!--测试包--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--开启注解扫描--> <context:component-scan base-package="cn.xw"></context:component-scan> <!--DriverManagerDataSource放入容器--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql:///demo_school"></property> <property name="username" value="root"></property> <property name="password" value="123"></property> </bean> <!--JdbcTemplate放入容器--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!--StudentDao放入容器--> <bean id="studentDao" class="cn.xw.dao.impl.StudentDaoImpl"> <property name="template" ref="jdbcTemplate"></property> </bean> <!--StudentService放入容器--> <bean id="studentService" class="cn.xw.service.impl.StudentServiceImpl"> <property name="studentDao" ref="studentDao"></property> </bean> </beans>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#####Student实体类 public class Student { private int id; //主键id private String name; //姓名 private String sex; //性别 private int age; //年龄 private double credit; //学分 private double money; //零花钱 private String address; //住址 private String enrol; //入学时间 //因为简单的单表CRUD就不涉及到外键 //private int fid; //外键 连接家庭表信息学生对家庭,一对一 //private int tid; //外键 连接老师信息 学生对老师,一对一 //创建构造器/get/set/toString就不展示了 } ++++++++++++++++++++++++++++++++++++++++++ #####StudentDao接口 /** * Student接口数据操作 * @author ant */ public interface StudentDao { //保存学生 void save(Student student); } ++++++++++++++++++++++++++++++++++++++++++ #####StudentDaoImpl实现类 /** * Student数据操作实现类 * @author ant */ public class StudentDaoImpl implements StudentDao { //聚合JdbcTemplate 后面的set注入 private JdbcTemplate template; public void setTemplate(JdbcTemplate template) { this.template = template; } //添加数据 public void save(Student student) { Object[] obj = {student.getName(), student.getSex(), student.getAge(), student.getCredit(), student.getMoney(), student.getAddress(), student.getEnrol()}; String sql = "insert into student (sname,ssex,sage,scredit,smoney,saddress,senrol) values (?,?,?,?,?,?,?) "; //添加 template.update(sql,obj); } } ++++++++++++++++++++++++++++++++++++++++++ #####StudentService接口 /** * Student业务处理接口 * @author ant */ public interface StudentService { //添加学生 void save(Student student); } ++++++++++++++++++++++++++++++++++++++++++ #####StudentServiceImpl实现类 /** * Student业务处理实现类 * @author ant */ public class StudentServiceImpl implements StudentService { //聚合StudentDao操作数据 后面set注入对象 private StudentDao studentDao; public void setStudentDao(StudentDao studentDao) { this.studentDao = studentDao; } //保存学生 public void save(Student student) { studentDao.save(student); } } ++++++++++++++++++++++++++++++++++++++++++ #####Client测试类 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") public class Client { @Autowired @Qualifier(value="studentService") private StudentService ss; @Test public void saveStudent(){ Student student = new Student(0, "王二虎", "男", 16, 55.5, 600.5, "安徽滁州", "2018-8-8"); ss.save(student); } }
①:简单介绍
<!--Spring的操作数据库坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.6.RELEASE</version> </dependency>
//上面导入的是使用Spring对数据库简单操作的坐标,其主要用到的对象就是JdbcTemplate
大家在看我上面的代码会发现我即没使用C3P0也没使用DBCP这2个连接池,其实我使用的是Spring为我们封装的内置数据源DriverManagerDataSource,这个使用也是挺简洁的。
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql:///demo_school"></property> <property name="username" value="root"></property> <property name="password" value="123"></property> </bean>
二:JdbcTemplate的单表增删改操作 SQL资料
1:增加、更新、删除(SQL语句不带参数)
这增删改的使用方式上都是大同小异,都是对数据库进行写操作,所以在这里我直接使用update就可以完成操作,所以我挑增加操作不带参数详细说一下,后面再简单举两个更新和删除操作
①:int update(String sql)
介绍:这个是最简单的不带参数完成增删改,关注点再SQL语句上 推荐 简单
//添加数据 不使用参数 public void saveA() { //编写SQL语句 String sql = "insert into student (sname,ssex,sage,scredit,smoney,saddress,senrol)" + " values ('王老虎','男',25,50,600,'安徽六安','2019-9-8') "; //直接放入update方法中 template.update(sql); }
②:int update(PreparedStatementCreator psc)
介绍:这个update方法里面嵌套了一个PreparedStatementCreator接口,通过回调会返回一个Connection,由用户自己创建相关的PreparedStatement
//添加数据 不使用参数 public void saveB() { //sql语句 final String sql = "insert into student (sname,ssex,sage,scredit,smoney,saddress,senrol)" + " values ('王小二','男',25,50,600,'安徽六安','2019-9-8') "; //调用update方法 传入PreparedStatementCreator接口的匿名内部类 template.update(new PreparedStatementCreator() { public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { //通过用户自己获得Connection后调用prepareStatement执行sql,原生写法 return connection.prepareStatement(sql); } }); }
//这里提示一下,在jdk1.8之前,创建匿名内部类的时候引用外部变量,那个变量要指定为final
③:int update(PreparedStatementCreator psc, KeyHolder generatedKeyHolder)
介绍:说到上一个方法,我们用户通过回调获得Connection,自己操作,这样我们就有扩展性,我可以用这个获取我当前插入数据的主键id(前提主键id是自增长)
//添加数据 不使用参数 并且返回插入数据的主键id public void saveC(){ //sql语句 final String sql = "insert into student (sname,ssex,sage,scredit,smoney,saddress,senrol)" + " values ('谢霆锋','男',25,50,600,'安徽蚌埠','2019-9-8') "; //创建GeneratedKeyHolder对象,用于接收主键id final GeneratedKeyHolder keyHolder = new GeneratedKeyHolder(); //调用update方法 并传入PreparedStatementCreator匿名内部类 和keyHolder template.update(new PreparedStatementCreator() { public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { //这里要注意5.1.7版本之后要加入Statement.RETURN_GENERATED_KEYS才可获取自增长的主键id return connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); } },keyHolder); //获取id并转换为int类型 System.out.println("当前插入数据的主键是:"+keyHolder.getKey().intValue()); }
④:void execute(String sql) 不推荐了解就行 局限性太大
//补充方法 public void saveD(){ //sql语句 String sql = "insert into student (sname,ssex,sage,scredit,smoney,saddress,senrol)" + " values ('蚂蚁小哥','男',25,50,600,'安徽蚌埠','2019-9-8') "; template.execute(sql); }
⑤:删、改简单演示
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//删除70号id学生 public void deleteA(){ template.update("delete from student where sid=70"); } //删除75号id学生 public void deleteB(){ template.update(new PreparedStatementCreator() { public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { return connection.prepareStatement("delete from student where sid=75"); } }); } //更新学生 public void update(){ template.update("update student set sname='潇洒哥' where sid=76 "); }
⑥:关于上面操作可能会遇到的异常
org.springframework.dao.TransientDataAccessResourceException: PreparedStatementCallback; Generated keys not requested.
You need to specify Statement.RETURN_GENERATED_KEYS to Statement.executeUpdate() or Connection.prepareStatement().;
nested exception is java.sql.SQLException: Generated keys not requested. You need to specify
Statement.RETURN_GENERATED_KEYS to Statement.executeUpdate() or Connection.prepareStatement().
//调用update方法 并传入PreparedStatementCreator匿名内部类 和keyHolder template.update(new PreparedStatementCreator() { public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { //这里要注意5.1.7版本之后要加入Statement.RETURN_GENERATED_KEYS才可获取自增长的主键id return connection.prepareStatement(sql); } },keyHolder);
我在写之前插入数据后返回自增长的主键id的时候报的错误,我根据我标黑的地方查了一下api和网上的解释,才发现,我导入的mysql驱动坐标是5.1.32,但是5.1.7版本版本之后的mysql-connector增加了返回GeneratedKeys的条件,
如果需要返回GeneratedKeys,则PreparedStatement需要显示添加一个参数Statement.RETURN_GENERATED_KEYS
static int CLOSE_ALL_RESULTS 该常量指示调用 getMoreResults 时应该关闭以前一直打开的所有 ResultSet 对象。 static int CLOSE_CURRENT_RESULT 该常量指示调用 getMoreResults 时应该关闭当前 ResultSet 对象。 static int EXECUTE_FAILED 该常量指示在执行批量语句时发生错误。 static int KEEP_CURRENT_RESULT 该常量指示调用 getMoreResults 时应该关闭当前 ResultSet 对象。 static int NO_GENERATED_KEYS 该常量指示生成的键应该不可用于获取。 static int RETURN_GENERATED_KEYS 该常量指示生成的键应该可用于获取。 static int SUCCESS_NO_INFO 该常量指示批量语句执行成功但不存在受影响的可用行数计数。
template.update(new PreparedStatementCreator() { public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { //这里要注意5.1.7版本之后要加入Statement.RETURN_GENERATED_KEYS才可获取自增长的主键id return connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); } },keyHolder);
2:增加、更新、删除(SQL语句带参数)
①:int update(String sql,PreParedStatementSetter pss)
//添加数据 带参数 public void saveE(final Student student){ //sql语句 final String sql = "insert into student (sname,ssex,sage,scredit,smoney,saddress,senrol)" + " values (?,?,?,?,?,?,?) "; //调用update方法完成添加 template.update(sql, new PreparedStatementSetter() { public void setValues(PreparedStatement preparedStatement) throws SQLException { //对sql的占位符一个一个手动赋值 ,要想使用原生,下面回介绍 preparedStatement.setString(1,student.getName()); preparedStatement.setString(2,student.getSex()); preparedStatement.setInt(3,student.getAge()); preparedStatement.setDouble(4,student.getCredit()); preparedStatement.setDouble(5,student.getMoney()); preparedStatement.setString(6,student.getAddress()); preparedStatement.setString(7,student.getEnrol()); } }); }
②:int update(String sql,Object[] args,int[] argTypes) 不推荐
介绍:这里主要就是数据和类型对应放入sql占位符上,sql:就是传入带占位符的sql语句,args:sql需要传入占位符的参数,argTypes:需要注入的SQL参数的JDBC类型(可以从java.sql.Types类中获取类型常量)
//添加数据 带参数 public void saveF(Student student){ //sql语句 final String sql = "insert into student (sname,ssex,sage,scredit,smoney,saddress,senrol)" + " values (?,?,?,?,?,?,?) "; //把student数据封装到Object数组中 Object[] obj = {student.getName(), student.getSex(), student.getAge(), student.getCredit(), student.getMoney(), student.getAddress(), student.getEnrol()}; //各数据对应的类型 int [] types={Types.VARCHAR,Types.VARCHAR,Types.INTEGER,Types.DOUBLE, Types.DOUBLE,Types.VARCHAR,Types.VARCHAR}; //调用方法存储 数据和类型对应 template.update(sql,obj,types); }
③:int update(String sql ,Object...args) 推荐,最常用
其实内部还是调用①实现的,JdbcTemplate提供这种更简单的方式“update(String sql, Object... args)”来实现设值,所以只要当使用该种方式不满足需求时才应使用PreparedStatementSetter(上面方法saveE)。
//添加数据 带参数 public void saveG(Student student){ //sql语句 final String sql = "insert into student (sname,ssex,sage,scredit,smoney,saddress,senrol)" + " values (?,?,?,?,?,?,?) "; //把student数据封装到Object数组中 Object[] obj = {student.getName(), student.getSex(), student.getAge(), student.getCredit(), student.getMoney(), student.getAddress(), student.getEnrol()}; //这update后面可以传入一个可变参,可变参的本身也是个伪数组,所以传数组和直接传值一样的 template.update(sql,obj); }
③:int update(PreparedStatementCreator psc)
介绍:使用该方法可以得到回调对象Connection,自己通过这个Connection对象使用原生JDBC方式来给sql注入参数,从而达到增删改
//添加数据 带参数 public void saveH(final Student student){//参数也是局部变量,也必须用final修饰,内部类中才能访问(全局变量不用) //sql语句 final String sql = "insert into student (sname,ssex,sage,scredit,smoney,saddress,senrol)" + " values (?,?,?,?,?,?,?) "; template.update(new PreparedStatementCreator() { public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { PreparedStatement prepareStatement = connection.prepareStatement(sql); //这就是原生jdbc底层使用prepareStatemnt一个一个问号赋值 prepareStatement.setString(1,student.getName()); prepareStatement.setString(2,student.getSex()); prepareStatement.setInt(3,student.getAge()); prepareStatement.setDouble(4,student.getCredit()); prepareStatement.setDouble(5,student.getMoney()); prepareStatement.setString(6,student.getAddress()); prepareStatement.setString(7,student.getEnrol()); return prepareStatement; } }); }
④:int update(PreparedStatementCreator psc ,KeyHolder generatedKeyHolder)
介绍:在插入数据的同时获取被插入数据自增长的主键id,并返回
//添加数据 并返回添加的主键id public void saveI(final Student student){ //sql语句 final String sql = "insert into student (sname,ssex,sage,scredit,smoney,saddress,senrol)" + " values (?,?,?,?,?,?,?) "; //用来接收返回的主键id KeyHolder keyHolder = new GeneratedKeyHolder(); //调用添加方法 template.update(new PreparedStatementCreator() { public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { //这里和前面的介绍一样 mysql驱动版本不同 要携带Statement.RETURN_GENERATED_KEYS PreparedStatement prepareStatement = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS); //这就是原生jdbc底层使用preparedStatement一个一个问号赋值 prepareStatement.setString(1,student.getName()); prepareStatement.setString(2,student.getSex()); prepareStatement.setInt(3,student.getAge()); prepareStatement.setDouble(4,student.getCredit()); prepareStatement.setDouble(5,student.getMoney()); prepareStatement.setString(6,student.getAddress()); prepareStatement.setString(7,student.getEnrol()); return prepareStatement; } },keyHolder); System.out.println("插入数据的id是:"+keyHolder.getKey().intValue()); }
⑤:更新和删除
在这里的增删改都使用同一种方法update,无非里面的参数不同,其实它们的操作都是一样的,可以参照上面的添加操作完成删除、更新功能
3:批量删除、更新、添加
①:int [] batchUpdate(String sql,BatchPreparedStatementSetter bpss)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//批量添加数据 public void batchSave(final List<Student> stus){ //sql语句 final String sql = "insert into student (sname,ssex,sage,scredit,smoney,saddress,senrol)" + " values (?,?,?,?,?,?,?) "; //实现批量添加 返回操作完成参数 完成就返回一个 1 ,假设3条记录都添加上就返回[1,1,1] int[] totalSave = template.batchUpdate(sql, new BatchPreparedStatementSetter() { public void setValues(PreparedStatement preparedStatement, int i) throws SQLException { //注入参数 preparedStatement.setString(1, stus.get(i).getName()); preparedStatement.setString(2, stus.get(i).getSex()); preparedStatement.setInt(3, stus.get(i).getAge()); preparedStatement.setDouble(4, stus.get(i).getCredit()); preparedStatement.setDouble(5, stus.get(i).getMoney()); preparedStatement.setString(6, stus.get(i).getAddress()); preparedStatement.setString(7, stus.get(i).getEnrol()); } //返回批量操作的数量 public int getBatchSize() { //传来的集合的size return stus.size(); } }); System.out.println("添加的总记录数:"+ totalSave.length); } #####测试代码方法 @Test public void saveStudentD(){ List<Student> list=new ArrayList<Student>(); Student stu1 = new Student(0, "张小俊", "男", 16, 55.5, 600.5, "安徽滁州", "2018-8-8"); Student stu2 = new Student(0, "王打破", "男", 16, 55.5, 600.5, "安徽滁州", "2018-8-8"); Student stu3 = new Student(0, "吴小莉", "男", 16, 55.5, 600.5, "安徽滁州", "2018-8-8"); list.add(stu1); list.add(stu2); list.add(stu3); ss.batchSave(list); }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//批量更新数据 public void batchUpdate(final List<Student> stus){ //更新的sql语句 final String sql="update student set sname=?,sage=?,saddress=? where sid=?"; //实现批量更改 返回操作完成参数 完成就返回一个 1 ,假设3条记录都添加上就返回[1,1,1] int [] totalUpdate=template.batchUpdate(sql, new BatchPreparedStatementSetter() { public void setValues(PreparedStatement preparedStatement, int i) throws SQLException { //注入参数 preparedStatement.setString(1, stus.get(i).getName()); preparedStatement.setInt(2, stus.get(i).getAge()); preparedStatement.setString(3, stus.get(i).getAddress()); preparedStatement.setInt(4, stus.get(i).getId()); } //返回批量操作更新的数量 public int getBatchSize() { return stus.size(); } }); System.out.println("更新的总数量:"+totalUpdate.length); } ######测试方法 @Test public void saveStudentD(){ List<Student> list=new ArrayList<Student>(); Student stu1 = new Student(1, "王大炮", null, 16, 0, 0, "安徽滁州", null); Student stu2 = new Student(2, "李筱思", null, 16, 0, 0, "安徽滁州", null); Student stu3 = new Student(3, "吴凡亦", null, 16, 0, 0, "安徽滁州", null); list.add(stu1); list.add(stu2); list.add(stu3); ss.batchUpdate(list); }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//完成批量删除 public void batchDelete(final List<Integer> ids) { //实现批量删除操作 int[] totalDelete = template.batchUpdate("delete from student where sid=?", new BatchPreparedStatementSetter() { public void setValues(PreparedStatement preparedStatement, int i) throws SQLException { preparedStatement.setInt(1, ids.get(i)); } //返回批量删除的数量 public int getBatchSize() { return ids.size(); } }); System.out.println("删除的总数量:" + totalDelete.length); } #####测试代码 @Test public void saveStudentD(){ List<Integer> list=new ArrayList<Integer>(); list.add(65); list.add(66); list.add(67); ss.batchDelete(list); }
三:JdbcTemplate的单表查询操作(重点)
㈠:queryForObject方法之介绍
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
①:String sql: 传入的SQL语句,如果SQL是预处理SQL带'?'占位符的,可通过后面有的参数指定数据源 ②:Object [] args: 指定数据源为Object数组,为预处理SQL传入指定的参数值 ③:Object...args: 指定数据源为Object伪数组,为预处理SQL传入指定参数值 ④:int [] argTypes: 指定数据库字段类型,可以通过java.sql.Type中获取类型常量 ⑤:Class<T> requiredType 指定返回的数据类型,只能是JDK内置类型,如String、Integer、Double等,自定义实体类型不行 ⑥:RowMapper<T> rowMapper: 用来对数据库字段和实体类属性不匹配,从而映射出对应的对象
1:查询一个值(不携带参数)
①:T queryForObject(String sql,Class<T> requiredType);
介绍:这个返回的是一个数值,注意参数requiredType只能是String、Integer、Double等JDK内置的类型,不能是自定义的实体类型
//查询一个值 聚合查询等一系列操作 public Integer totalCount(){ //查询表中数据的总记录数 Integer total = template.queryForObject("select count(sid) from student", Integer.class); //比如查询学生的总学分等等 //Integer sumCredit = template.queryForObject("select sum(scredit) from student", Integer.class); return total; }
2:查询一个值(携带参数)
①:T queryForObject(String sql ,Object [] obj ,Class<T> requiredType)
介绍:此查询返回一个值,但是可传入预处理SQL,后期通过Object数组来传入占位符的数值,还是和上面一样只能指定Class类型为JDK内置的
//查询一个值带参数 返回一个名字 public String findByIdReturnName(Integer id){ //因为只能接收Object数组,所以我把传来的数据封装成数组传入下面方法 Object [] obj={id}; //查询 String name = template.queryForObject("select sname from student where sid=?", obj, String.class); return name; }
3:查询一行记录,映射为对象实体类(不携带参数)
①:T queryForObject(String sql,RowMapper<T> rowMapper)
介绍:此查询返回一个实体对象,通过RowMapper接口的实现类来映射
//查询一行记录,无传入参数 public Student findOneForId(){ //查询固定SQL指定的id为6的学生 Student student = template.queryForObject("select * from student where sid=6", new RowMapper<Student>() { public Student mapRow(ResultSet resultSet, int i) throws SQLException { //创建一个学生对象,因为实体类和字段不对应 ,我要每个字段都要映射 Student stu = new Student(); stu.setId(resultSet.getInt("sid")); stu.setName(resultSet.getString("sname")); stu.setSex(resultSet.getString("ssex")); stu.setAge(resultSet.getInt("sage")); stu.setCredit(resultSet.getDouble("scredit")); stu.setMoney(resultSet.getDouble("smoney")); stu.setAddress(resultSet.getString("saddress")); stu.setEnrol(resultSet.getString("senrol")); return stu; } }); return student; }
4:查询一行记录,映射为对象实体类(携带参数)
①:T queryForObject(String sql , Object [] args, RowMapper<T> rowMapper)
介绍:和上面一种操作差不多,都用到了映射对象,但是唯一多一个数组参数,所以sql可以是预处理SQL
//查询一行记录,传入指定id public Student findById(Integer id){ //因为只能接收Object []数组 所以我把传来的数据加工成数组传入查询中 Object [] obj={id}; //查询指定id的学生数据 Student student = template.queryForObject("select * from student where sid=?",obj, new RowMapper<Student>() { public Student mapRow(ResultSet resultSet, int i) throws SQLException { //创建一个学生对象,因为实体类和字段不对应 ,我要每个字段都要映射 Student stu = new Student(); stu.setId(resultSet.getInt("sid")); stu.setName(resultSet.getString("sname")); stu.setSex(resultSet.getString("ssex")); stu.setAge(resultSet.getInt("sage")); stu.setCredit(resultSet.getDouble("scredit")); stu.setMoney(resultSet.getDouble("smoney")); stu.setAddress(resultSet.getString("saddress")); stu.setEnrol(resultSet.getString("senrol")); return stu; } }); return student; }
5:查询多行多列数据,存入List中(不携带参数)
①:List<Map<String,Object>> queryForList(String sql)
介绍:直接把查询的数据放入List<Map<String,Object>>类型中,假设有3条记录,把每一条记录都以键值对放入map中,键是String,值全部使用Object类型,然后封装,有3条记录就封装这样的类型3次,然后依次放入List中。注:也可以当作多行单列查询,但是数据封装为List<Map<String,Object>>
//查询多行多列数据 封装到List中类型为Map public List<Map<String,Object>> findByStudent(){ //查询 固定sql不带参 List<Map<String, Object>> maps = template.queryForList("select * from student where sid in(1,6,8)"); return maps; } ####测试代码 @Test public void TestA() { List<Map<String, Object>> byStudent = ss.findByStudent(); //遍历数据 for (Map<String, Object> maps : byStudent) { for(Map.Entry<String,Object> obj:maps.entrySet()){ System.out.println(obj.getKey()+" "+obj.getValue()); } } }
㈡:queryForList方法之介绍
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
①:String sql: 传入的SQL语句,如果SQL是预处理SQL带'?'占位符的,可通过后面有的参数指定数据源 ②:Class<T> elementType: 传入获取每个数据要封装的类型,传入这个就代表只可以获取多列单行数据了 ③:Object [] args: 指定数据源为Object数组,为预处理SQL传入指定的参数值 ④:Object...args: 指定数据源为Object伪数组,为预处理SQL传入指定参数值 ⑤:int [] argTypes: 指定数据库字段类型,可以通过java.sql.Type中获取类型常量
1:查询多行单列数据,存入List中(不携带参数)
①:List<T> queryForList(String sql , Class<T> elementType)
介绍:查询多行单列,因为指定了获取的数据的类型,如果要获取全部数据可以使用第5种方法,
//查询一行单列数据 封装到List<T>中 public List<String> findAllByReturnName(){ //查询多行单列数据 Class<T> elementType参数指定当前要查询列的类型 List<String> stunames = template.queryForList("select sname from student where sid in(1,5,3,6,8,9)", String.class); return stunames; }
2:查询多行多列数据,存入List中(携带参数)①
①:List<Map<String,Object>> queryForList(String sql , Object . . . args)
介绍:这种和第5的差不多,只是多了一个可变参,也就是我们常说的伪数组,可以传入指定的值,最后封装的类型还是List<Map<String,Object>>
//查询多行多列,根据传入多个id查询(携带参数) public List<Map<String,Object>> findByManyId(Integer ... args){ List<Map<String, Object>> maps = template.queryForList("select * from student where sid in(?,?,?)", args); return maps; }
3:查询多行多列数据,存入List中(携带参数)②
①:List<Map<String,Object>> queryForList(String,Object [] args,int [] types)
介绍:这个查询总体来说和第5一样,只是多了2个参数Object [] args 和int [] type,说明传入的参数和传入的类型必须挨个对应
//查询多行多列,更根据性别和模糊姓名查询,并指定limit查询几行(携带参数) 这里的name参数因为是模糊查询 传入 “张%” public List<Map<String,Object>> findBySexAndLikeNameAndLimit(String sex,String name,Integer order){ //带有占位符的sql语句 String sql="select * from student where ssex=? and sname like ? limit ?"; //把传入的数据封装为Object数组 Object [] obj={sex,name,order}; //封装传入数值对应的数据库字段类型 int [] types={Types.VARCHAR,Types.VARCHAR,Types.INTEGER}; List<Map<String, Object>> maps = template.queryForList(sql, obj, types); return maps; /** * 这里说明一下Object[]和int[]里面的数值和类型必须挨个对应,就算是传入limit查询个数,也要加入 * 数据库类型INTEGER类型 */ }
4:查询多行单列数据,存入List中(携带参数)
①:List<T> queryForList(String sql,Object [] args , Class<T> elementType)
介绍:查询多行单列,因为指定了Class<T> elementType参数,而且还是携带参数
//查询多行单列 携带参数 查询性别为?的姓名 public List<String> findAllReturnName(String sex){ //因为接收Object[] 类型,所以转换传入的数据 Object [] obj={sex}; //查询封装返回 List<String> strings = template.queryForList("select sname from student where sex=?", obj, String.class); return strings; }
5:说明其它相关方法
①:List<T> queryForList(String sql ,Class<T> elementType , Object...args)
介绍:这个也是查询多行单列的查询方法,但是唯独出现了一个可变参伪数组,还有就算传入获取数据的类型
②:List<T> queryForList(String sql , Object [] args,int [] argType , Class<T> elementType)
介绍:这个也是查询多行单列,这里注意args和argType传入的数据和类型必须挨个对应,还有就是传入一个获取的数据类型
㈢:query方法之介绍(用的最多,也是要掌握的)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
一:查询无返回值 void ①:query (String sql, RowCallbackHandler rch) ②:query (String sql, Object[] args, RowCallbackHandler rch) ③:query (String sql, 0bject[] args, int[] argTypes, RowCallbackHandler rch) ④:query (String sql, RowCallbackHandler rch, 0bject... args) ⑤:query (String sql, PreparedStatementSetter pss, RowCa1lbackHandler rch) ⑥:query (PreparedStatementCreator psc, RowCallbackHandler rch) 二:查询后返回List<T> 类型集合 List<T> ①:query (String sql, RowMapper<T> rowMapper ) ②:query (String sql, 0bject[] args, RowMapper<T> rowMapper ) ③:query (String sql, 0bject[] args, intl] argTypes, RowMapper<T> rowMapper ) ④:query (String sql, RowMapper<T> rowMapper, 0bject.... args) ⑤:query (String sql, PreparedStatementSetter pss, RowMapper<T> rowMapper ) ⑥:query (PreparedStatementCreator psc, RowMapper<T> rowMapper ) 三:返回自定义数据类型 T ①:query (String sql, ResultSetExtractor<T> rse ) ②:query (String sql, 0bject[] args, ResultSetExtractor<T> rse) ③:query (String sql, 0bject[] args, int[] argTypes, ResultSetExtractor<T> rse ) ④:query (String sql, ResultSetExtractor<T> rse, 0bject... args) ⑤:query (String sql, PreparedStatementSetter pss, ResultSetExtractor<T> rse) ⑥:query (PreparedStatementCreator psc, ResultSetExtractor<T> rse ) ⑦:query (PreparedStatementCreator psc, PreparedStatementSetter pss, ResultSetExtractor<T> rse)
1:一些常用的查询介绍
①:List<T> query(String sql , RowMapper<T> rowMapper)
介绍:查询多行多列数据,但是查询的具体类型可以由RowMapper来映射出自己的实体类
//查询全部数据 不懈怠参数 public List<Student> findAll(){ //查询全部数据 通过RowMapper的实现类来映射匹配 List<Student> students = template.query("select * from student", new RowMapper<Student>() { public Student mapRow(ResultSet resultSet, int i) throws SQLException {
//创建一个学生对象 然后挨个映射赋值 Student stu = new Student(); stu.setId(resultSet.getInt("sid")); stu.setName(resultSet.getString("sname")); stu.setSex(resultSet.getString("ssex")); stu.setAge(resultSet.getInt("sage")); stu.setCredit(resultSet.getDouble("scredit")); stu.setMoney(resultSet.getDouble("smoney")); stu.setAddress(resultSet.getString("saddress")); stu.setEnrol(resultSet.getString("senrol")); return stu; } }); return students; }
但是我们只停留表面,其实RowMapper<T>还是挺强大的,我们可以把每一行数据转换为自定义key-value的map对象映射为RowMapper<Map<String,Object>>等一系列操作,由此会发现使用起来特别方便,像这种实现RowMapper<T>接口的匿名类,T可以为任意类型
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//查询全部数据 不懈怠参数 public List<Map<String, Object>> findAll(){ //查询全部数据 通过RowMapper的实现类来映射匹配 List<Map<String, Object>> lits = template.query("select * from student limit 1", new RowMapper<Map<String, Object>>() { public Map<String, Object> mapRow(ResultSet resultSet, int i) throws SQLException { Map<String, Object> map = new HashMap<String, Object>(); map.put("ID", resultSet.getInt("sid")); map.put("姓名",resultSet.getString("sname")); return map; } }); return lits; }
②:void query(String sql , RowCollbackHandler rch)
介绍:这个query方法,如果内部使用了RowCollbackHandler回调类后,这个方法就变成了没有返回值,如果要获取数据必须在外部创建一个List对象,然后在回调类里面通过添加的方式来为list添加数据,这种操作也可以转换为任意类型,比如Map等等
//查询全部数据 不携带参数 public List<Student> findAll(){ //创建一个List集合 用来存入查询的数据 (要使用final才可以在内部类中使用该变量) final List<Student> students=new ArrayList<Student>(); //查询全部信息 template.query("select * from student", new RowCallbackHandler() { public void processRow(ResultSet resultSet) throws SQLException { //创建一个学生对象,因为实体类和字段不对应 ,我要每个字段都要映射 Student stu = new Student(); stu.setId(resultSet.getInt("sid")); stu.setName(resultSet.getString("sname")); stu.setSex(resultSet.getString("ssex")); stu.setAge(resultSet.getInt("sage")); stu.setCredit(resultSet.getDouble("scredit")); stu.setMoney(resultSet.getDouble("smoney")); stu.setAddress(resultSet.getString("saddress")); stu.setEnrol(resultSet.getString("senrol")); //把数据存入到带有final属性的list对象中 students.add(stu); } }); return students; }
③:T query(String sql , ResultSetExtractor<T> rse)
介绍:ResultSetExtractor使用回调方法extractData(ResultSet rs)提供给用户整个结果集,让用户来决定这个结果集,同理也可以封装为Map类型等等
//查询全部数据 不携带参数 public List<Student> findAll() {
//查询全部 这里注意如果ResultSetExtractor<T> 为什么类型,后面的返回结果也是指定这个类型 List<Student> students = template.query("select * from student", new ResultSetExtractor<List<Student>>() { public List<Student> extractData(ResultSet resultSet) throws SQLException, DataAccessException { //创建list集合用来存储Student List<Student> list = new ArrayList<Student>(); //遍历ResultSet结果集 while (resultSet.next()) { Student stu = new Student(); stu.setId(resultSet.getInt("sid")); stu.setName(resultSet.getString("sname")); stu.setSex(resultSet.getString("ssex")); stu.setAge(resultSet.getInt("sage")); stu.setCredit(resultSet.getDouble("scredit")); stu.setMoney(resultSet.getDouble("smoney")); stu.setAddress(resultSet.getString("saddress")); stu.setEnrol(resultSet.getString("senrol")); //存储添加Student list.add(stu); } //返回集合 return list; } }); //返回查询数据 return students; }
③:List<T> query(PreparedStatementCreator psc ,RowMapper<T> rowMapper)
//查询全部数据 不携带参数 public List<Student> findAll() { List<Student> students = template.query(new PreparedStatementCreator() { public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { return connection.prepareStatement("select * from student"); } }, new RowMapper<Student>() { public Student mapRow(ResultSet resultSet, int i) throws SQLException { //创建一个学生对象,因为实体类和字段不对应 ,我要每个字段都要映射 Student stu = new Student(); stu.setId(resultSet.getInt("sid")); stu.setName(resultSet.getString("sname")); stu.setSex(resultSet.getString("ssex")); stu.setAge(resultSet.getInt("sage")); stu.setCredit(resultSet.getDouble("scredit")); stu.setMoney(resultSet.getDouble("smoney")); stu.setAddress(resultSet.getString("saddress")); stu.setEnrol(resultSet.getString("senrol")); return stu; } }); return students; }
2:复杂的query查询
这下面要介绍的查询稍微有点繁琐,但是看明白了就明了对query方法有一定的认识了,这下面的三个方法也是对应上面查询的复杂3大类,但是在平常用不到
①:query (PreparedStatementCreator psc, PreparedStatementSetter pss, ResultSetExtractor<T> rse)
//查询全部数据 不携带参数 可对下面方法拆分为3部分回调函数,也和原生JDBC流程一样 public List<Student> findAll() { //query (PreparedStatementCreator psc, PreparedStatementSetter pss, ResultSetExtractor<T> rse) List<Student> students = template.query(new PreparedStatementCreator() { //new PreparedStatementCreator匿名内部类 就像我们以前使用jdbc原生来执行sql语句 public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { return connection.prepareStatement("select * from student where ssex=? limit ?"); } }, new PreparedStatementSetter() { //new PreparedStatementSetter匿名内部类 此操作就像我们原生使用jdbc对占位符使用preparedStatement赋值 public void setValues(PreparedStatement preparedStatement) throws SQLException { preparedStatement.setString(1, "男"); preparedStatement.setInt(2, 5); } }, new ResultSetExtractor<List<Student>>() { //new ResultSetExtractor<T> 匿名内部类让用户自定义数据 就像原生jdbc我们操作resultSet一样 public List<Student> extractData(ResultSet resultSet) throws SQLException, DataAccessException { List<Student> list = new ArrayList<Student>(); while (resultSet.next()) { Student stu = new Student(); stu.setId(resultSet.getInt("sid")); stu.setName(resultSet.getString("sname")); stu.setSex(resultSet.getString("ssex")); stu.setAge(resultSet.getInt("sage")); stu.setCredit(resultSet.getDouble("scredit")); stu.setMoney(resultSet.getDouble("smoney")); stu.setAddress(resultSet.getString("saddress")); stu.setEnrol(resultSet.getString("senrol")); list.add(stu); } return list; } }); return students; }
②:List<T> query (String sql, PreparedStatementSetter pss, RowMapper<T> rowMapper )
//查询全部数据 不携带参数 public List<Student> findAll() { //query (String sql, PreparedStatementSetter pss, RowMapper<T> rowMapper ) //SQL语句,带有占位符 String sql = "select * from student where ssex=? and sname like ?"; //查询数据 List<Student> students = template.query(sql, new PreparedStatementSetter() { public void setValues(PreparedStatement preparedStatement) throws SQLException { preparedStatement.setString(1, "男"); preparedStatement.setString(2, "张%"); } }, new RowMapper<Student>() { public Student mapRow(ResultSet resultSet, int i) throws SQLException { //关系映射并返回映射好的对象 Student stu = new Student(); stu.setId(resultSet.getInt("sid")); stu.setName(resultSet.getString("sname")); stu.setSex(resultSet.getString("ssex")); stu.setAge(resultSet.getInt("sage")); stu.setCredit(resultSet.getDouble("scredit")); stu.setMoney(resultSet.getDouble("smoney")); stu.setAddress(resultSet.getString("saddress")); stu.setEnrol(resultSet.getString("senrol")); return stu; } }); return students; }
③:void query (String sql, PreparedStatementSetter pss, RowCa1lbackHandler rch)
//查询全部数据 不携带参数 public List<Student> findAll() { //query (String sql, PreparedStatementSetter pss, RowCa1lbackHandler rch) //创建一个final类型的集合用来存储查询的数据 final List<Student> students=new ArrayList<Student>(); //SQL语句,带有占位符 String sql = "select * from student where ssex=? and sname like ?"; //查询 query不带返回值,只有通过内部数据存入到事先定义好的集合上才可获取 template.query(sql, new PreparedStatementSetter() { public void setValues(PreparedStatement preparedStatement) throws SQLException { preparedStatement.setString(1, "男"); preparedStatement.setString(2, "张%"); } }, new RowCallbackHandler() { public void processRow(ResultSet resultSet) throws SQLException { Student stu = new Student(); stu.setId(resultSet.getInt("sid")); stu.setName(resultSet.getString("sname")); stu.setSex(resultSet.getString("ssex")); stu.setAge(resultSet.getInt("sage")); stu.setCredit(resultSet.getDouble("scredit")); stu.setMoney(resultSet.getDouble("smoney")); stu.setAddress(resultSet.getString("saddress")); stu.setEnrol(resultSet.getString("senrol")); //把映射好的对象放入final修饰的集合上 students.add(stu); } }); return students; }
四:JdbcTemplate的细节使用
在前面的操作CRUD操作已经介绍完了,其实用到的方法也就那么5种左右,其它的方法里面属性只是为了使程序有一个更好的拓展和灵活性,在接下来我讲为大家介绍JdbcTemplate的细节及技巧
1:关于RowMapper<T>映射的问题
大家在写带有RowMapper属性的时候,发现映射关系每次都要重写一下,如果有20个查询方法,那RowMapper就得写20次,这显然代码冗余了,那么怎么解决呢?因为RowMapper<T>原本就算接口,那么为什么我们不使用一个类继承它呢?接下来就看操作
//编写RowMapper实现类 class StudentRowMapper implements RowMapper<Student>{ //Student属性 private Student student; //重写方法 public Student mapRow(ResultSet resultSet, int i) throws SQLException { student = new Student(); student.setId(resultSet.getInt("sid")); student.setName(resultSet.getString("sname")); student.setSex(resultSet.getString("ssex")); student.setAge(resultSet.getInt("sage")); student.setCredit(resultSet.getDouble("scredit")); student.setMoney(resultSet.getDouble("smoney")); student.setAddress(resultSet.getString("saddress")); student.setEnrol(resultSet.getString("senrol")); return student; } } ####### 查询方法 //查询全部数据 不携带参数 public List<Student> findAll() { //查询方法 后面就不创建RowMapper的匿名内部类了,直接创建一个实现类 List<Student> students = template.query("select * from student", new StudentRowMapper()); return students; }
2:关于创建JdbcTemplate对象问题
public class StudentDaoImpl implements StudentDao { //聚合JdbcTemplate 后面的set注入 private JdbcTemplate template; public void setTemplate(JdbcTemplate template) { this.template = template; }
...后面是一些增删改查的操作
这是一个普通的创建方式,由applicationContext.xml配置文件来创建这个容器并注入数据
<!--DriverManagerDataSource放入容器--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql:///demo_school"></property> <property name="username" value="root"></property> <property name="password" value="123"></property> </bean> <!--JdbcTemplate放入容器--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!--StudentDao放入容器--> <bean id="studentDao" class="cn.xw.dao.impl.StudentDaoImpl"> <property name="template" ref="jdbcTemplate"></property> </bean>
我有个假设,如果现在的项目足够大,xxxDao实现类就有50多个,那么我们每个xxxDao的实现类里面都要包含private JdbcTemplate template;属性吗?现在我们就是这样的,然后在配置文件里面注入50多次,这显然代码冗余,其实String也为我们想到了这个问题,所以我接下来就和大家说说吧!
public class StudentDaoImpl extends JdbcDaoSupport implements StudentDao {} //①:说明:其实我们只需要继承这个类就可以完成了,那我们看看这到底是什么类 public abstract class JdbcDaoSupport extends DaoSupport { @Nullable private JdbcTemplate jdbcTemplate; .....下面的方法暂时省略 } //②说明:这不正是我们平常写的JdbcTemplate属性吗?那猜测肯定有get/set方法吧 //③:废话不多说 我翻翻这个类JdbcDaoSupport public final void setJdbcTemplate(@Nullable JdbcTemplate jdbcTemplate){...} public final JdbcTemplate getJdbcTemplate() {...} //可以看出原来JdbcDaoSupport为我们提供了get/set,但是都使用final修饰,表示不可继承 //我们就可以通过super.getJdbcTemplate()来得到一个JdbcTemplate对象, //④:那继承了就可以使用了吧?别忘了这个要修改一下注入,看看属性名是否匹配,因为是被继承 //到StudentDaoImpl中,所以我直接在这里面注入 //<!--StudentDao放入容器--> <bean id="studentDao" class="cn.xw.dao.impl.StudentDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean>
//后期在dao里面使用jdbcTemplate就使用getJdbcTemplate()获取
.