新一次的内部提升开始了,如果您想写一个框架从Apache Commons DbUtils开始学习是一种不错的选择,我们先学习应用这个小“框架”再把源代码理解,然后写一个属于自己的ORM框架不是梦。

一、简介

DbUtils是Apache下commons工具集中的一个小工具,它主要是对JDBC封装的ORM小工具,简化了JDBC的操作。之所以把它称之为工具而不是框架,是因为它和其他的ORM框架还是由很大的区别(例如Hibernate)。DbUtils并不支持所谓的聚合关联映射、缓存机制、实体状态的管理、延迟加载技术等等,它纯粹只是对JDBC的API进行封装。但也由于它的这种简单,因此性能高也是它的特点。DbUtils的源代码并不多,也很容易读得懂,非常适合于初学者阅读和学习。

二、ORM概要

对象关系映射(Object Relational Mapping),简称ORM。网上有很多专业的解释,但对于初学者来说这些专业的术语也许不太好理解。所以我们还是通过一些实际例子来说明。

在日常的开发中我们经常用到实体或者DTO对象,这似乎对每一个程序员来说都是再熟悉不过的了。但这些所谓的实体或者DTO对象按照领域驱动设计的说法,它们只有自己的属性,却没有属于自己的业务行为(get和set那不叫业务行为)。因此我们把他们称之为贫血模型。那用这些贫血模型来做什么呢?没错,就是封装数据。我们经常会将一些不同类型的数据封装到这些对象中。

Users user = new Users();
user.setUserName(“张三”);
user.setAge(20);

给对象属性赋完值以后,便把这个实体传递给Dao层执行保存操作。最终将数据持久化到数据库的某张表中。

public int persist(Users user) {
String sql = “INSERT INTO USERS_INFO(U_NAME, U_AGE) VALUES(?,?)”;
Connection conn = null;
PreparedStatment ps = null;
int row = 0;
try {
conn = ConnUtil.getConnection();
ps = conn.preparedStatment(sql);
ps.setString(1, user.getUserName);
ps.setInt(2, user.getAge());
row = ps.executeUpdate();
} catch(SQLException e){
e.printStackTrace();
} finally {
ConnUtil.close(null, ps, conn);
}
return row;
}

在这个过程我们发现一点,数据在Java中是以对象的形式存储,而最终持久化到数据库的时候是以关系型表格的形式存储,也就是说,我们把一个对象化结构的数据映射到了关系型数据库中的这个过程,就是对象关系映射。反之,当我们从关系型数据库中查询出的数据,又转换成一个对象模型的数据结构,这也是对象关系映射。

public Users findUserById(int id) {
String sql = “SELECT * FROM USERS_INFO WHERE U_ID = ?”;
Connection conn = null;
PreparedStatment ps = null;
ResultSet rs = null;
Users user = null; try {
conn = ConnUtil.getConnection();
ps = conn.preparedStatment(sql);
ps.setString(1, id);
rs = ps.executeQuery(); if(rs.next()) {
user = new Users();
user.setId(rs.getInt(1));
user.setUserName(rs.getString(2));
user.setAge(rs.getInt(3));
}
} catch(SQLException e){
e.printStackTrace();
} finally {
ConnUtil.close(rs, ps, conn);
}
return user;
}

因此,我们可以将对象关系映射理解为它是一种对象模型和关系型数据库之间相互转换的过程。在实际开发中,我们会遇到大量的ORM操作,然而你会发现,这种操作其实大部分都是重复劳动,频繁的给PreparedStatment设置参数,又或者是频繁的从ResultSet中读取数据保存到实体中,这些操作让我们在开发中降低了效率。我们能否将这些繁琐的操作封装起来,我给你一个实体,你会自动帮我保存到数据库。我告诉你一个对象的类型,你会自动将结果集中的数据封装到这个对象中返回给我。这样就大大简化的JDBC的操作,提高了开发效率。接下来我们所学习的DbUtils就帮我们完成了这些事情。

三、下载与安装

下载:

http://commons.apache.org/proper/commons-dbutils/download_dbutils.cgi

安装:

