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>
pom.xml坐标
<?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>
applicationContext.xml
#####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的自学之路之入JdbcTemplate

spring boot 系列之三:spring boot 整合JdbcTemplate

Spring框架之使用JdbcTemplate开发Dao层程序

spring之jdbcTemplate

Spring Boot之JdbcTemplate多数据源配置与使用