16---Day03-JDBC&连接池-笔记++


typora-root-url: img
typora-copy-images-to: img

JDBC

回顾

数据库的三大范式

第一范式: 表中的字段不能再拆分(原子性)

第二范式:一张表只描述一件事情, 给每张表添加主键

第三范式: 一张表的字段,引用另一张表的主键

表连接的分类

表连接分类语法
内连接隐式SELECT 字段 FROM 表1, 表2 WHERE 条件;
显式SELECT 字段 FROM 左表 INNER JOIN 右表 ON 条件 WHERE 查询条件
外连接左外SELECT 字段 FROM 左表 LEFT OUTER JOIN 右表 ON 条件 WHERE 查询条件
右外SELECT 字段 FROM 左表 RIGHT OUTER JOIN 右表 ON 条件 WHERE 查询条件

手动提交事务

功能MySQL语句
开启事务START TRANSACTION;
提交事务COMMIT;
回滚事务ROLLBACK;

学习目标

  1. 能够理解JDBC的概念
  2. 能够使用Connection接⼝
  3. 能够使用Statement接⼝
  4. 能够使用ResultSet接⼝
  5. 能够使用JDBC实现对单表数据增、删、改、查
  6. 能够使用JDBC操作事务
  7. 能够完成JDBC实现登录案例例
  8. 能够使用C3P0连接池
  9. 能够使用DRUID连接池
  10. 能够编写连接池工具类

JDBC的概念

目标

  1. 学习JDBC的概念
  2. 学习JDBC的作用
客户端操作MySQL数据库的方式

[外链图片转存失败(img-uIC31BWP-1565017901593)(/1550289380140.png)]

  1. 使用DOS命令行方式
  2. 使用第三方客户端来访问MySQL:SQLyog、Navicat、SQLWave、MyDB Studio、EMS SQL Manager for MySQL
  3. 通过Java来访问MySQL数据库,今天要学习的内容
什么是JDBC

Java Data Base Connectivity:Java数据库连接,JDBC是Java操作数据库的规范

JDBC的作用

通过JDBC可以让Java操作数据库

JDBC的由来
  1. 直接写代码操作数据库
    [外链图片转存失败(img-BhCdGZIf-1565017901596)(/jdbc01.png)]
    直接写代码操作数据库存在的问题:

    1. 不知道MySQL数据库的操作方式,解析方式
    2. 代码繁琐,写起来麻烦
    3. MySQL和Oracle等其他数据库的操作方式和解析方式不同,每个数据库都要写一套代码
    4. MySQL和Oracle等其他数据库相互切换麻烦
  2. JDBC规范定义接口,具体的实现由各大数据库厂商来实现
    JDBC是Java访问数据库的标准规范。真正怎么操作数据库还需要具体的实现类,也就是数据库驱动。每个数据库厂商根据自家数据库的通信格式编写好自己数据库的驱动。所以我们只需要会调用JDBC接口中的方法即可。数据库驱动由数据库厂商提供。
    [外链图片转存失败(img-mCqNzaCy-1565017901598)(/1550373498072.png)]

JDBC的好处
  1. 我们只需要会调用JDBC接口中的方法即可,使用简单
  2. 使用同一套Java代码,进行少量的修改就可以访问其他JDBC支持的数据库

小结

  1. 说出JDBC的概念?

    JAVA数据库连接,JDBC是Java操作数据库的规范(接口)
    
  2. 说出JDBC的作用?

    通过JDBC可以让Java操作数据库
    

JDBC核心API的介绍

目标

学习JDBC四个核心对象

JDBC会用到的包
  1. java.sql:JDBC访问数据库的基础包,如:java.sql.Connection
  2. javax.sql: JDBC访问数据库的扩展包
  3. 数据库的驱动,各大数据库厂商来实现。如:MySQL的驱动:com.mysql.jdbc.Driver
JDBC四个核心对象

这几个类都是在java.sql包中

  1. DriverManager: 用于注册驱动(可以理解为厂商的实现类)
  2. Connection: 表示数据库的连接
  3. Statement: 执行SQL语句的对象
  4. ResultSet: 结果集或一张虚拟表
    [外链图片转存失败(img-8ZN4YwLG-1565017901600)(/jdbc03.png)]
JDBC访问数据库的步骤
  1. 通过DriverManager注册驱动
  2. 获取连接Connection
  3. 通过Statement,将我们写的SQL语句运送到数据库执行
  4. 返回结果集ResultSet

小结

JDBC四个核心对象?

DriverManager:注册驱动

Connection:Java程序和数据库之间的连接(桥梁)

Statement:执行SQL语句的对象(小货车)

ResultSet:结果集,保存查询到的数据

JDBC注册驱动

我们Java程序需要通过数据库驱动才能连接到数据库,因此需要注册驱动。

目标

学习导入mysql驱动Jar包

学习JDBC注册数据库驱动

在注册驱动前需要先导入驱动的Jar包

导入驱动Jar包

[外链图片转存失败(img-pTzRRCGb-1565017901602)(/1555942738648.png)]
[外链图片转存失败(img-Ktu94BJ0-1565017901604)(/1555942725646.png)]

注册驱动

我们Java程序需要通过数据库驱动才能连接到数据库,因此需要注册驱动。
MySQL的驱动的入口类是:com.mysql.jdbc.Driver
[外链图片转存失败(img-tyO6IOtu-1565017901606)(/jdbc07.png)]

API介绍

java.sql.DriverManager类用于注册驱动。提供如下方法注册驱动

static void registerDriver(Driver driver) 
向 DriverManager 注册给定驱动程序。
使用步骤

1.DriverManager.registerDriver(驱动对象); 传入对应参数即可

案例代码
public class Demo01 {
	public static void main(String[] args) throws Exception {
		// 注册驱动
		DriverManager.registerDriver(new com.mysql.jdbc.Driver());
	}
}

通过查询com.mysql.jdbc.Driver源码,我们发现Driver类“主动”将自己进行注册

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    static {
        try {
            // 自己自动注册
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }
    public Driver() throws SQLException {
    }
}

[外链图片转存失败(img-bnIjF1Vb-1565017901608)(/jdbc08.png)]

注意:使用DriverManager.registerDriver(new com.mysql.jdbc.Driver());,存在两方面不足

  1. 硬编码,后期不易于程序扩展和维护
  2. 驱动被注册两次

使用Class.forName("com.mysql.jdbc.Driver");加载驱动,这样驱动只会注册一次

public class Demo01 {
	public static void main(String[] args) throws Exception {
		Class.forName("com.mysql.jdbc.Driver"); // 后期可以将"com.mysql.jdbc.Driver"字符串写在文件中.
	}
}

演示:Class.forName("包名.类名");会走这个类的静态代码块
[外链图片转存失败(img-2OpKMjyN-1565017901609)(/jdbc10.png)]

通常开发我们使用Class.forName() 加载驱动。Class.forName("com.mysql.jdbc.Driver");会走Driver类的静态代码块。在静态代码块中注册一次驱动。
[外链图片转存失败(img-oCqe0n3k-1565017901611)(/jdbc09.png)]

总结:注册MySQL驱动使用Class.forName("com.mysql.jdbc.Driver");

小结

  1. 导入mysql驱动Jar包

  2. 通过JDBC注册数据库驱动?

    Class.forName("com.mysql.jdbc.Driver");
    

获取连接和Statement

Connection表示Java程序与数据库之间的连接,只有拿到Connection才能操作数据库。

目标

学习JDBC获取数据库连接

学习获取Statement对象

API介绍

java.sql.DriverManager类中有如下方法获取数据库连接

static Connection getConnection(String url, String user, String password) 
连接到给定数据库 URL ,并返回连接。 
参数说明
  1. String url:连接数据库的URL,用于说明连接的数据库位置
  2. String user:数据库的账号
  3. String password:数据库的密码

