核心技术(卷二)05、第4章-数据库编程

数据库编程

JDBC

JDBC并非Java Database Connectivity的缩写,而是一个注册了商标的术语,它的命名体现了对ODBC(为C语言访问数据库提供了一套编程接口)的致敬。

JDBC与ODBC有基于同一个思想:根据API编写的程序都可以与驱动管理器 进行通信,而驱动管理器则通过驱动程序与实际的数据库进行通信。

JDBC的典型用法

核心技术(卷二)05、第4章-数据库编程_第1张图片

数据库URL

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,ocikprb,用的最多的就是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 参考这里

JDBC驱动

数据库 DriverClass Library
MySql 5.1版本:com.mysql.jdbc.Driver/8.0版本:com.mysql.cj.jdbc.Driver
				

	mysql
	mysql-connector-java
	8.0.19

				
			
Oracle oracle.jdbc.OracleDriver
				

	com.oracle.ojdbc
	ojdbc8
	19.3.0.0

				
			
MongoDB
				

	org.mongodb
	mongodb-driver
	3.12.3

				
			
Postgresql org.postgresql.Driver
				

	org.postgresql
	postgresql
	42.2.12

				
			

执行Sql语句

注册启动器类

对于遵循JDBC4的驱动程序都是支持自动注册的,若不支持,可使用一下方式手动注册:

  • Class.forName(driverClass);
  • 设置jdbc.drivers属性:java -Djdbc.drivers=driverClass ProgramName
  • System.setProperty("jdbc.drivers",driverClass);

连接到数据库

打开数据库连接

Connection conn = DriverManager.getConnection(url,username,password);

驱动管理器遍历所有注册过的驱动程序,找到与数据库URL中子协议匹配的驱动程序。

获得语句对象(Statement)

Statement statement = conn.createStatement();

执行SQL语句

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,StatementResultSet对象都使用了数据库有限的资源,我们在使用完这些资源后都应该及时关闭。这三个类都实现了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();
...

读写LOB

数据库还可以存储大对象,例如图片,文档等。在SQL中,二进制大对象称为BLOB,字符型大对象称为CLOB

写BLOB

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();
    }
}

读Blob

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();
    }
}

写Clob

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();
    }
}

读Clob

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();
    }
}

SQL转义

日期与时间

日期和时间字面常量随数据库的不同而变化很大。要嵌入日期或时间字面常量,需要按照一定格式指定它的值,之后驱动程序会将它转译为本地格式。

  • DATE:{d ‘2020-04-20’}
  • TIME: {t ‘11:25:35’}
  • TIMESTAMP: {ts ‘2020-04-20 11:25:35.999’}

标量函数

标量函数是指仅返回一个值的函数。要调用函数,需要像下面这样嵌入标准的函数名和参数:

{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有:

核心技术(卷二)05、第4章-数据库编程_第2张图片

resultSetConcurrency有:

核心技术(卷二)05、第4章-数据库编程_第3张图片

期值为:

并不是所有的数据库都支持这些模式,我们应该检查结果集的功能。

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,则表示该语句执行失败。

你可能感兴趣的:(java)