Spring JdbcTemplate 调用 Oracle 存储过程 与 Oracle 驱动下载

目录

前  言

Oracle jdbc 驱动版本兼容

Oracle jdbc 驱动下载

Oracle 数据源配置

execute 调用无返回值存储过程

execute 调用单个返回值存储过程

execute 调用返回结果集存储过程

call 方法调用存储过程


前  言

1、关于 JdbcTemplate 的介绍、pom 依赖、DI 注入可以参考《Spring JdbcTemplate 模板剖析 之 常用 增删改查》,本文继续介绍 JdbcTemplate 调用数据库的存储过程,虽然 Mysql 也有存储过程,但是为了尽可能的多覆盖一点,本文选择调用 Oracle 的存储过程,其它数据库也是同理。

1)execute 方法:能执行任何 SQL 语句,一般用于执行 DDL 语句;以及 建表、删表等等 SQL.
2)update、batchUpdate 方法:update 方法用于执行新增、修改、删除等语句;batchUpdate 方法用于执行批处理相关语句;
3)queryForObject、queryForList、query 方法:用于执行查询相关语句;queryForObject 查询的结果只能是1条,多余或少于都会抛异常;
4)queryForList 与 query 查询结果为空时,返回的 list 大小为0,不会引发空指针异常。
5)call 方法:用于执行存储过程、函数相关语句。

2、本文环境:Oracle 11g(驱动版本 ojdbc8-19.3.0.0) + Java JDK 1.8 + Spring Boot 2.1.5 +  + IDEA 2018.

3、JdbcTemplate 本身就是对 JDBC 的轻量级封装,所以调用存储过程也类似 "JDBC 调用存储过程/函数"。

4、为了测试方便,先提前准备数据:准备员工表与部门表测试数据

Oracle jdbc 驱动版本兼容

1、下表介绍了每个版本中支持的 Oracle 数据库版本、等效支持的 JDK 版本、以及符合 JDBC 的版本,还介绍了特定版本需要使用的 JDBC jar 文件名。官网各种支持的 Oracle 数据库版本、符合 JDBC 的版本与支持的 JDK 版本?

Oracle Database 版本 支持的 JDK 版本 JDBC 规范合规性 特定于版本的 JDBC Jar 文件
12.2 或 12cR2 JDK8 和 JDBC 4.2 JDK 8 中的 JDBC 4.2 适用于 JDK 8 的 ojdbc8.jar
12.1 或 12cR1 JDK8、JDK 7 和 JDK 6 JDK 8 和 JDK 7 驱动程序中的 JDBC 4.1
JDK 6 驱动程序中的 JDBC 4.0
适用于 JDK 8 和 JDK 7 的 ojdbc7.jar
适用于 JDK 6 的 ojdbc6.jar
11.2 或 11gR2 JDK 6 和 JDK 5
11.2.0.3 和 11.2.0.4 中支持的 JDK 7 和 JDK 8
JDK 6 驱动程序中的 JDBC 4.0
JDK 5 驱动程序中的 JDBC 3.0
适用于 JDK 8、JDK 7 和 JDK 6 的 ojdbc6.jar。
适用于 JDK 5 的 ojdbc5.jar
11.1 或 11gR1 JDK 6 和 JDK 5 JDK 6 驱动程序中的 JDBC 4.0       JDK 5 驱动程序中的 JDBC 3.0 适用于 JDK 6 的 ojdbc6.jar
适用于 JDK 5 的 ojdbc5.jar

Oracle jdbc 驱动下载

1、不同于开源的 Mysql ,Oracle 是收费的,从 Maven 中央仓库上面通常无法成功下载 Oracle 依赖,只能直接去 Oracle 官网下载:



 
     com.oracle.jdbc
     ojdbc8
     12.2.0.1
 

ojdbc6 匹配 jdk 1.6、ojdbc8 匹配 jdk 1.8,ojdbc10 匹配 jdk 10 依此类推,而 ojdbc14 匹配的是 jdk1.4.

