[疯狂Java]JDBC:Statement、ResultSet、连接资源自动关闭、Properties配置文件

时间:2022-05-22 03:46:51

1. 建立连接得到Connection对象后如何继续操作数据库?

    1) 查询数据库就必须要执行SQL语句,可以通过Connection对象conn获取一个代表要提交给数据库的SQL语句的句柄,即Statement对象;

    2) 使用Connection的createStatement方法得到SQL语句句柄:Statement Connection.createStatement(); // 这个是对象方法

    3) 注意!得到的Statement句柄(对象)是从属于创建它的连接conn的,通过该Statement提交的SQL查询语句将直接作用于conn所连接的数据库实例!!

    4) 接下来就可以通过Statement对象的execute系列方法将指定的SQL语句提交给数据库进行查询并返回结果,execute系列方法的用法大致为:返回结果集 excuteXXX(SQL语句);

    5) 一个创建查询语句句柄的示例:Statement stmt = conn.createStatement();


2. 先了解如何处理结果集ResultSet——查询返回的结果集:

    1) 如果是select查询,那么返回的结果集就是一张临时的视图,JDBC用ResultSet对象来接受返回的结果集;

    2) 由于结果集其实就是一张链表,每条记录就是链表中的一个节点,因此ResultSet需要用一个记录指针来定位当前指向哪一条记录,而ResultSet提供了如下方法来操作记录指针的指向:所有返回boolean的结果表示如果移动后不存在有效的记录将返回false,结果集中头节点之前和最后一个节点之后都是有效的(即恒为空,再往外走就无效了)

         i. boolean next(); // 下移一位

         ii. boolean previous(); // 上移一位

         iii. boolean first(); // 直接定位到头节点,如果表为空则返回false

         iv. boolean last(); // 直接定位到尾节点,如果表位空则返回false

         v. void beforeFirst(); // 定位到头节点之前,由于头节点之前永远存在且有效,因此返回void,这也是ResultSet的得到结果后的初始状态

         vi. void afterLast(); // 定位到尾节点之后,同样该节点永远有效

         vii. boolean absolute( int row ); // 绝对定位到某一行,0表示头节点之前,正数表示从头开始数,负数表示从尾开始数,-1就表示倒数第一个节点,1就表示头节点

    3) 遍历记录:

         i. 首先要获取当前指针所指向的记录的内容,使用ResultSet的getXxx()方法获得当前记录的指定列的值;

         ii. 首先Xxx几乎包括了所有的Java基础类型:String、Int、Double、Time...

         iii. 其次是参数有两个重载版本,一个是传入一个列号(从1开始计,1就表示第1列),另一个是列名(直接就是个字符串):

xxx ResultSet.getXxx(int columnIndex | String columnLabel);

!列号采用的是索引随机访问,效率更高,而使用列名可读性更好,但效率不如索引;

         iv. 如果你不清楚某列的数据类型,那必定可以使用getString获取,因为任何类型的数据都可以最终转化成String类型(自动转换的),除了Blob类型不能转化成String外其余都可以;

    4) 示例:

ResultSet rs = 获取查询结果;
while (rs.next()) { // 不停移动记录指针遍历记录
System.out.println(rs.getInt(1) + '\t' + // 第1列是一个int值
rs.getString(2) + '\t' + // 后面3列都是String值
rs.getString(3) + '\t' +
rs.getString(4));
}


3. 再回过头来看如何执行SQL语句并得到结果集:

    1) Statement提供了3中方法执行SQL语句:

         i. executeQuery:专门用来执行select查询;

         ii. executeUpdate:专门用来执行DDL语句和DML语句;

         iii. execute:可以执行任何类型的SQL语句,但使用比较麻烦,通常在不清楚要执行的是什么类型的SQL语句时才使用它,通常不建议使用该方法;

    2) 查询:

         i. 原型:ResultSet Statement.executeQuery(String sql);

         ii. sql必须是select查询语句,返回的临时视图保存在ResultSet结果集中;

         iii. 示例:ResultSet rs = stmt.executeQuery("select * from student_table");

    3) DDL和DML:

         i. 所有的DDL和DML语句都可以用executeUpdate执行;

         ii. 原型:int Statement.executeUpdate(String sql);

         iii. sql必须是DML或者DDL语句;

         iv. 对于DML语句,返回的是受影响的行数,对于DDL就返回0;

    4) DDL/DML示例:

         i. DDL:stmt.executeUpdate("create database MyData");

         ii. DML:stmt.executeUpdate("insert into table1 values('134', 'Peter')"):

         iii. Java8提供了一个executeLargeUpdate方法,其实就是executeUpdate的增强版,只不过返回的是long,即如果影响的行数超过Integer的最大值时使用该方法执行SQL语句(一般影响行数超过10位数),但不过这仅仅是Java8留的一个接口,具体实现还是要有数据库供应商来完成,可惜的是MySQL并没有提供该接口的实现,因此就用executeUpdate就行了;


4. 使用execute执行任意SQL语句:

    1) 原型:boolean Statement.execute(String sql);

    2) 该方法可以执行任意类型的SQL语句,但通常不建议使用该方法,因为executeUpdate和executeQuery覆盖的很好;

    3) 如果返回true则表示执行的是select查询并返回ResultSet结果集,如果返回的是false则表示执行的是非select查询而且没有返回ResultSet结果集;