连接数据库的URL地址格式:协议名:子协议://服务器名或IP地址:端口号/数据库名
[外链图片转存失败(img-SyyKm4UO-1565017901612)(/jdbc13.png)]
MySQL写法:jdbc:mysql://localhost:3306/day16
如果是本地服务器,端口号是默认的3306,则可以简写:jdbc:mysql:///day16

注意事项

如果数据出现乱码需要加上参数: ?characterEncoding=utf8,表示让数据库以UTF-8编码来处理数据。
如: jdbc:mysql://localhost:3306/day16?characterEncoding=utf8

使用步骤
  1. 注册驱动
  2. 调用方法:DriverManager.getConnection(url, user, password); 传入对应参数即可
案例代码
public class Demo01 {
	public static void main(String[] args) throws Exception {
		Class.forName("com.mysql.jdbc.Driver");
		// 连接到MySQL
		// url: 连接数据库的URL
		// user: 数据库的账号
		// password: 数据库的密码
		Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day24", "root", "root");
		System.out.println(conn);
	}
}
案例效果

1.连接成功
[外链图片转存失败(img-DhcGcczm-1565017901613)(/jdbc14.png)]
2.连接失败
[外链图片转存失败(img-G3yPITST-1565017901616)(/jdbc15.png)]

小结

  1. JDBC获取数据库连接使用哪个API?

    DriverManager类中的
    Connection getConnection(数据库URL, 数据库账号, 数据库密码);
    
  2. 通过JDBC连接mysql的URL写法?

    jdbc:mysql://主机名:端口号/数据库名
    

JDBC实现对单表数据增、删、改

目标

学习JDBC实现对单表数据增、删、改

准备数据
-- 创建分类表
CREATE TABLE category (
  cid INT PRIMARY KEY AUTO_INCREMENT,
  cname VARCHAR(100)
);
-- 初始化数据
INSERT INTO category (cname) VALUES('家电');
INSERT INTO category (cname) VALUES('服饰');
INSERT INTO category (cname) VALUES('化妆品');
JDBC实现对单表数据增、删、改

我们要对数据库进行增、删、改、查,需要使用Statement对象来执行SQL语句。

API介绍

获取Statement对象

java.sql.Connection接口中有如下方法获取到Statement对象

Statement createStatement() 
创建一个 Statement 对象来将 SQL 语句发送到数据库

Statement的API介绍

  1. boolean execute(String sql)
    用执行任何SQL语句,如果是查询返回true,如果不是查询语句返回false; 通常不用
    
  2. int executeUpdate(String sql)
    用于执行增删改等语句; 返回影响的行数
    
  3. ResultSet executeQuery(String sql)
    用于执行查询语句; 返回查询到的结果集
    

    executeQuery:用于执行查询SQL

    executeUpdate:用于执行除查询外的SQL

使用步骤
  1. 注册驱动
  2. 获取连接
  3. 获取Statement对象
  4. 使用Statement对象执行SQL语句
  5. 释放资源
案例代码
public class Demo03 {
	public static void main(String[] args) throws Exception {
		Class.forName("com.mysql.jdbc.Driver");

		Connection conn = DriverManager.getConnection("jdbc:mysql:///day24", "root", "root");
		System.out.println(conn);

		// 从连接中拿到一个Statement对象
		Statement stmt = conn.createStatement();

		// 1.插入记录
		String sql = "INSERT INTO category (cname) VALUES ('手机');";
		int i = stmt.executeUpdate(sql);
		System.out.println("影响的行数:" + i);

		// 2.修改记录
		sql = "UPDATE category SET cname='汽车' WHERE cid=4;";
		i = stmt.executeUpdate(sql);
		System.out.println("影响的行数:" + i);

		// 3.删除记录
		sql = "DELETE FROM category WHERE cid=1;";
		i = stmt.executeUpdate(sql);
		System.out.println("影响的行数:" + i);
		
		// 释放资源
		stmt.close();
		conn.close();
	}
}
案例效果

[外链图片转存失败(img-TL0npboU-1565017901617)(/jdbc17.png)]

小结

JDBC实现增删改的步骤

  1. 注册驱动
  2. 获取连接
  3. 获取小货车
  4. 执行SQL语句
  5. 关闭资源

Statement对象的用于执行除查询外的SQL语句的方法
int executeUpdate(String sql);

JDBC实现对单表数据查询

目标

JDBC实现对单表数据查询

ResultSet用于保存执行查询SQL语句的结果。
我们不能一次性取出所有的数据,需要一行一行的取出。

ResultSet的原理
  1. ResultSet内部有一个指针,记录获取到哪行数据
  2. 调用next方法, ResultSet内部指针会移动到下一行数据
  3. 我们可以通过ResultSet得到一行数据 getXxx得到某列数据
    [外链图片转存失败(img-e6GPFXCu-1565017901618)(/jdbc18.png)]
ResultSet获取数据的API

其实ResultSet获取数据的API是有规律的get后面加数据类型。我们统称getXXX()
[外链图片转存失败(img-H16GZn8r-1565017901619)(/jdbc19.png)]

使用JDBC查询数据库中的数据的步骤
  1. 注册驱动
  2. 获取连接
  3. 获取到Statement
  4. 使用Statement执行SQL
  5. ResultSet处理结果
  6. 关闭资源
案例代码
public class Demo04 {
	public static void main(String[] args) throws Exception {
		Class.forName("com.mysql.jdbc.Driver");
		
		Connection conn = DriverManager.getConnection("jdbc:mysql:///day24", "root", "root");
		Statement stmt = conn.createStatement();
		
		String sql = "SELECT * FROM category;";
		ResultSet rs = stmt.executeQuery(sql);
		
		// 内部有一个指针,只能取指针指向的那条记录
		while (rs.next()) { // 指针移动一行,有数据才返回true
			// 取出数据
			int cid = rs.getInt("cid");
			String cname = rs.getString("cname");
			
			System.out.println(cid + " == " + cname);
		}
		
		// 关闭资源
		rs.close();
		stmt.close();
		conn.close();
	}
}
案例效果

[外链图片转存失败(img-vuFB302r-1565017901621)(/jdbc20.png)]

小结

  1. 从ResultSet中能一次取出所有数据吗?

    不能,一次只能处理一行

  2. 如何通过ResultSet取数据

    while (rs.next()) {
    	获取这行数据
    }
    

JDBC事务

目标

学习JDBC操作事务

讲解

之前我们是使用MySQL的命令来操作事务。接下来我们使用JDBC来操作银行转账的事务。

准备数据
CREATE TABLE account (
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(10),
	balance DOUBLE
);
-- 添加数据
INSERT INTO account (NAME, balance) VALUES ('张三', 1000), ('李四', 1000);
API介绍

Connection接口中与事务有关的方法

  1. void setAutoCommit(boolean autoCommit) throws SQLException;
    false:开启事务, ture:关闭事务
    
  2. void commit() throws SQLException;
    提交事务
    
  3. void rollback() throws SQLException;
    回滚事务
    
使用步骤
  1. 注册驱动
  2. 获取连接
  3. 开启事务
  4. 获取到Statement
  5. 使用Statement执行SQL
  6. 提交或回滚事务
  7. 关闭资源
案例代码
public class Demo05 {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			// 拿到连接
			Class.forName("com.mysql.jdbc.Driver");
			conn = DriverManager.getConnection("jdbc:mysql:///day24", "root", "root");
			
			// 开启事务
			conn.setAutoCommit(false);
			
			Statement pstmt = conn.createStatement();
			
			// 张三减500
			String sql = "UPDATE account SET balance = balance - 500 WHERE id=1;";
			pstmt.executeUpdate(sql);
			// 模拟异常
			// int i = 10 / 0;
			
			// 李四加500
			sql = "UPDATE account SET balance = balance + 500 WHERE id=2;";
			pstmt.executeUpdate(sql);
			
			pstmt.close();
			// 成功,提交事务
			System.out.println("成功,提交事务");
			conn.commit();
		} catch (Exception e) {
			// 失败,回滚事务
			try {
				System.out.println("出了异常,回滚事务");
				conn.rollback();
			} catch (SQLException e1) {
				e1.printStackTrace();
			}
		} finally {
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}
案例效果

