目录

前  言

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 驱动下载相关推荐

  1. spring jdbctemplate调用存储过程,返回list对象

    注:本文来源于< spring jdbctemplate调用存储过程,返回list对象 > spring jdbctemplate调用存储过程,返回list对象 方法: /*** 调用存储 ...

  2. oracle 存储过程 db,oracle数据库的存储过程是什么?

    oracle数据库的存储过程:一组为了完成特定功能的SQL语句集,经编译后存储在数据库中.存储过程是由流控制和SQL语句书写的过程,这个过程经编译和优化后存储在数据库服务器中,应用程序使用时只要调用即 ...

  3. oracle存储过程function,oracle 存储过程跟function

    当前位置:我的异常网» 数据库 » oracle 存储过程跟function oracle 存储过程跟function www.myexceptions.net  网友分享于:2014-11-26   ...

  4. oracle 存储过程返回,Oracle 存储过程返回结果集 (转)

    Oracle 存储过程返回结果集 (转)[@more@] 1.返回数组 (作者:/Message_Board/Send.?sendto=enhydraboy" target=_blank&g ...

  5. oracle存储过程 论文,Oracle中基于Java的存储过程开发_计算机论文

    论文导读::存储过程是一种数据库对象,将执行计划存储在数据库的服务器中,它的执行速度比独立执行同样的程序要快.任何一个设计良好的数据库应用程序都应该用到存储过程.存储过程可以使得对数据库的管理.显示关 ...

  6. oracle 存储过程举例,oracle存储过程举例讲解

    oracle 存储过程创建和使用举例 1.创建存储过程 create or replace package pk_1 as  //创建包的声明 TYPE cur is ref cursor; //声明 ...

  7. oracle存储过程ddl,Oracle 存储过程中的DDL语句

    Oracle的存储过程,是我们使用数据库应用开发的重要工具手段.在存储过程中,我们大部分应用场景都是使用DML语句进行数据增删改操作.本篇中,我们一起探讨一下数据定义语句DDL在存储过程中使用的细节和 ...

  8. oracle 存储过程 状态,Oracle存储过程(定时更新短信状态汇报)

    Oracle存储过程(定时更新短信状态报告) 1.定时更新异网短信状态报告,后台程序接收状态报告errorcode和标示client_id存入yw_detail表中 2.log_yw_mobile.y ...

  9. oracle 存储过程 输入,Oracle 存储过程加密方法

    软件环境: 1.操作系统:Windows 2000 Server 2.数 据 库:Oracle 8i R2 (8.1.7) for NT 企业版 3.安装路径:C:ORACLE 实现方法: 1. D: ...

  10. oracle存储过程遍历,oracle存储过程中遍历的疑惑

    此前很少使用oracle,现在进入一项目,使用的是oracle,瞬间拙计. 发现别人写的存储过程中有两种写法去遍历记录,如下: Procedure Syn_His_Main Is sqlStr Var ...

最新文章

  1. InteractiveGraph 实现酷炫关系图谱之前瞻
  2. LINUX中断学习笔记【转】
  3. Linux基础命令---e2fsck
  4. 深入理解分布式技术 - 服务注册与发现背后的逻辑
  5. 验证字符串是否为汉字
  6. VB.NET 创建WORD文档
  7. oracle 中least,ORACLE 内置函数之GREATEST和LEAST
  8. mysql数据库备份(完全备份,增量备份)
  9. 搜狗浏览器缓冲区溢出漏洞EXP
  10. PyCharm使用技巧(六):Regullar Expressions的使用
  11. ajax对日期处理,AJAX获取服务器当前时间及时间格式输出处理
  12. 二阶偏微分方程组 龙格库塔法_数值方法(MATLAB版)(原书第3版)[Numerical Methods Using MATLAB,Third Edition]pdf...
  13. IOS-项目中常见文件介绍
  14. matplotlib绘制横向柱状图
  15. 送给大家一个很好的Web前端开发工具
  16. 北京明年拟新增3万个幼儿园学位 并再筹建6万套政策性产权房
  17. python读取.ttf字体文件
  18. 厦大计算机学院2018夏令营6,厦门大学信息学院(国家示范性软件学院)2020暑期夏令营报名指南(6月30日申请截止)...
  19. 线性代数及矩阵论(七)
  20. SiT5711:±5~±8ppb超高精度Stratum 3E恒温振荡器OCXO,1-60MHz

热门文章

  1. 初中级工程师是否应急于学习html5?
  2. 多线程编程(4) - 从 CreateThread 说起[续二]
  3. ASP.NET 2.0 中 Web 事件
  4. 如果P = NP 则 NP = co-NP.
  5. java 邮箱模板_Java:Spring同时集成JPA与Mybatis
  6. python3 一年中的天数 时间转化为北京时_三年级数学《年月日》时间知识详解,帮助孩子重点知识不丢分...
  7. Path of Equal Weight (30 分)
  8. 微信小程序教程笔记4
  9. JSP教程第3讲笔记
  10. 混淆矩阵confusion matrix