Java面试 4.11 Java 数据库操作 - Java 程序员面试笔试宝典

4.11.1 如何通过 JDBC 访问数据库

Java 数据库连接(Java DataBase Connectivity,JDBC)用于在 Java 程序中实现数据库操作功能,它提供了执行 SQL 语句、访问各种数据库的方法,并为各种不同的数据库提供统一的操作接口,java.sql 包中包含了 JDBC 操作数据库的所有类。通过 JDBC 访问数据库一般有如下几个步骤:

1)加载 JDBC 驱动器。将数据库的 JDBC 驱动加载到 classpath 中,在基于 JavaEE 的 Web 应用开发过程中,通常要把目标数据库产品的 JDBC 驱动复制到 WEB-INF/lib 下。

2)加载 JDBC 驱动,并将其注册到 DriverManager 中。一般使用反射 Class.forName(String driveName)。

3)建立数据库连接,取得 Connection 对象。一般通过 DriverManager.getConnection(url,username,passwd)方法实现,其中,url 表示连接数据库的字符串,username 表示连接数据库的用户名,passwd 表示连接数据库的密码。

4)建立 Statement 对象或是 PreparedStatement 对象。

5)执行 SQL 语句。

6)访问结果集 ResultSet 对象。

7)依次将 ResultSet、Statement、PreparedStatement、Connection 对象关闭,释放掉所占用资源,例如 rs.close(),con.close()等。为什么要这么做呢?原因在于 JDBC 驱动在底层通常都是通过网络 IO 实现 SQL 命令与数据传输的。

3.提供 Java 存取数据库能力的包是( )。

A.java.sql B.java.awt C.java.lang D.java.swing

答案:A。对数据库操作的所有类都在 java.sql 包中。


4.11.2 JDBC 处理事务采用什么方法

一个事务是由一条或多条对数据库操作的 SQL 语句所组成的一个不可分割的工作单元,只有当事务中的所有操作都正常执行完了,整个事务才会被提交给数据库。在 JDBC 中,一般是通过 commit()方法或 rollback()方法来结束事务的操作。其中 commit()方法表示完成对事务的提交,rollback()方法表示完成事务回滚,多用于在处理事务的过程中出现了异常的情况,这两种方法都位于 java.sql.Connection 类中。一般而言,事务默认操作是自动提交,即操作成功后,系统将自动调用 commit()方法,否则将调用 rollback()方法。

当然,在 JDBC 中,也可以通过调用 setAutoCommit(false)方法来禁止自动提交,然后就可以把多个数据库操作的表达式作为一个事务,在操作完成后调用 commit()方法实现整体提交,如果其中一个表达式操作失败,就会抛出异常而不会调用 commit()方法。在这种情况下,就可以在异常捕获的代码块中调用 rollback()方法进行事务回滚。通过此种方法可以保持对数据库的多次操作后,数据仍然保持一致性。

引申:JDBC 有哪些事务隔离级别?

为了解决与「多个线程请求相同数据」相关的问题,事务之间通常会用锁相互隔离开。如今,大多数主流的数据库支持不同类型的锁。因此,JDBC API 支持不同类型的事务,它们由 Connection 对象指派或确定。在 JDBC 中,定义了以下 5 种事务隔离级别:

1)TRANSACTION_NONE JDB。不支持事务。

2)TRANSACTION_READ_UNCOMMITTED。未提交读。说明在提交前一个事务可以看到另一个事务的变化。这样读「脏」数据、不可重复读和虚读都是允许的。

3)TRANSACTION_READ_COMMITTED。已提交读。说明读取未提交的数据是不允许的。这个级别仍然允许不可重复读和虚读产生。

4)TRANSACTION_REPEATABLE_READ。可重复读。说明事务保证能够再次读取相同的数据而不会失败,但虚读仍然会出现。

5)TRANSACTION_SERIALIZABLE。可序列化。是最高的事务级别,它防止读「脏」数据、不可重复读和虚读。