[外链图片转存失败(img-N1DG8TcN-1565017901623)(/jdbc21.png)]

小结

JDBC中与事务相关的API?

Connection接口中的方法。

  1. setAutoCommit(false); 开启事务
  2. commit(); 提交事务
  3. rollback(); 回滚事务

编写JDBC工具类

目标

编写JDBC获取连接与关闭连接工具类

​ 通过上面案例需求我们会发现每次去执行SQL语句都需要注册驱动,获取连接,得到Statement,以及释放资源。发现很多重复的劳动,我们可以将重复的代码定义到某个类的方法中。直接调用方法,可以简化代码。
​ 那么我们接下来定义一个JDBCUtils类。把注册驱动,获取连接,得到Statement,以及释放资源的代码放到这个类的方法中。以后直接调用方法即可。

编写JDBC工具类步骤
  1. 将固定字符串定义为常量
  2. 在静态代码块中注册驱动
  3. 提供一个获取连接的方法static Connection getConneciton();
  4. 定义关闭资源的方法close(Connection conn, Statement stmt, ResultSet rs)
  5. 重载关闭方法close(Connection conn, Statement stmt)
案例代码

JDBCUtils.java

public class JDBCUtils {
	// 1.将固定字符串定义为常量
	private static final String DRIVER_CLASS = "com.mysql.jdbc.Driver";
	private static final String URL = "jdbc:mysql:///day24";
	private static final String USER = "root";
	private static final String PASSWORD = "root";
	
	// 2.在静态代码块中注册驱动(只注册一次)
	// 当这个类加载到内存的时候就走这个静态代码块,再去触发Driver类中的静态代码块,主动注册
	static {
		try {
			Class.forName(DRIVER_CLASS);
		} catch (ClassNotFoundException e) {}
	}
	
	// 3.提供一个获取连接的方法static Connection getConneciton();
	// 我们面向JDBC编程
	public static Connection getConnection() throws SQLException {
		InputStream is = JDBCUtils.class.getResourceAsStream("/jdbc.properties");
		Properties pp = new Properties();
		pp.load(is);

		Connection conn = DriverManager.getConnection(URL, pp);
		return conn;
	}
	
	// 5.重载关闭方法close(Connection conn, Statement stmt)
	public static void close(Connection conn, Statement stmt) {
		close(conn, stmt, null);
	}
	
	// 4.定义关闭资源的方法close(Connection conn, Statement stmt, ResultSet rs)
	public static void close(Connection conn, Statement stmt, ResultSet rs) {
		if (rs != null) {
			try {
				rs.close();
			} catch (SQLException e) {}
		}
		
		if (stmt != null) {
			try {
				stmt.close();
			} catch (SQLException e) {}
		}
		
		if (conn != null) {
			try {
				conn.close();
			} catch (SQLException e) {}
		}
	}
}

小结

编写JDBC工具类的目的是什么?

我们发现JDBC操作数据库,代码都是固定的,都是重复的,封装成工具类,让JDBC使用更加简单。

JDBC实现登录案例

目标

模拟用户输入账号和密码登录网站

  1. 输入正确的账号,密码,显示登录成功
    [外链图片转存失败(img-nUO589B7-1565017901625)(/登录案例01.png)]
  2. 输入错误的账号,密码,显示登录失败
    [外链图片转存失败(img-ufQNMc8Z-1565017901626)(/登录案例02.png)]
案例分析
  1. 使用数据库保存用户的账号和密码
  2. 让用户输入账号和密码
  3. 使用SQL根据用户的账号和密码去数据库查询数据
  4. 如果查询到数据,说明登录成功
  5. 如果查询不到数据,说明登录失败
实现步骤
  1. 创建一个用户表保存用户的账号和密码,并添加一些数据,SQL语句如下:

    CREATE TABLE USER (
      id INT AUTO_INCREMENT PRIMARY KEY,
      NAME VARCHAR(50),
      PASSWORD VARCHAR(50)
    );
    INSERT INTO USER (NAME, PASSWORD) VALUES('admin', '123'), ('test', '123'), ('gm', '123');
    
  2. 编写代码让用户输入账号和密码

    public class Demo07 {
       public static void main(String[] args) {
       Scanner sc = new Scanner(System.in);
       System.out.println("请输入账号: ");
       String name = sc.nextLine();
       System.out.println("请输入密码: ");
       String password = sc.nextLine();
    }
    
  3. 使用SQL根据用户的账号和密码去数据库查询数据

    public class Demo07 {
       public static void main(String[] args) throws Exception {
       // 让用户输入账号和密码
       Scanner sc = new Scanner(System.in);
       System.out.println("请输入账号: ");
       String name = sc.nextLine();
       System.out.println("请输入密码: ");
       String password = sc.nextLine();
       
       // 使用SQL根据用户的账号和密码去数据库查询数据
       Connection conn = JDBCUtils.getConnection();
       Statement stmt = conn.createStatement();
       String sql = "SELECT * FROM user WHERE name='" + name + "' AND password='" + password + "';";
       }
    }
    
  4. 如果查询到数据,说明登录成功,如果查询不到数据,说明登录失败

    public class Demo07 {
    	public static void main(String[] args) throws Exception {
    	   // 让用户输入账号和密码
    	   Scanner sc = new Scanner(System.in);
    	   System.out.println("请输入账号: ");
    	   String name = sc.nextLine();
    	   System.out.println("请输入密码: ");
    	   String password = sc.nextLine();
    	   
    	   // 使用SQL根据用户的账号和密码去数据库查询数据
    	   Connection conn = JDBCUtils.getConnection();
    	   Statement stmt = conn.createStatement();
    	   String sql = "SELECT * FROM user WHERE name='" + name + "' AND password='" + password + "';";
    	   
    	   // 如果查询到数据,说明登录成功,如果查询不到数据,说明登录失败
    	   ResultSet rs = stmt.executeQuery(sql);
    	
    	   if (rs.next()) {
    	      //能进来查询到了数据.
    	      String name2 = rs.getString("name");
    	      System.out.println("欢迎您," + name2);
    	   } else {
    	      //查询不到数据,说明登录失败
    	      System.out.println("账号或密码错误...");
    	   }
    	
    	   JDBCUtils.close(conn, stmt, rs);
    	   }
    	}
    }
    

小结

登录案例步骤

创建用户表,保存一些用户的数据

让用户输入账号和密码

将输入的账号和密码,拼接一个查询语句,并执行

如果查询到数据,登录成功

如果查询不到数据,登录失败

SQL注入问题

目标

学习SQL注入的概念

SQL注入问题

在我们前面JDBC实现登录案例中,当我们输入以下密码,我们发现我们账号和密码都不对竟然登录成功了

请输入用户名:
hehe
请输入密码:
a' or '1'='1

问题分析:

// 代码中的SQL语句
"SELECT * FROM user WHERE name='" + name + "' AND password='" + password + "';";
// 将用户输入的账号密码拼接后
"SELECT * FROM user WHERE name='hehe' AND password='a' or '1'='1';"

我们让用户输入的密码和SQL语句进行字符串拼接。用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义,以上问题称为SQL注入。

要解决SQL注入就不能让用户输入的密码和我们的SQL语句进行简单的字符串拼接。需要使用PreparedSatement类解决SQL注入。

小结

什么是SQL注入?

用户输入的数据拼接成SQL语句,改变了SQL语句的含义。

PreparedStatement预编译对象

目标

了解PreparedSatement的执行原理

PreparedStatement的执行原理

继承结构:
[外链图片转存失败(img-YCbyynxH-1565017901628)(/pstmt01.png)]
我们写的SQL语句让数据库执行,数据库不是直接执行SQL语句字符串。和Java一样,数据库需要执行编译后的SQL语句(类似Java编译后的字节码文件)。

  1. Satement对象每执行一条SQL语句都会先将这条SQL语句发送给数据库编译,数据库再执行。