2、解决方式一:网络上有好心人和组织共享了一些可供下载的、且与官网等价的 maven 依赖,比如下面这个(亲测有效):




    com.github.noraui
    ojdbc8
    12.2.0.1

3、解决方式二:虽然从 maven 中央仓库无法下载 ojdbc 驱动,但是直接从 Oracle 官网是可以下载 jar 包的。

3.1、先从官网下载 ojdbc8.jar :https://www.oracle.com/database/technologies/jdbc-ucp-122-downloads.html

从 Oracle 官网下载它们家的东西都是需要先登陆的,所以如果没有账号,需要先注册,下载驱动不收费。

3.3、然后使用下面的 mvn install 命令进行项目部署,它会自动存放在本地仓库中:

mvn install:install-file -DgroupId=com.oracle.jdbc -DartifactId=ojdbc8 -Dversion=12.2.0.1 -Dpackaging=jar -DgeneratePom=true -Dfile=C:\Users\Think\Downloads\ojdbc8.jar

-DgroupId:指定 groupId 的值,可以自定义,建议与人家官网的一致即可
-DartifactId:指定 artifactId 的值,可以自定义,建议与人家官网的一致即可
-Dversion:指定 version 的值,可以自定义,建议与人家官网的一致即可,也可以从打开 ojdbc8.jar ,其中的 MANIFEST.MF 文件中也可以看到版本号
-Dpackaging:指定打包的类型,如 jar、war 包
-DgeneratePom:指定是否生成 pom.xml 文件,指定让它生成
-Dfile:需要部署的 jar 包文件路径

Oracle 数据源配置

1、在开始下面的代码编写之前,需要在全局配置文件中指定数据源配置:

#数据源配置
spring:
  profiles: oracleDb
  datasource:
    username: hnbs_3
    password: 1
    driverClassName: oracle.jdbc.driver.OracleDriver
    url: jdbc:oracle:thin:@127.0.0.1:1521:ORCL

更多配置项可以参考官网:https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/htmlsingle/#common-application-properties

或者:org.springframework.boot.autoconfigure.jdbc.DataSourceProperties.java

execute 调用无返回值存储过程

1、Mysql 中有 "drop table if exists 表名"的操作,当表存在时才进行删除,Oracle 中并没有 if exists 的判断,所以直接删除时,如果表不存在,"drop table 表名 " 就会报错,

2、这里用一个存储过程来解决这个问题,Oracle 数据库中准备存储过程如下,功能就是传入表名作为参数,如果表已经存在,则删除它,否则不进行操作,存储过程不进行返回。

--表名作为参数,如果表已经存在,则删除它。
create or replace procedure pro_drop_table_by_name(tableName in user_tables.TABLE_NAME%type)
is
  flag number := 0; --表是否存在的表示,大于0表示表已经存在
begin
    --user_tables 是系统定义的视图,可以查看当前用户下的所有表信息,表中的表名区分大小写,而且是大写
    select count(1) into flag from user_tables where table_name = upper(tableName) ;
    if flag > 0 then
        execute immediate 'drop table '|| tableName ;--如果表已经存在,则删除它
    end if;
end;

-- 数据库中调用存储过程:call pro_drop_table_by_name('student');

注意:这里为了保证数据完整性,并没有做级联删除,也就是说当被删除的表中的数据如果被其它表引用,则删除时会报错

    /**
     * 删除表
     * http://localhost:8080/emp/dropTable?tableName=emp
     * http://localhost:8080/emp/dropTable?tableName=dept
     * emp 员工表引用了 dept 部门表,如果先删除 dept 表,其中有数据被 emp 表引用,则删除时报错:
     * oracle.jdbc.OracleDatabaseException: ORA-02449: 表中的唯一/主键被外键引用
     *
     * @param tableName
     * @return
     */
    @GetMapping("emp/dropTable")
    public String dropTableByName(@RequestParam String tableName) {
        JsonObject jsonObject = new JsonObject();
        try {
            //sql 和在数据库中完全一样
            String sql = "call pro_drop_table_by_name('" + tableName + "')";
            jdbcTemplate.execute(sql);

            jsonObject.addProperty("code", 200);
            jsonObject.addProperty("message", sql);
        } catch (DataAccessException e) {
            logger.error(e.getMessage(), e);
            jsonObject.addProperty("code", 500);
            jsonObject.addProperty("message", e.getMessage());
        }
        return jsonObject.toString();
    }

