Java基础篇笔记(二):JDBC编程相关

一、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编程大致按照如下步骤进行:

  1. 加载数据库驱动。通常使用Class类的forName()静态方法来加载驱动Class.forName(driverClass);
    driverClass就是数据库驱动类所对应的字符串。例如加载MySQL的驱动采用如下方法:Class.forName("com.mysql.cj.jdbc.Driver");
  2. 通过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没有固定写法。
  3. 通过Connection对象创建Statement对象。
  4. 使用Statement执行SQL语句。
  5. 操作结果集,如果执行的SQL语句是查询语句,执行后返回一个ResultSet对象,该对象里保存了SQL语句查询的结果。
  6. 回收数据库资源,包括关闭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多了三个好处:

  1. 预编译SQL语句,性能更好。
  2. 无须“拼接”SQL语句,编程更简单。
  3. 防止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对象。

猜你喜欢

转载自blog.csdn.net/laobanhuanghe/article/details/98596255