JDBC的全称是Java DataBase Connection,也就是Java数据库链接,咱们能够用它来操做关系型数据库。JDBC接口及相关类在java.sql包和javax.sql包里。咱们能够用它来链接数据库,执行SQL查询,存储过程,并处理返回的结果。java
JDBC接口让Java程序和JDBC驱动实现了松耦合,使得切换不一样的数据库变得更加简单。mysql
有四类JDBC驱动。和数据库进行交互的Java程序分红两个部分,一部分是JDBC的API,实际工做的驱动则是另外一部分。web
A JDBC-ODBC Bridge plus ODBC Driver(类型1):它使用ODBC驱动链接数据库。须要安装ODBC以便链接数据库,正由于这样,这种方式如今已经基本淘汰了。sql
B Native API partly Java technology-enabled driver(类型2):这种驱动把JDBC调用适配成数据库的本地接口的调用。数据库
C Pure Java Driver for Database Middleware(类型3):这个驱动把JDBC调用转发给中间件服务器,由它去和不一样的数据库进行链接。用这种类型的驱动须要部署中间件服务器。这种方式增长了额外的网络调用,致使性能变差,所以不多使用。apache
D Direct-to-Database Pure Java Driver(类型4):这个驱动把JDBC转化成数据库使用的网络协议。这种方案最简单,也适合经过网络链接数据库。不过使用这种方式的话,须要根据不一样数据库选用特定的驱动程序,好比OJDBC是Oracle开发的Oracle数据库的驱动,而MySQL Connector/J是MySQL数据库的驱动。数组
JDBC API使用Java的反射机制来实现Java程序和JDBC驱动的松耦合。随便看一个简单的JDBC示例,你会发现全部操做都是经过JDBC接口完成的,而驱动只有在经过Class.forName反射机制来加载的时候才会出现。缓存
我以为这是Java核心库里反射机制的最佳实践之一,它使得应用程序和驱动程序之间进行了隔离,让迁移数据库的工做变得更简单。在这里能够看到更多JDBC的使用示例。tomcat
JDBC链接是和数据库服务器创建的一个会话。你能够想像成是一个和数据库的Socket链接。服务器
建立JDBC链接很简单,只须要两步:
A. 注册并加载驱动:使用Class.forName(),驱动类就会注册到DriverManager里面并加载到内存里。 B. 用DriverManager获取链接对象:调用DriverManager.getConnnection()方法并传入数据库链接的URL,用户名及密码,就能获取到链接对象。
1 Connection con = null; 2 try{ 3 // load the Driver Class 4 Class.forName("com.mysql.jdbc.Driver"); 5 // create the connection now 6 con = DriverManager.getConnection("jdbc:mysql://localhost:3306/DBName", 7 "username", 8 "password"); 9 }catch (SQLException e) { 10 System.out.println("Check database is UP and configs are correct"); 11 e.printStackTrace(); 12 }catch (ClassNotFoundException e) { 13 System.out.println("Please include JDBC MySQL jar in classpath"); 14 e.printStackTrace(); 15 } 16 }
JDBC的DriverManager是用来作什么的?
JDBC的DriverManager是一个工厂类,咱们经过它来建立数据库链接。当JDBC的Driver类被加载进来时,它会本身注册到DriverManager类里面,你能够看下JDBC Driver类的源码来了解一下。
而后咱们会把数据库配置信息传成DriverManager.getConnection()方法,DriverManager会使用注册到它里面的驱动来获取数据库链接,并返回给调用的程序。
使用DatabaseMetaData能够获取到服务器的信息。当和数据库的链接成功创建了以后,能够经过调用getMetaData()方法来获取数据库的元信息。DatabaseMetaData里面有不少方法,经过它们能够获取到数据库的产品名称,版本号,配置信息等。
DatabaseMetaData metaData = con.getMetaData();
String dbProduct = metaData.getDatabaseProductName();
JDBC的Statement是什么?
Statement是JDBC中用来执行数据库SQL查询语句的接口。经过调用链接对象的getStatement()方法咱们能够生成一个Statement对象。咱们能够经过调用它的execute(),executeQuery(),executeUpdate()方法来执行静态SQL查询。
因为SQL语句是程序中传入的,若是没有对用户输入进行校验的话可能会引发SQL注入的问题,若是想了解更多关于SQL注入的,能够看下这里。
默认状况下,一个Statement同时只能打开一个ResultSet。若是想操做多个ResultSet对象的话,须要建立多个Statement。Statement接口的全部execute方法开始执行时都默认会关闭当前打开的ResultSet。
Statement的execute(String query)方法用来执行任意的SQL查询,若是查询的结果是一个ResultSet,这个方法就返回true。若是结果不是ResultSet,好比insert或者update查询,它就会返回false。咱们能够经过它的getResultSet方法来获取ResultSet,或者经过getUpdateCount()方法来获取更新的记录条数。
Statement的executeQuery(String query)接口用来执行select查询,而且返回ResultSet。即便查询不到记录返回的ResultSet也不会为null。咱们一般使用executeQuery来执行查询语句,这样的话若是传进来的是insert或者update语句的话,它会抛出错误信息为 “executeQuery method can not be used for update”的java.util.SQLException。
Statement的executeUpdate(String query)方法用来执行insert或者update/delete(DML)语句,或者 什么也不返回DDL语句。返回值是int类型,若是是DML语句的话,它就是更新的条数,若是是DDL的话,就返回0。
只有当你不肯定是什么语句的时候才应该使用execute()方法,不然应该使用executeQuery或者executeUpdate方法。
PreparedStatement对象表明的是一个预编译的SQL语句。用它提供的setter方法能够传入查询的变量。
因为PreparedStatement是预编译的,经过它能够将对应的SQL语句高效的执行屡次。因为PreparedStatement自动对特殊字符转义,避免了SQL注入攻击,所以应当尽可能的使用它。
可使用它的setNull方法来把null值绑定到指定的变量上。setNull方法须要传入参数的索引以及SQL字段的类型,像这样:
ps.setNull(10, java.sql.Types.INTEGER);.
有的时候表会生成主键,这时候就能够用Statement的getGeneratedKeys()方法来获取这个自动生成的主键的值了。
它和Statement相比优势在于:
PreparedStatement的一个缺点是,咱们不能直接用它来执行in条件语句;须要执行IN条件语句的话,下面有一些解决方案:
关于这个问题更详细的分析能够看下这篇文章。
在查询数据库后会返回一个ResultSet,它就像是查询结果集的一张数据表。
ResultSet对象维护了一个游标,指向当前的数据行。开始的时候这个游标指向的是第一行。若是调用了ResultSet的next()方法游标会下移一行,若是没有更多的数据了,next()方法会返回false。能够在for循环中用它来遍历数据集。
默认的ResultSet是不能更新的,游标也只能往下移。也就是说你只能从第一行到最后一行遍历一遍。不过也能够建立能够回滚或者可更新的ResultSet,像下面这样。
Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
当生成ResultSet的Statement对象要关闭或者从新执行或是获取下一个ResultSet的时候,ResultSet对象也会自动关闭。
能够经过ResultSet的getter方法,传入列名或者从1开始的序号来获取列数据。
根据建立Statement时输入参数的不一样,会对应不一样类型的ResultSet。若是你看下Connection的方法,你会发现createStatement和prepareStatement方法重载了,以支持不一样的ResultSet和并发类型。
一共有三种ResultSet对象。
ResultSet有两种并发类型。
setMaxRows能够用来限制返回的数据集的行数。固然经过SQL语句也能够实现这个功能。好比在MySQL中咱们能够用LIMIT条件来设置返回结果的最大行数。
setFetchSize理解起来就有点费劲了,由于你得知道Statement和ResultSet是怎么工做的。当数据库在执行一条查询语句时,查询到的数据是在数据库的缓存中维护的。ResultSet其实引用的是数据库中缓存的结果。
假设咱们有一条查询返回了100行数据,咱们把fetchSize设置成了10,那么数据库驱动每次只会取10条数据,也就是说得取10次。当每条数据须要处理的时间比较长的时候而且返回数据又很是多的时候,这个可选的参数就变得很是有用了。
咱们能够经过Statement来设置fetchSize参数,不过它会被ResultSet对象设置进来的值所覆盖掉。
存储过程就是数据库编译好的一组SQL语句,能够经过JDBC接口来进行调用。咱们能够经过JDBC的CallableStatement接口来在数据库中执行存储过程。初始化CallableStatement的语法是这样的:
1 CallableStatement stmt = con.prepareCall("{call insertEmployee(?,?,?,?,?,?)}"); 2 stmt.setInt(1, id); 3 stmt.setString(2, name); 4 stmt.setString(3, role); 5 stmt.setString(4, city); 6 stmt.setString(5, country); 7 //register the OUT parameter before calling the stored procedure 8 stmt.registerOutParameter(6, java.sql.Types.VARCHAR); 9 stmt.executeUpdate();
咱们得在执行CallableStatement以前注册OUT参数。关于这个更详细的资料能够看这里。
有时候相似的查询咱们须要执行不少遍,好比从CSV文件中加载数据到关系型数据库的表里。咱们也知道,执行查询能够用Statement或者PreparedStatement。除此以外,JDBC还提供了批处理的特性,有了它,咱们能够在一次数据库调用中执行多条查询语句。
JDBC经过Statement和PreparedStatement中的addBatch和executeBatch方法来支持批处理。
批处理比一条条语句执行的速度要快得多,由于它须要不多的数据库调用,想进一步了解请点这里。
默认状况下,咱们建立的数据库链接,是工做在自动提交的模式下的。这意味着只要咱们执行完一条查询语句,就会自动进行提交。所以咱们的每条查询,实际上都是一个事务,若是咱们执行的是DML或者DDL,每条语句完成的时候,数据库就已经完成修改了。
有的时候咱们但愿由一组SQL查询组成一个事务,若是它们都执行OK咱们再进行提交,若是中途出现异常了,咱们能够进行回滚。
JDBC接口提供了一个setAutoCommit(boolean flag)方法,咱们能够用它来关闭链接自动提交的特性。咱们应该在须要手动提交时才关闭这个特性,否则的话事务不会自动提交,每次都得手动提交。数据库经过表锁来管理事务,这个操做很是消耗资源。所以咱们应当完成操做后尽快的提交事务。在这里有更多关于事务的示例程序。
经过Connection对象的rollback方法能够回滚事务。它会回滚此次事务中的全部修改操做,并释放当前链接所持有的数据库锁。
JDBC的保存点(Savepoint)是什么,如何使用?
有时候事务包含了一组语句,而咱们但愿回滚到这个事务的某个特定的点。JDBC的保存点能够用来生成事务的一个检查点,使得事务能够回滚到这个检查点。
一旦事务提交或者回滚了,它生成的任何保存点都会自动释放并失效。回滚事务到某个特定的保存点后,这个保存点后全部其它的保存点会自动释放而且失效。能够读下这个了解更多关于JDBC Savepoint的信息。
DataSource即数据源,它是定义在javax.sql中的一个接口,跟DriverManager相比,它的功能要更强大。咱们能够用它来建立数据库链接,固然驱动的实现类会实际去完成这个工做。除了能建立链接外,它还提供了以下的特性:
关于JDBC数据源的示例请看下这里。
对部署在servlet容器中的WEB程序而言,建立数据库链接池很是简单,仅须要如下几步。
1 <Resource name="jdbc/MyDB" 2 global="jdbc/MyDB" 3 auth="Container" 4 type="javax.sql.DataSource" 5 driverClassName="com.mysql.jdbc.Driver" 6 url="jdbc:mysql://localhost:3306/UserDB" 7 username="pankaj" 8 password="pankaj123" 9 maxActive="100" 10 maxIdle="20" 11 minIdle="5" 12 maxWait="10000"/> 13 <ResourceLink name="jdbc/MyLocalDB" 14 global="jdbc/MyDB" 15 auth="Container" 16 type="javax.sql.DataSource" />
在WEB应用程序中,先用InitialContext来查找JNDI资源,而后获取链接。
Context ctx = new InitialContext(); DataSource ds = (DataSource) ctx.lookup("java:/comp/env/jdbc/MyLocalDB");
完整的示例请看这里。
若是用DataSource来获取链接的话,一般获取链接的代码和驱动特定的DataSource是紧耦合的。另外,除了选择DataSource的实现类,剩下的代码基本都是同样的。
Apache的DBCP就是用来解决这些问题的,它提供的DataSource实现成为了应用程序和不一样JDBC驱动间的一个抽象层。Apache的DBCP库依赖commons-pool库,因此要确保它们都在部署路径下。
完整的使用示例请看这里。
当咱们为了数据的一致性使用事务时,数据库系统用锁来防止别人访问事务中用到的数据。数据库经过锁来防止脏读,不可重复读(Non-Repeatable Reads)及幻读(Phantom-Read)的问题。
数据库使用JDBC设置的隔离级别来决定它使用何种锁机制,咱们能够经过Connection的getTransactionIsolation和setTransactionIsolation方法来获取和设置数据库的隔离级别。
隔离级别 | 事务 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
TRANSACTION_NONE | 不支持 | 不可用 | 不可用 | 不可用 |
TRANSACTION_READ_COMMITTED | 支持 | 阻止 | 容许 | 容许 |
TRANSACTION_READ_UNCOMMITTED | 支持 | 容许 | 容许 | 容许 |
TRANSACTION_REPEATABLE_READ | 支持 | 阻止 | 阻止 | 容许 |
TRANSACTION_SERIALIZABLE | 支持 | 阻止 | 阻止 | 阻止 |
RowSet用于存储查询的数据结果,和ResultSet相比,它更具灵活性。RowSet继承自ResultSet,所以ResultSet能干的,它们也能,而ResultSet作不到的,它们仍是能够。RowSet接口定义在javax.sql包里。
RowSet提供的额外的特性有:
RowSet分为两大类:
A. 链接型RowSet——这类对象与数据库进行链接,和ResultSet很相似。JDBC接口只提供了一种链接型RowSet,javax.sql.rowset.JdbcRowSet,它的标准实现是com.sun.rowset.JdbcRowSetImpl。 B. 离线型RowSet——这类对象不须要和数据库进行链接,所以它们更轻量级,更容易序列化。它们适用于在网络间传递数据。有四种不一样的离线型RowSet的实现。
RowSet继承自ResultSet,所以它有ResultSet的所有功能,同时它本身添加了些额外的特性。RowSet一个最大的好处是它能够是离线的,这样使得它更轻量级,同时便于在网络间进行传输。
具体使用哪一个取决于你的需求,不过若是你操做ResultSet对象的时间较长的话,最好选择一个离线的RowSet,这样能够释放数据库链接。
有如下这些:
CLOB意思是Character Large OBjects,字符大对象,它是由单字节字符组成的字符串数据,有本身专门的代码页。这种数据类型适用于存储超长的文本信息,那些可能会超出标准的VARCHAR数据类型长度限制(上限是32KB)的文本。
BLOB是Binary Larget OBject,它是二进制大对象,由二进制数据组成,没有专门的代码页。它能用于存储超过VARBINARY限制(32KB)的二进制数据。这种数据类型适合存储图片,声音,图形,或者其它业务程序特定的数据。
当咱们使用事务时,有可能会出现这样的状况,有一行数据刚更新,与此同时另外一个查询读到了这个刚更新的值。这样就致使了脏读,由于更新的数据尚未进行持久化,更新这行数据的业务可能会进行回滚,这样这个数据就是无效的。
数据库的TRANSACTIONREADCOMMITTED,TRANSACTIONREPEATABLEREAD,和TRANSACTION_SERIALIZABLE隔离级别能够防止脏读。
当咱们在分布式系统上同时使用多个数据库时,这时候咱们就须要用到两阶段提交协议。两阶段提交协议能保证是分布式系统提交的原子性。在第一个阶段,事务管理器发全部的事务参与者发送提交的请求。若是全部的参与者都返回OK,它会向参与者正式提交该事务。若是有任何一个参与方返回了停止消息,事务管理器会回滚全部的修改动做。
从广义上讲,有两种锁机制来防止多个用户同时操做引发的数据损坏。
乐观锁——只有当更新数据的时候才会锁定记录。 悲观锁——从查询到更新和提交整个过程都会对数据记录进行加锁。
不只如此,一些数据库系统还提供了行锁,表锁等锁机制。
DDL(数据定义语言,Data Definition Language)语句用来定义数据库模式。Create,Alter, Drop, Truncate, Rename都属于DDL语句,通常来讲,它们是不返回结果的。
DML(数据操做语言,Data Manipulation Language)语句用来操做数据库中的数据。select, insert, update, delete, call等,都属于DML语句。
java.util.Date包含日期和时间,而java.sql.Date只包含日期信息,而没有具体的时间信息。若是你想把时间信息存储在数据库里,能够考虑使用Timestamp或者DateTime字段。
可使用BLOB类型将图片或者原始的二进制数据存储到数据库里。
幻读是指一个事务屡次执行一条查询返回的倒是不一样的值。假设一个事务正根据某个条件进行数据查询,而后另外一个事务插入了一行知足这个查询条件的数据。以后这个事务再次执行了这条查询,返回的结果集中会包含刚插入的那条新数据。这行新数据被称为幻行,而这种现象就叫作幻读。
只有TRANSACTION_SERIALIZABLE隔离级别才能防止产生幻读。
SQLWarning是SQLException的子类,经过Connection, Statement, Result的getWarnings方法均可以获取到它。 SQLWarning不会中断查询语句的执行,只是用来提示用户存在相关的警告信息。
若是Oracle的存储过程的入参出参中包含数据库对象,咱们须要在程序建立一个一样大小的对象数组,而后用它来生成Oracle的STRUCT对象。而后能够经过数据库对象的setSTRUCT方法传入这个struct对象,并对它进行使用。
若是你的SQL URL串格式不正确的话,就会抛出这样的异常。无论是使用DriverManager仍是JNDI数据源来建立链接都有可能抛出这种异常。它的异常栈看起来会像下面这样。
org.apache.tomcat.dbcp.dbcp.SQLNestedException: Cannot create JDBC driver of class 'com.mysql.jdbc.Driver' for connect URL ''jdbc:mysql://localhost:3306/UserDB' at org.apache.tomcat.dbcp.dbcp.BasicDataSource.createConnectionFactory(BasicDataSource.java:1452) at org.apache.tomcat.dbcp.dbcp.BasicDataSource.createDataSource(BasicDataSource.java:1371) at org.apache.tomcat.dbcp.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044) java.sql.SQLException: No suitable driver found for 'jdbc:mysql://localhost:3306/UserDB at java.sql.DriverManager.getConnection(DriverManager.java:604) at java.sql.DriverManager.getConnection(DriverManager.java:221) at com.journaldev.jdbc.DBConnection.getConnection(DBConnection.java:24) at com.journaldev.jdbc.DBConnectionTest.main(DBConnectionTest.java:15) Exception in thread "main" java.lang.NullPointerException at com.journaldev.jdbc.DBConnectionTest.main(DBConnectionTest.java:16)
解决这类问题的方法就是,检查下日志文件,像上面的这个日志中,URL串是'jdbc:mysql://localhost:3306/UserDB,只要把它改为jdbc:mysql://localhost:3306/UserDB就行了。
下面列举了其中的一些: