前言:
这篇笔记的内容为使用JDBC操作MySQL数据库,主要内容为概念介绍和编写Demo来使用JDBCAPI,而关于API的具体内容就没有详细说明了,代码中用到的API可以在https://docs.oracle.com/javase/8/docs/api/中找到。主要的参考为Core Java卷二中关于数据库编程的描述。
目录:一:概念介绍
1.什么是JDBC
我们可以将JDBC看作是一组用于用JAVA操作数据库的API,通过这个API接口,可以连接到数据库,并且使用结构化查询语言(SQL)完成对数据库的查找,更新等操作。
2.为什么要使用JDBC
在如今,存在着许多不同的数据库,如MySQL,Oracle,SQL Serverd等等,它们使用的协议各不相同。因此,就无法通过扩展java,使人们用“纯”java语言与任何数据库进行直接通信。因此,java为SQL访问提供了一套“纯”javaAPI,同时提供一个驱动管理器,这样可以插入第三方驱动程序(这是数据库供应商自己提供的)到驱动管理器中来连接到特定的数据库。这就成为一种向驱动管理器注册第三方驱动程序的简单机制。这与前面说的区别在于,java没有直接的操作数据库,它是操作的是驱动管理器,这个驱动管理器连接的第三方驱动才真正的直接操作数据库。因此,对于我们来说,想在java程序中操作数据库,JDBCAPI是不得不使用的接口。
3.JDBC驱动程序的种类
第一类驱动程序将JDBC翻译为ODBC(微软开创的标准数据库API),然后使用一个ODBC驱动程序与数据库进行通信。
第二类驱动程序是由部分java程序和部分本地代码组成。
第三类驱动程序是纯Java客户端类库,它使用一种与具体数据库无关的协议将数据库请求发送给服务器构件,然后该构件再将数据库请求翻译成数据库相关的协议。这简化了部署,因为平台相关的代码只位于服务器端。
第四类驱动程序是纯java类库,它将JDBC请求直接翻译成数据库相关的协议。
4.使用JDBC前需要了解的概念
驱动程序的jar文件:根据前文的描述,在使用JDBC时,需要提供驱动程序来连接到数据库,因此,我们需要先获得这个驱动程序,MySQL驱动程序下载页面:https://dev.mysql.com/downloads/connector/j/;
注册驱动器类:根据前文的描述,我们需要将驱动程序注册到驱动管理器。许多JDBC的JAR文件(包含META-INF/services/java.sql.Driver文件的JAR文件)会自动注册驱动程序器类,在这种情况下,可以跳过手动注册步骤。手动注册有两种方式,一是在java程序中加载驱动器类,如Class.forName("com.mysql.cj.jdbc.Driver");语句将使得驱动器类被加载,由此将执行可以注册驱动器的静态初始化器。其中com.mysql.cj.jdbc.Driver是MySql的驱动器类名。第二种方式是设置jdbc.drivers属性,这种方式可以提供多个驱动器。
数据库URL:在通过jdbc连接到数据库时,我们需要告诉驱动管理器,是想要连接哪种数据库,然后根据这种具体的数据库的协议再提供替它信息,一般是主机名,端口号和数据库名。因此。JDBC使用了一种与普通URL相类似的语法来描述数据源,一般为 jdbc:subprotocol:other stuff.其中subprotocol用于选择连接到数据库的具体驱动程序。other stuff参数的格式随着使用的protocol的不同而不同。对于连接到本地MySQL数据库来说,格式一般为 jdbc:mysql://localhost:3306/DatabaseName。
启动数据库:数据库服务器在连接之前需要先启动,并且建好需要用到的数据库,这里取名为testjdbc。
二:使用JDBC操作数据库
代码为:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class CreateTable {
public static void main (String[] args){
try {
//注册驱动程序
Class.forName("com.mysql.cj.jdbc.Driver");
//设置URL和登陆MySQL需要的用户名和密码
String url = "jdbc:mysql://localhost:3306/testjdbc?useSSL=false&serverTimezone=Hongkong";
String username = "root";
String password = "613781zs";
//创建连接到数据库的Connection
Connection conn = DriverManager.getConnection(url,username,password);
Statement stat = conn.createStatement();
//SQL语句,用于建表
String sql = "Create table books " +
"(" +
"name varchar(50)," +
"author varchar(50)," +
"price double" +
");";
//执行SQL语句
stat.execute(sql);
} catch (ClassNotFoundException|SQLException e) {
e.printStackTrace();
}
}
}
注意,在这里向MySql传入了两个参数,否则会报错,并且为了简便没有释放资源。当程序没有报错时,查看数据库中是否已经建好表了:
从结果中可以看到,数据库中已经存在这个表了,并且表结构也是正确的,到此位置,已经可以使用jdbc连接数据了,基本的环境已经是没有问题的了。
2.Statement对象
在执行SQL语句之前,首先要创建一个Statement对象,如上面的代码一样,要通过Statement的方法来执行SQL语句。获得Statement对象的步骤在代码中也已经写出来了。
Statement类的重要API:
ResultSet executeQuery(String sql)
执行给定字符串中的SQL语句,并返回一个用于查看结果的ResultSet对象。只是用于查询。
int executeUpdate(String sql)
long executeLargeUpdate(String sql)
执行字符串中指定的INSERT,UPDATE,DELETE的DML语句。还可以执行DDL的语句,如CREATE TABLE。返回受影响的行数,如果是没有更新计数的语句,返回0。不能用于查询。
boolean execute(String sql)
执行给定字符串中的SQL语句这个方法可以执行任意的SQL语句。可能会产生多个结果集和更新计数。如果sql中的语句第一个执行结果是结果集,返回true,否则返回false。再根据返回的值是true时调用getResultSet获取查询结果,或者false时调用getUpdateCount获得执行得到的第一个执行结果被影响的行数。
void close()
关闭statement对象以及它对应的结果集。
已经知道如果Satement执行的是查询语句的话,会有一个ResultSet类型的对象被返回。可以通过每次一行的方式地迭代的遍历所有的查询结果:
ResultSet rs = stat.executeQuery("Select * from books");
while(rs.next()){
look at a row of the result set;
}
结果集中行的顺序是任意排列的。除非使用ORDER BY子句指定行的顺序。查看每一行时,想要知道其内容,可以通过访问器来获取信息。不同的数据类型有不同的访问器,如getString和getDouble。要根据查看的字段类型选择合适的访问器。在查看字段内容时,需要指出是查看哪个字段的内容,具体方法有两种形式,指出字段的所在列数(从1开始),或指出字段的列名;如
String bookname = rs.getString(1);或
String bookname = rs.getString("name");
ResultSet的其余重要API:
boolean next();
将结果集中的当前行向前移动一行,如果已经到最后一行的后面,则返回false。初始情况下必须调用这个方法才能转到第一行。
void close()
立即关闭当前结果集;
3.PreparedSatement
利用PreparedStatement来执行SQL插入语句,之后再将数据打印出来,代码为:
import java.sql.*;
import java.util.Scanner;
public class TestPreparedStatement {
public static void main (String[] args) throws SQLException {
Connection conn = getConnection();
String sql = "Insert into books values(?,?,?)";
//这里使用了PreparedStatement
PreparedStatement pstat = conn.prepareStatement(sql);
//从终端读取数据 并插入到表中
Scanner in = new Scanner(System.in);
while (in.hasNext()){
String parameter = in.nextLine();
if(parameter.equalsIgnoreCase("exit")){
break;
}
//分割字段
String[] parameters = parameter.trim().split(" ");
for(int i = 0;i
结果为:
在这个程序中,也没有进行资源关闭,使用了预备语句(Prepared statement)。因为在连续执行很多条相似SQL语句的时候,我们没有必要在每插入一个数据时都新建一个语句,它们除了字段值以外,其余部分都是相同的。因此,准备了一个带有宿主变量的查询语句,每次操作时只需要为该变量填入不同的字符串就可以反复多次地使用该语句。这一技术改进了查询性能,因为当数据库执行某个操作的时候,可以先通过计算来确定策略,以便高效的执行操作。使用preparedstatement,这样准备策略的步骤就只需要做一次了。在预备语句中,每个宿主变量都有"?"来表示,如果存在一个以上的变量,那么在设置的时候必须注意“?”的位置。在执行语句之前,需要将值绑定到变量“?”上,这通过set方法,与ResultSet的get方法类似,不同的数据类型有不同的get方法。
三:结果集,行集
1:可滚动的和可跟新的结果集
使用ResultSet接口中的next方法可以迭代遍历结果集中的所有行。这个遍历只能是一个方向的从头到尾遍历,但用户可能希望在结果集上前后移动,或者跳到任意位置上,这样的功能就需要可滚动的结果集。同时,在显示这些内容给用户的时候,他们可能希望编辑这些内容,在可更新的结果集上,可以以编程的方式来更新其中的项,使得数据库可以自动更新数据。
在默认的情况下,结果集是不可滚动,不可更新的。为了设置这两个功能,需要通过下面的这个方法来获得一个不同的Statement对象或PreparedStatement。
Statement stat = conn.createStatement(type,concurrency);
PreparedStatement = conn.preparedSatement(command,type,concurrency);
type的取值为:
TYPE_FORWARD_ONLY 结果集不能滚动
TYPE_SCOLL_INSENSITIVE 结果集可以滚动,但对数据库变化不敏感
TYPE_SCOLL_SENSITIVE 结果集可以滚动,但对数据库变化敏感
concurrency的取值为:
CONCUR_READ_ONLY 结果集不能用与数据库更新
CONCUR_UPDATABLE 结果集可以用与数据库更新
2.行集
可滚动的结果集虽然功能强大,但是有一个重要的缺陷:在与用户交互的整个过程中,必须始终与数据库保持连接。但数据库连接是属于稀有资源,在用户可能长时间后才再次操作数据库时,可以使用行集,RowSet接口扩展自ResultSet接口,却无需保持与数据的连接。
以下为javax.sql.rowset包提供的接口,他们都扩展了RowSet接口:
CacheRowSet运行在端口链接的状态下执行相关操作
WebRowSet对象代表了一个被缓存的行集,该行集可以保存为XML文件。
FilteredRowSet和JoinRowSet接口支持对行集的轻量级操作,他们等同与SQl中的Select和Join操作
JdbcRowSet是ResultSet接口的一个瘦包装器。它在RowSet接口中添加了有用的方法。
获取行集的标准方式:
RowSetFactory factory = RowSetRrovider.newFactory();
CachedRowSet crs = factory.createCachedRowSet();
对于其他类型行集的对象也有类型方法。
其中CachedRowSet可用于分页。
四:元数据
在SQL中,描述数据库或者其组成部分的数据成为元数据。例如,可以获取某个数据库中所有表的列表,也可以获得某个表中所有列的名称和数据类型等。我们可以获得三类元数据:关于数据库的元数据,关于结果集的元数据,预备语句参数的元数据。
五:事务
首先来了解事务的概念,事务就是要将多个语句组合在一起执行,但这几个语句要么表现为全部都执行成功,要么就表现的像全部都没执行过一样。当全部执行成功时就可以提交(commit)这个事务,否则事务就将被回滚(rollback)为全部都没执行时的状态。将多个语句组合成事务的主要原因是为了确保数据库完整性。经典的案例就是转账,当一个账户A给另一个账户B转账的时候,从A中扣去金额与给B增加相应的金额这两个更新操作要么全部成功,要么就全部没执行一样,否则数据库的完整性就受到了破坏。