13.1 JDBC基础
JDBC定义了一套标准接口,即访问数据库的通用API,不同的数据库厂商根据各自数据库的特点去实现这些接口
13.2 第一个JDBC程序
- 引入ojdbc6.jar
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JDBCTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1. 要使用JDBC接口,需要先将对应数据库的具体实现类加载进来,jdk6以后不需显示加载驱动
Class.forName("oracle.jdbc.driver.OracleDriver");
//2. 根据url连接参数,找到与之匹配的Driver对象,调用其方法获取连接
Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@192.168.41.128:1521:fcrhost","c50hst","c50hst");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select * from teacher");
while(rs.next()){
System.out.println(rs.getString("id"));
System.out.println(rs.getString("name"));
System.out.println(rs.getString("title"));
}
//3. 依次关闭ResultSet,Statement,Connection等资源
rs.close();
stmt.close();
conn.close();
}
}
13.3 Connection
代表数据库连接对象,每个Connection代表一个物理连接会话,要想访问数据库,必须先获得数据库连接
13.3.1 方法
1. Statement createStatement()
2. PreparedStatement prepareStatement(String sql)
3. CallableStatement prepareCall(String sql)
4. Savepoint setSavepoint():创建保存点
5. Savepoint setSavepoint(String name):以指定名字创建保存点
6. void setTransactionIsolation(int level):设置事务隔离级别
7. void rollback()
8. void rollback(Savepoint savepoint):回滚事务到指定保存点
9. void setAutoCommit(boolean autoCommit):关闭自动提交,即打开事务
10. void commit():提交事务
11. DatabaseMetaData getMetaData():获取数据库的描述信息
13.4 Statement
可以执行任何语句,只不过使用其不同方法,执行不同种语句时,返回值不同
13.4.1 方法
1. ResultSet executeQuery(String sql):执行select语句时返回查询结果对应的ResultSet对象,非select语句也可以执行但是返回的ResultSet对象无法操作
2. int executeUpdate(String sql):一般用于DML(select、insert、update、delete)或DDL(create、drop)语句,DDL时返回0,DML时返回受影响的行数
3. boolean execute(String sql):执行select语句返回true,其它情况基本返回false
4. ResultSet getResultSet():如果其execute方法返回true,获取查询出的结果集
5. int getUpdateCount():如果其execute方法返回false,获取修改条数
boolean bl = stmt.execute("select * from teacher");
if(bl){
ResultSet st = stmt.getResultSet();
while(st.next()){
System.out.println(st.getString(2));
}
}else{
int upc = stmt.getUpdateCount();
System.out.println(upc);
}
13.5 PreparedStatement
允许使用"?“进行占位,其实就是比Statement多了setXxx方法,为占位符”?"赋值。且创建PreparedStatement对象时,需要传入sql,而Statement是在调用其对象的executeXxxx方法中传入的sql
13.5.1 作用
- 为Statement接口的子接口
- 多次执行时比Statement效率高
- 防止SQL注入
//如果将userName输入为'or true or',那么语句变为select * from jdbc_test where jdbc_name='' or true or '' and jdbc_desc='';此时不输密码,也可以登录成功
String sql = "select * from jdbc_test where jdbc_name='"+userName+"'and jdbc_desc='"+userPass+"'";
13.5.2 方法
PreparedStatement pstmt = conn.prepareStatement("select * from teacher where id=?");
//1. void setXxx(int parameterIndex, Xxx x):为第parameterIndex个占位符赋值为Xxx类型的值x
pstmt.setString(1,"2");
ResultSet rs = pstmt.executeQuery();
--对Oracle中二进制长对象的处理(Blob、Clob)
--数据库语句:
--create table liu(a blob);
//java语句
PreparedStatement p = conn.prepareStatement("insert into liu values(?)");
File f = new File("C:\\Users\\ThinkPad\\Desktop\\截图\\1.png");
InputStream it = new FileInputStream(f);
//p.setBinaryStream(1, it);也可以
p.setBlob(1, it);
int i = p.executeUpdate();
13.6 CallableStatement
可以执行存储过程,其实就是比PreparedStatement多了registerOutParameter与getXxx方法,为占位符"?"对应的数据设置类型,并获取该值
13.6.1 作用
- 为PreparedStatement接口的子接口
- 通常用于调用存储过程
13.6.2 方法
//存储过程add_pro用于拼接传入的第一个与第二个字符串作为第三个字符串
CallableStatement pstmt = conn.prepareCall("call add_pro(?,?,?)");
//1. void registerOutParameter(int parameterIndex, int sqlType):将存储过程中占位符代表的返回列注册为sqlType值对应的类型
pstmt.registerOutParameter(3,Types.VARCHAR);
pstmt.setString(1, "wu");
pstmt.setString(2, "sihan");
pstmt.execute();
//2. Xxx getXxx(int parameterIndex):返回第parameterIndex占位符代表的值
System.out.println(pstmt.getString(3));
13.7 ResultSet
13.7.1 详解
- 代表结果集对象
- 默认情况下ResultSet不可以滚动也不可以更新
- 可以通过以下方法开启滚动与更新功能
//1. resultSetType:设置是否可以滚动,只有允许任意前后滚动才能使用除next()以外的修改指针指向的方法
//ResultSet.TYPE_FORWARD_ONLY:只能向后滚动
//ResultSet.TYPE_SCROLL_INSENSITIVE:支持任意前后滚动,底层数据库改变不影响ResultSet内容
//ResultSet.TYPE_SCROLL_SENSITIVE:支持任意前后滚动,底层数据库改变影响ResultSet内容
//以上两个常量需要数据库驱动支持,对于有些数据库驱动而言,这两个值没有什么区别
//2. resultSetConcurrency:设置是否允许更改,只有允许更改才可以使用Result对象的updateXxx相关方法
//可更新结果集需满足条件:所有数据来自同一表,选出数据集必须包含主键列(针对mysql有效,对于oracle无效)
//ResultSet.CONCUR_READ_ONLY:只读
//ResultSet.CONCUR_UPDATABLE:允许更新
//注意select * from teacher语句是不允许被更新的,必须写成select id,name,title from teacher
Statement st = conn.createStatement(int resultSetType, int resultSetConcurrency);
13.7.2 方法
1. void close():释放ResultSet对象
2. boolean absolute(int row):将结果集的记录指针移动到第row行,如果row为负数,移动到倒数第row行,如果指针指向一条有效记录返回true
3. void beforeFirst():指针指向首行之前,即初始状态
4. boolean first():指针指向首行,如果指针指向一条有效记录,返回true
5. boolean previous():指向上一行
6. boolean next():指向下一行
7. boolean last():指向最后一行
8. void afterLast():指向最后一行之后
9. getXxx(int columnIndex):获取指针所在行的第columnIndex列的值
//Blob getBlob(int/String column):可以获取数据库中Blob类型数据
//Blob类有getBinaryStream()方法,将Blob对象转为输入流对象
10. getXxx(String columnLabel):获取指针所在行的名为columnLabel列的值
11. updateXxx(int columIndex,Xxx value):更新指针所在行的第columIndex列的内容为value
12. updateXxx(String columnLabel,Xxx value)
13. updateRow():调用updateXxx方法后,在指针没离开该行前,必须调用该方法将数据写入数据库,否则修改不会被提交
13.8 ResultSetMetaData
获取ResultSet的描述信息(元数据)
13.8.1 方法
1. int getColumnCount():获取ResultSet列数量
2. String getColumnName(int column):获取第column列的列名
3. int getColumnType(int column):获取第column列的数据类型
13.9 事务处理
13.9.1 事务的概念
- 事务是由一步或几步数据库操作序列组成的逻辑执行单元,这系列操作要么全部执行,要么全部放弃执行,程序和事务是两个不同概念,一般而言一段程序包含多个事务
- 默认事务不开启,也就是执行一个语句就会提交一个语句
- 当事务开启后,conn.commit和conn.close都会导致事务的提交,开启事务后,当conn.commit提交后,那么该语句前面代码都会提交,后面的代码,只能等到conn.close时,才会提交
- 开启事务
Connection conn = openConnection();
try {
// 关闭自动提交:
conn.setAutoCommit(false);
// 执行多条SQL语句:
insert(); update(); delete();
// 提交事务:
conn.commit();
} catch (SQLException e) {
// 回滚事务:
conn.rollback();
} finally {
conn.setAutoCommit(true);
conn.close();
}
- 保存点:savepoint,当一个操作集合中,包含多条sql,但只想让其中一部分成功
--注意以下为sql中语句
--语句1
--语句2
savepoint sp1;
--语句3
--此时,语句1、2执行成功,3被回滚
rollback to sp1;
--注意只要最后没commit提交,那么语句3的状态,相当于就还是没决定提交还是回滚,而语句1和2已经提交
13.9.2 事务的特性ACID:Oracle默认采用Read Committed,MySql采用Repeatable Read
- 原子性:事务中的所有操作,或者全部完成,或者全部不完成
- 一致性:事务结束后,数据库中各表中内容和开发人员预想的一样
- 隔离性:可以防止多个事务并发执行时由于交叉执行而导致数据的不一致,严格的隔离性会导致性能降低,在某些情况下,为提高程序执行效率,需要降低隔离级别,任何数据库都只有如下四个隔离级别
- Read Uncommitted:可以读到未提交的数据。一个事务会读到另一个事务更新后但未提交的数据,如果另一个事务回滚,那么当前事务读到的数据就是脏数据,这就是脏读(Dirty Read)
- Read Committed:可以读到提交过的数据。一个事务会读到另一个事务提交的数据,如果一个事务在另一个事务提交前后分别查询同一数据,两次读取的数据就可能不一致,这叫做不可重复度(Non Repeatable Read)问题
- Repeatable Read:可重复读,不会出现两次读取数据不一致。一个事务不会读到其他事务提交过的数据,却可以修改该数据,且修改后就可以读到新的数据了,这种现象叫做幻读
- Serializable:序列化。所有事务按照次序依次执行,因此,脏读、不可重复读、幻读都不会出现,但性能太低
- 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失
13.9.3 数据库的事务组成
- 一组dml语句(select、insert、update、delete)
- 一条ddl(create)语句或一条dcl(grant revoke)语句:因为ddl和dcl语句会导致事务立即提交
13.9.4 数据库提交事务方式
- 显示提交:commit
- 自动提交:ddl、dcl
13.9.5 数据库回滚事务
显示回滚:rollback
13.10 使用连接池管理连接
- 使用DriverManager获取Connection对象时,每个Connection对象对应一个物理数据库连接,频繁打开关闭该连接会造成系统性能低下
- 数据库连接池:程序启动时,系统主动建立足够多的数据库连接,并将这些连接组成一个连接池,每次应用请求数据库连接时,无需重新打开连接,而是从连接池中取出已有连接使用,使用后也不关闭,而是将连接归还连接池,大大提升性能
- JDBC提供一个接口javax.sql.DataSource代表数据源,一个数据源包含连接池和连接池管理两部分,习惯上把DataSource也称为连接池
- C3P0与DBCP提供了对DataSource的实现
13.10.1 C3P0编程
- 引入c3p0-0.9.1.2.jar
- C3P0可以自动清理不再使用的Connection、Statement、ResultSet对象
import java.beans.PropertyVetoException;
import java.io.FileNotFoundException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class JDBCTest {
public static void main(String[] args)
throws ClassNotFoundException, SQLException, FileNotFoundException, PropertyVetoException {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass("oracle.jdbc.driver.OracleDriver");
ds.setJdbcUrl("jdbc:oracle:thin:@192.168.41.128:1521:fcrhost");
ds.setUser("c50hst");
ds.setPassword("c50hst");
// 设置连接池最大连接数
ds.setMaxPoolSize(40);
// 设置连接池最小连接数
ds.setMinPoolSize(2);
// 设置连接池初始连接数
ds.setInitialPoolSize(10);
// 设置连接池的缓存Statement的最大数
ds.setMaxStatements(180);
Connection conn = ds.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select * from teacher");
while (rs.next()) {
System.out.println(rs.getString("id"));
System.out.println(rs.getString("name"));
System.out.println(rs.getString("title"));
}
}
}