5件你不知道的事情... Java数据库连接
来源:互联网 发布:淘宝清缓存会删除什么 编辑:程序博客网 时间:2024/06/04 17:57
许多Java开发人员今天通过数据访问平台(如Hibernate或Spring)知道Java数据库连接(JDBC)API。但是JDBC不仅仅是数据库连接的背景播放器。你知道的越多,RDBMS的交互效率就越高。
在本文中,我将演示最新版本的JDBC中介绍的几个功能。考虑到现代软件开发挑战的设计,这些功能支持应用程序的可扩展性和开发人员的生产力 - 这是Java开发人员面临的常见挑战之一。
标量函数
不同的RDBMS实现为SQL和/或增值功能提供不规则支持,旨在使开发人员的生活更轻松。例如,众所周知,SQL提供了一个标量操作, COUNT()
以返回满足特定SQL过滤条件(即WHERE
谓词)的行数。但是除此之外,尝试修改由SQL返回的值可能是棘手的 - 尝试从数据库获取当前的日期和时间可能会驱动即使是最有耐心的JDBC开发人员(也可能秃头)。
为此,JDBC规范通过标量函数提供对不同RDBMS实现的一定程度的隔离/适配。JDBC规范包括JDBC驱动程序必须识别并适应其特定数据库实现的受支持操作的列表。因此,对于支持返回当前日期和/或时间的数据库,可以像清单1那样简单:
清单1.几点了?
Connection conn = ...; // get it from someplace
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(“{fn CURRENT_DATE()}”);
JDBC API识别的标量函数的完整列表在JDBC规范的附录中给出(参见相关主题),但是给定的驱动程序或数据库可能不支持完整列表。您可以使用DatabaseMetaData
返回的对象Connection
来获取给定JDBC实现支持的函数,如清单2所示:
清单2.你能为我做些什么?
Connection conn = ...; // get it from someplace
DatabaseMetaData dbmd = conn.getMetaData();
标量函数的列表是String
从各种DatabaseMetaData
方法返回的逗号分隔符。例如,通过getNumericFunctions()
调用列出所有数字标量 。做一个String.split()
结果和 - voilà!- 即时 equals()-testable
列表。
值得一提的是,除了JDBC 4之外,其中一个改进是安全地删除了该Class.forName("some driver class")
语句。
2.可滚动的结果集
在JDBC中创建Connection
对象(或获取现有对象)并使用它来创建一个 非常常见的过程Statement
。的Statement
,被供给的 SQL SELECT
,返回一个ResultSet
。在 ResultSet
随后通过送入while
循环(没有什么不同的Iterator
),直到ResultSet
认为它是空的,用循环提取一列的尸体在左到右的顺序一次。
这个整个操作是如此的普遍,已经变得几乎是神圣的:这样做完全是因为这样做的方式。唉,这是完全不必要的。
介绍可滚动的ResultSet
许多开发人员不知道JDBC多年来已经大大增强,尽管这些增强功能反映在新版本号和版本中。在撰写本文时,JDBC代表版本4.2。
有趣的(虽然经常被忽视)的增强之一是能够“滚动” ResultSet
,意思是我们可以向前或向后,甚至两者都按需要。这样做需要一些前瞻性的思想,但是JDBC调用必须表明它ResultSet
在Statement
创建时 需要滚动。
如果底层的JDBC驱动程序支持滚动,ResultSet
则会从中返回滚动 Statement
,但最好是在询问驱动程序之前是否支持滚动性。您可以询问通过DatabaseMetaData
对象滚动 ,可以从任何一个获得 Connection
,如前所述。
一旦有一个DatabaseMetaData
对象,调用 getJDBCMajorVersion()
将检索此驱动程序的主要JDBC版本号。当然,一个司机可能就其给定规格的支持程度而言,因此要特别安全地使用,请称之为supportsResultSetType()
所需ResultSet
类型的方法。(这是ResultSet
课堂上的一个常数 ;我们将在短短一秒钟内讨论每个值。)
清单3.你可以滚动吗?
int JDBCVersion = dbmd.getJDBCMajorVersion();
boolean srs = dbmd.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE);
if (JDBCVersion > 2 || srs == true)
{
// scroll, baby, scroll!
}
请求可滚动的ResultSet
假设您的司机表示是(如果没有,您需要一个新的驱动程序或数据库),您可以ResultSet
通过传递两个参数来请求滚动Connection.createStatement()
,如清单4所示:
清单4.我想滚动!
Statement stmt = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
ResultSet scrollingRS = stmt.executeQuery("SELECT * FROM whatever");
调用时必须特别小心, createStatement()
因为它的第一个和第二个参数都是int
s。任何int
值(包括错误的常数值)都可以使用createStatement()
。
表示“可滚动性”的第一个参数 ResultSet
可以是三个接受的值之一:
ResultSet.TYPE_FORWARD_ONLY
:这是我们知道和喜欢的默认的,firehose风格的游标。ResultSet.TYPE_SCROLL_INSENSITIVE
:这样ResultSet
可以进行向后迭代以及转发,但是如果数据库中的数据发生变化,ResultSet
则不会反映出来。此滚动ResultSet
可能是最常见的类型。ResultSet.TYPE_SCROLL_SENSITIVE
:ResultSet
创建的不仅可以允许双向迭代,还可以在数据库中更改数据时提供“实时”视图。
第二个参数在下一个提示中讨论,所以挂起来。
定向滚动
一旦你获得了ResultSet
从 Statement
,通过它向后滚动只是调用的问题previous()
,其落后的推移而不是向前行,因为next()
会。或者你可以打电话 first()
回到开始 ResultSet
,或者打电话last()
到最后ResultSet
,或者...好吧,你得到这个想法。
的relative()
和absolute()
方法也可以是有益的:在第一移动指定的行数(正向如果该值是正的,如果向后的值是负的),而后者移动到指定行中的ResultSet
不管光标所在。当然,当前行号可以通过 getRow()
。
如果您计划在特定方向上进行大量滚动,可以ResultSet
通过调用来指定方向 来帮助您setFetchDirection()
。(A ResultSet
将无论其滚动方向如何,但事先知道可以优化其数据检索。)
3.可更新的结果集
JDBC不仅支持双向ResultSet
s,还支持ResultSet
s 的就地更新。这意味着,您ResultSet
不需要创建一个新的SQL语句来更改当前存储在数据库中的值,所以您可以修改该值中所保留的值 ,并将自动发送到该行该列的数据库。
请求可更新ResultSet
类似于请求可滚动的过程ResultSet
。实际上,你将使用第二个参数createStatement()
。而不是指定ResultSet.CONCUR_READ_ONLY
第二个参数发送ResultSet.CONCUR_UPDATEABLE
,如清单5所示:
清单5.我想要一个可更新的ResultSet
Statement stmt = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet scrollingRS = stmt.executeQuery("SELECT * FROM whatever");
假设您的驱动程序支持可更新的游标(这是JDBC规范的另一个功能,哪些大多数“真实世界”数据库将支持),您可以ResultSet
通过导航到该行并调用其中的一个update...()
方法来更新任何给定值在清单6中)。像get...()
上的方法ResultSet
,update...()
超载在实际列类型ResultSet
。所以要改变名为“ PRICE
” 的浮点列,调用 updateFloat("PRICE")
。但是,这样做只会更新该值 ResultSet
。要将值推送到数据库备份,请调用updateRow()
。cancelRowUpdates()
清单6.更好的方法
Statement stmt = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet scrollingRS =
stmt.executeQuery("SELECT * FROM lineitem WHERE id=1");
scrollingRS.first();
scrollingRS.udpateFloat("PRICE", 121.45f);
// ...
if (userSaidOK)
scrollingRS.updateRow();
else
scrollingRS.cancelRowUpdates();
JDBC 4.0不仅仅支持更新。如果用户想要添加一个全新的行,而不是创建一个新的Statement
并执行一个INSERT
,只需调用moveToInsertRow()
,调用update...()
每一列,然后调用 insertRow()
完成工作。如果未指定列值,则假定为SQL NULL
(SQLException
如果数据库模式不允许NULL
该列使用 s),则可能会触发该值。
当然,如果ResultSet
支持更新一行,它也必须支持删除一个,通过deleteRow()
。
呵呵,在我忘记之前,所有的这种可滚动性和可更新性同样适用于PreparedStatement
(通过将这些参数传递给prepareStatement()
方法),Statement
由于SQL注入攻击的持续危险,这是非常优于常规的。
行列
如果所有这些功能在十年的更好的部分已经在JDBC中,为什么大多数开发人员仍然停留在正向滚动 ResultSet
和断开连接的访问?
主要原因是可扩展性。将数据库连接保持在最低限度是支持Internet可以为公司网站带来的大量用户的关键。因为滚动和/或更新 ResultSet
通常需要开放的网络连接,所以许多开发人员不会(或不能)使用它们。
幸运的是,有一个替代方法可以让你做许多与之相同的事情ResultSet
,而不需要保持数据库连接的打开。
在概念上,a Rowset
本质上是一个ResultSet
,但是允许连接的或断开的模型。所有你需要做的是创建一个Rowset
,指向一个ResultSet
,当它完成它的填充,使用它ResultSet
,如清单7所示:
清单7. Rowset替换ResultSet
Statement stmt = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet scrollingRS = stmt.executeQuery("SELECT * FROM whatever");
if (wantsConnected)
JdbcRowSet rs = new JdbcRowSet(scrollingRS); // connected
else
CachedRowSetImpl crs = new CachedRowSetImpl(); // disconnected
cachedRowSet.populate(scrollingRS);
JDBC附带了接口的五个“实现”(意思是扩展接口) Rowset
。JdbcRowSet
是一个连接的 Rowset
实现; 其余四个断开连接:
CachedRowSet
只是一个断开连接Rowset
。WebRowSet
是一个子类,CachedRowSet
它知道如何将其结果转换为XML并重新执行。JoinRowSet
是一个WebRowSet
也知道如何形成相当于一个SQL JOIN
而不必连接回数据库。FilteredRowSet
是一个WebRowSet
也知道如何进一步过滤掉数据,而不必连接到数据库。
Rowsets
是完整的JavaBeans,意味着它们支持侦听器风格的事件,因此Rowset
如果需要,可以捕获,检查和执行任何修改。事实上, Rowset
甚至可以管理完整的行为对数据库,如果有Username
,Password
, URL
,和DatasourceName
属性进行设置(这意味着它会创建使用连接 DriverManager.getConnection()
)或它的Datasource
属性集(这是很可能是通过JNDI获得)。然后,您将指定要在Command
属性中执行的SQL ,调用 execute()
并开始使用结果 - 无需进一步的工作。
Rowset
实现通常由JDBC驱动程序提供,因此实际的名称和/或包将取决于您使用的JDBC驱动程序。Rowset
实施一直是标准分发的一部分,所以你应该能够创建一个 ...RowsetImpl()
和去。(在不太可能的情况下,您的驱动程序不提供,Sun提供了参考实现;请参阅链接的相关主题。)
批量更新
尽管它们有用,但Rowset
有时候根本就不能满足您的所有需求,您可能需要回避写入直SQL语句。在这些情况下,特别是当您面临一系列工作时,您可能会感到能够进行批量更新,对于数据库执行多个SQL语句,作为一次网络往返的一部分。
要确定JDBC驱动程序是否支持批量更新,可以快速调用该DatabaseMetaData.supportsBatchUpdates()
函数来生成一个布尔值来讲述故事。假设批量更新被支持(由任何非标记指示SELECT
),排队一次并在爆炸中释放它,如清单8所示:
清单8.让数据库拥有它!
conn.setAutoCommit(false);
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO lineitems VALUES(?,?,?,?)");
pstmt.setInt(1, 1);
pstmt.setString(2, "52919-49278");
pstmt.setFloat(3, 49.99);
pstmt.setBoolean(4, true);
pstmt.addBatch();
// rinse, lather, repeat
int[] updateCount = pstmt.executeBatch();
conn.commit();
conn.setAutoCommit(true);
调用setAutoCommit()
是必要的,因为默认情况下,驱动程序将尝试提交它所提供的每个语句。除此之外,其余的代码是非常简单的:使用Statement
或PreparedStatement
(而不是调用execute()
)调用通常的SQL函数executeBatch()
,它调用 该调用而不是立即发送它。
当整个语句的混乱准备就绪时,将它们全部在数据库中启动executeBatch()
,该数组返回一个整数值数组,每个数组都保持与使用相同的结果 executeUpdate()
。
如果批处理中的语句失败,如果驱动程序不支持批量更新,或者批处理中的语句返回a ResultSet
,则驱动程序将抛出一个BatchUpdateException
。在某些情况下,驱动程序可能尝试在抛出异常后继续执行语句。JDBC规范不要求特定的行为,所以建议您事先对您的驱动程序进行实验,以便您确切了解其行为。(但是当然你会运行单元测试,所以你会发现这个错误很久以前就是一个问题,对吧?)
结论
作为Java开发的主要部分,JDBC API是每个Java开发人员应该知道的,就像他或她的手背。有趣的是,大多数开发人员多年来一直没有跟上API的增强,所以他们错过了本文中描述的节省时间的技巧。
当然,您是否决定使用JDBC的新功能取决于您。要考虑的一个关键方面将是您正在开展的系统的可扩展性。扩展需求越高,对数据库的使用就越有限制,因此您需要减少网络流量。Rowset
s,标量调用和批量更新将成为您的朋友。否则,尝试可滚动和可更新 ResultSet
的(不会消耗尽可能多的内存 Rowset
),并测量可伸缩性命中。它可能不会像你预期的那样糟糕。
- 5件你不知道的事情... Java数据库连接
- 十四件你不知道的事情
- 你所不知道的五件事情:当心可变性
- Golang -- 10件你不知道的事情
- 你所不知道的五件事情--Java集合框架API(第一部分)(译)
- 你所不知道的五件事情--Java集合框架API(第一部分)
- 你所不知道的五件事情--Java集合框架API(第二部分)
- 你所不知道的五件事情--java.util.concurrent(第一部分)
- 你所不知道的五件事情--java.util.concurrent(第二部分)
- JAVA集合5个你不知道的事情
- Java关于多线程你不知道的5件事
- 女人不知道的10件事情
- java switch/case你不知道的事情
- 你所不知道的五件事情--JVM的命令行选项
- 你可能不知道的关于golang 的10件事情
- MOSS企业级搜索----你可能还不知道的16件事情
- 你所不知道的五件事情(并发多线程编程)
- 你应该知道的10件关于Java 6的事情-Java基础-Java-编程开发
- SLF4J 日志门面的使用
- C#笔记,socket通信代码
- Subway FZU
- E
- SPI通讯随记
- 5件你不知道的事情... Java数据库连接
- 设计模式(16) 中介者模式(简单入门 行为模式)
- nginx 配置
- Nginx 日志分析及性能排查
- 谢谢官人打赏
- iOS开发数据库-FMDB
- zxing生成二维码去白边
- Kotlin基础了解
- React Native嵌入原生应用的坑