Statement stmt = conn.createStatement();
stmt.executeUpdate("INSERT INTO users VALUES (1, '张三', '123456');");
stmt.executeUpdate("INSERT INTO users VALUES (2, '李四', '666666');");

上面2条SQL语句我们可以看到大部分内容是相同的,只是数据略有不一样。数据库每次执行都编译一次。如果有1万条类似的SQL语句,数据库需要编译1万次,执行1万次,显然效率就低了。

  1. prepareStatement()会先将SQL语句发送给数据库预编译。PreparedStatement会引用着预编译后的结果。可以多次传入不同的参数给PreparedStatement对象并执行。相当于调用方法多次传入不同的参数。
String sql = "INSERT INTO users VALUES (?, ?, ?);";
// 会先将SQL语句发送给数据库预编译。PreparedStatement会引用着预编译后的结果。
PreparedStatement pstmt = conn.prepareStatement(sql);

// 设置参数
pstmt.setString(1, 1);
pstmt.setInt(2, "张三");
pstmt.setString(3, "123456");
pstmt.executeUpdate();

// 再次设置参数
pstmt.setString(1, 2);
pstmt.setInt(2, "李四");
pstmt.setString(3, "66666");
pstmt.executeUpdate();

上面预编译好一条SQL,2次传入了不同的参数并执行。如果有1万条类似的插入数据的语句。数据库只需要预编译一次,传入1万次不同的参数并执行。减少了SQL语句的编译次数,提高了执行效率。

示意图
[外链图片转存失败(img-fioq1HuB-1565017901629)(/pstmt09.png)]

PreparedStatement的好处
  1. prepareStatement()会先将SQL语句发送给数据库预编译。PreparedStatement会引用着预编译后的结果。可以多次传入不同的参数给PreparedStatement对象并执行。减少SQL编译次数,提高效率。
  2. 安全性更高,没有SQL注入的隐患。
  3. 提高了程序的可读性。

小结

PreparedSatement的好处?

  1. 提高效率
  2. 提高安全性
  3. 提高SQL可读性

PreparedStatement的API介绍

目标

学习PreparedSatement相应的API

继承结构

Statement
↑ 继承
PreparedStatement

获取PreparedStatement

java.sql.Connection有获取PreparedSatement对象的方法

PreparedStatement prepareStatement(String sql)
会先将SQL语句发送给数据库预编译。PreparedStatement对象会引用着预编译后的结果。
"SELECT * FROM user WHERE name=? AND password=?;"; 参数化的SQL
PreparedStatement的API介绍

java.sql.PreparedStatement中有设置SQL语句参数,和执行参数化的SQL语句的方法

  1. void setDouble(int parameterIndex, double x) 将指定参数设置为给定 Java double 值。
    
  2. void setFloat(int parameterIndex, float x) 将指定参数设置为给定 Java REAL 值。 
    
  3. void setInt(int parameterIndex, int x) 将指定参数设置为给定 Java int 值。
    
  4. void setLong(int parameterIndex, long x) 将指定参数设置为给定 Java long 值。 
    
  5. void setObject(int parameterIndex, Object x) 使用给定对象设置指定参数的值。  
    
  6. void setString(int parameterIndex, String x) 将指定参数设置为给定 Java String 值。 
    
  7. ResultSet executeQuery() 
    在此 PreparedStatement 对象中执行 SQL 查询,并返回该查询生成的ResultSet对象。 
    
  8. int executeUpdate() 
    在此 PreparedStatement 对象中执行 SQL 语句,该语句必须是一个 SQL 数据操作语言DML语句,比如 INSERT、UPDATE 或 DELETE 语句;或者是无返回内容的 SQL 语句,比如 DDL 语句。 
    
PreparedSatement使用步骤
  1. 获取连接
  2. 编写SQL语句,未知内容使用?占位:"SELECT * FROM user WHERE name=? AND password=?;"; 参数化的SQL
  3. 获得PreparedStatement对象
  4. 设置实际参数
  5. 执行参数化SQL语句
  6. 关闭资源

小结

PreparedSatement如何设置参数

void setXxx(第几个问号, 问号的具体值);

PreparedSatement如何执行SQL

ResultSet executeQuery() 
int executeUpdate() 

PreparedStatement实现增删查改

目标

学习PreparedSatement实现增删改

创建表结构
CREATE TABLE employee (
  id INT PRIMARY KEY AUTO_INCREMENT,
  NAME VARCHAR(20),
  age INT,
  address VARCHAR(50)
);
添加数据
向Employee表添加3条记录
// 添加数据: 向Employee表添加3条记录
public static void addEmployee() throws Exception {
	Connection conn = JDBCUtils.getConnection();
	String sql = "INSERT INTO employee VALUES (NULL, ?, ?, ?);";
	// prepareStatement()会先将SQL语句发送给数据库预编译。
	PreparedStatement pstmt = conn.prepareStatement(sql);

	// 设置参数
	pstmt.setString(1, "刘德华");
	pstmt.setInt(2, 57);
	pstmt.setString(3, "香港");
	int i = pstmt.executeUpdate();
	System.out.println("影响的行数:" + i);

	// 再次设置参数
	pstmt.setString(1, "张学友");
	pstmt.setInt(2, 55);
	pstmt.setString(3, "澳门");
	i = pstmt.executeUpdate();
	System.out.println("影响的行数:" + i);

	// 再次设置参数
	pstmt.setString(1, "黎明");
	pstmt.setInt(2, 52);
	pstmt.setString(3, "香港");
	i = pstmt.executeUpdate();
	System.out.println("影响的行数:" + i);

	JDBCUtils.close(conn, pstmt);
}

效果:
[外链图片转存失败(img-A4aktdM9-1565017901631)(/pstmt05.png)]

PreparedStatement修改数据

将id为2的学生地址改成台湾

// 修改数据: 将id为2的学生地址改成台湾
public static void updateEmployee() throws Exception {
	Connection conn = JDBCUtils.getConnection();

	String sql = "UPDATE employee SET address=? WHERE id=?;";
	PreparedStatement pstmt = conn.prepareStatement(sql);
	pstmt.setString(1, "台湾");
	pstmt.setInt(2, 2);
	int i = pstmt.executeUpdate();
	System.out.println("影响的行数:" + i);

	JDBCUtils.close(conn, pstmt);
}

效果:
[外链图片转存失败(img-4IKq7f0U-1565017901633)(/pstmt06.png)]

PreparedStatement删除数据

删除id为2的员工

// 删除数据: 删除id为2的员工
public static void deleteEmployee() throws Exception {
	Connection conn = JDBCUtils.getConnection();

	String sql = "DELETE FROM employee WHERE id=?;";
	PreparedStatement pstmt = conn.prepareStatement(sql);
	pstmt.setInt(1, 2);
	int i = pstmt.executeUpdate();
	System.out.println("影响的行数:" + i);

	JDBCUtils.close(conn, pstmt);
}

效果:
[外链图片转存失败(img-0x2woKbu-1565017901634)(/pstmt07.png)]

小结

PreparedSatement使用步骤?

  1. 获取连接
  2. 编写参数化SQL,带?的SQL
  3. 获取PreparedStatement
  4. 设置参数
  5. 执行SQL
  6. 释放资源

PreparedSatement实现增删改使用哪个方法?

int executeUpdate();

不是任何地方都可以写?,字段值不确定的时候才可以?

使用PreparedSatement改写登录案例

目标

使用PreparedSatement改写登录案例

  1. 输入正确的账号密码:
    [外链图片转存失败(img-UQ1WEp1X-1565017901635)(/1551606829574.png)]
  2. 输入错误的密码:
    [外链图片转存失败(img-prhKCv3n-1565017901636)(/1551606884763.png)]

讲解

