JDBC操作数据库的学习(1)
单单对数据库的操作,比如说MySQL,我们可以在命令行窗口中执行,但是一般是应用程序要操作数据库,因此我们应该在程序中的代码上体现对数据库的操作,那么使用程序应用如何操作数据库呢?那就要使用到数据库的连接驱动,应用程序通过这些驱动来操作数据库:
但是这里就又有一个问题了,不同的数据库有各自的驱动程序,而一个应用程序要操作不同的数据库,那么就要懂得要使用的数据库的驱动如何操作,这样就增加了学习成本。好在我们使用Java开发应用,而Java中只需要使用JDBC就能操作所有的数据库,因为JDBC提供的接口,是所有数据库厂商以这些接口来实现各自的方法,所以我们只需要学会如何使用JDBC即可懂得如何操作数据库了:
本篇中讲述的是如何使用JDBC操作MySQL数据库,因此我们在安装完MySQL数据库后,还要从MySQL官网上下载用于Java程序的驱动连接的jar包:
左侧选择“Connector/J”,这是专门给Java程序的,下载完后,在文件目录中包含了程序jar包,src源码,和doc文档:
只要在机子上安装了MySQL数据库,同时我们也配有数据库的驱动连接jar包,那么在开发Java应用中我们就可以使用JDBC来操作MySQL数据库了。
例1:使用JDBC对数据库进行查询操作
我们先在数据库中导入一些数据,这里我们采用直接对数据库添加操作,因为本例的重点在于使用JDBC进行查询。
在cmd命令行窗口中依次输入一下sql脚本:
1:登录MySQL
mysql -u root -p
2:创建一个数据库
create database jdbcdemo;
3:使用刚创建的数据库
use jdbcdemo;
4:创建一个user表
create table user(
id int primary key,
name varchar(40),
age int
);
5:向刚创建的user表中添加数据
insert into user(id,name,age) value(1,"Ding",25);
insert into user(id,name,age) value(2,"LRR",24);
通过以上步骤,在MySQL数据库中已经含有了我们想要查询的数据,现在创建一个工程,在工程中导入MySQL的Java驱动连接
编写代码:
1 String url = "jdbc:mysql://localhost:3306/jdbcdemo"; //在协议中就确定了使用哪个库
2 String user = "root";
3 String password = "root";
4
5 //1,加载驱动
6 //DriverManager.registerDriver(new com.mysql.jdbc.Driver()); 一般不推荐使用这种方法
7 Class.forName("com.mysql.jdbc.Driver");
8
9 //2,获取与数据库的连接
10 Connection conn = DriverManager.getConnection(url, user, password); //Connection要选Java自己的包Java.sql.Connection
11
12 //3,获取用于向数据库发送sql语句的Statement对象
13 Statement st = conn.createStatement(); //Statement接口也要选择Java自己的包java.sql.Statement
14
15 //4,使用字符串编写sql语句
16 String sql = "select * from user";
17
18 //5,向数据库发送sql语句,并获取代表结果集的ResultSet对象
19 ResultSet rs = st.executeQuery(sql);
20
21 //6,从结果集ResultSet对象中取出数据
22 while(rs.next()) {
23 System.out.println("id=" + rs.getObject("id"));
24 System.out.println("name=" + rs.getObject("name"));
25 System.out.println("age=" + rs.getObject("age"));
26 }
27
28 //7,关闭连接,释放资源
29 rs.close();
30 st.close();
31 conn.close();
执行程序观察结果:
注意,该示例只是一个简单的使用JDBC操作数据库的例子,这里面的代码只是提供一个最简洁的过程,真正在开发中还会对其进行很多健壮性代码的添加,比如在释放资源的部分(请看例3)。
例1中的Connection,Statement,ResultSet这三个接口的包请都选择为Java中的java.sql包,运用多态的知识,只要是能使用JDBC操作的数据库厂商都会分别创建出这三个接口的对象,并实现这三个接口中的对象,因此在java.sql中的这三个接口是任何数据库驱动中封装的Connection,Statement,ResultSet的顶层父接口。
作为学习JDBC的入门案例,当然要好好分析下上面代码里面的内容。
分析:
⑴ 加载注册驱动
在上面的例子中,我们可以看到在加载驱动这一步,使用DriverManager.registerDriver方法和使用反射Class.forName方法都可以向驱动管理器“DriverManager”中注册驱动。
先来看看被注释掉的DriverManager.registerDriver(new com.mysql.jdbc.Driver()); 这一行代码,这里面的DriverManager就代表注册驱动器。要想使用数据库就必须先要在这个驱动管理器中注册对应的驱动。驱动管理器的API如下:
在驱动管理器中有一些方法,例如注册驱动registerManager方法,删除驱动deregisterManager方法,获取所有的驱动getDrivers方法,获取链接getConnection方法,该DriverManager类中所有的方法都是静态方法。
但是在实际开发中并不推荐采用registerManager方法注册驱动。原因如下:
① 从DriverManager.registerDriver(new com.mysql.jdbc.Driver()); 从这行代码可以看出在方法的参数上传入的是com.mysql.jdbc.Driver类的对象,这是MySQL的驱动jar包中的类,如果这个jar包在工程中丢失,那么程序就无法编译,并且切换数据库时也会非常麻烦。而我们后面会看到采用反射方式只要字符串即可,非常方便。
② 是因为我们如果使用的是com.mysql.jdbc.Driver类,这个我们看MySQL的驱动器源码就能发现在这个类创建对象的过程中,在构造器之前还有个静态代码块,这个要比构造器更先执行,这个静态代码块中的内容是这样的:
可以看到这个静态代码块中已经使用驱动管理器注册了该驱动一次,如果我们在程序再使用驱动管理器注册,那么就会产生两次Driver对象,因此我们不推荐采用registerManager方法。
而我们推荐使用Class.forName("com.mysql.jdbc.Driver"); 这个方法来注册驱动,通过上图的源码可以看到,使用反射方式,将该MySQL驱动类Driver加载进内存时,就会调用静态代码块,这时就会在驱动管理器中注册该驱动类Driver对象。这种方式的好处在于首先驱动使用字符串,而且字符串还能以配置文件的方式不写死,同时能保证一定能注册一个驱动对象。
注:静态代码块会并且只会在类首次加载进内存时执行一次。
⑵ 获取连接
在加载驱动之后,我们就要获取与数据库的连接,通过连接来向数据库发送命令同时获取数据库返回的结果。在DriverManager驱动管理器中,有三个静态方法都可以以不同的方式获取连接:
第一种方式中,除了URL,其实我们也可以将用户名和密码以URL参数的形式添加在URL之后,例:jdbc:mysql://localhost:3306/jdbcdatabase?user=root&password=root,如果我们不想在程序中写入用户名和密码,也可以将用户名和密码放置在properties文件中,如上图中第二种方式。
Connection对象是数据库编程中最重要的一个对象,客户端与数据库所有的交互都是同connection对象来完成的,主要有如下几个方法(部分):
· createStatement() : 创建想数据库发送SQL语句的Statement对象。
· prepareStatement(String sql) : 创建向数据库发送欲编译SQL的PrepareStatement对象。
· prepareCall(String sql) : 创建执行存储过程的CallableStatement对象。
· setAutoCommit(boolean autoCommit) : 设置事务是否自动提交。
· commit() : 在此连接上提交事务
· rollback() : 在此连接上回滚事务。
(详细请看相关API)
这里说明下数据库中的URL是用于标示数据库的位置,通过URL地址告诉JDBC连接哪个数据库,URL的写法为:
常用的几种数据库URL地址的写法:
Oracle写法: jdbc:oracle:thin:@localhost:1521:jdbcDatabase
SqlServer写法: jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=jdbcDatabase
MySQL写法: jdbc:mysql://localhost:3306/jdbcDatabase
如果使用MySQL连接的是本地主机的数据库,同时连接是3306端口,那么URL地址可以简写为: jdbc:mysql:///jdbcDatabase
后面的参数名和参数值我们除了会使用用户名和密码外,通常还会设定在MySQL中使用的编码表,例如 useUnicode=true&characterEncoding=UTF-8,这样跟随在URL地址后面,虽然我们在安装MySQL时已经指定了使用的码表,但是在程序中应该养成一个好习惯,同时也方便于开发人员指定。
⑶ 获取发送封装SQL的Statement对象
Statement对象用于向数据库发送SQL脚步语句,Statement对象中常用的方法如下:
· executeQuery(String sql) : 用于向数据库发送查询语句。返回结果集ResultSet对象。
· executeUpdate(String sql) : 用于向数据库发送Insert,Update或者Delete语句。返回整型数值,代表该语句成功执行影响表中项目的行数。
· execute(String sql) : 用于向数据库发送任意SQL语句。返回布尔型变量。
· addBatch(String sql) : 把多条SQL语句放到一个批处理中。
· executeBatch() : 向数据库发送一批SQL语句并执行。
execute方法虽然可以执行增删改查方法,但是平常不太常用,如果是只查询,一般只用executeQuery方法,返回结果集对象ResultSet,可以直接对该结果集进行处理;如果是添加,修改或删除一般使用executeUpdate方法,返回整型值,通常我们只需要判断是否大于0 即可明白该语句是否执行成功。
⑷ 如果是查询语句,获取返回的结果集ResultSet对象
执行Statement的executeQuery查询方法,将返回的是将查询结果进行封装成一个对象返回,这个对象就是结果集ResultSet对象。ResultSet对象在封装数据库查询出来的数据时,采用类似表格的方式,同时配有一个指向表格行的光标,以该对象中的next()方法(返回Boolean变量)来判断是否还有下一个行数据项。
ResultSet类主要封装了两大类方法,一类用于获取内部光标指定行的数据项中指定的列数据,一类用于将内部光标进行移动。
获取指定行数据项中任意类型的列数据,可以指定是第几列(index),也可以指定该列的关键字(columnName) :
getObject(int index)
getObject(String columnName)
获取指定行数据项中指定类型的列数据:
getString(int index)
getString(String columnName)
getInt(int index)
。。。等等
请注意,指定获取数据库行数据项的第几列数据(index)都是从1开始的,但通常我们不推荐使用索引值,而是采用该列的关键字进行获取数据,因为这样更直观。
而将内部光标进行移动有以下几种常用方法:
· next() : 移动到下一行,如果有下一行则返回true。
· previours() : 移动到前一行,如果有前一行则返回true。
· absolute(int row) : 移动到指定行。
· beforeFirst() : 移动到resultSet结果集的最前面。
· afterLast () : 移动到resultSet结果集的最后面。
在上面的例1中,因为我们只是对每个数据进行打印到控制台,所以没有管数据到底是什么类型的,但是在一般开发过程中,我们需要对数据进行对应类型的引用,关于在MySQL中的类型和在Java中的类型对应如下图所示:
其中,如果如果在MySQL中定义的类型是Date类型的话,那么我们使用ResultSet对象调用getDate方法返回的是java.sql.Date对象,但是没有关系,因为java.sql.Date类是java.util.Date类的子类,因此我们使用java.util.Date类的对象依然能引用ResultSet.getDate方法获取的对象。其他同理。
例2:对例1 进行修改,将查询到的每一个结果封装到JavaBean对象中
根据存入数据库的user表,我们可以创建一个User类的JavaBean:
1 public class User {
2 private int id;
3 private String name;
4 private int age;
5 public int getId() {
6 return id;
7 }
8 public void setId(int id) {
9 this.id = id;
10 }
11 public String getName() {
12 return name;
13 }
14 public void setName(String name) {
15 this.name = name;
16 }
17 public int getAge() {
18 return age;
19 }
20 public void setAge(int age) {
21 this.age = age;
22 }
23 }
对例1中结果集ResultSet的部分进行处理,此处省略其他代码:
1 //5,向数据库发送sql语句,并获取代表结果集的ResultSet对象
2 ResultSet rs = st.executeQuery(sql);
3
4 //6,从结果集ResultSet对象中取出数据
5 while(rs.next()) {
6 User user = new User();
7 user.setId(rs.getInt("id"));
8 user.setName(rs.getString("name"));
9 user.setAge(rs.getInt("age"));
10 }
这样结果集ResultSet在获取数据时就能在设置JavaBean属性时提供正确的类型了。
⑸ 释放连接
释放连接可以说是在整个利用JDBC操作数据库中最为重要的一步,无论前面中的步骤是否有出现异常,都不应该影响我们将连接断开,所以说例1 的代码是很不严谨的。数据库的连接是十分宝贵的,由于数据库不会对已连接却长时间不适用的连接自动关闭,因此如果我们自己不及时释放连接资源,那么连接数会越来越少,在多个请求访问时就可能导致系统宕机。
因此在程序中我们要对于数据库进行交互的对象在最后都要及时释放资源,这些对象通常就是Connection,Statement,ResultSet等对象。
尤其是Connection对象,这是数据库中最稀有的资源,用完必须马上释放。一般Connection释放的原则就是“尽量晚创建,尽量早释放”。所以对于这些对象的创建顺序通常就是Connection ---> Statement --->ResultSet,而在释放资源的过程中,这些资源关闭的顺序通常为ResultSet ---> Statement ---> Connection。
为了这些资源一定能被释放,资源释放的代码一定要在finally语句中!
例3:对例1进行修改,保证释放资源一定能够被执行:
1 String url = "jdbc:mysql://localhost:3306/jdbcdemo"; //在协议中就确定了使用哪个库 2 String username = "root"; 3 String password = "root"; 4 5 Connection conn = null; 6 Statement st = null; 7 ResultSet rs = null; 8 try{ 9 //1,加载驱动 10 Class.forName("com.mysql.jdbc.Driver"); 11 12 //2,获取与数据库的连接 13 conn = DriverManager.getConnection(url, username, password); //Connection要选Java自己的包Java.sql.Connection 14 15 //3,获取用于向数据库发送sql语句的Statement对象 16 st = conn.createStatement(); //Statement接口也要选择Java自己的包java.sql.Statement 17 18 //4,使用字符串编写sql语句 19 String sql = "select * from user"; 20 21 //5,向数据库发送sql语句,并获取代表结果集的ResultSet对象 22 rs = st.executeQuery(sql); 23 24 //6,从结果集ResultSet对象中取出数据 25 while(rs.next()) { 26 User user = new User(); 27 user.setId(rs.getInt("id")); 28 user.setName(rs.getString("name")); 29 user.setAge(rs.getInt("age")); 30 } 31 }finally{ 32 //7,关闭连接,释放资源 33 if(rs!=null) { 34 try{ 35 rs.close(); 36 }catch (Exception e) { 37 e.printStackTrace(); 38 } 39 } 40 if(st!=null) { 41 try{ 42 st.close(); 43 }catch (Exception e) { 44 e.printStackTrace(); 45 } 46 } 47 if(conn!=null) { 48 try{ 49 conn.close(); 50 }catch (Exception e) { 51 e.printStackTrace(); 52 } 53 } 54 } 55 }