一.连接池(数据源)
1.不用连接池时的问题
- 直接使用DriverManager时:
- 每次调用它都会创建新连接,而没有复用连接
- 它没有管理连接上限,当并发数大时可能导致数据库崩溃
2.连接池的作用
- 连接池的作用就是解决上述问题:
- 它能复用连接,提高效率
- 它能管理连接上限,避免数据库崩溃
3.有哪些常用连接池
- DBCP
- C3P0
4.连接池的工作原理
- 当创建连接池后,它会自动创建一批(配)连接放于池内
- 当用户调用它时,它会给用户一个连接,并将连接标为占用
- 当用户使用完连接后,将连接归还给连接池,它会将连接标为空闲
- 若连接池发现连接快不够用时,它会再创建一批(配)连接
- 若占用的连接达到数据库上限(配)时,连接池会让新用户等待
- 在高峰期过后,连接池会关闭一批(配)连接
5.如何使用连接池(*)
- sun规定了连接池的接口: DataSource
- DBCP实现了连接池的接口: BasicDataSource
二.Statement和PreparedStatement
1.它们的联系(背)
- 都是用来执行SQL的
- PreparedStatement extends Statement
2.它们的区别(背)
- Statement适合执行静态(无条件)SQL
- PreparedStatement适合执行动态(有条件)SQL
3.Statement原理(理解)
4.PreparedStatement原理(理解)
5.PS可以避免注入攻击
三.结果集
1.关于结果集的指针
2.元数据
- Meta: 根本、本质
- MetaData: 数据的根本,即数据的概述信息
- ResultSetMetaData: 对结果集的概述(描述)信息
案例代码
db.properties
注意:如果使用连接池,可以在这个文件中增加对连接池的相关设置:
连接池参数,常用参数有:
- 初始连接数
- 最大连接数
- 最小连接数
- 每次增加的连接数
- 超时时间
- 最大空闲连接
- 最小空闲连接
# db connection parameters
# key=value
driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@192.168.201.227:1521:orcl
user=openlab
pwd=open123
# datasource parameters
initSize=1
maxSize=1
DBUtil.java
说明:DBUtil是DBTool的升级版,采用了连接池来管理连接。
package util;import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;import org.apache.commons.dbcp.BasicDataSource;/*** 1.DBUtil是DBTool的升级版* 2.采用了连接池来管理连接*/
public class DBUtil {
//DBCP连接池提供的实现类private static BasicDataSource ds;static {Properties p = new Properties();try {//1.读取参数p.load(DBUtil.class.getClassLoader().getResourceAsStream("db.properties"));String driver = p.getProperty("driver");String url = p.getProperty("url");String user = p.getProperty("user");String pwd = p.getProperty("pwd");String initSize = p.getProperty("initSize");String maxSize = p.getProperty("maxSize");//2.创建连接池(1次)ds = new BasicDataSource();//3.向连接池设置参数ds.setDriverClassName(driver);ds.setUrl(url);ds.setUsername(user);ds.setPassword(pwd);ds.setInitialSize(new Integer(initSize));ds.setMaxActive(new Integer(maxSize));} catch (IOException e) {//异常的处理原则://1.记录日志(Log4j)e.printStackTrace();//2.能解决就解决(看开发规范)//3.解决不了向上抛给调用者//具体抛出哪种类型的异常看开发规范throw new RuntimeException("加载配置文件失败", e);}}public static Connection getConnection() throws SQLException {return ds.getConnection();}/*** 1.目前我们使用连接都是连接池创建的* 2.连接池重写了连接对象内部的close()* 3.目前close()内部的逻辑是归还:* - 清除连接对象内部包含的所有数据* - 将连接对象状态设置为空闲态*/public static void close(Connection conn) {if(conn != null) {try {conn.close();} catch (SQLException e) {e.printStackTrace();throw new RuntimeException("关闭连接失败", e);}}}}
Test.java
说明:这个类用于测试DBUtil
package jdbc;import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;import org.junit.Test;import util.DBUtil;public class TestDay02 {
/*** 1.测试DBUtil* 2.执行DQL* 查询部门ID为1的员工*/@Testpublic void test1() {//假设页面传入的查询条件是int deptno = 1;Connection conn = null;try {conn = DBUtil.getConnection();System.out.println(conn);Statement smt = conn.createStatement();String sql = "select * from emps_lhh "+ "where deptno="+deptno;ResultSet rs = smt.executeQuery(sql);while(rs.next()) {System.out.println(rs.getInt("empno"));System.out.println(rs.getString("ename"));}} catch (SQLException e) {e.printStackTrace();//测试代码可以适当简化异常处理} finally {DBUtil.close(conn);}}/*** 演示如何使用PS执行DML*/@Testpublic void test2() {//假设页面传入的数据是String ename = "曹操";String job = "丞相";int mgr = 0;Date hiredate = Date.valueOf("2017-01-22");Double sal = 8000.0;Double comm = 9000.0;int deptno = 3;Connection conn = null;try {conn = DBUtil.getConnection();//写sql时条件用?代替String sql = "insert into emps_lhh values("+ "emps_seq_lhh.nextval,"+ "?,?,?,?,?,?,?)";//创建PS并传入sql,PS会立刻发送此sqlPreparedStatement ps = conn.prepareStatement(sql);//先设置条件:给?赋值//ps.set类型(?的序号,?的值)ps.setString(1, ename);ps.setString(2, job);ps.setInt(3, mgr);ps.setDate(4, hiredate);ps.setDouble(5, sal);ps.setDouble(6, comm);ps.setInt(7, deptno);//发送参数,执行SQL(计划)ps.executeUpdate();} catch (SQLException e) {e.printStackTrace();} finally {DBUtil.close(conn);}}/*** 演示如何使用PS执行DQL*/@Testpublic void test3() {//假设页面传入的查询条件是int empno = 1;Connection conn = null;try {conn = DBUtil.getConnection();String sql = "select * from emps_lhh "+ "where empno=?";PreparedStatement ps = conn.prepareStatement(sql);ps.setInt(1, empno);ResultSet rs = ps.executeQuery();if(rs.next()) {System.out.println(rs.getString("ename"));System.out.println(rs.getString("job"));}} catch (SQLException e) {e.printStackTrace();} finally {DBUtil.close(conn);}}/*** 使用PS执行查询语句,可以避免注入攻击*/@Testpublic void test4() {//假设页面传入的参数是String user = "zhangsan";String pwd = "a' or 'b'='b";Connection conn = null;try {conn = DBUtil.getConnection();String sql = "select * from users_lhh "+ "where username=? "+ "and password=?";PreparedStatement ps = conn.prepareStatement(sql);ps.setString(1, user);ps.setString(2, pwd);ResultSet rs = ps.executeQuery();if(rs.next()) {System.out.println("登录成功");} else {System.out.println("账号或密码错误");}} catch (SQLException e) {e.printStackTrace();} finally {DBUtil.close(conn);}}}
Test.java
演示如何从ResultSetMetaData中读取结果集相关的描述信息.
模拟转账业务.
批量添加员工(共108个,每批加50个)
添加部门及员工数据,添加员工时需要获取到部门的ID
package jdbc;import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;import org.junit.Test;import util.DBUtil;public class TestDay03 {
/*** 演示如何从ResultSetMetaData* 中读取结果集相关的描述信息.*/@Testpublic void test1() {//假设页面传入的查询条件是int empno = 1;Connection conn = null;try {conn = DBUtil.getConnection();String sql = "select * from emps_lhh "+ "where empno=?";PreparedStatement ps = conn.prepareStatement(sql);ps.setInt(1, empno);ResultSet rs = ps.executeQuery();//获取结果集元数据,它是一个对象,//内部封装了对结果集的描述信息.ResultSetMetaData md = rs.getMetaData();//多少列System.out.println(md.getColumnCount());//第1列的列名System.out.println(md.getColumnName(1));//第1列的类型的编号(常量)System.out.println(md.getColumnType(1));//第1列的类型的名称System.out.println(md.getColumnTypeName(1));} catch (SQLException e) {e.printStackTrace();} finally {DBUtil.close(conn);}}/*** 模拟转账业务.* * 假设此时用户已经登录了网银,* 并且已经输入了收款方账号和* 转账的金额,点击了转账.* * 转账的步骤:* 1.验证收款方账号是否存在(查询)* 2.验证付款方余额是否够用(查询)* 3.将付款方余额-N元(修改)* 4.将收款方余额+N元(修改)*/@Testpublic void test2() {//假设用户输入的信息如下//付款方账号String payId = "00001";//收款方账号String recId = "00002";//转账的金额double mny = 1000.0;//转账是一个完整的业务流程,必须保证//它的完整性,即该流程应处于一个事务//之内,所以创建一个连接.Connection conn = null;try {conn = DBUtil.getConnection();//取消自动提交事务conn.setAutoCommit(false);//1.查询收款方账号并验证String sql = "select * from accounts_lhh "+ "where id=?";PreparedStatement ps = conn.prepareStatement(sql);ps.setString(1, recId);ResultSet rs = ps.executeQuery();if(!rs.next()) {throw new SQLException("收款方账号不存在");}double recMny = rs.getDouble("money");//2.查询付款方余额并验证String sql2 = "select * from accounts_lhh "+ "where id=?";PreparedStatement ps2 = conn.prepareStatement(sql2);ps2.setString(1, payId);ResultSet rs2 = ps2.executeQuery();double payMny = 0.0;if(rs2.next()) {payMny = rs2.getDouble("money");if(payMny<mny) {throw new SQLException("余额不足");}}//3.修改付款方余额String sql3 = "update accounts_lhh set "+ "money=? where id=?";PreparedStatement ps3 = conn.prepareStatement(sql3);ps3.setDouble(1, payMny-mny);ps3.setString(2, payId);ps3.executeUpdate();Integer.valueOf("断电了");//4.修改收款方余额String sql4 ="update accounts_lhh set "+ "money=? where id=?";PreparedStatement ps4 = conn.prepareStatement(sql4);ps4.setDouble(1, recMny+mny);ps4.setString(2, recId);ps4.executeUpdate();//转账是一个完整的过程,只需要在//整个流程完成后,提交一次事务即可.conn.commit();} catch (Exception e) {e.printStackTrace();try {conn.rollback();} catch (SQLException e1) {e1.printStackTrace();}} finally {DBUtil.close(conn);}}/*** 批量添加员工(共108个,每批加50个)*/@Testpublic void test3() {//这是一个完整的业务,只创建//1个连接,提交1次事务Connection conn = null;try {conn = DBUtil.getConnection();conn.setAutoCommit(false);String sql = "insert into emps_lhh values("+ "emps_seq_lhh.nextval,"+ "?,?,?,?,?,?,?)";PreparedStatement ps = conn.prepareStatement(sql);for(int i=1;i<=108;i++) {//每次循环都将数据暂存到ps上ps.setString(1, "好汉"+i);ps.setString(2, "打劫");ps.setInt(3, 0);ps.setDate(4, Date.valueOf("2017-01-23"));ps.setDouble(5, 6000.0);ps.setDouble(6, 4000.0);ps.setInt(7, 9);ps.addBatch();//每循环50次发送一次数据if(i%50==0) {ps.executeBatch();//清空ps中的数据,以便于//暂存下一轮的数据ps.clearBatch();}}//循环结束后,为了避免有零头(8),//再单独批量发送一次数据.由于这//是最后一次发送,所以不用清空ps了ps.executeBatch();conn.commit();} catch (SQLException e) {e.printStackTrace();try {conn.rollback();} catch (SQLException e1) {e1.printStackTrace();}} finally {DBUtil.close(conn);}}/*** 添加部门及员工数据* 添加员工时需要获取到部门的ID*/@Testpublic void test4() {//假设页面传入的数据是//部门String dname = "财务部";String loc = "杭州";//员工String ename = "郭嘉";String job = "谋士";int mgr = 0;Date hiredate = Date.valueOf("2017-01-23");double sal = 6000.0;double comm = 2000.0;Connection conn = null;try {conn = DBUtil.getConnection();conn.setAutoCommit(false);//增加部门String sql = "insert into depts values("+ "depts_seq.nextval,?,?)";//参数2是一个数组,声明需要ps记住//的字段的名称,ps在执行SQL时会//记住这些字段的值.PreparedStatement ps = conn.prepareStatement(sql, new String[]{
"deptno"});ps.setString(1, dname);ps.setString(2, loc);ps.executeUpdate();//获取部门ID//返回的结果集中存储了一条数据,//该行数据包括我们让ps记录的所有字段.ResultSet rs = ps.getGeneratedKeys();System.out.println("rs"+rs); //rsorg.apache.commons.dbcp.DelegatingResultSet@27efef64rs.next();//获取ps记录的字段时必须使用序号int deptno = rs.getInt(1);System.out.println("deptno"+deptno); //deptno 4//增加员工String sql2 = "insert into emps values("+ "emps_seq.nextval,"+ "?,?,?,?,?,?,?)";PreparedStatement ps2 = conn.prepareStatement(sql2);ps2.setString(1, ename);ps2.setString(2, job);ps2.setInt(3, mgr);ps2.setDate(4, hiredate);ps2.setDouble(5, sal);ps2.setDouble(6, comm);ps2.setInt(7, deptno);ps2.executeUpdate();conn.commit();} catch (SQLException e) {e.printStackTrace();try {conn.rollback();} catch (SQLException e1) {e1.printStackTrace();}} finally {DBUtil.close(conn);}}}