Spring JdbcTemplate 调用 Oracle 存储过程 与 Oracle 驱动下载
目录
前 言
Oracle jdbc 驱动版本兼容
Oracle jdbc 驱动下载
Oracle 数据源配置
execute 调用无返回值存储过程
execute 调用单个返回值存储过程
execute 调用返回结果集存储过程
call 方法调用存储过程
原生 JDBC 调用存储过程
前 言
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 官网下载:
<!-- https://mvnrepository.com/artifact/com.oracle.jdbc/ojdbc8 -->
<!-- oracle 官网提供的驱动依赖,通常都会下载失败,需要手动在本地仓库进行 mvn install 安装--><dependency><groupId>com.oracle.jdbc</groupId><artifactId>ojdbc8</artifactId><version>12.2.0.1</version></dependency>
ojdbc6 匹配 jdk 1.6、ojdbc8 匹配 jdk 1.8,ojdbc10 匹配 jdk 10 依此类推,而 ojdbc14 匹配的是 jdk1.4.
2、解决方式一:网络上有好心人和组织共享了一些可供下载的、且与官网等价的 maven 依赖,比如下面这个(亲测有效):
<!-- https://mvnrepository.com/artifact/com.github.noraui/ojdbc8 -->
<!--这是网络上的雷锋提供的开源 ojdbc8 驱动,功能与官网的是一样的,专门用于替代 Oracle 官网 maven 下载失败-->
<dependency><groupId>com.github.noraui</groupId><artifactId>ojdbc8</artifactId><version>12.2.0.1</version>
</dependency>
3、解决方式二:虽然从 maven 中央仓库无法下载 ojdbc 驱动,但是直接从 Oracle 官网是可以下载 jar 包的。
3.1、先从官网下载 ojdbc8.jar 包:12.2.0.1 - JDBC and UCP Download Page
从 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: oracleDbdatasource:username: hnbs_3password: 1#oracle.jdbc.OracleDriver 继承于oracle.jdbc.driver.OracleDriverdriverClassName: oracle.jdbc.driver.OracleDriver url: jdbc:oracle:thin:@127.0.0.1:1521:ORCL
更多配置项可以参考官网:Spring Boot Reference Guide 或者: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)
isflag number := 0; --表是否存在的表示,大于0表示表已经存在
begin--user_tables 是系统定义的视图,可以查看当前用户下的所有表信息,表中的表名区分大小写,而且是大写select count(1) into flag from user_tables where table_name = upper(tableName) ;if flag > 0 thenexecute 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;-- 数据库中调用存储过程:
declaretableName varchar2(30) := 'demp'; //被检查的表名ifExists number; //返回参数
beginpro_check_table_by_name(tableName,ifExists);dbms_output.put_line(ifExists);//打印返回值
end;
2、execute(CallableStatementCreator csc, CallableStatementCallback<T> 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 创建调用存储的写法@Overridepublic 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<Object>() {//正式调用存储过程以及处理返回的值.@Overridepublic 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 调用无关
declarevrows sys_refcursor ;--声明引用游标vrow emp%rowtype; --定义变量接收遍历到的每一行数据
beginpro_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 创建调用存储的写法@Overridepublic 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<Object>() {//正式调用存储过程以及处理返回的值.@Overridepublic Object doInCallableStatement(CallableStatement callableStatement) throws SQLException {//存储返回结果List<Map<String, Object>> resultMapList = new ArrayList<>(8);//遍历时临时对象Map<String, Object> 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 按顺序对应占位符参数。
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<String, Object> callCheckTableByName(@RequestParam String tableName) {//SqlParameter 表示存储过程的传入参数,可以不指定参数名称,但是必须指定参数类型//SqlOutParameter 表示存储过程的输出参数,必须指定名称和类型,名称自定义即可,会被作为返回值存放在 map 中List<SqlParameter> sqlParameterList = new ArrayList<>(4);sqlParameterList.add(new SqlParameter(OracleTypes.VARCHAR));sqlParameterList.add(new SqlOutParameter(tableName, OracleTypes.NUMBER));//call 方法在 execute 的基础上对返回结果进行进一步的封装,只需要创建 CallableStatement//List<SqlParameter> 中的每一个 SqlParameter 按顺序对应占位符参数//返回的 map 包含返回参数Map<String, Object> call = jdbcTemplate.call(new CallableStatementCreator() {@Overridepublic 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<Map<String, Object>> callPageQuery(@RequestParam Integer pageNo, @RequestParam Integer pageSize) {//设置存储过程参数//SqlParameter 表示存储过程的传入参数,可以不知道参数名称,但是必须指定参数类型//SqlOutParameter 表示存储过程的输出参数,必须指定名称和类型,名称自定义即可,会被作为返回值存放在 map 中List<SqlParameter> 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>,自动转换好了Map<String, Object> call = jdbcTemplate.call(new CallableStatementCreator() {//创建可回调语句,方法里面就是纯 jdbc 创建调用存储的写法@Overridepublic 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<Map<String, Object>> dataList = (List<Map<String, Object>>) call.get("resultSet");return dataList;
}
源码:https://github.com/wangmaoxiong/jdbc_template_app
原生 JDBC 调用存储过程
1、程序中执行存储过程相当于是在程序中执行了这一段 sql 脚本,与程序是同一个事务,千万不能理解为调用存储过程是数据库服务器端单独执行的。
2、比如程序中有一个业务方法(同一个事务):第一步先新增数据到表A中,第二步调用存储过程P对表A新增的数据进行清洗,第三步再从 A 表中查询清洗后的数据。
/*** 根据配置调用存储过程处理数据** @param customProcedure :存储过程名称* @param uuid :数据批次ID* @return*/
@Override
public boolean customProcedureOfConfig(String customProcedure, String uuid) {if (StringUtils.isBlank(customProcedure)) {return false;}String sql = "{call " + customProcedure + "(?)}";java.sql.Connection conn = null;java.sql.CallableStatement call;javax.sql.DataSource ds = null;try {ds = jdbcTemplate.getDataSource();conn = org.springframework.jdbc.datasource.DataSourceUtils.getConnection(ds);call = conn.prepareCall(sql);call.setString(1, uuid);call.execute();} catch (SQLException e1) {throw new BasicException("调用存储过程【" + customProcedure + "】失败:" + ExceptionUtils.getStackTrace(e1));} finally {try {org.springframework.jdbc.datasource.DataSourceUtils.releaseConnection(conn, ds);} catch (Exception e) {logger.error(e.getMessage(), e);}}return true;
}
Spring JdbcTemplate 调用 Oracle 存储过程 与 Oracle 驱动下载相关推荐
- spring jdbctemplate调用存储过程,返回list对象
注:本文来源于< spring jdbctemplate调用存储过程,返回list对象 > spring jdbctemplate调用存储过程,返回list对象 方法: /*** 调用存储 ...
- oracle 存储过程 db,oracle数据库的存储过程是什么?
oracle数据库的存储过程:一组为了完成特定功能的SQL语句集,经编译后存储在数据库中.存储过程是由流控制和SQL语句书写的过程,这个过程经编译和优化后存储在数据库服务器中,应用程序使用时只要调用即 ...
- oracle存储过程function,oracle 存储过程跟function
当前位置:我的异常网» 数据库 » oracle 存储过程跟function oracle 存储过程跟function www.myexceptions.net 网友分享于:2014-11-26 ...
- oracle 存储过程返回,Oracle 存储过程返回结果集 (转)
Oracle 存储过程返回结果集 (转)[@more@] 1.返回数组 (作者:/Message_Board/Send.?sendto=enhydraboy" target=_blank&g ...
- oracle存储过程 论文,Oracle中基于Java的存储过程开发_计算机论文
论文导读::存储过程是一种数据库对象,将执行计划存储在数据库的服务器中,它的执行速度比独立执行同样的程序要快.任何一个设计良好的数据库应用程序都应该用到存储过程.存储过程可以使得对数据库的管理.显示关 ...
- oracle 存储过程举例,oracle存储过程举例讲解
oracle 存储过程创建和使用举例 1.创建存储过程 create or replace package pk_1 as //创建包的声明 TYPE cur is ref cursor; //声明 ...
- oracle存储过程ddl,Oracle 存储过程中的DDL语句
Oracle的存储过程,是我们使用数据库应用开发的重要工具手段.在存储过程中,我们大部分应用场景都是使用DML语句进行数据增删改操作.本篇中,我们一起探讨一下数据定义语句DDL在存储过程中使用的细节和 ...
- oracle 存储过程 状态,Oracle存储过程(定时更新短信状态汇报)
Oracle存储过程(定时更新短信状态报告) 1.定时更新异网短信状态报告,后台程序接收状态报告errorcode和标示client_id存入yw_detail表中 2.log_yw_mobile.y ...
- oracle 存储过程 输入,Oracle 存储过程加密方法
软件环境: 1.操作系统:Windows 2000 Server 2.数 据 库:Oracle 8i R2 (8.1.7) for NT 企业版 3.安装路径:C:ORACLE 实现方法: 1. D: ...
- oracle存储过程遍历,oracle存储过程中遍历的疑惑
此前很少使用oracle,现在进入一项目,使用的是oracle,瞬间拙计. 发现别人写的存储过程中有两种写法去遍历记录,如下: Procedure Syn_His_Main Is sqlStr Var ...
最新文章
- InteractiveGraph 实现酷炫关系图谱之前瞻
- LINUX中断学习笔记【转】
- Linux基础命令---e2fsck
- 深入理解分布式技术 - 服务注册与发现背后的逻辑
- 验证字符串是否为汉字
- VB.NET 创建WORD文档
- oracle 中least,ORACLE 内置函数之GREATEST和LEAST
- mysql数据库备份(完全备份,增量备份)
- 搜狗浏览器缓冲区溢出漏洞EXP
- PyCharm使用技巧(六):Regullar Expressions的使用
- ajax对日期处理,AJAX获取服务器当前时间及时间格式输出处理
- 二阶偏微分方程组 龙格库塔法_数值方法(MATLAB版)(原书第3版)[Numerical Methods Using MATLAB,Third Edition]pdf...
- IOS-项目中常见文件介绍
- matplotlib绘制横向柱状图
- 送给大家一个很好的Web前端开发工具
- 北京明年拟新增3万个幼儿园学位 并再筹建6万套政策性产权房
- python读取.ttf字体文件
- 厦大计算机学院2018夏令营6,厦门大学信息学院(国家示范性软件学院)2020暑期夏令营报名指南(6月30日申请截止)...
- 线性代数及矩阵论(七)
- SiT5711:±5~±8ppb超高精度Stratum 3E恒温振荡器OCXO,1-60MHz
热门文章
- 初中级工程师是否应急于学习html5?
- 多线程编程(4) - 从 CreateThread 说起[续二]
- ASP.NET 2.0 中 Web 事件
- 如果P = NP 则 NP = co-NP.
- java 邮箱模板_Java:Spring同时集成JPA与Mybatis
- python3 一年中的天数 时间转化为北京时_三年级数学《年月日》时间知识详解,帮助孩子重点知识不丢分...
- Path of Equal Weight (30 分)
- 微信小程序教程笔记4
- JSP教程第3讲笔记
- 混淆矩阵confusion matrix