一、JDBC基础
- JDBC全称:Java Database Connectivity(Java数据库连接)
- 通过JDBC,就可以实现同一种API访问不同的数据库,例如,我们的代码既可以与Oracle数据库连接,也可与MySql数据库连接,二者的区别只是使用了不同的驱动程序。
- JDBC的作用
1) 建立与数据库的连接
2) 执行SQL语句
3) 获得sql语句的执行结果 - JDBC的4种常用驱动
1) JDBC-ODBC桥:基本不用
2) 直接将JDBC API映射成数据库特定的客户端API:性能高,但编码维护困难。
3) 支持三层结构的JDBC访问方式,主要用于Applet阶段,通过Applet访问数据库
4) 纯Java的,直接与数据库实例交互,该方式智能,它知道数据库使用的底层协议
最常用的是第四种,而对性能有严格要求时,应选择第二种
二、JDBC的典型用法
DriverManager:用于获取Connectoin对象
public static synchronized Connection getConnection(String url,String user,String psw);
可以看出,获取数据库连接是一个同步的方法。Connection:代表数据库连接对象,常用方法如下:
1) Statement createStatement() throws Exception:获得Statement对象
2) PreparedStatement preparedStatement(String sql):返回预编译的Statement对象
3) CallableStatement prepareCall(String sql):返回调用存储过程的CallableStatement
其中PreparedStatement和CallableStatement都是Statement的子类- Connection的控制事务的方法
1) Savepoint setSavepoint():创建一个事务保存点(中间点)
2) void setTransactionIsolation(int level):设置事务隔离级别
3) void rollback():回滚事务
4) void rollback(Savepoint point):将事务回滚到指定的保存点
5) void setAutoCommit(Boolean b):是否开启事务(false为开启)
6) void commit():提交事务
7) void setNetworkTimeout(Executor e,int milliseconds):设置数据库连接超时行为 - Statement:用于执行SQL语句的工具接口
1) ResultSet executeQuery(String sql):执行查询SQL语句
2) int executeUpdate():执行除了查询以外的其他SQL语句。执行DDL语句返回0,执行DML语句返回受影响行数
3) boolean execute(String sql):执行任何SQL语句。如果执行SQL语句结果是ResultSet则返回true,否则返回false
4) void closeOnCompletion():当所有依赖该Statement的ResultSet关闭时,则该Statement对象自动关闭
通常没有必要使用execute()方法执行SQL语句,除非不清楚SQL语句类型,因为比较麻烦;一般使用比较简单的executeQuery()方法或executeUpdate()方法 - PreparedStatement:预编译的Statement对象
1) 因为PreparedStatement对象已编译了SQL语句,所以它的有关执行SQL语句的方法不再需要传入SQL语句了,如executeQuery()、executeUpdate()、execute()
2) setXxx(int index,Xxx value):给指定索引位置(占位符)传入值,如:
setString(2,”张三”):给第二个占位符传入”张三”
3) prearedStatement执行SQL语句的优点:
a) 使用占位符,既避免了传参时拼接字符串,又避免了SQL注入
b) 传参时若不知道参数类型时,可用setObject()方法
c) 因为是预编译SQL语句(只需要传一条SQL语句就OK了),所以执行效率高 - ResultSet:结果集对象
1) 通过列名或列索引获得数据
getXxx(int index)、getXxx(String columnName)
T getObject(int index)、getObject(String columnName)
2) boolean absolute(int row):移动到第row行,row为负数时指倒数
3) void beforeFirst():将记录指针移到第一行之前
4) boolean first():将ResultSet的指针定位到首行
5) boolean previous():定位到上一行
6) boolean next():定位到下一行
7) boolean last():定位到最后一行
8) void afterLast():定位到最后一行之后
9) void close():释放ResultSet对象
注:ReslutSet的getString()方法可获得除Blob类型之外的任意类型类的值,因为这些类型都可以自动转换成字符串
三、JDBC编程示例与步骤
- 用executeQuery方法执行SQL语句:
public static void main(String[] args) throws Exception{
Class.forName(“com.mysql.jdbc.Driver”); // 加载驱动
String url=”jdbc:mysql://127.0.0.1:3306/db”;
Connection con= DriverManager.getConnection(url,”root”,”root”);//获得数据库连接
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(“select * from user”);
while(rs.next()){
System.out.println(rs.getInt(“id”)+”==”+rs.getString(“username”));
}
}
- 使用execute方法时部分代码示例
Statement st = con.createStatement();
boolean b = st.execute(“select * from user”);
if(b){ // 说明执行结果是ResultSet
ResultSet rs = st.getResult();
int columnNum = rs.getColumnCount(); // 获得列数
while(){
for(int i=0;i<columnNum;i++){
System.out.println(rs.getString(i+1));
}
}
}else{
//说明执行SQL结果不是查询,而是DDL或DML操作
}
- 用:PreparedStatement 执行SQL语句部分代码示例
preparedStatement ps = conn.preparedStatement(“insert into user values(“null,?”);
for(int i=0;i<100;i++){
ps.setString(1,”姓名”+i); // 给第一个占位符赋值
ps.executeUpdate();
}
- 使用CallalbeStatement调用存储过程
CallalbeStatement cs = conn.prepareCall(“{call add_user(?,?,?)}”); //add_user为存储过程名字
cs.setInt(1,4); //给第一个占位符传参
cs.setInt(2,5); //给第二个占位符传参
cs.registerOutParameter(3,Types.INTEGER);// 第三个占注册成传出参数,并指定类型
ex.execute();
System.out.println(“执行结果是:”+cs.getInt(3));
- 处理Blob类型数据,即Binary Long Object(二进制长对象)
1) 插入数据库用PreparedStatement的setBinaryStream(int index , InputStream in)方法
2) 取出Blob数据时,用ResultSet的getBlob(int index)方法获得Blob对象,Blob对象方法:
getBinaryStream():取出输入流
getBytes():取出二进制流
3) Mysql数据库的Blob类型只能存64k的内容,mediumblob类型能存16M的内容
6.ReslutSetMetaData
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(sql); //sql字符串省略了
ReslutSetMetaData rsmd = rs.getMetaData();
int columnNum = rsmd.getColumnCount(); // 列数量
String columnName = rsmd.getColumnName(2);// 获得索引为2的列的列名
int columnType = rsmd.getColumnType(2); // 获得索引为2的列的列类型
虽然ReslutSetMetaData对象可准确分析出ResultSet里包含多少列,以及每列的列名和类型,但使用它会有一定的系统开销,因此建议能不用就不用。
四、RowSet
RowSet接口规范类图
离线RowSet:Connection关闭前,可将底层的数据读取到内存中进行离线操作
连线RowSet:程序得到ResultSet后,必须立即处理数据,否则一旦Connection关闭,再通过ResultSet去读取时会报错通过RowSetProvider创建RowSetFactory,然后通过RowSetFactory创建各类型的RowSet
JdbcRowSet createJdbcRowSet()
CachedRowSet createCachedRowSet()
WebRowSet createWebRowSet()
JoinRowSet createJoinRowSet()
FilteredRowSet createFilteredRowSet()- 离线RowSet(CachedRowSet)的分页
setPageSize(int pageSize):设置返回条数
previousPage():读取上一页记录
nextPage():读取下一页记录
populate(ResultSet rs, int startRow):填充RowSet startRow=(pageNo-1)*pageSize+1
示例:
….省略部分代码
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(sql);
RowSetFactory f = RowSetProvider.newFactory();// 获得创建RowSet的工厂
CachedRowSet cache = f.createCachedRowSet();// 创建离线RowSet
cache.setPageSize(10); //每次取多少条
cache.populate(rs,(pageNo-1)*pageSize+1) );//缓存结果,并求出从第几条开始取
while(rs.next()){
//…..
}
五、批量更新
- 批量更新,是将多个SQL语句一起提交给数据库执行(需要数据库支持)
- 注意事项
批量更新时不能有select查询语句,否则程序会出错
必须把批量操作当成单个事务,如果有一条SQL语句执行失败,则回滚到之前的状态
MySql不支持Java8的executeLargeBatch() 和executeLargeUpdate()方法 - 示例
boolean flag = con.getAutoCommit(); // 保存当前事务状态
con.setAutoCommit(false); // 开启事务
Statement st = con.createStatement();
st.addBatch(sql1);
st.addBatch(sql2);
st.addBatch(sql3);
st.executeBatch(); // 执行批量操作
con.commit(); // 提交事务
con.setAutoCommit(flag); // 将事务恢复到之前的状态
六、使用连接池管理数据库连接
- 什么是连接池?
连接池是生产并管理数据库连接的工厂 - 为什么需要连接池?
数据库连接的建立和关闭都是极为耗资源的操作,频繁地打开和关闭连接,将造成系统性能低下。 - 连接池工作原理
应用程序每次请求数据库连接时,都从连接池取出一个已有的空闲连接,使用完后,不会关闭连接,而是归还给连接池等待下一个请求来获取使用 - 两种连接池
DBCP数据源:参见P638
C3P0数据源:参见P639。C3P0要优于DBCP,Hibernate推荐使用C3P0