教程中使用的是1.6的版本,下载的压缩包是 commons-dbutils-1.6-bin.zip。解压后将commons-dbutils-1.6.jar导入工程即可。

四、DML操作

首先,我们在数据中创建USERS_INFO表。(mysql数据库)

CREATE TABLE USERS_INFO (
ID INT PRIMARY KEY AUTO_INCREMENT, -- 主键
U_NAME VARCHAR(50) NOT NULL, --姓名
U_AGE INT NOT NULL --年龄
) CHARSET=UTF8 

这里我们使用DBCP连接池作为数据源。DBCP也是commons工具集中一个小工具。简单点说,它主要用于监听和管理JDBC的Connection对象,达到连接复用的效果(连接池的原理及好处可以在JDBC教程的章节中进行查阅)。

DBCP连接池需要的jar文件:

  1. commons-dbcp2-2.1.1-bin.zip

下载地址: http://commons.apache.org/proper/commons-dbcp/download_dbcp.cgi

  1. commons-pool2-2.4.2-bin.zip

下载地址: http://commons.apache.org/proper/commons-pool/download_pool.cgi

  1. commons-logging-1.2-bin.zip

下载地址: http://commons.apache.org/proper/commons-logging/download_logging.cgi

解压后将commons-dbcp2-2.1.1.jar、commons-pool2-2.4.2.jar、commons-logging-1.2.jar这三个jar文件导入工程。

接下来编写一个DBCP连接池的工具类,用于获取DataSource

public class DBCPUtil {
private static Properties prop = new Properties();
private static DataSource dataSource;
/**
* 初始化连接池
*/
static { //驱动
prop.setProperty("driverClassName", "com.mysql.jdbc.Driver"); //连接url
prop.setProperty("url", "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8"); //用户名
prop.setProperty("username", "root"); //密码
prop.setProperty("password", "root"); //初始连接数
prop.setProperty("initialSize", "5"); //最大活动连接数
prop.setProperty("maxTotal", "20"); //最小空闲连接 prop.setProperty("minIdle", "5"); //最大空闲连接
prop.setProperty("maxIdle", "10"); //等待连接的最大超时时间(单位:毫秒)
prop.setProperty("maxWaitMillis", "1000"); //连接未使用时是否回收
prop.setProperty("removeAbandonedOnMaintenance", "true");
prop.setProperty("removeAbandonedOnBorrow", "true");
try {
dataSource = BasicDataSourceFactory.createDataSource(prop);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取DataSource
* @return
*/
public static DataSource getDataSource(){
return dataSource;
}
}

4.1、QueryRunner

这个类用于发送执行SQL语句并返回相应的结果。其实现当中对Connection以及PreparedStatment接口的API进行了封装。QueryRunner有两种方式来管理连接,一种是在构建QueryRunner实例时通过构造方法传递一个数据源DataSource实例;另一种则是在调用相应的操作方法,如query、update、batch等这些方法时传入一个Connection对象。这两种方式有什么区别呢?通过源码的阅读,我们不难发现,其实对于DataSource的管理,在每次执行完相应操作后,DbUtils会自动关闭数据源的连接对象。而在调用相应的操作方法时传入的Connection对象,在使用完之后是需要我们手动去关闭这个资源的。在以下所有的例子中,我们都将使用DataSouce的方式进行操作。

4.2、Insert操作

/**
* 添加操作
* @param userName 姓名
* @param age 年龄
* @return int 影响的行数
* @throws SQLException
*/
public int persist(String userName, int age) throws SQLException{
String sql = "INSERT INTO USERS_INFO(U_NAME,U_AGE) VALUES(?,?)";
//创建Query执行器,通过构造方法传入一个DataSource对象
QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());
// 执行update方法,方法的第一个和第二个参数分别是Connection对象和要执行的sql语句
// 第三个参数开始是一个可变参数,分别是sql语句中所需的参数,对应上面语句中问号的顺序
// 执行完成后会返回影响的行数
return qr.update(sql, userName, age);
}

4.3、Update操作