execute 调用单个返回值存储过程

1、Oracle 数据库中准备存储过程如下:

--表名作为参数,同时指定返回参数,如果表名存在,则返回 1,不存在返回 0
create or replace procedure pro_check_table_by_name(tableName in user_tables.TABLE_NAME%type, ifExists out number) is
begin
    --user_tables 是系统定义的视图,可以查看当前用户下的所有表信息,表中的表名区分大小写,而且是大写
    select count(1) into ifExists from user_tables where table_name = upper(tableName) ;
end;

-- 数据库中调用存储过程:
declare
   tableName varchar2(30) := 'demp'; //被检查的表名
   ifExists number; //返回参数
begin
  pro_check_table_by_name(tableName,ifExists);
  dbms_output.put_line(ifExists);//打印返回值
end;

2、execute(CallableStatementCreator csc, CallableStatementCallback action) 调用存储过程底层就是"JDBC 调用存储过程/函数",所以写起来完全一样,CallableStatementCreator  中创建 java.sql.CallableStatement,CallableStatementCallback 中进行调用以及获取返回值。

/**
 * 检查某个表在数据库中是否已经存在,存在时返回1,否则返回0
 * http://localhost:8080/emp/checkTableByName?tableName=emp
 *
 * @param tableName
 * @return
 */
@GetMapping("emp/checkTableByName")
public Integer checkTableByName(@RequestParam String tableName) {
	Integer execute = (Integer) jdbcTemplate.execute(new CallableStatementCreator() {

		//创建可回调语句,方法里面就是纯 jdbc 创建调用存储的写法
		@Override
		public CallableStatement createCallableStatement(Connection connection) throws SQLException {
			//存储过程调用 sql,通过 java.sql.Connection.prepareCall 获取回调语句
			String sql = "call pro_check_table_by_name(?,?)";
			CallableStatement callableStatement = connection.prepareCall(sql);
			//设置第一个占位符参数值,传入参数。参数索引从1开始
			callableStatement.setString(1, tableName);
			//注册第二个参数(返回值)的数据类型
			callableStatement.registerOutParameter(2, OracleTypes.INTEGER);
			return callableStatement;
		}
	}, new CallableStatementCallback() {
		//正式调用存储过程以及处理返回的值.
		@Override
		public Object doInCallableStatement(CallableStatement callableStatement) throws SQLException {
			//执行调用存储过程
			callableStatement.execute();
			//参数索引从1开始,获取村存储过程的返回值.
			return callableStatement.getInt(2);
		}
	});
	return execute;
}
 
  

execute 调用返回结果集存储过程

1、数据中准备存储过程如下:


--创建存储过程,用于分页查询
--传入参数:pageNo 查询的页码,pageSize 每页的条数;输出参数:vrows 使用一个引用游标用于接收多条结果集。普通游标无法做到,只能使用引用游标
create or replace procedure pro_query_emp_limit(pageNo in number,pageSize in number,vrows out sys_refcursor) is
begin
  --存储过程中只进行打开游标,将 select 查询出的所有数据放置到 vrows 游标中,让调用着进行获取
open vrows for select t.empno,t.ename,t.job,t.mgr,t.hiredate,t.sal,t.comm,t.deptno from (select rownum r,t1.* from emp t1) t 
     where t.r between ((pageNo-1) * pageSize+1) and pageNo * pageSize;
end;

--数据库中使用引用游标读取上面的存储过程返回的值。下面只是加深理解,和 java 调用无关
declare
     vrows sys_refcursor ;--声明引用游标
     vrow emp%rowtype; --定义变量接收遍历到的每一行数据