PreparedSatement使用步骤
  1. 编写SQL语句,未知内容使用?占位
  2. 获得PreparedStatement对象
  3. 设置实际参数
  4. 执行参数化SQL语句
  5. 关闭资源
案例代码
public class Demo02 {
	public static void main(String[] args) throws Exception {
        // 让用户输入账号和密码
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入账号: ");
        String name = sc.nextLine();
        System.out.println("请输入密码: ");
        String password = sc.nextLine();
        
		// 获取连接
		Connection conn = JDBCUtils.getConnection();
		
		// 编写SQL语句,未知内容使用?占位
		String sql = "SELECT * FROM user WHERE name=? AND password=?;";
		
		// prepareStatement()会先将SQL语句发送给数据库预编译。
		PreparedStatement pstmt = conn.prepareStatement(sql);
		
		// 指定?的值
		// parameterIndex: 第几个?,从1开始算
		// x: 具体的值
		pstmt.setString(1, name);
		pstmt.setString(2, password); // 正确的密码
		
		ResultSet rs = pstmt.executeQuery();
		
		if (rs.next()) {
			String name = rs.getString("name");
			System.out.println("name:" + name);
		} else {
			System.out.println("没有找到数据...");
		}
		
		JDBCUtils.close(conn, pstmt, rs);
	}
}
案例效果
  1. 输入正确的账号密码:
    [外链图片转存失败(img-JuGTjNXs-1565017901638)(/1551606829574.png)]
  2. 输入错误的密码:
    [外链图片转存失败(img-KtlDzEbL-1565017901640)(/1551606884763.png)]

小结

使用PreparedStatement可以解决SQL注入问题

不要拼接SQL语句,有未知内容使用?先占位,然后使用PreparedStatement设置参数,并执行

PreparedSatement查询数据

目标

使用PreparedSatement实现查询数据

[外链图片转存失败(img-ftTnjkhF-1565017901641)(/1551613022301.png)]

查询id小于8的员工信息,并保存到员工类中

讲解

[外链图片转存失败(img-mAuhabaH-1565017901643)(/1551613450029.png)]

查询id小于8的员工信息,并保存到员工类中

实现步骤
  1. 定义Employee类
  2. 得到连接对象
  3. 得到PreparedStatement对象
  4. 编写SQL语句并执行,保存ResultSet
  5. 创建一个集合用于保存所有的员工对象
  6. 每次循环将一条记录存放到一个员工对象中
  7. 把员工对象放到集合中
  8. 关闭资源
  9. 遍历集合,循环输出员工对象
代码
public class Employee {
	private int id;
	private String name;
	private int age;
	private String address;
	public Employee() {
	}
	
	public Employee(int id, String name, int age, String address) {
		this.id = id;
		this.name = name;
		this.age = age;
		this.address = address;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	@Override
	public String toString() {
		return "Employee2 [id=" + id + ", name=" + name + ", age=" + age + ", address=" + address + "]";
	}
}
// 查询数据: 查询id小于8的员工信息,并保存到员工类中
public static void queryEmployee() throws Exception {
	Connection conn = JDBCUtils.getConnection();
	String sql = "SELECT * FROM employee WHERE id<?;";
	PreparedStatement pstmt = conn.prepareStatement(sql);
	pstmt.setInt(1, 26);
	ResultSet rs = pstmt.executeQuery();

	// 创建集合存放多个Employee2对象
	ArrayList<Employee> list = new ArrayList<>();

	while (rs.next()) {
		// 移动到下一行有数据,取出这行数据
		int id = rs.getInt("id");
		String name = rs.getString("name");
		int age = rs.getInt("age");
		String address = rs.getString("address");

		// 创建Employee2对象
		Employee e = new Employee(id, name, age, address);
		// 将创建好的员工添加到集合中
		list.add(e);
	}

	// 输出对象
	for (Employee e : list) {
		System.out.println(e);
	}
	
	JDBCUtils.close(conn, pstmt, rs);
}

效果:
[外链图片转存失败(img-ze1tYpjD-1565017901644)(/pstmt08.png)]

小结

表对应类

一条记录对应一个对象

字段值对应对象的成员变量的值

PreparedSatement实现查询使用哪个方法?

ResultSet executeQuery();

ParameterMetaData元数据

目标

学习元数据的概念

学习ParameterMetaData元数据使用

元数据的基本概述

=什么是元数据:数据库、表、列的定义信息
[外链图片转存失败(img-HkWbhnFR-1565017901645)(元数据01.png)]

ParameterMetaData作用

ParameterMetaData可用于获取有关PreparedStatement对象中每个参数标记的类型和属性。

select * from user where name=? and password=?
// ParameterMetaData可以用来获取?的个数和类型
如何获取ParameterMetaData

[外链图片转存失败(img-wP1BVyJT-1565017901646)(元数据02.png)]
通过PreparedStatementgetParameterMetaData()方法来获取到ParameterMetaData对象

ParameterMetaDataAPI介绍
  1. int getParameterCount() 获取PreparedStatement的SQL语句参数?的个数
    
  2. int getParameterType(int param) 获取指定参数的SQL类型。  
    
使用步骤
  1. 获取ParameterMetaData对象
  2. 使用对象调用方法
注意事项

不是所有的数据库驱动都能后去到参数类型(MySQL会出异常)

案例代码
public class Demo01 {
	public static void main(String[] args) throws Exception {
		Connection conn = DataSourceUtils.getConnection();
		String sql = "INSERT INTO  student (name, age, score) VALUES (?, ?, ?)";
		PreparedStatement stmt = conn.prepareStatement(sql);
		ParameterMetaData md = stmt.getParameterMetaData();
		System.out.println("参数个数: " + md.getParameterCount());

		// Parameter metadata not available for the given statement
		// MySQL不支持获取参数类型
		// System.out.println("参数类型: " + md.getParameterType(1));
	}
}

小结

  1. ParameterMetaData的作用

表示参数的元数据,可以得到SQL中?的个数和类型(mysql得不到)

ResultSetMetaData元数据

目标

学习ResultSetMetaData元数据

ResultSetMetaData作用

ResultSetMetaData可用于获取有关ResultSet对象中列的类型和属性的信息。
[外链图片转存失败(img-VuUA5ZNU-1565017901648)(元数据03.png)]

如何获取ResultSetMetaData

[外链图片转存失败(img-L5of9xAa-1565017901650)(元数据04.png)]
通过ResultSetgetMetaData()方法来获取到ResultSetMetaData对象

ResultSetMetaDataAPI介绍
  1. int getColumnCount() 返回此 ResultSet对象中的列数
    
  2. String getColumnName(int column) 获取指定列的名称
    
  3. String getColumnTypeName(int column) 获取指定列的数据库特定类型名称
    
使用步骤
  1. 获取ResultSetMetaData对象
  2. 使用对象调用方法
案例代码
// ResultSetMetaData
public static void test02() throws SQLException {
    Connection conn = DataSourceUtils.getConnection();
    String sql = "SELECT * FROM student WHERE id=1";
    PreparedStatement stmt = conn.prepareStatement(sql);
    ResultSet rs = stmt.executeQuery();
    // 获取结果集元数据
    ResultSetMetaData md = rs.getMetaData();
    int num = md.getColumnCount();
    System.out.println("列数:" + num);
    for (int i = 0; i < num; i++) {
        System.out.println("列名:" + md.getColumnName(i + 1)); // 获取列名
        System.out.println("列类型:" + md.getColumnTypeName(i + 1)); // 获取类的类型
        System.out.println("-----------");
    }
}

小结

ResultSetMetaData的作用

表示结果集元数据,可以得到结果集的字段数量,字段名称,字段类型

/*
目标:使用结果集元数据【对结果进行自动封装】
 */
public class Demo13 {
    public static void main(String[] args) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        // 得到数据库连接
        Connection conn = JDBCUtils.getConnection();
        // 编写SQL
        String sql = "SELECT * FROM employee;";