 /**

*  更新操作,用的是同样的方法,仅是sql语句的不同
* @param userName 姓名
* @param age 年龄
* @param id 主键
* @return int 影响的行数
* @throws SQLException
*/ public int update(String userName, int age, int id) throws SQLException{
String sql = "UPDATE USERS_INFO SET U_NAME = ?, U_AGE = ? WHERE ID = ?";
QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());
return qr.update(sql, userName, age, id);
}

4.4、Delete操作

 /**
* 删除操作,用的是同样的update方法,仅是sql语句的不同
* @param userName 姓名
* @param id 主键
* @return int 影响的行数
* @throws SQLException
*/ public int delete(int id) throws SQLException{
String sql = "DELETE FROM USERS_INFO WHERE ID = ?";
QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());
return qr.update(sql, id);

4.5、批量操作

/**
* 批量操作
* @param params 批量执行SQL所需的参数,必须是一个二维数组
* @return int[] 影响的行数
* @throws SQLException
*/ public int[] betch(Object[][] params) throws SQLException{
String sql = "INSERT INTO USERS_INFO(U_NAME,U_AGE) VALUES(?,?)";
QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());
//使用batch方法,第三个参数是一个二维数组,数组中的每个元素对应每次sql执行所需的参数
//返回影响的行数的是一个int类型的数组
return qr.batch(sql, params);
}

五、DQL操作

5.1、ResultSetHandler接口

这个接口的核心作用是将查询结果进行封装(O/R Mapping)。它有许多不同的实现类,每一个实现类都将ResultSet中的结果封装成不同类型的数据对象。如下图:

在ResultSetHandler众多的处理器实现类中主要分为两类,一类是处理单条结果集的,一类是处理多条结果集的。

单条数据处理器:BeanHandler、ArrayHandler、MapHandler、ScalarHandler

多条数据处理器:AbstractKeyedHandler(KeyedHandler、BeanMapHandler)、AbstractListHandler(ColumnListHandler、ArrayListHandler、MapListHandler)

5.1、BeanHandler

将单条查询结果封装为Bean对象

/**
* Users实体
*/ public class Users { private String userName;
private int age;
public String getUserName() {
return userName;
} public void setUserName(String userName) {
this.userName = userName;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
}
}

使用BeanHandler查询单条记录:

/**
* 使用BeanHandler查询单条记录
* @param id 主键
* @return Users
*/ public Users findUserById(int uid) throws SQLException{
//当表的列名和实体的属性名不一致时,在sql中使用as关键字给当前列指定别名,
//别名和实体的属性名对应即可
String sql = "SELECT U.U_NAME AS userName, U.U_AGE AS age FROM USERS_INFO U WHERE U.ID = ?"; //创建QueryRunner实例
QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource()); //使用BeanHandler类,泛型参数指定实体名称。构造方法指定实体的Class对象
BeanHandler<Users> handler = new BeanHandler<>(Users.class); //调用query方法执行查询,该方法的参数一和参数二为连接对象和sql语句,
//参数三为ResultSetHandler接口的实现类对象,这里是BeanHandler,
//方法的第四个参数为可变参数,是sql查询时所需的条件参数
//返回值则是一个封装好的实体对象
Users user = qr.query(sql, handler, uid); return user;
}

将多条查询结果封装为List集合,集合中的每个元素都是一个Bean对象

/**
* 使用BeanListHandler查询多条记录
* @return List<Users>
*/ public List<Users> findUsers() throws SQLException{
//当表的列名和实体的属性名不一致时,在sql中使用as关键字给当前列指定别名,
//别名和实体的属性名对应即可
String sql = "SELECT U.U_NAME AS userName, U.U_AGE AS age FROM USERS_INFO U"; //创建QueryRunner实例
QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource()); //使用BeanListHandler类
BeanListHandler<Users> handler = new BeanListHandler<>(Users.class); //同样调用query方法执行查询,返回值则是一个List对象,List的泛型参数为实体类型
List<Users> list = qr.query(sql, handler); return list;
}

5.3、ArrayHandler

将单条查询结果封装为一个Object数组