begin
     pro_query_emp_limit(5,3,vrows);--调用存储过程
     loop 
       fetch vrows into vrow; -- fetch into 获取游标的值
       exit when vrows%notfound; -- 如果没有获取到值,则退出循环
       dbms_output.put_line('姓名:'|| vrow.ename || ' 薪水:'|| vrow.sal);
     end loop;
end;

2、调用和上面的单挑结果返回基本一致,区别就是将返回的结果改成 ResultSet 结果集:

/**
 * 存储过程实现分页查询,传入页码和条数即可进行分页返回
 * http://localhost:8080/emp/pageQuery?pageNo=2&pageSize=5
 *
 * @param pageNo   页码
 * @param pageSize 每页显示的条数
 * @return
 */
@GetMapping("emp/pageQuery")
public List pageQuery(@RequestParam Integer pageNo, @RequestParam Integer pageSize) {
	List execute = (List) jdbcTemplate.execute(new CallableStatementCreator() {

		//创建可回调语句,方法里面就是纯 jdbc 创建调用存储的写法
		@Override
		public CallableStatement createCallableStatement(Connection connection) throws SQLException {
			//存储过程调用 sql,通过 java.sql.Connection.prepareCall 获取回调语句,sql 外围可以花括号括起来
			String sql = "{call pro_query_emp_limit(?,?,?)}";
			CallableStatement callableStatement = connection.prepareCall(sql);
			//设置第占位符参数值
			callableStatement.setInt(1, pageNo);
			callableStatement.setInt(2, pageSize);
			//输出参数类型设置为引用游标
			callableStatement.registerOutParameter(3, OracleTypes.CURSOR);
			return callableStatement;
		}
	}, new CallableStatementCallback() {
		//正式调用存储过程以及处理返回的值.
		@Override
		public Object doInCallableStatement(CallableStatement callableStatement) throws SQLException {
			//存储返回结果
			List> resultMapList = new ArrayList<>(8);
			//遍历时临时对象
			Map temp;
			//执行调用存储过程,将结果转为 java.sql.ResultSet 结果集
			callableStatement.execute();
			ResultSet resultSet = (ResultSet) callableStatement.getObject(3);

			//遍历结果集
			while (resultSet.next()) {
				temp = new HashMap<>(8);
				//根据字段名称取值
				temp.put("empno", resultSet.getInt("empno"));
				temp.put("ename", resultSet.getString("ename"));
				temp.put("job", resultSet.getString("job"));
				temp.put("mgr", resultSet.getInt("mgr"));
				temp.put("hiredate", resultSet.getDate("hiredate"));
				temp.put("sal", resultSet.getFloat("sal"));
				resultMapList.add(temp);
			}
			return resultMapList;
		}
	});
	return execute;
} 
  

call 方法调用存储过程

1、开篇就已经说过 call 方法专门用于执行存储过程、函数相关语句。call 方法在 execute 的基础上对返回结果进行进一步的封装,只需要创建 CallableStatement 即可,不用再关心结果转换。

2、exexute 的 CallableStatementCallback 回调改为使用 List,其中的每一个 SqlParameter 按顺序对应占位符参数。

SqlParameter 表示存储过程的传入参数,可以不指定参数名称,但是必须指定参数类型
SqlOutParameter 表示存储过程的输出参数,必须指定名称和类型,名称自定义即可,会被作为返回值存放在 map 中

3、下面改用 call 方法来实现上面的功能,使用存储过程检查某个表在数据库中是否已经存在,存在时返回1,否则返回0,使用 call 方法进行调用:

/**
 * 存储过程检查某个表在数据库中是否已经存在,存在时返回1,否则返回0,使用 call 方法进行调用
 * http://localhost:8080/emp/callCheckTableByName?tableName=emp
 *
 * @param tableName
 * @return
 */