(备注:① 读「脏」数据。一个事务读取了另一个事务尚未提交的数据,例如,当事务 A 与事务 B 并发执行时,当事务 A 更新后,事务 B 查询读取到 A 尚未提交的数据,此时事务 A 回滚,则事务 B 读到的数据是无效的「脏」数据。② 不可重复读。一个事务的操作导致另一个事务前后两次读取到不同的数据,例如,当事务 A 与事务 B 并发执行时,当事务 B 查询读取数据后,事务 A 更新操作更改事务 B 查询到的数据,此时事务 B 再次读去该数据,发现前后两次的数据不一样。③ 虚读。一个事务的操作导致另一个事务前后两次查询的结果数据量不同,例如,当事务 A 与事务 B 并发执行时,当事务 B 查询读取数据后,事务 A 新增或删除了一条满足事务 A 的查询条件的记录,此时,事务 B 再次查询,发现查询到前次不存在的记录,或者前次的某个记录不见了。)

事务隔离级别越高,为避免冲突所花的精力也就越多。可以通过 Connection 对象的 conn.setTransactionLevel()方法来设置隔离级别,通过 conn.getTransactionIsolation()方法来确定当前事务的级别。


4.11.3 Class.forName 的作用是什么

在 Java 语言中,任何类只有被装载到 JVM 上才能运行。Class.forName()方法的作用就是把类加载到 JVM 中,它会返回一个与带有给定字符串名的类或接口相关联的 Class 对象,并且 JVM 会加载这个类,同时 JVM 会执行该类的静态代码段。

在使用 JDBC 连接数据库前,一般都会调用 Class.forName("com.mysql.jdbc.Driver")方法来加载 JDBC 驱动,那么是否一定需要调用这个方法呢?如果是,那为什么要调用这个方法呢?其实,并不一定非要调用这种方法,例如 Test t=(Test)Class.forName(“Test”).newInstance()语句和 Test t=new Test()语句就具有相同的效果,所以使用 new 也可以,但二者的区别也非常明显:创建对象的方式不同。前者使用类加载机制,后者是创建了一个新的类。使用第一种方法往往能提高软件的可扩展性,例如,一个软件项目开发后会被多家公司来使用,每家公司的处理流程大致相同,只有个别公司的业务逻辑不同,在开发过程中,完全可以把不通用的地方抽取出来定义成一个接口 BussinessInterface,针对每个公司不同的业务流程定义不同的实现类 sub1、sub2、sub3 等,通过创建不同的子类来完成不同公司的业务需求。为了达到良好的可扩展性,可以把子类采用配置文件的方式放到 XML 文件中。在程序部署时,只需要从读配置文件中读取类名 className,然后采用 BussinessInterface b=(BussinessInterface)Class.forName(className).newInstance()创建实例即可提高开发人员的开发效率。当以后再有新的需求时,即使开发了新的子类,也不需要修改创建实例的代码,只需要修改配置文件即可,从而使得程序具有很好的可扩展性。


4.11.4 Statement、PreparedStatement 和 CallableStatement 有什么区别

3)安全性更好。使用 PreparedStatement 能够预防 SQL 注入攻击,所谓 SQL 注入,指的是通过把 SQL 命令插入到 Web 表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器,达到执行恶意 SQL 命令的目的。注入只对 SQL 语句的编译过程有破坏作用,而执行阶段只是把输入串作为数据处理,不再需要对 SQL 语句进行解析,因此也就避免了类似 select∗from user where name=′aa′and password=′bb′or 1=1 的 SQL 注入问题的发生。

CallableStatement 由 prepareCall()方法所创建,它为所有 DBMS(Database Management Sys-tem,数据库管理系统)提供了一种以标准形式调用已储存过程的方法。它从 PreparedStatement 中继承了用于处理输入参数的方法,而且还增加了调用数据库中的存储过程和函数以及设置输出类型参数的功能。

常见笔试题:

用于调用存储过程的对象是( )。

A.ResultSet B.DriverManager C.CallableStatemet D.PreparedStatement

答案:C。JDBC 中的 CallableStatement 对象为所有 RDBMS(Relational Database Management System,关系数据库管理系统)提供了一种标准形式调用存储过程的方法。其对存储过程的调用存在两种形式:带结果参数和不带结果参数。结果参数是一种输出参数,是存储过程的返回值。两种形式都可带有数量可变的输入(IN 参数)、输出(OUT 参数)或输入和输出(IN-OUT 参数)的参数。