/**
* 使用ArrayHandler查询单条记录
* @param id 主键
* @return Object []
*/ public Object[] findUserById(int uid) throws SQLException{
String sql = "SELECT U.U_NAME, U.U_AGE FROM USERS_INFO U WHERE U.ID = ?";
//创建QueryRunner实例
QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource()); //使用ArrayHandler类,由于ArrayHandler将结果集封装为Object数组,因此这个handler是不需要指定泛型的
ArrayHandler handler = new ArrayHandler(); //调用query方法执行查询,返回值则是一个Object数组
Object[] objects = qr.query(sql, handler, uid); return objects; }

5.4、ArrayListHandler

将多条查询结果封装为List集合,集合中的每个元素都是一个Object数组

/**
* 使用ArrayListHandler查询多条记录
* @return List<Object[]>
*/
public List<Object[]> findUsers() throws SQLException{ String sql = "SELECT U.U_NAME, U.U_AGE FROM USERS_INFO U";
//创建QueryRunner实例
QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource()); //使用ArrayListHandler类
ArrayListHandler handler = new ArrayListHandler(); //同样调用query方法执行查询,返回值则是一个List对象,List的泛型参数为Object数组类型
List<Object[]> list = qr.query(sql, handler); return list;
}

5.5、MapHandler

将单条查询结果封装为一个Map对象, Key保存的是查询的列名,Value保存的是列的值

/**
* 使用MapHandler查询单条记录
* @param id 主键
* @return Map<String, Object>
*/ public Map<String, Object> findUserById(int id) throws SQLException{ //当表的列名和实体的属性名不一致时,在sql中使用as关键字给当前列指定别名,
//别名和实体的属性名对应即可
String sql = "SELECT U.U_NAME, U.U_AGE FROM USERS_INFO U WHERE U.ID = ?"; //创建QueryRunner实例
QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource()); //使用MapHandler类,由于返回的是一个Map,因此这个handler也是是不需要指定泛型的
MapHandler handler = new MapHandler(); //调用query方法执行查询,返回值则是Map对象
Map<String, Object> map = qr.query(sql, handler, uid); return map;
}

5.6、MapListHandler

将多条查询结果封装为一个List集合,集合中的每个元素都是一个Map对象

/**

* 使用MapListHandler查询多条记录
* @return List<Map<String, Object>>
*/ public List<Map<String, Object>> findUsers() throws SQLException{ String sql = "SELECT U.U_NAME, U.U_AGE FROM USERS_INFO U"; //创建QueryRunner实例
QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource()); //使用MapListHandler类
MapListHandler handler = new MapListHandler(); //同样调用query方法执行查询,返回值则是一个List对象,List的泛型参数为Map类型
List<Map<String, Object>> list = qr.query(sql, handler); return list;
}

5.7、ScalarHandler

将单条查询结果中的某一列转换为指定的类型

/**
* 使用ScalarHandler查单条询记录中某一列
* @param id 主键
* @return String
*/ public String findUserNameById(int id) throws SQLException{ String sql = "SELECT U.U_NAME, U.U_AGE FROM USERS_INFO U WHERE U.ID = ?"; //创建QueryRunner实例
QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource()); //使用ScalarHandler类,泛型参数指定要返回的数据类型,构造方法指定查询结果中的某一列的下标
ScalarHandler<String> handler = new ScalarHandler<>(1); //调用query方法执行查询,返回值则是String类型
String userName = qr.query(sql, handler, id); return userName; }

5.8、ColumnListHandler

将多条查询结果中的某一列封装为List集合

