Spring之JdbcTemplate使用
Posted 蚂蚁小哥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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
<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>
<?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>
#####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); }
⑤:删、改简单演示
//删除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());以上是关于Spring之JdbcTemplate使用的主要内容,如果未能解决你的问题,请参考以下文章
Spring之004: jdbcTemplate基本使用Spring实物控制
spring boot 系列之三:spring boot 整合JdbcTemplate