        // 得到PreparedStatement,并执行SQL
        PreparedStatement pstmt = conn.prepareStatement(sql);
        // 得到结果集
        ResultSet rs = pstmt.executeQuery();
        // 得到结果集元数据
        ResultSetMetaData metaData = rs.getMetaData();
        Class<?> cls = Class.forName("com.itheima.demo11PreparedStatement实现查询.Employee");
        ArrayList<Employee> list = new ArrayList<>();
        while (rs.next()) {
            Employee e = new Employee();
            // 自动封装查询的结果到对象
            // 得到结果集中字段的数量(列数)
            int count = metaData.getColumnCount();
            for (int i = 1; i <= count; i++) { // i = 1, 2, 3, 4
                // 下面语句思路:得到结果集中某个字段的值,并且放到对象的某个成员变量中

                // 得到字段名,也就是得到类的成员变量名
                String typeName = metaData.getColumnName(i);
                // 通过字段名得到字段值,也就是得到类的成员变量对应的值
                Object value = rs.getObject(typeName);

                //问题来了,如何把值赋值给对应的成员变量呢?
                // 答:通过反射,可以给成员变量赋值
                Field field = cls.getDeclaredField(typeName.toLowerCase());//有些字段值是大写如Name,与Employee类中的name成员变量不同(毕竟java是区分大小写的)
                field.setAccessible(true);//暴力反射
                field.set(e, value);
            }

            // 经过循环后,所有字段都赋值啦.
            list.add(e);
        }

        // 释放资源
        JDBCUtils.close(conn, pstmt, rs);

        for (Employee e : list) {
            System.out.println("e = " + e);
        }
    }
}

项目分层介绍

分层介绍-公司中分层

[外链图片转存失败(img-uma7KNo4-1565017901651)(分层00.png)]

软件中分层

[外链图片转存失败(img-ey5EPbQ0-1565017901652)(分层03.png)]

分层的作用:

  1. 解耦:降低层与层之间的耦合性。
  2. 可维护性:提高软件的可维护性,对现有的功能进行修改和更新时不会影响原有的功能。
  3. 可扩展性:提升软件的可扩展性,添加新的功能的时候不会影响到现有的功能。

连接池介绍

目标

学习连接池的原理和好处

讲解

没有连接池的现状
  1. 之前JDBC访问数据库的步骤:
    创建数据库连接运行SQL语句关闭连接
    每次数据库访问执行这样重复的动作
    [外链图片转存失败(img-gpS9THT4-1565017901653)(/连接池01.png)]

  2. 每次创建数据库连接的问题

    • 获取数据库连接需要消耗比较多的资源,而每次操作都要重新获取新的连接对象,执行一次操作就把连接关闭,而数据库创建连接通常需要消耗相对较多的资源,创建时间也较长。这样数据库连接对象的使用率低。
    • 假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出

    我们现实生活中每日三餐。我们并不会吃一餐饭就将碗丢掉,而是吃完饭后将碗放到碗柜中,下一餐接着使用。目的是重复利用碗,我们的数据库连接也可以重复使用,可以减少数据库连接的创建次数。提高数据库连接对象的使用率。

连接池的概念: 连接池是创建和管理数据库连接的缓冲池技术。连接池就是一个容器,连接池中保存了一些数据库连接,这些连接是可以重复使用的。

连接池的原理
  1. 当程序启动的时候,我们就建立一个连接池,先创建一些连接
  2. 当有人需要使用连接的时候,直接从连接池中取出一些连接
  3. 当连接使用完的时候,还回连接池[外链图片转存失败(img-3xGoD6lC-1565017901655)(/1551614039056.png)]
连接池好处

连接池中保存了一些数据库连接,这些连接是可以重复使用的。节省数据库的资源消耗。

常用连接池的介绍

javax.sql.DataSource表示数据库连接池,是JDK中提供的一个接口,没有具体的实现,它的实现由连接池的厂商去实现。我们只需要学习这个工具如何使用即可。

public interface DataSource {
	Connection getConnection();
	...
}

常用的连接池实现组件有以下这些

  1. 阿里巴巴-德鲁伊Druid连接池:Druid是阿里巴巴开源平台上的一个项目
  2. C3P0是一个开源的连接池,目前使用它的开源项目有Hibernate,Spring等。
  3. DBCP(DataBase Connection Pool)数据库连接池,是Apache上的一个Java连接池项目,也是Tomcat使用的连接池组件。

小结

连接池的好处?
连接池中会保存一些连接,这些连接可以重复使用,降低数据库的资源消耗

连接池的原理?

1.启动连接池时,连接池会初始化一些连接
2.当有人要用连接时,从连接池中取出一个连接
3.当连接使用完毕后,还回连接池中

C3P0连接池

目标

学习C3P0连接池

[外链图片转存失败(img-gZeh3uOM-1565017901657)(/1551619323240.png)]

讲解

C3P0连接池简介

C3P0地址:https://sourceforge.net/projects/c3p0/?source=navbar
C3P0是一个开源的连接池。Hibernate框架,默认推荐使用C3P0作为连接池实现。
C3P0的jar包:c3p0-0.9.1.2.jar
[外链图片转存失败(img-CyoyL3t8-1565017901658)(/1550387401441.png)]

C3P0常用的配置参数解释
参数说明
initialPoolSize连接池刚启动时,连接池内包含的连接数量
maxPoolSize连接池中最多可以放多少个连接
checkoutTimeout连接池中没有连接时最长等待时间
maxIdleTime连接池中的空闲连接多久没有使用就会回收。默认是0,0表示不回收
C3P0配置文件
我们看到要使用C3P0连接池,需要设置一些参数。那么这些参数怎么设置最为方便呢?使用配置文件方式。

配置文件的要求:

  1. 文件名:c3p0-config.xml
  2. 放在源代码即src目录下

配置文件c3p0-config.xml

 <c3p0-config>
   <!-- 使用默认的配置读取连接池对象 -->
   <default-config>
     <!--  连接参数 -->
     <property name="driverClass">com.mysql.jdbc.Driver</property>
     <property name="jdbcUrl">jdbc:mysql://localhost:3306/day16</property>
     <property name="user">root</property>
     <property name="password">root</property>

     <!-- 连接池参数 -->
     <property name="initialPoolSize">5</property>
     <property name="maxPoolSize">10</property>
     <property name="checkoutTimeout">2000</property>
     <property name="maxIdleTime">1000</property>
   </default-config>

   <named-config name="itheimac3p0"> 
     <!--  连接参数 -->
     <property name="driverClass">com.mysql.jdbc.Driver</property>
     <property name="jdbcUrl">jdbc:mysql://localhost:3306/day25</property>
     <property name="user">root</property>
     <property name="password">root</property>

     <!-- 连接池参数 -->
     <property name="initialPoolSize">5</property>
     <property name="maxPoolSize">15</property>
     <property name="checkoutTimeout">2000</property>
     <property name="maxIdleTime">1000</property>
   </named-config>
 </c3p0-config>
API介绍

com.mchange.v2.c3p0.ComboPooledDataSource类表示C3P0的连接池对象,常用2种创建连接池的方式:

1.无参构造,使用默认配置

2.有参构造,使用命名配置

  1. public ComboPooledDataSource()
    无参构造使用默认配置(使用xml中default-config标签中对应的参数)
    
  2. public ComboPooledDataSource(String configName)
    有参构造使用命名配置(configName:xml中配置的名称,使用xml中named-config标签中对应的参数)
    
  3. public Connection getConnection() throws SQLException
    从连接池中取出一个连接
    
使用步骤
  1. 导入jar包c3p0-0.9.1.2.jar
  2. 编写c3p0-config.xml配置文件,配置对应参数
  3. 将配置文件放在src目录下
  4. 创建连接池对象ComboPooledDataSource,使用默认配置或命名配置
  5. 从连接池中获取连接对象
  6. 使用连接对象操作数据库
  7. 关闭资源,将连接还回连接池中
注意事项

C3P0配置文件名称必须为c3p0-config.xml
C3P0命名配置可以有多个