@GetMapping("emp/callCheckTableByName")
@SuppressWarnings("all")
public Map callCheckTableByName(@RequestParam String tableName) {

	//SqlParameter 表示存储过程的传入参数,可以不指定参数名称,但是必须指定参数类型
	//SqlOutParameter 表示存储过程的输出参数,必须指定名称和类型,名称自定义即可,会被作为返回值存放在 map 中
	List sqlParameterList = new ArrayList<>(4);
	sqlParameterList.add(new SqlParameter(OracleTypes.VARCHAR));
	sqlParameterList.add(new SqlOutParameter(tableName, OracleTypes.NUMBER));

	//call 方法在 execute 的基础上对返回结果进行进一步的封装,只需要创建 CallableStatement
	//List 中的每一个 SqlParameter 按顺序对应占位符参数
	//返回的 map 包含返回参数
	Map call = jdbcTemplate.call(new CallableStatementCreator() {
		@Override
		public CallableStatement createCallableStatement(Connection connection) throws SQLException {
			//存储过程调用 sql,通过 java.sql.Connection.prepareCall 获取回调语句,sql 外围可以花括号括起来
			String sql = "{call pro_check_table_by_name(?,?)}";
			CallableStatement callableStatement = connection.prepareCall(sql);
			//设置第一个占位符参数值,传入参数。参数索引从1开始
			callableStatement.setString(1, tableName);
			//注册第二个参数(返回值)的数据类型,oracle.jdbc.OracleTypes 中定义了全部的数据类型常量
			callableStatement.registerOutParameter(2, OracleTypes.INTEGER);
			return callableStatement;
		}
	}, sqlParameterList);
	return call;
}

4、使用 call 方法调用存储过程进行分页查询,使用了 call 之后对于返回的游标就方便多了,不再需要自己一个一个取值了,它会自动进行转换,推荐方式:

/**
 * 使用 call 方法调用存储过程进行分页查询,推荐方式
 * http://localhost:8080/emp/callPageQuery?pageNo=2&pageSize=5
 *
 * @param pageNo
 * @param pageSize
 * @return
 */
@GetMapping("emp/callPageQuery")
@SuppressWarnings("all")
public List> callPageQuery(@RequestParam Integer pageNo, @RequestParam Integer pageSize) {
	//设置存储过程参数
	//SqlParameter 表示存储过程的传入参数,可以不知道参数名称,但是必须指定参数类型
	//SqlOutParameter 表示存储过程的输出参数,必须指定名称和类型,名称自定义即可,会被作为返回值存放在 map 中
	List sqlParameterList = new ArrayList<>(4);
	sqlParameterList.add(new SqlParameter(OracleTypes.NUMBER));
	sqlParameterList.add(new SqlParameter(OracleTypes.NUMBER));
	sqlParameterList.add(new SqlOutParameter("resultSet", OracleTypes.CURSOR));

	//使用了 call 之后对于返回的游标就方便多了,不再需要自己一个一个取值了,它会自动进行转换
	//call 的 key 会是 resultSet,然后它的值会是一个 List,自动转换好了
	Map call = jdbcTemplate.call(new CallableStatementCreator() {
		//创建可回调语句,方法里面就是纯 jdbc 创建调用存储的写法
		@Override
		public CallableStatement createCallableStatement(Connection connection) throws SQLException {
			//存储过程调用 sql,通过 java.sql.Connection.prepareCall 获取回调语句,sql 外围可以花括号括起来
			String sql = "{call pro_query_emp_limit(?,?,?)}";
			CallableStatement callableStatement = connection.prepareCall(sql);
			//设置第占位符参数值
			callableStatement.setInt(1, pageNo);
			callableStatement.setInt(2, pageSize);
			//输出参数类型设置为引用游标
			callableStatement.registerOutParameter(3, OracleTypes.CURSOR);
			return callableStatement;
		}
	}, sqlParameterList);

	//没有值时就是空 list,不会控制在异常
	List> dataList = (List>) call.get("resultSet");
	return dataList;
}

源码:https://github.com/wangmaoxiong/jdbc_template_app

你可能感兴趣的:(Spring,Framework,Spring,Boot,Oracle)