4.11.5 getString()方法与 getObject()方法有什么区别

JDBC 提供了 getString()、getInt()和 getData()等方法从 ResultSet 中获取数据,当查询结果集中的数据量较小时,不用考虑性能,使用这些方法完全能够满足需求,但是当查询结果集中的数据量非常大时,则会抛出异常:OracleException 未处理:ORA-01000:maximum open cursors exceeded(以访问 Oracle 数据库为例)。而通常情况下,使用 getObject()方法就可以解决这个问题。

getString()或 getInt()等方法在被调用时,程序会一次性地把数据都放到内存中,然后通过调用 ResultSet 的 next()和 getString()等方法来获取数据。当数据量大到内存中放不下时就会抛出异常,而使用 getObject()方法就不会这种问题,因为数据不会一次性被读到内存中,每次调用时会直接从数据库中去获取数据,因此使用这种方法不会因为数据量过大而出错。


4.11.6 使用 JDBC 时需要注意哪些问题

在使用 JDBC 编程时,首先需要建立于数据库的连接,才能完成对数据库的访问,由于与数据库的连接是非常重要的资源。JDBC 连接池提供了 JDBC 连接定义和数目有限的连接,如果连接数量不够,就需要长时间的等待。不正常关闭 JDBC 连接会导致等待回收无效的 JDBC 连接。只有正常的关闭和释放 JDBC 连接,JDBC 资源才可以被快速地重用,从而使得系统性能得到改善。因此在编程时,一定要保证释放不再使用的连接。

一般来讲,在使用 JDBC 访问数据库时,createStatement 和 prepareStatement 最好放在循环外面,而且使用了这些 Statement 后,需要及时关闭。最好是在执行了一次 executeQuery、exe-cuteUpdate 等之后,如果不需要使用结果集(ResultSet)的数据,就马上将 Statment 关闭。因为每次执行 conn.createStatement()或 conn.prepareStatement(),实际上都相当于在数据库中打开了一个 cursor(游标),如果把对这两个方法的调用放到循环内,会一直不停地打开 cursor。如果不能及时地关闭,会导致程序抛出异常。


4.11.7 什么是 JDO

Java 数据对象(Java Data Object,JDO)是一个用于存取某种数据仓库中的对象的标准化 API,它使开发人员能够间接地访问数据库。

JDO 是 JDBC 的一个补充,它提供了透明的对象存储,因此对开发人员来说,存储数据对象完全不需要额外的代码(例如 JDBC API 的使用)。这些烦琐的工作已经转移到 JDO 产品提供商身上,使开发人员解脱出来,从而集中时间和精力在业务逻辑上。另外,相较于 JDBC,JDO 更灵活、更通用,它提供了到任何数据底层的存储功能,例如关系数据库、文件、XML 以及对象数据库管理系统(Object Database Management System,ODBMS)等,使得应用可移植性更强。


4.11.8 JDBC 与 Hibernate 有什么区别

Hibernate 是 JDBC 的封装,采用配置文件的形式将数据库的连接参数写到 XML 文件中,至于对数据库的访问还是通过 JDBC 来完成的。

Hibernate 是一个持久层框架,它将表的信息映射到 XML 文件中,再从 XML 文件映射到相应的持久化类中,这样可以使用 Hibernate 独特的查询语言 Hibernate 查询语言(Hibernate Que-ry Language,HQL)了。Hibernate 的 HQL 查询语句返回的是 List<Object[.]> 类,而 JDBC 通过 statement 返回的查询结果是 ResultSet 并且有时候需要自己封装到 List 中。另外一个重要区别在于,Hibernate 具有访问层(DAO 类层,DAO 全称为 Data Access Object 数据访问接口,意为数据访问接口),该层是 HQL 查询语句唯一出现的位置,再往上层则不会出现查询语句,而 JDBC 可以随时连接随时访问,例如有 100 个类都有 SQL 查询语句,如果表名改变了,那么要使用 JDBC 的方式,就必须重写所有查询语句,而采用 Hibernate 的方式只需修改 DAO 层的类即可,因此 Hibernate 具有很好的维护性和扩展性。

发布了101 篇原创文章 · 获赞 20 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_40993412/article/details/104065173