案例代码
  1. 准备数据

    CREATE TABLE student (
       id INT PRIMARY KEY AUTO_INCREMENT,
       NAME VARCHAR(20),
       age INT,
       score DOUBLE DEFAULT 0.0
    );
    
  2. 配置文件

     <c3p0-config>
       <!-- 使用默认的配置读取连接池对象 -->
       <default-config>
         <!--  连接参数 -->
         <property name="driverClass">com.mysql.jdbc.Driver</property>
         <property name="jdbcUrl">jdbc:mysql://localhost:3306/day25</property>
         <property name="user">root</property>
         <property name="password">root</property>
    
         <!-- 连接池参数 -->
         <property name="initialPoolSize">5</property>
         <property name="maxPoolSize">10</property>
         <property name="checkoutTimeout">2000</property>
         <property name="maxIdleTime">1000</property>
       </default-config>
    
       <named-config name="itheimac3p0"> 
         <!--  连接参数 -->
         <property name="driverClass">com.mysql.jdbc.Driver</property>
         <property name="jdbcUrl">jdbc:mysql://localhost:3306/day25</property>
         <property name="user">root</property>
         <property name="password">root</property>
    
         <!-- 连接池参数 -->
         <property name="initialPoolSize">5</property>
         <property name="maxPoolSize">15</property>
         <property name="checkoutTimeout">2000</property>
         <property name="maxIdleTime">1000</property>
       </named-config>
     </c3p0-config>
    
  3. java代码

     public class Demo01 {
    
         public static void main(String[] args) throws Exception {
             // 方式一: 使用默认配置(default-config)
             // new ComboPooledDataSource();
     //		 ComboPooledDataSource ds = new ComboPooledDataSource();
    
             // 方式二: 使用命名配置(named-config:配置名)
             // new ComboPooledDataSource("配置名");
             ComboPooledDataSource ds = new ComboPooledDataSource("otherc3p0");
    
     //		for (int i = 0; i < 10; i++) {
     //			Connection conn = ds.getConnection();
     //			System.out.println(conn);
     //		}
    
             // 从连接池中取出连接
             Connection conn = ds.getConnection();
    
             // 执行SQL语句
             String sql = "INSERT INTO student VALUES (NULL, ?, ?, ?);";
             PreparedStatement pstmt = conn.prepareStatement(sql);
             pstmt.setString(1, "张三");
             pstmt.setInt(2, 25);
             pstmt.setDouble(3, 99.5);
    
             int i = pstmt.executeUpdate();
             System.out.println("影响的行数: " + i);
             pstmt.close();
             conn.close(); // 将连接还回连接池中
         }
     }
    
案例效果
  1. 正常获取连接池中连接
    [外链图片转存失败(img-cLadAi6R-1565017901660)(/连接池06.png)]

  2. 获取连接池中连接超时
    [外链图片转存失败(img-1JUecuXD-1565017901661)(/连接池07.png)]

  3. 使用连接池中的连接往数据库添加数据
    [外链图片转存失败(img-fi7PmQRu-1565017901663)(/连接池05.png)]

注意:配置文件名称必须为:c3p0-config.xml,将配置文件放在src目录下

使用配置文件方式好处

只需要单独修改配置文件,不用修改代码
多个配置的好处:

  1. 可以连接不同的数据库:db1,db2
  2. 可以使用不同的连接池参数:maxPoolSize
  3. 可以连接不同厂商的数据库:Oracle或MySQL

小结

CP30使用步骤?

  1. 导入Jar包:c3p0-0.9.5.2.jar,mchange-commons-java-0.2.12.jar
  2. 复制c3p0-config.xml,修改参数
  3. c3p0-config.xml放到src里面
  4. 在代码中创建C3P0连接池对象,CombopooledDataSource对象
  5. 从连接池中获取连接
  6. 执行SQL语句
  7. 将连接还回连接池中

C3P0常用参数?

initialPoolSize启动连接池时,初始化的连接数量
maxPoolSize连接池最大的连接数量
checkoutTimeout连接池没有连接时,最大的等待时间,单位是毫秒
maxIdleTime连接池中的连接空闲多久会被销毁,默认是0,0表示不销毁

Druid连接池

目标

学习Druid连接池的使用

[外链图片转存失败(img-M9Gg4gJA-1565017901664)(/1551620535241.png)]

讲解

Druid是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池,同时加入了日志监控,可以很好的监控数据库连接池和SQL的执行情况。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。Druid地址:https://github.com/alibaba/druid 

DRUID连接池使用的jar包:druid-1.0.9.jar

[外链图片转存失败(img-a1h0B2rS-1565017901665)(/1550388331344.png)]

Druid常用的配置参数
参数说明
initialSize刚启动连接池时,连接池中包含连接的数量
maxActive连接池中最多可以放多少个连接
maxWait获取连接时最大等待时间,单位毫秒
Druid连接池基本使用

API介绍

com.alibaba.druid.pool.DruidDataSourceFactory类有创建连接池的方法

public static DataSource createDataSource(Properties properties)
创建一个连接池,连接池的参数使用properties中的数据

我们可以看到Druid连接池在创建的时候需要一个Properties对象来设置参数,所以我们使用properties文件来保存对应的参数。
Druid连接池的配置文件名称随便,放到src目录下面方便加载
druid.properties文件内容:

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/day17
username=root
password=root
initialSize=5
maxActive=10
maxWait=3000
使用步骤
  1. 导入druid的jar包

  2. 在src目录下创建一个properties文件,并设置对应参数

  3. 加载properties文件的内容到Properties对象中

  4. 创建Druid连接池,使用配置文件中的参数

  5. 从Druid连接池中取出连接

  6. 执行SQL语句

  7. 关闭资源

案例代码
  1. 在src目录下新建一个Druid配置文件,命名为:druid.properties,内容如下
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/day25
username=root
password=root
initialSize=5
maxActive=10
maxWait=3000

java代码

public class Demo04 {
	public static void main(String[] args) throws Exception {
		// 加载配置文件中的配置参数
		InputStream is = Demo04.class.getResourceAsStream("/druid.properties");
		Properties pp = new Properties();
		pp.load(is);
		
		// 创建连接池,使用配置文件中的参数
		DataSource ds = DruidDataSourceFactory.createDataSource(pp);
		
//		for (int i = 0; i < 10; i++) {
//			Connection conn = ds.getConnection();
//			System.out.println(conn);
//		}
		
		// 从连接池中取出连接
		Connection conn = ds.getConnection();
		
		// 执行SQL语句
		String sql = "INSERT INTO student VALUES (NULL, ?, ?, ?);";
		PreparedStatement pstmt = conn.prepareStatement(sql);
		pstmt.setString(1, "王五");
		pstmt.setInt(2, 35);
		pstmt.setDouble(3, 88.5);
		
		int i = pstmt.executeUpdate();
		System.out.println("影响的行数: " + i);
		
		// 执行查询
		sql = "SELECT * FROM student;";
		ResultSet rs = pstmt.executeQuery(sql);
		
		while (rs.next()) {
			int id = rs.getInt("id");
			String name = rs.getString("name");
			int age = rs.getInt("age");
			double score = rs.getDouble("score");
			System.out.println("id: " + id + " ,name: " + name + " ,age = " + age + " ,score = " + score);
		}
		
		pstmt.close();
		conn.close(); // 将连接还回连接池中
	}
}
案例效果
  1. 正常获取连接池中的连接
    [外链图片转存失败(img-BjUJkL3e-1565017901667)(/连接池09.png)]
  2. 获取连接池中的连接超时
    [外链图片转存失败(img-QXGNvHRD-1565017901668)(/连接池10.png)]
  3. 使用DRUID连接池中的连接操作数据库
    [外链图片转存失败(img-DyoGS3q7-1565017901669)(/连接池11.png)]

小结

