一、JDBC常用接口和类简介
- DriverManager:用于管理JDBC驱动的服务类。程序中使用该类的主要功能是获取Connection对象,该类包含如下方法:
public static synchronized Connection getConnection(String url,String user,String pass) throws SQLException 该方法获得url对应数据库的连接。
- Connection:代表数据库连接对象,每个Connection代表一个物理连接会话。要想访问数据库,必须先获得数据库的连接。该接口的常用方法如下:
Statement createStatement() throws SQLExcetion 该方法返回一个Statement对象。
PrepaerdStatement prepareStatement(String sql) throws SQLException 该方法返回预编译的Statement对象,即将SQL语句提交到数据库进行预编译。
CallableStatement prepareStatement(String sql) throws SQLException 该方法返回CallableStatement对象,该对象用于调用存储过程。
- Statement:用于执行SQL语句的工具接口。该对象即可以用于指定DDL、DCL语句,也可用于执行DML语句,还可以用于执行SQL查询。当执行SQL查询时,返回查询到的结果集。常用方法如下:
ResultSet executeQuery(String sql) throws SQLException 该方法用于执行查询语句,并返回查询结果对应的ResultSet对象。该方法只能用于执行查询语句。
int executeUpdate(String sql) throws SQLException 该方法用于执行DML语句,并返回受影响的行数;该方法也可用于执行DDL语句,执行DDL语句将返回0。
boolean execute(String sql) throws SQLException 该方法可执行任何SQL语句。如果执行后第一个结果为ResultSet对象则返回true;如果执行后第一个结果为受影响的行数或没有任何结果,则返回false。
- PreparedStatement:预编译的Statement对象。PreparedStatement是Statement的子接口,它允许数据库预编译SQL语句(这些SQL语句通常带有参数),以后每次只改变SQL命令的参数,避免数据库每次都需要编译SQL语句,因此性能更好。相对于Statement而言,PreparedStatement执行SQL语句时,无须再传入SQL语句,只要为预编译的SQL语句传入参数值即可,所以它多了如下方法:
void setXxx(int parameterIndex,Xxx value) 该方法根据传入参数值的类型不同,需要使用不同的方法。传入的值根据索引传给SQL语句中指定位置的参数。
- ResultSet:结果集对象。该对象包含访问查询结果的方法,ResultSet可以通过列索引或列名获得列数据,它包含如下常用方法来记录指针:
void close() 释放ResultSet对象。
boolean absolute(int row) 将结果集的记录指针移动到第row行,如果row是负数,则移动到倒数第row行。如果移动后的记录指针指向一条有效记录,则该方法返回true。
void beforeFirst() 将ResultSet的记录指针定位到首行之前,这是ResultSet结果集记录指针的初始状态——记录指针的起始位置位于第一行之前。
boolean first() 将ResultSet的记录指针定位到首行。如果移动后的记录指针指向一条有效记录,则该方法返回true。
boolean previous() 将ResultSet的记录指针定位到上一行,如果移动后的记录指针指向一条有效记录,则该方法返回ture。
boolean next() 将ResultSet的记录指针定位到一下一行,如果移动后的记录指针指向一条有效记录,则该方法返回true。
boolean last() 将ResultSet的记录指针定位到最后一行,如果移动后的记录指针指向一条有效记录,则该方法返回true。
void afterLast() 将ResultSet的记录指针定位到最后一行之后。
二、JDBC编程
JDBC编程大致按照如下步骤进行:
- 加载数据库驱动。通常使用Class类的forName()静态方法来加载驱动
Class.forName(driverClass);
driverClass就是数据库驱动类所对应的字符串。例如加载MySQL的驱动采用如下方法:Class.forName("com.mysql.cj.jdbc.Driver");
- 通过DriverManager获取数据库连接。DriverManager提供了如下方法
DriverManager.getConnection(String url,String user,String pass);
使用该方法通常需要传入三个参数:数据库URL、登录数据库的用户名和密码。在本机上写法如下DriverManager.getConnection("jdbc:mysql://localhost:3306/database_name?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true","user","password");
需要强调的是,URL没有固定写法。 - 通过Connection对象创建Statement对象。
- 使用Statement执行SQL语句。
- 操作结果集,如果执行的SQL语句是查询语句,执行后返回一个ResultSet对象,该对象里保存了SQL语句查询的结果。
- 回收数据库资源,包括关闭Result、Statement和Connection等资源。
下面程序示范了简单的JDBC编程(executeQuery()来执行查询语句):
(在JDBC前先把MySQL的jar包导入,否则会抛出ClassNotFoundException)
public class SQLTest {
public static void main(String[] args) throws Exception
{
//1.利用反射加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
try(
//2.使用DriverManager获取数据库连接,其中Connection代表了Java程序与数据库的连接
//目前最新的MySQL驱动推荐使用SSL连接(安全的网络连接,可以保证更好的数据安全性)
Connection conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/database_name?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true","user","password");
//3.使用Connection来创建一个Statement对象
Statement stmt=conn.createStatement();
//4.执行SQL语句,executeQuery返回的是一个查询结果集
ResultSet rs=stmt.executeQuery("select t.* , teacher_id from teacher t")
)
{
while(rs.next()){
System.out.println(rs.getInt(1)+"\t"+rs.getString(2));
}
}
}
}
下面程序示范了用executeUpdate()来创建数据表。该示例没有直接把数据库连接信息写在程序里,而是使用了一个mysql.ini文件(即properties文件)来保存数据库信息,这样做的好处是需要吧应用程序从开发环境移植到生产环境时,无须修改源代码,只需修改mysql.ini配置文件即可。
public class ExecuteDDL {
private String driver;
private String url;
private String user;
private String password;
public void initParam(String paramFile) throws Exception{
//使用Properties类来加载属性文件
Properties properties = new Properties();
properties.load(new FileInputStream(paramFile));
driver = properties.getProperty("driver");
url = properties.getProperty("url");
user = properties.getProperty("user");
password = properties.getProperty("password");
}
public void createTable(String sql) throws Exception{
//加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
try(
//获取数据库连接,此处严格按照规定的写法写
Connection connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/teacher?useUnicode=true
&characterEncoding=utf-8
&useSSL=false
&serverTimezone=UTC
&allowPublicKeyRetrieval=true"
, "username"
, "password");
//使用Connection来创建一个Statement对象
Statement statement = connection.createStatement()
)
{
//执行DDL语句创建数据表
statement.executeUpdate(sql);
}
}
public static void main(String[] args) throws Exception{
ExecuteDDL executeDDL = new ExecuteDDL();
//调用上述的initParam方法
executeDDL.initParam("my.ini");
executeDDL.createTable("create table jdbc_test"
+ "(jdbc_id int auto_increment primary key, "
+ "jdbc_name varchar(255), "
+ "jdbc_desc text);");
System.out.println("建表成功");
}
}
运行上面的程序,执行成功后会看到数据库中新添加了一个jdbc_test的数据表,表明JDBC执行DDL语句成功。
前面有提到,Statement的execute()方法几乎可以执行任何SQL语句,不过它执行SQL语句的时候非常麻烦,通常不需要。但是如果不知道SQL语句的类型,就只能用这个方法了。使用该方法返回的值是一个boolean值,它表明执行该SQL语句是否返回了ResultSet对象。Statement提供了两个方法来获取执行结果:
getResultSet() 获取该Statement执行查询语句所返回的ResultSet对象。
getUpdateCount() 获取该Statement()执行DML语句所影响的记录条数。
如果程序需要反复执行结构类似的SQL语句,例如:
insert into table_name values(null,'xxx',1);
insert into table_name values(null,'xxxx',2);
这种情况我们可以使用占位符(?)参数的SQL语句来代替它:
insert into table_name values(null,?,?);
Statement执行SQL语句时并不允许使用问号占位符参数,为了满足这种功能,JDBC提供了PreparedStatement接口,前面有介绍,它可以预编译SQL语句,预编译后的SQL语句被存储在PreparedStatement对象中,然后可以使用该对象多高效地执行该语句。即这种方法效率比较高。创建PreparedStatement对象使用Connection的prepareStatement()方法,该方法需要传入一个SQL字符串,该SQL字符串可以包含占位符参数:ps=conn.perpareStatement("insert into table_name values(null,?,1)");
PreparedStatement也提供了excute()、executeUpdate()、executeQuery()三个方法来执行SQL语句,不过这三个方法无须参数,因为PreparedStatement已经存储了预编译的SQL语句。
使用PreparedStatement还有一个很好的作用就是防止SQL注入。
总结一下,使用PreparedStatement比使用Statement多了三个好处:
- 预编译SQL语句,性能更好。
- 无须“拼接”SQL语句,编程更简单。
- 防止SQL注入,安全性更好。
RowSet接口继承了ResultSet接口,RowSet接口下包含JdbcRowSet、CachedRowSet、FilteredRowSet、JoinRowSet、WebRowSet常用子接口。除了JdbcRowSet需要保持与数据库的连接之外,其余四个子接口都是离线的RowSet,无须时刻保持与数据库的连接。与ResultSet相比,RowSet默认是可滚动、可更新、可序列化的结果集,而且作为JavaBean使用,因此能方便地在网络上传输,用于同步两端的数据。对于离线的RowSet而言,程序在创建RowSet时已把数据从底层数据库读取到了内容,因此可充分利用计算机的内存,从而降低数据库服务器的负载,提高程序性能。
RowSetFactory提供了如下方法来创建RowSet实例:
CachedRowSet creatCachedRowSet() 创建一个默认的CachedRowSet。
FilteredRowSet creatFilteredRowSet() 创建一个默认的FilteredRowSet。
JdbcRowSet creatJdbcRowSet() 创建一个默认的JdbcRowSet。
JoinRowSet creatJoinRowSet() 创建一个默认的JoinRowSet。
WebRowSet creatWebRowSet() 创建一个默认的WebRowSet。
通过使用RowSetFactoy,就可以把应用程序与RowSet实现类分离开,避免直接使用JdbcRow SetImpl等非公开的API,也更有利于后期的升级、扩展。
为了让RowSet能抓取到数据库的数据,需要为RowSet设置数据库的URL、用户名、密码等连接信息,因此RowSet定义了如下访问方法:
setUrl(String url) 设置该RowSet要访问的数据库的URL。
setUsername(String name) 设置该RowSet要访问的数据库的用户名。
setPassword(String password) 设置该RowSet要访问的数据库的密码。
setCommand(String sql) 设置使用该sql语句的查询结果来装填该RowSet。
excute() 执行查询。
三、事务处理
对于任何数据库而言,事务都是非常重要的,事务是保证底层数据完整的重要手段,没有事务支持的数据库应用将会非常脆弱。
事务是由一步或几步数据库操作序列组成的逻辑执行单元,这些列操作要么全部执行,要么全部放弃执行。程序和事务是两个不同的概念,一般而言,一段程序中可能包含多个事务。
事务具备4个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持续性(Durability)。这四个特性也被称为ACID性。
- 原子性:事务是应用中最小的执行单位,就如原子是自然界的最小颗粒,具有不可再分的特征一样,事务是应用中不可再分的最小逻辑执行体。
- 一致性:事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库只包含事务成功提交的结果时,数据库处于一致性状态。如果系统运行发生中断,某个事务尚未完成而被迫中断,而该未完成的事务对数据库所做的修改已被写入数据库,此时数据库将处于一个不正确的状态。一致性是通过原子性来保证的。
- 隔离性:各个事务的执行互不干扰,任意一个事物的内部操作对其他并发的事务都是隔离的。也就是说并发执行额事物之间不能看到对方的中间状态,并发执行的事务之间不能互相影响。
- 持续性:指事务一旦提交,对数据所做的任何改变都要记录到永久存储器中,通常就是保存进物理数据库。
数据库事务由下列语句组成:
- 一组DML语句。
- 一条DDL语句。
- 一条DCL语句。
其中,DDL和DCL语句最多只能由一条,因为DDL和DCL语句都会导致事务立即提交。
当事务包含的全部数据库操作都成功执行后,应该提交(commit)事务,使这些修改永久生效。事务提交有两种方式:显示提交和自动提交。
- 显示提交:使用commit。
- 自动提交:执行DDL或DCL语句,或者程序正常退出。
当事务包含的任意一个数据库操作执行失败后,应该回滚(rollback)事务,使该事务中所做的修改全部失效。事务回滚有两种方式:显示回滚和自动回滚。
- 显示回滚:使用rollback。
- 自动回滚:系统错误或者强行退出。
MySQL默认关闭事务(即打开自动提交),在默认情况下,用户在MySQL控制台输入一条DML语句,这条DML语句将会立即保存到数据库里。为了开启MySQL的事务支持,可以显示调用如下命令:set autocommit={0|1} 0为关闭自动提交,即开启事务
。(ps:自动提交和开启事务恰好相反,如果开启自动提交就是关闭事务;关闭自动提交就是开启事务)
一旦在MySQL的命令行窗口中输入set autocommit=0 开启了事务,该命令行窗口里的所有DML语句都不会立即生效,上一个事务结束后第一条DML语句将开始一个新事务,而后续执行的所有SQL语句都处于该事务中,除非显示使用commit来提交任务,或者正常退出,或者运行DDL、DCL语句导致事务隐式提交。当然也可以使用rollback回滚来结束事务,使用rollback结束事务将导致本次事务中DML语句所做的修改全部失效。
如果不想关闭整个命令行窗口的自动提交,而只是想临时性地开始事务,则可以使用MySQL提供的start transaction 或 begin两个命令,它们都表示临时性地开始一次事务,处于是start transaction 和 begin 命令后的DML语句不会立即生效,除非使用commit显示提交事务,或者执行DDL、DCL语句来隐式提交事务。
如下SQL代码将不会对数据库有任何影响:
#临时开始事务
begin;
insert into table_name
values(null,'xx',1)
insert into table_name
values(null,'xx',1);
#查询table_name 表的记录①
select * from table_name;
#回滚事务
rollback;
#再次查询②
select * from table_name;
执行到①处,发现能查到插入的两条记录。如果打开MySQL的其他命令行窗口将看不到这两条记录,这体现了事务的隔离性。接着程序rollback了事务中的全部修改,所以执行到②处看到数据库又恢复到原来的状态。
不管是显示提交还是隐式提交,都会结束当前事务;不管是显示回滚还是隐式回滚都会结束当前事务。
除此之外,MySQL还提供了了savepoint来设置事务的中间点,这个操作可以使事务回滚到指定a点,而不是回滚全部任务:savepoint a;
回滚到指定中间点:rollback to a;
(普通的提交、回滚都会结束当前事务,但回滚到指定中间点因为依然处于事务之中,所以不会结束当前事务)
JDBC连接也提供了事务支持,主要由Connection提供,它默认打开自动提交,即关闭事务。我们可以调用Connection的setAutoCommit() 方法来关闭自动提交,开启事务:conn.setAutoCommit(false);
,程序也可以调用Connection的getAutoCommit()方法来返回该连接的自动提交模式。一旦事务开始之后,程序可以像平常一样创建Statement对象,创建了该对象之后,就可以执行任意多行DML语句。这些SQL语句虽然被执行了,但这些SQL语句所做的修改不会生效,因为事务还没有结束。提交事务可以:conn.commit();
如果任意一条SQL语句执行失败,则应该回滚:conn.rollback();
(实际上,当Connection遇到一个未处理的SQLException异常时,系统将会非正常的退出,事务也会自动回滚)
Connection也提供了设置中间点的方法:
- Savepoint setSavepoint() 在当前事务中创建一个未命名的中间点,并返回代表该中间点的Savepoint对象。
- Savapoint setSavepoint(String name) 在当前事务中创建一个具有指定名称的中间点,并返回代表该中间点的Savepoint对象。