!!简单的来讲就是返回值告诉你是否产生了结果集;

    4) 那如何获取结果集或者改变了多上行的信息呢?因为这毕竟是query和update的结合:

         i. ResultSet Statement.getResultSet(); // 返回之前查询得到的结果集,如果没有结果集则返回null

         ii. int Statement.getUpdateCount(); // 返回更新后被影响的行数,当如如果没有更新则返回0

         iii. 因此在获取相应信息前最好是现根据execute的返回值判断是什么类型的查询!!


5. Connection、Statement、ResultSet自动关闭的特性:

    1) Connection、Statement、ResultSet三个接口都继承了AutoCloseable接口,因此都可以实现自动关闭(类似于C++的析构函数),这样就可以不必再显式地在finalize方法中释放数据库连接资源了;

    2) 所有实现了AutoCloseable接口的类对象都可以使用try语句来关闭!

    3) Statement和ResultSet都是从属于Connection的,因此Connection关闭就会导致其拥有的Statement和ResultSet也随之关闭;

!!!!这也就意味着,如果连接对象conn被释放(过了其作用域又没被引用等,反正就是被垃圾回收器回收了)其所属的Statement、ResultSet即使仍然被引用着也会被强制关闭(被释放内存),如果还是强行要引用就会抛出异常!

    4) 连接后及时处理!!

         i. 这就意味着连接得到conn后就应该立即构造Statement查询并得到ResultSet,并且得到结果集后应该立即读取并解析!

         ii. 这三个步骤千万不要多线程进行,或者分解在若干个方法中各自进行,因为一旦conn先释放就会导致其余两者无法使用,这就会导致一些不可预计的后果;

    5) 最好的做法:将连接、查询、结果分析放在同一个方法的同一个作用域中!!


6. 项目配置文件:

    1) 当需要把应用程序从开发环境移植到生产环境时肯定希望不修改源代码也可以改变程序中的信息或者行为;

    2) 因此可以把上面程序中的这些变量写到一个外部配置文件中,每次程序启动时动态读取配置文件中的信息来进行初始化,这样每次就不用改动源码更不用重新编译了!!

    3) 配置文件的格式:

         i. 文件名可以任意取,但一般会以.ini(表示启动配置文件),或者.properties(直接表示笼统的配置文件)作为后缀;

         ii. 内容就是键值对,例如:

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/select_test
user=root
pass=32147
!这就是一个典型的配置文件的内容,即由等号连接的一个个键值对,两边都不用加双引号,因为类型都是纯字符串,除非要用到一些特殊字符采用引号引起来;

    4) 在Java中加载并使用配置文件中的信息:

         i. 首先utils包中提供了一个Properties类来操作配置文件;

         ii. 构造器:Properties();  // 就是一个简单的无参构造器

!!接下里就是利用Properties的各种对象方法加载并利用配置文件了

         iii. 加载配置文件:synchronized void load(InputStream inStream);

              a. 加载是直接从InputStream加载的,因此可以看到配置信息可以用多种形式保存,如果配置信息是以文件形式保存在磁盘中则可以利用FileInputStream读取,例如:

Properties prop. = new Properties();
props.load(new FileInputStream("my.ini"));
              b. 由于配置文件也是一种竞争资源,可以被读也可以被写,因此考虑到有些进程可能会加读写锁(可以读也可以写),因此即使load是读也被设置为同步方法了!

         iv. 解析键值对:String getProperty(String key);  // 根据key返回相应的value

!!一切就这么简单;


7. 示例:executeUpdate、executeQuery、execute、Properties的应用

public class Test {

private String driver;
private String url;
private String user;
private String passwd;

private Connection conn;
private Statement stmt;

private String sqlCreateTable = "create table table_test(col1 int, col2 int)";
private String sqlDropTable = "drop table if exists table_test";
private String sqlInsertInto = "insert into table_test values(1, 2), (3, 4)";
private String sqlQuery = "select * from table_test";

private void print(String s) {
System.out.println(s);
}

public void init() throws Exception {
Properties props = new Properties();
props.load(new FileInputStream("mysql.ini"));
driver = props.getProperty("driver");
url = props.getProperty("url");
user = props.getProperty("user");
passwd = props.getProperty("pass");

Class.forName(driver);
conn = DriverManager.getConnection(url, user, passwd);
stmt = conn.createStatement();

int res = 0;
stmt.execute(sqlDropTable);

print("使用executeUpdate和executeQuery");
print("创建表...");
res = stmt.executeUpdate(sqlCreateTable);
print("插入记录...");
res = stmt.executeUpdate(sqlInsertInto);
print("受影响的行数:" + res);
print("查询表...");
ResultSet rs = stmt.executeQuery(sqlQuery);
while (rs.next()) {
print(rs.getInt(1) + "\t" + rs.getInt(2));
}

print("使用execute");
print("删除表...");
stmt.execute(sqlDropTable);
print("创建表...");
stmt.execute(sqlCreateTable);
print("插入记录...");
stmt.execute(sqlInsertInto);
print("受影响的行数:" + stmt.getUpdateCount());
print("查询表");
if (stmt.execute(sqlQuery)) {
rs = stmt.getResultSet();
while (rs.next()) {
print(rs.getInt(1) + "\t" + rs.getInt(2));
}
}
}

public static void main(String[] args) throws Exception {
new Test().init();
}

}