Druid使用步骤?

  1. 导入Druid的Jar包
  2. 复制druid.properties到src下,并修改配置
  3. 在代码中加载properties到Properties对象中
  4. 创建Druid连接池
  5. 从Druid连接池中取出连接
  6. 执行SQL
  7. 关闭

Druid常用的配置参数

参数说明
initialSize刚启动连接池时,初始化连接的数量
maxActive连接池中最大的连接数量
maxWait没有连接时,最大的等待时间

Druid连接池基本使用不管是C3P0连接池,配置大致都可以分为2种:1.连接数据库的参数2.连接池的参数,这2种配置大致参数作用都相同,只是参数名称可能不一样。

编写连接池工具类

我们每次操作数据库都需要创建连接池,获取连接,关闭资源,都是重复的代码。我们可以将创建连接池和获取连接池的代码放到一个工具类中,简化代码。

目标

编写一个连接池的工具类,简化连接池的使用

连接池工具类步骤
  1. 声明静态连接池成员变量
  2. 创建连接池对象
  3. 定义得到连接池的方法
  4. 定义得到连接对象的方法
  5. 定义关闭资源的方法
案例代码

DataSourceUtils.java

public class DataSourceUtils {
	// 1.声明静态数据源成员变量
	private static DataSource ds;

	// 2.创建连接池对象
	static {
		// 加载配置文件中的数据
		InputStream is = DataSourceUtils.class.getResourceAsStream("/druid.properties");
		Properties pp = new Properties();
		try {
			pp.load(is);
			// 创建连接池,使用配置文件中的参数
			ds = DruidDataSourceFactory.createDataSource(pp);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// 3. 定义得到数据源的方法
	public static DataSource getDataSource() {
		return ds;
	}

	// 4. 定义得到连接对象的方法
	public static Connection getConnection() throws SQLException {
		return ds.getConnection();
	}

	// 5.定义关闭资源的方法
	public static void close(Connection conn, Statement stmt, ResultSet rs) {
		if (rs != null) {
			try {
				rs.close();
			} catch (SQLException e) {}
		}

		if (stmt != null) {
			try {
				stmt.close();
			} catch (SQLException e) {}
		}

		if (conn != null) {
			try {
				conn.close();
			} catch (SQLException e) {}
		}
	}

	// 6.重载关闭方法
	public static void close(Connection conn, Statement stmt) {
		close(conn, stmt, null);
	}
}

测试类代码

public class Demo03 {
	public static void main(String[] args) throws Exception {
		// 拿到连接
		Connection conn = DataSourceUtils.getConnection();

		// 执行sql语句
		String sql = "INSERT INTO student VALUES (NULL, ?, ?, ?);";
		PreparedStatement pstmt = conn.prepareStatement(sql);
		pstmt.setString(1, "李四");
		pstmt.setInt(2, 30);
		pstmt.setDouble(3, 50);
		int i = pstmt.executeUpdate();
		System.out.println("影响的函数: " + i);

		// 关闭资源
		DataSourceUtils.close(conn, pstmt);
	}
}

使用连接池工具类后可以简化代码,我们重点是写SQL去执行。

小结

编写连接池工具类步骤

  1. 定义了静态的连接池成员变量
  2. 在静态代码块中创建连接池对象
  3. 定义一个方法返回连接池
  4. 定义一个方法返回连接
  5. 定义close方法,关闭资源

总结

  1. 能够理解JDBC的概念

    Java数据库连接,是Java操作数据库的规范

  2. 能够使用Connection接⼝

    Connection conn = DriverManager.getConnection("jdbc:mysql:///day16", "root", "root");
    
    Statement stmt = conn.createStatement();
    
  3. 能够使用Statement接⼝

    执行SQL语句的对象,相当于小货车
    
    Statement stmt = conn.createStatement();
    int i = stmt.executeUpdate(sql);
    ResultSet rs = stmt.executeQuery(sql);
    
  4. 能够使用ResultSet接⼝

    ResultSet接⼝:
    boolean next(); 将游标向下移动一行,如果有数据返回true,没有数据返回false
    Xxx getXxx(); 得到字段的值
    
    ResultSet rs = stmt.executeQuery("SELECT * FROM category;");
    // 5.ResultSet处理结果
    while (rs.next()) {
    	int cid = rs.getInt(1);
    	String cname = rs.getString(2);
    	System.out.println(cid + "::" + cname);
    }
    
  5. 能够使用JDBC实现对单表数据增、删、改、查

    // 1.注册驱动
    Class.forName("com.mysql.jdbc.Driver");
    // 2.获取连接
    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day16", "root", "root");
    // 3.获取小货车
    Statement stmt = conn.createStatement();
    // 4.执行SQL语句
    // 添加数据
    // String sql = "INSERT INTO category (cname) VALUES ('情趣用品');";
    
    // 修改数据
    // String sql = "UPDATE category SET cname='计生用品' WHERE cid=2;";
    
    // 删除数据
    String sql = "DELETE FROM category WHERE cid=3;";
    // int executeUpdate(String sql)
    int i = stmt.executeUpdate(sql);
    System.out.println("影响的行数: " + i);
    
    // 5.关闭资源
    stmt.close();
    conn.close();
    
  6. 能够使用JDBC操作事务

    操作事务的方法在Connection中
    void setAutoCommit(false): 开启事务
    void commit(); 提交事务
    void rollback(); 回滚事务
    
    
    public static void main(String[] args) {
    	Connection conn = null;
    	Statement stmt = null;
    	try {
    		// 1.注册驱动
    		Class.forName("com.mysql.jdbc.Driver");
    		// 2.获取连接
    		conn = DriverManager.getConnection("jdbc:mysql:///day16", "root", "root");
    		// 3.开启事务
    		// false: 表示关闭自动提交,开始事务
    		conn.setAutoCommit(false);
    		// 4.获取到Statement
    		stmt = conn.createStatement();
    		// 5.使用Statement执行SQL
    		// 张三-500,李四+500
    		stmt.executeUpdate("UPDATE account SET balance = balance - 500 WHERE id=1;");
    
    		// 模拟出问题啦
    		int a = 10 / 0;
    
    		stmt.executeUpdate("UPDATE account SET balance = balance + 500 WHERE id=2;");
    
    		// 没有出问题就提交事务
    		System.out.println("提交事务");
    		conn.commit();
    	} catch (Exception e) {
    		// 6.提交或回滚事务
    		System.out.println("出问题啦,回滚事务");
    		try {
    			conn.rollback();
    		} catch (SQLException ex) {
    			System.out.println("回滚失败");
    		}
    	} finally {
    		// 7.关闭资源
    		if (conn != null) {
    			try {
    				conn.close();
    			} catch (SQLException e) {
    				e.printStackTrace();
    			}
    		}
    
    		if (stmt != null) {
    			try {
    				stmt.close();
    			} catch (SQLException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
  7. 能够完成JDBC实现登录案例

    核心去数据中查询是否有用户输入账号和密码
    public static void main(String[] args) throws SQLException {
    	// 1.使用数据库保存用户的账号和密码
    	// 2.让用户输入账号和密码
    	Scanner sc = new Scanner(System.in);
    	System.out.println("请输入账号:");
    	String name = sc.nextLine();
    	System.out.println("请输入密码:");
    	String password = sc.nextLine();
    
    	// 3.使用SQL根据用户的账号和密码去数据库查询数据
    	String sql = "SELECT * FROM user WHERE name='" + name+ "' AND password='" + password + "';";
    
    	// SELECT * FROM user WHERE name='nba' AND password='a' or '1'='1';
    	System.out.println(sql);
    
    	Connection conn = JDBCUtils.getConnection();
    	Statement stmt = conn.createStatement();
    	ResultSet rs = stmt.executeQuery(sql);
    	if (rs.next()) {
    		// 查到用户
    		// 4.如果查询到数据,说明登录成功
    		System.out.println("恭喜您, " + name + "登录成功!");
    	} else {
    		// 没有查到用户
    		// 5.如果查询不到数据,说明登录失败
    		System.out.println("用户名或密码错误");
    	}
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值