JDBC并非Java Database Connectivity的缩写,而是一个注册了商标的术语,它的命名体现了对ODBC(为C语言访问数据库提供了一套编程接口)的致敬。
JDBC与ODBC有基于同一个思想:根据API编写的程序都可以与驱动管理器 进行通信,而驱动管理器则通过驱动程序与实际的数据库进行通信。
JDBC URL的一般语法为:JDBC:subprotocol:other stuff
其中,subprotocol用于选择连接到数据库的具体驱动程序。other stuff参数的格式由subprotocol决定。
数据库 | subprotocol | otherstuff | 备注 |
---|---|---|---|
MySql | mysql | //[hosts][/database][?properties] |
参考这里 |
Oracle | oracle | :driver_type:[username/password]@ [database_specifier](https://docs.oracle.com/cd/B28359_01/java.111/b31224/urls.htm#JJDBC08200) |
driver_type 包括thin ,oci 和kprb ,用的最多的就是thin ,它的格式为@//host_name:port_number/service_name 。参考这里 |
redis | redis、rediss、redis-socket、redis-sentinel | //[[username :] password@] host [: port] [/ database][? [timeout=timeout[d\|h\|m\|s\|ms\|us\|ns]]] |
参看这里 |
mongodb | mongodb | //[username:password@]host1[:port1][,…hostN[:portN]][/[defaultauthdb][?options]] | 参考这里 |
postgresql | postgresql | :[//host:[port]/]database | 参考这里 |
数据库 | DriverClass | Library |
MySql | 5.1版本:com.mysql.jdbc.Driver/8.0版本:com.mysql.cj.jdbc.Driver | |
Oracle | oracle.jdbc.OracleDriver | |
MongoDB | |
|
Postgresql | org.postgresql.Driver | |
对于遵循JDBC4的驱动程序都是支持自动注册的,若不支持,可使用一下方式手动注册:
Class.forName(driverClass);
java -Djdbc.drivers=driverClass ProgramName
System.setProperty("jdbc.drivers",driverClass);
打开数据库连接
Connection conn = DriverManager.getConnection(url,username,password);
驱动管理器遍历所有注册过的驱动程序,找到与数据库URL中子协议匹配的驱动程序。
Statement statement = conn.createStatement();
statement.execuet(sql);
int rows = statement.execuetUpdate(sql);//返回受该sql影响的行数
ResultSet resultSet = executeQuery(sql);//查询语句必须使用该方法
通常使用下列的循环来分析查询结果
while(resultSet.next()){//每一次调用next,就将结果集中的游标向后移一行
//分析一行结果
//通过传入列标签或列序号取回数据
result.getString(colLable);
result.getString(colIndex);//序号从1开始
}
每一个Connection对象可以创建一到多个Statement对象,每个Statement对象可以执行多条Sql语句,但是一个Statement对象最多只能有一个打开的结果集。若需要同时分析多个结果集,就需要创建多个Statement对象。如果结果集相互关联,我们最好是使用关联查询。在数据库进行关联查询比在程序中遍历多个结果集效率高的多。
每一个Connection
,Statement
和ResultSet
对象都使用了数据库有限的资源,我们在使用完这些资源后都应该及时关闭。这三个类都实现了AutoClouseable
,所以使用带资源的tyr/catch
块是一个不错的选择。
使用PreparedStatement
对象,我们可以在预备语句中将宿主变量(查询条件、插入值)用"?"代替,在执行预备语句之前使用set方法将变量绑定到实际值上。在执行下一次相同查询时,只需要再次使用set方法更新宿主变量就行。
String sql = " select * from books where name = ? ";
PreparedStatement pStatement = new connection.prepareStatement(sql);
pStatement.setString(1,"Core Java");//下标从1开始
ResultSet resultSet = pStatement.executeQuery();
...
数据库还可以存储大对象,例如图片,文档等。在SQL中,二进制大对象称为BLOB,字符型大对象称为CLOB。
public static void saveBlob(){
byte[] bytes = new byte[1024];
try{
try(Connection connection = DriverManager.getConnection(url,userName,passWord)){
Blob blob = connection.createBlob();//获取Blob对象
try(OutputStream out = blob.setBinaryStream(1); //获取Blob对象的输出流
InputStream in = new FileInputStream("images.PNG");){
while(in.available() > 0){
in.read(bytes);
out.write(bytes);
}
out.flush();
}
PreparedStatement preparedStatement = connection.prepareStatement(" insert into test (name,blob) values(?,?)");
preparedStatement.setString(1,"file.zip");
preparedStatement.setBlob(2,blob);
preparedStatement.executeUpdate();
}
}catch(SQLException e){
e.printStackTrace();
}catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
}
public static void readBlob(){
byte[] bytes = new byte[1024];
try{
try(Connection connection = DriverManager.getConnection(url,userName,passWord)){
Statement statement = connection.createStatement();
try(ResultSet resultSet = statement.executeQuery("select * from test")){
if (resultSet.next()){
Blob blob = resultSet.getBlob(3);//获取Blob对象
try( InputStream in = blob.getBinaryStream();//获取Blob对象的输入流
OutputStream outputStream = new FileOutputStream("images_copy.PNG",true);){
while(in.read(bytes) > 0){
outputStream.write(bytes);
}
}
}
}
}
}catch (SQLException e){
e.printStackTrace();
}catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
}
public static void saveClob(){
try{
try(Connection connection = DriverManager.getConnection(url,userName,passWord)){
Clob clob = connection.createClob();//获取Clob对象
try(Reader reader = new FileReader("test2.txt")){
PreparedStatement statement = connection.prepareStatement(" insert into test (name,clob) values(?,?)");
statement.setString(1,"test.txt");
Writer writer = clob.setCharacterStream(1);//获取Clob的字节输出流
char[] chars = new char[10];
while(reader.read(chars)>0){
writer.write(chars);
}
writer.close();
statement.setClob(2,clob);
statement.executeUpdate();
}
}
}catch (SQLException e){
e.printStackTrace();
}catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
}
public static void readClob(){
try{
try(Connection connection = DriverManager.getConnection(url,userName,passWord)){
Statement statement = connection.createStatement();
try(ResultSet resultSet = statement.executeQuery("select clob from test where name='test.txt'")){
if (resultSet.next()){
Clob clob = resultSet.getClob(1);
BufferedReader reader = new BufferedReader(clob.getCharacterStream());//获取Clob的字节输入流
while (reader.ready()){
logger.info(reader.readLine());
}
}
}
}
}catch (Exception e){
e.printStackTrace();
}
}
日期和时间字面常量随数据库的不同而变化很大。要嵌入日期或时间字面常量,需要按照一定格式指定它的值,之后驱动程序会将它转译为本地格式。
标量函数是指仅返回一个值的函数。要调用函数,需要像下面这样嵌入标准的函数名和参数:
{fn left(?,20)}
{fn user()}
使用call
转义命令来调用存储过程:
{call PROC1(?,?)}
{call PROC2} //没有参数就不用加括号
{call ?=PROC3(?)} //用=来捕获存储过程的返回值
%
和_
这两个字符在LIKE子句中具有特殊含义。_
用来匹配一个字符,%
用来匹配资格字符序列。
如果想要匹配包含_
的字符串,可以使用下面结构:
where ? like %_%{escapge '!'}
这里将!
定义为转义字符。
statement.executeUpdate(insertSql,Statement.RETURN_GENERATED_KEYS);
ResultSet rs = statement.getGeneratedKeys();
if (rs.next){
int key = rs.getInt(1);
}
如果只执行的语句是插入语句,且指定了Statement.RETURN_GENERATED_KEYS,那么结果集的第一列就是数据库自动生成的主键。
默认情况下获得结果集都是不可滚动和更新的。使用下面的方式来构造不同的Statement
,以实现结果集可滚动和可更新
Statement createStatement(int resultSetType, int resultSetConcurrency);
PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency);
resultSetType有:
resultSetConcurrency有:
期值为:
并不是所有的数据库都支持这些模式,我们应该检查结果集的功能。
DataBaseMetaData metaData = connection.getMetaData();
if (metaData.supporsResultSetType(ResultSet.TYPE_SCROLL_SENSITIVE)){
...
}
if (metaData.supportResultSetConcurrency(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE)){
...
}
rs.privous();//向后滚动
rs.next();//向前滚动
rs.relative(n);//n为正数,则向前滚动,n为负数则向后滚动,超过结果集范围,则游标被置于第一行之前或最后一行之后且返回false
rs.absolute(n);//将游标置于指定的行号上
rs.getRow();//获取当前行号
resultSet.updateXXX(colNameorIndex,valaue); //所有对应于SQL类型的数据类型都配有updateXxx方法,比如updaateDouble,updateString等
resultSet.updateRow();//更新字段后一定要调用这个方法,否则所有的字段更新都会被遗弃。
//插入数据
rs.moveToInsertRow();//将游标移动大奥特定的可插入行
rs.updateXxx();
...
rs.insertRow();//将新增行插入数据库
rs.moveToCurrentRow();//将游标移回调用moveToInserRow方法之前的位置
rs.deleteRow();//删除游标所指行
并非所有的结果集都是可更新的
默认情况下,数据库连接处于自动提交模式。每个SQL语句一旦被执行便被提交给数据库,一旦被提交,便无法回滚。
在使用事务时,需要关闭自动提交:
connection.setAutoCommit(false);
Statement statement = connection.createStatement();
try{
statment.execuetUpdate(commond1);
statment.execuetUpdate(commond2);
statment.execuetUpdate(commond3);
statment.execuetUpdate(commond4);
//没有出错,提交事务
connection.commit();
}catch(SQLException e){
//出错了,回滚事务
connection.rollback();
}
使用保存点,可以更细粒度的控制事务
connection.setAutoCommit(false);
Statement statement = connection.createStatement();
statment.execuetUpdate(commond1);
statment.execuetUpdate(commond2);
Savepoint svpt = connection.setSaavepoint();
statment.execuetUpdate(commond3);
statment.execuetUpdate(commond4);
//没有出错,提交事务
if (...){
connetion.rollback(svpt);//回滚到执行common3之前
}
//当不在需要保存点时,必须释放它
connection.releaseSavepoint(svpt);
首先需要判断数据库是否支持批量更新:
DatabaseMetaData databaseMetaData = connection.getMetaData();
if (databaseMetaData.supportsBatchUpdates()){
...
}
为了在批量模式下正确的处理异常,必须将整个批量更新视为单个事务,需要关闭自动提交。批量更新的语句应该是INSERT
,UPDATE
,DELETE
等操作,也可以是数据库定义的语句,如CREATE TABLE
,DROP TABLE
等,不能 是SELECT
,批量更新中有查询语句会抛出异常。
DatabaseMetaData databaseMetaData = connection.getMetaData();
if (databaseMetaData.supportsBatchUpdates()){
connection.setAutoCommit(false);
Statement statment = connection.createStatement();
while(...){
String Sql = ...;
statement.addBacth(Sql);
}
//提交整个提交语句
int[] counts = statment.executeBatch();
connection.commit();
connection.setAutoCommit(true);
}
executeBath
返回一个数组,其中的每个元素都对应一条语句。如果其值 非负数,则代表受该条语句影响的记录数;如果其值为Statement.SUCCESS_NO_INFO
,则表示执行成功,单没有记录数可用;如果其值为Statement.EXECUTE_FAILED
,则表示该语句执行失败。