/**
* 使用ColumnListHandler查单多询记录中某一列
* @return List<String>
*/ public List<String> findUserNames() throws SQLException{ String sql = "SELECT U.U_NAME, U.U_AGE FROM USERS_INFO U"; //创建QueryRunner实例 QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource()); //使用ColumnListHandler类, 泛型参数指定要返回的数据类型,构造方法指定查询结果中的某一列的下标 ColumnListHandler<String> handler = new ColumnListHandler<>(1); //同样调用query方法执行查询,返回值则是一个List对象,List的泛型参数指定为查询结果转换的类型 List<String> list = qr.query(sql, handler); return list; } 

5.9、KeyedHandler

将多条查询结果转换为Map,并将某列保存为Key,而Value则与MapHandler的查询结果一样,封装的是一个Map集合

/**
* 使用KeyedHandler查询结果转换为Map,并将某列的值保存为Key
* @return Map<Integer, Map<String, Object>>
*/ public Map<Integer, Map<String, Object>> findUsers() throws SQLException{
String sql = "SELECT * FROM USERS_INFO U";
//创建QueryRunner实例
QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource()); //使用KeyedHandler类,泛型参数指定key的类型,构造方法中的参数指定哪一列的值作为key保存
//构造方法的参数可以是查询结果中某列的下标,也可以是列的名称
//KeyedHandler<Integer> handler = new KeyedHandler<>("ID");
KeyedHandler<Integer> handler = new KeyedHandler<>(1);
//同样调用query方法执行查询,返回值则是一个Map集合,key为查询某列的值,value为封装当前行的Map对象
Map<Integer, Map<String, Object>> map = qr.query(sql, handler);
return map; }

5.10. BeanMapHandler

将多条查询结果转换为Map,并将某列保存为Key,而Value则与BeanHandler的查询结果一样,封装的是一个Bean对象

/**
* 使用MapBeanHandler查询结果转换为Map,并将某列的值保存为Key
* @return Map<Integer, Users>
*/ public Map<Integer, Users> findUsers() throws SQLException{ String sql = "SELECT U.ID, U.U_NAME AS userName, U.U_AGE AS age FROM USERS_INFO U";
//创建QueryRunner实例
QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource()); //使用KeyedHandler类,泛型第一个参数指定key的类型,第二个参数指定查询结果转换的Bean类型,
//构造方法中的第一个参数指定Bean的Class对象,第二个参数指定将查询结果的哪一列的值作为key保存
//构造方法的参数可以是查询结果中某列的下标,也可以是列的名称
//BeanMapHandler<Integer, Users> handler = new BeanMapHandler<>(Users.class, "ID");
BeanMapHandler<Integer, Users> handler = new BeanMapHandler<>(Users.class, 1);
//同样调用query方法执行查询,返回值是一个Map集合,key为查询某列的值,value为封装当前行的Bean对象
Map<Integer, Users> map = qr.query(sql, handler);
return map;
}

六、作业

6.1、作业要求

1. 任意数据库,建立一个表student,字段有id,name(id是自增长的,name是char类型)
2. 利用DbUtils完成CRUD操作
3. insert操作要能得到返回的自增长值
4. Connection对象要能正确处理

6.2、提交内容

1. 整个项目的源代码打包发到我的qq或者直接把你的项目的git地址告诉我即可

6.3、提交时间

2017-11-2号 星期四 中午12:00前

七、资料下载与说明

7.1、参考

7.2、资料下载

链接:
https://pan.baidu.com/s/1mhK2d5Y 密码: d78g

7.3、说明

文章内容由“王亮”老师提供,内部学习时由“陈军”老师讲授

写一个ORM框架的第一步的更多相关文章

  1. 写一个ORM框架的第一步(Apache Commons DbUtils)

    新一次的内部提升开始了,如果您想写一个框架从Apache Commons DbUtils开始学习是一种不错的选择,我们先学习应用这个小“框架”再把源代码理解,然后写一个属于自己的ORM框架不是梦. 一 ...

  2. 从 0 开始手写一个 Mybatis 框架,三步搞定!

    阅读本文大概需要 3 分钟. MyBatis框架的核心功能其实不难,无非就是动态代理和jdbc的操作,难的是写出来可扩展,高内聚,低耦合的规范的代码. 本文完成的Mybatis功能比较简单,代码还有许 ...

  3. 手写开源ORM框架介绍

    手写开源ORM框架介绍 简介 前段时间利用空闲时间,参照mybatis的基本思路手写了一个ORM框架.一直没有时间去补充相应的文档,现在正好抽时间去整理下.通过思路历程和代码注释,一方面重温下知识,另 ...

  4. 剖析手写Vue,你也可以手写一个MVVM框架

    剖析手写Vue,你也可以手写一个MVVM框架# 邮箱:563995050@qq.com github: https://github.com/xiaoqiuxiong 作者:肖秋雄(eddy) 温馨提 ...

  5. 看年薪50W的架构师如何手写一个SpringMVC框架

    前言 做 Java Web 开发的你,一定听说过SpringMVC的大名,作为现在运用最广泛的Java框架,它到目前为止依然保持着强大的活力和广泛的用户群. 本文介绍如何用eclipse一步一步搭建S ...

  6. 手写一个RPC框架

    一.前言 前段时间看到一篇不错的文章<看了这篇你就会手写RPC框架了>,于是便来了兴趣对着实现了一遍,后面觉得还有很多优化的地方便对其进行了改进. 主要改动点如下: 除了Java序列化协议 ...

  7. 【手撸一个ORM】第六步、对象表达式解析和Select表达式解析

    说明 一个Orm自然不仅仅包含条件表达式,还会有如下的场景: OrderBy(s => s.StudentName) Select<StudentDto>(s => new S ...

  8. 手写MyBatis ORM框架实践

    一.实现手写Mybatis三个难点 1.接口既然不能被实例化?那么我们是怎么实现能够调用的? 2.参数如何和sql绑定 3.返回结果 下面是Mybatis接口 二.Demo实现 1.创建Maven工程 ...

  9. 如何做好一个ORM框架

    很多人都不太认可以第三方ORM,因为考虑的点不够全面,没有用户群体大的ORM有保证,这点是不可否认确是事实. 但是往往用户群体大的ORM又有不足之处,就拿用户群体最多的两个ORM来说一下吧 1.EF ...

随机推荐

  1. Gvim安装nerd_tree插件

    1.先去官网下载nerd_tree插件 http://www.vim.org/scripts/script.php?script_id=1658 2.解压缩将nerd_tree目录下的doc目录和pl ...

  2. Spring+mybatis 实现aop数据库读写分离,多数据库源配置

    在数据库层面大都采用读写分离技术,就是一个Master数据库,多个Slave数据库.Master库负责数据更新和实时数据查询,Slave库当然负责非实时数据查询.因为在实际的应用中,数据库都是读多写少 ...

  3. C-C++到底支不支持VLA以及两种语言中const的区别

    C-C++到底支不支持VLA以及两种语言中const的区别 到底支不支持VLA VLA就是variable-length array,也就是变长数组. 最近写程序的时候无意间发现,gcc中竟然支持下面 ...

  4. CCIE-MPLS VPN-实验手册(下卷)

    10:跨域的MPLS VPN (Option A) 10.1 实验拓扑 10.1 实验需求 a.       R1 R2 R3 组成P-NETWORK R1 R2 R3 位于AS 1,底层协议采用EI ...

  5. 结对作业-基于GUI的四则运算

    一.需求分析 1.题目要求: 我们在个人作业1中,用各种语言实现了一个命令行的四则运算小程序.进一步,本次要求把这个程序做成GUI(可以是Windows PC 上的,也可以是Mac.Linux,web ...

  6. IT之光

    作为一个IT界的新新人才,现在拥有第一个博客,可以在这里学习和分享IT方面的知识和技术.

  7. 【Alpha】——Third Scrum Meeting

    一.今日站立式会议照片 二.每个人的工作 成员 昨天已完成的工作 今天计划完成的工作 李永豪 基本完成添加功能 继续完善添加功能 郑靖涛 基本完成删除功能 继续完善删除功能 杨海亮 基本完成查找功能 ...

  8. 201521123034《Java程序设计》第八周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 2. 书面作业 本次作业题集集合 List中指定元素的删除(题目4-1) 1.1 实验总结 答:这题是在课堂上 ...

  9. 201521123065《Java程序设计》第1周学习总结

    1. 本周学习总结 java是门语言较为简单,并且可以在多种平台运行编译的语言. JDK是java开发工具,可以将源程序编译成字节码. JRE:java运行环境. JVM:虚拟机,是java实现多平台 ...

  10. 201521123044 《Java程序设计》第10周学习总结

    1. 本章学习总结 2. 书面作业 本次PTA作业题集异常丶多线程 1.finally题目4-2 1.1 截图你的提交结果 1.2 4-2中finally中捕获异常需要注意什么? 1.无论try-ca ...