文章目录
1、数据库驱动
驱动:声卡、显卡、数据库都有驱动,利用驱动程序它们才能被应用程序使用并正常工作!
小结:我们的程序会通过数据库驱动和数据库来打交道!
2、JDBC
概念:
- SUN 公司为了简化开发人员的(对数据库的统一)操作,提供了一个(Java操作数据库的)规范,俗称 JDBC。
- 这些规范的实现由具体的厂商来做。
- 对于开发人员来说,我们只需要掌握JDBC接口的操作即可!
- JDBC是一组通过Java语言操作数据库的包(是一组使用数据库驱动的库文件)
JDBC组件的层次:(计算机的著名格言:没有什么问题是加一层解决不了的)
3、第一个JDBC程序
创建测试数据库
CREATE DATABASE jdbcStudy CHARACTER SET utf8 COLLATE utf8_general_ci;
USE jdbcStudy;
CREATE TABLE users(
id INT PRIMARY KEY,
NAME VARCHAR(40),
PASSWORD VARCHAR(40),
email VARCHAR(60),
birthday DATE
);
INSERT INTO users(id,NAME,PASSWORD,email,birthday) VALUES(1,'zhansan','123456','zs@sina.com','1980-12-04'), (2,'lisi','123456','lisi@sina.com','1981-12-04'),
(3,'wangwu','123456','wangwu@sina.com','1979-12-04');
JDBC使用:
1、创建一个普通的Java项目
2、首先、创建一个lib目录,将jdbc的包放入其中
3、 其次,导入数据库驱动:
4、导入成功的结果:
5、编写测试代码:
import java.sql.*;
/**
* @Author: 58378
* @CreateTime: 2020-02-10 15:49
* @Description:西北大学信科院709
*/
public class JdbcFirstDemo {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 1.加载驱动:
Class.forName("com.mysql.jdbc.Driver");
// 2.用户信息和url
// useUnicode=true&characterEncoding=utf8&useSSL=true
String url = "jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true";
String username = "root";
String password = "123456";
// 3.连接成功,返回数据库对象 Connection (代表数据库)
Connection connection = DriverManager.getConnection(url, username, password);
// 4.获取执行SQL的对象 Statement
Statement statement = connection.createStatement();
// 5.编写需要执行的SQL语句:
String sql = "SELECT * FROM users";
// 6.利用执行SQL的对象去执行SQL,可能存在结果,查看返回结果
ResultSet resultSet = statement.executeQuery(sql); //结果中封装了我们全部查询出来的结果
while (resultSet.next()){
System.out.println("id="+resultSet.getObject("id"));
System.out.println("name="+resultSet.getObject("NAME"));
System.out.println("pwd="+resultSet.getObject("PASSWORD"));
System.out.println("email="+resultSet.getObject("email"));
System.out.println("birth="+resultSet.getObject("birthday"));
System.out.println("===========================");
}
// 7.释放连接(倒序释放,跟创建的顺序相反)
resultSet.close();
statement.close();
connection.close();
}
}
---------------------
输出:
id=1
name=zhansan
pwd=123456
email=zs@sina.com
birth=1980-12-04
===========================
id=2
name=lisi
pwd=123456
email=lisi@sina.com
birth=1981-12-04
===========================
id=3
name=wangwu
pwd=123456
email=wangwu@sina.com
birth=1979-12-04
===========================
Class.forName()主要功能:
- Class.forName(xxx.xx.xx)返回的是一个类。
- Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段。
com.mysql.jdbc.Driver类的源码如下:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());//首先new一个Driver对象,并将它注册到DriverManage中
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
接下来我们再看看这个DriverManager.registerDriver 方法:
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
registerDriver(driver, null);
}
继续看这个registerDriver(driver, null) 方法:
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();// registeredDrivers 是一个支持并发的arraylist
......
public static void registerDriver(java.sql.Driver driver, DriverAction da)
throws SQLException {
if (driver != null) {
//如果该驱动尚未注册,那么将他添加到 registeredDrivers 中去。这是一个支持并发情况的特殊ArrayList
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
此时,Class.forName(“com.mysql.jdbc.Driver”) 的工作就完成了。工作就是:将mysql驱动注册到DriverManager中去。
步骤总结:
1、加载驱动
2、连接数据库 DiverManager
3、获取执行sql的对象 Statement
4、利用Statement 对象去执行我们的sql语句并返回结果
5、释放连接
DiverManager:
// DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver"); // 固定写法,加载驱动
Connection connection = DriverManager.getConnection(url, username, password); // connection 代表数据库
connection.setAutoCommit(); // 数据库设置自动提交
connection.commit(); // 事务提交
connection.rollback(); // 事务滚回
URL
String url = "jdbc:mysql://localhost:3306/jdbcstudy? useUnicode=true&characterEncoding=utf8&useSSL=true"; // mysql -- 3306
// 协议 ://主机地址:端口号/数据库名?参数1&参数2&参数3
// oralce -- 1521 (Oracle的链接url和Mysql不一样!)
//jdbc:oracle:thin:@localhost:1521:sid 123456
Statement 是执行SQL的对象,PrepareStatement 执行SQL的对象(防止SQL注入)
String sql = "SELECT * FROM users"; // 编写SQL
statement.executeQuery(); //查询操作返回 ResultSet
statement.execute(); // 执行任何SQL,用于执行返回多个结果集、多个更新计数或二者组合的语句(一般用不到!)
statement.executeUpdate(); // 更新、插入、删除。都是用这个,返回一个受影响的行数
ResultSet 查询的结果集:封装了所有的查询结果
获得指定的数据类型:
resultSet.getObject(); // 在不知道列类型的情况下使用
// 如果知道列的类型就使用指定的类型
resultSet.getString();
resultSet.getInt();
resultSet.getFloat();
resultSet.getDate();
resultSet.getObject();
....
遍历,指针
resultSet.beforeFirst(); // 移动到最前面
resultSet.afterLast(); // 移动到最后面
resultSet.next(); //移动到下一个数据
resultSet.previous(); //移动到前一行
resultSet.absolute(row); //移动到指定行
释放资源
//6、释放连接
resultSet.close();
statement.close();
connection.close(); // 耗资源,用完关掉!
10.4、Statement 对象
概念:Jdbc中的statement对象用于向数据库发送SQL语句,想完成对数据库的增删改查,只需要通过这个对象向数据库发送增删改查语句即可。
Statement对象的查用方法:
- Statement对象的executeUpdate方法,用于向数据库发送增、删、改的sql语句,executeUpdate执行完后,将会返回一个整数(即增删改语句导致了数据库几行数据发生了变化)。
- Statement.executeQuery方法用于向数据库发送查询语句,executeQuery方法返回代表查询结果的ResultSet对象。
CRUD操作:create
使用executeUpdate(String sql)方法完成数据添加操作,示例操作:
Statement st = conn.createStatement();
String sql = "insert into user(….) values(…..) ";
int num = st.executeUpdate(sql);
if(num>0){
System.out.println("插入成功!!!");
}
CRUD操作:delete
使用executeUpdate(String sql)方法完成数据删除操作,示例操作:
Statement st = conn.createStatement();
String sql = "delete from user where id=1";
int num = st.executeUpdate(sql);
if(num>0){
System.out.println(“删除成功!!!");
}
CRUD操作:update
使用executeUpdate(String sql)方法完成数据修改操作,示例操作:
Statement st = conn.createStatement();
String sql = "update user set name='' where name=''";
int num = st.executeUpdate(sql);
if(num>0){
System.out.println(“修改成功!!!");
}
CRUD操作:read
使用executeQuery(String sql)方法完成数据查询操作,示例操作:
Statement st = conn.createStatement();
String sql = "select * from user where id=1";
ResultSet rs = st.executeQuery(sql);
while(rs.next()){
//根据获取列的数据类型,分别调用rs的相应方法映射到java对象中
}
代码实现
在src下建立资源文件db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy?
useUnicode=true&characterEncoding=utf8&useSSL=true
username=root
password=123456
1、提取工具类:
import java.io.InputStream;
import java.util.Properties;
import java.sql.*;
/**
* @Author: 58378
* @CreateTime: 2020-02-10 16:47
* @Description:西北大学信科院709
*/
public class JdbcUtils {
private static String driver = null;
private static String url = null;
private static String username = null;
private static String password = null;
// 加载静态代码块(加载驱动!)
static {
try{
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(in);
driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
// 驱动只用加载一次:
Class.forName(driver);
} catch (Exception e){
e.printStackTrace();
}
}
// 获取连接
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url,username,password);
}
// 释放连接资源:
public static void release(Connection conn,Statement st, ResultSet rs){
if(rs!=null){
try {
rs.close();
}catch (SQLException e){
e.printStackTrace();
}
}
if(st!=null){
try {
st.close();
}catch (SQLException e){
e.printStackTrace();
}
}
if(conn !=null){
try {
conn.close();
}catch (SQLException e){
e.printStackTrace();
}
}
}
}
2、编写增删改的方法:executeUpdate
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* @Author: 58378
* @CreateTime: 2020-02-10 17:03
* @Description:西北大学信科院709
*/
public class TestInsert {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection(); //获取数据库连接
st = conn.createStatement(); // 获取SQL的执行对象
String sql = "INSERT INTO users(id,`NAME`,`PASSWORD`,`email`,`birthday`)"+"VALUES(4,'xiaobo','123456','583783843@qq.com','2020-01-01')";
int i = st.executeUpdate(sql);
if(i > 0){
System.out.println("插入成功!");
}
}catch (SQLException e){
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
}
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* @Author: 58378
* @CreateTime: 2020-02-10 17:10
* @Description:西北大学信科院709
*/
public class TestDelete {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
st = conn.createStatement();
String sql = "DELETE FROM users WHERE id=4";
int i = st.executeUpdate(sql);
if(i > 0){
System.out.println("删除成功!");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
}
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* @Author: 58378
* @CreateTime: 2020-02-10 17:13
* @Description:西北大学信科院709
*/
public class TestUpdate {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
st = conn.createStatement();
String sql = "UPDATE users SET `NAME`='xiaoduo',`email`='584783843@qq.com' WHERE id=1";
int i = st.executeUpdate(sql);
if(i>0){
System.out.println("更新成功!");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3、查询executeQuery
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* @Author: 58378
* @CreateTime: 2020-02-10 17:18
* @Description:西北大学信科院709
*/
public class TestSelect {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
st = conn.createStatement();
String sql = "SELECT * FROM users where id =1";
rs = st.executeQuery(sql);
while (rs.next()){
System.out.println(rs.getString("NAME"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
}
SQL注入的问题:
概念:sql作为一种解释型语言,在运行时是由一个运行时组件解释语言代码并执行其中包含的指令的语言。基于这种执行方式,产生了一系列叫做代码注入(code injection)的漏洞 。它的数据其实是由程序员编写的代码和用户提交的数据共同组成的。程序员在web开发时,没有过滤敏感字符,绑定变量,导致攻击者可以通过sql灵活多变的语法,构造精心巧妙的语句,不择手段,达成目的,或者通过系统报错,返回对自己有用的信息。
案例:(SQL注入演示)
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* @Author: 58378
* @CreateTime: 2020-02-11 02:19
* @Description:西北大学信科院709
*/
public class SQL注入 {
public static void main(String[] args) {
// login("xiaobo","123456");
login(" ' or '1=1"," ' or '1=1"); //SQL拼接技巧!(因为where针对所有数据行返回的结果都为True,索引返回了所有的数据!)
}
// 登录业务
public static void login(String username, String password){
Connection conn =null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
st = conn.createStatement();
// SELECT * FROM users WHERE `NAME`='xiaoduo' AND `PASSWORD` = '123456';
// SELECT * FROM users WHERE `NAME`='' or '1=1' AND `PASSWORD` = '' or '1=1';
String sql = "select * from users where `NAME`='"+username+"' AND `PASSWORD`='"+password+"'";
rs = st.executeQuery(sql);
while (rs.next()){
System.out.println(rs.getString("NAME"));
System.out.println(rs.getString("password"));
System.out.println("======================");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(conn,st,rs);
}
}
}
------------------
输出:(返回了数据库中的所有数据!)
xiaoduo
123456
======================
lisi
123456
======================
wangwu
123456
======================
5、PreparedStatement 对象
**概念:**预编译语句java.sql.PreparedStatement ,扩展自 Statement,不但具有 Statement 的所有能力而且具有更强大的功能。不同的是,PreparedStatement 是在创建语句对象的同时给出要执行的sql语句。这样,sql语句就会被系统进行预编译,执行的速度会有所增加,尤其是在执行大语句的时候,效果更加理想。而且PreparedStatement中绑定的sql语句是可以带参数的。
1、插入数据:
package edu.nwu.prepared;
import edu.nuw.test.JdbcUtils;
import java.sql.Connection;
import java.util.Date;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @Author: 58378
* @CreateTime: 2020-02-11 02:37
* @Description:西北大学信科院709
*/
public class TestInsert {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
try {
conn = JdbcUtils.getConnection();
// 与普通SQL的区别:使用?占位符代替参数:
String sql = "insert into users(`id`,`NAME`,`PASSWORD`,`email`,`birthday`)values(?,?,?,?,?)";
st = conn.prepareStatement(sql);
// 手动给参数赋值:
st.setInt(1,4);
st.setString(2,"xiaobo");
st.setString(3,"123124");
st.setString(4,"123456789@qq.com");
st.setDate(5, new java.sql.Date(new Date().getTime()));
// 注意点: java.sql.Date() 是数据库的时间日期的类
// 其参数是 new Date().getTime() 获得时间戳。
// 执行
int i = st.executeUpdate();
if(i>0){
System.out.println("插入成功!");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
2、删除数据:
package edu.nwu.prepared;
/**
* @Author: 58378
* @CreateTime: 2020-02-11 02:52
* @Description:西北大学信科院709
*/
import edu.nuw.test.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
public class TestDelete {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
try {
conn = JdbcUtils.getConnection();
// 区别
// 使用? 占位符代替参数
String sql = "delete from users where id=?";
st = conn.prepareStatement(sql); //预编译SQL,先写sql,然后不执行
// 手动给参数赋值
st.setInt(1, 4);
//执行
int i = st.executeUpdate();
if (i > 0) {
System.out.println("删除成功!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(conn, st, null);
}
}
}
3、更新数据:
package edu.nwu.prepared;
/**
* @Author: 58378
* @CreateTime: 2020-02-11 02:54
* @Description:西北大学信科院709
*/
import edu.nuw.test.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TestUpdate {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
try {
conn = JdbcUtils.getConnection();
// 区别
// 使用? 占位符代替参数
String sql = "update users set `NAME`=? where id=?;";
st = conn.prepareStatement(sql); //预编译SQL,先写sql,然后不执行
// 手动给参数赋值
st.setString(1, "小波");
st.setInt(2, 1);
//执行
int i = st.executeUpdate();
if (i > 0) {
System.out.println("更新成功!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(conn, st, null);
}
}
}
4、查询数据:
package edu.nwu.prepared;
/**
* @Author: 58378
* @CreateTime: 2020-02-11 02:56
* @Description:西北大学信科院709
*/
import edu.nuw.test.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TestSelect {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
String sql = "select * from users where id = ?"; // 编写SQL
st = conn.prepareStatement(sql); // 预编译
st.setInt(1, 2); //传递参数
rs = st.executeQuery(); //执行
if (rs.next()) {
System.out.println(rs.getString("NAME"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(conn, st, rs);
}
}
}
5、防止SQL注入:
本质,把传递进来的参数当做字符,假设其中存在转义字符,比如说 ’ 则会被直接转义。
package edu.nwu.prepared;
/**
* @Author: 58378
* @CreateTime: 2020-02-11 02:57
* @Description:西北大学信科院709
*/
import edu.nuw.test.JdbcUtils;
import java.sql.*;
public class SQL注入 {
public static void main(String[] args) {
// login("lisi","123456");
login("'' or 1=1", "123456"); //
}
// 登录业务
public static void login(String username, String password) {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
// PreparedStatement 防止SQL注入的本质,把传递进来的参数当做字符
// 假设其中存在转义字符,比如说 ' 会被直接转义
String sql = "select * from users where `NAME`=? and `PASSWORD`=?"; // Mybatis
st = conn.prepareStatement(sql);
st.setString(1, username);
st.setString(2, password);
rs = st.executeQuery(); //查询完毕会返回一个结果集
while (rs.next()) {
System.out.println(rs.getString("NAME"));
System.out.println(rs.getString("password"));
System.out.println("============================");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(conn, st, rs);
}
}
}
---------
输出:(输出若不为空,则说明防止SQL注入失败!)
6、使用IEDA连接数据库(类似于SQLyog)
首先,打开数据库连接界面:
打开界面以后(这里有可能会出现驱动问题,直接连接Download即可),可以选择数据库:
双击需要查看的数据库中的某个表,即可展示:
更新数据:
编写SQL代码:
执行SQL代码:
8、在JDBC中使用事务
原子性特点:要么都执行,要么根本不执行
ACID 原则
原子性:要么全部完成,要么都不完成
一致性:总数不变
隔离性:多个进程互不干扰
持久性:一旦提交不可逆,持久化到数据库了
如果隔离性不完善会导致的问题:(参考MySQL的博客)
- 脏读:一个事务读取了另一个没有提交的事务
- 不可重复读:在同一个事务内,重复读取表中的数据,表数据发生了改变
- 虚读(幻读):在一个事务内,读取到了别人插入的数据,导致前后读出来结果不一致
代码实现:
1、开启事务 conn.setAutoCommit(false);
2、一组业务执行完毕,提交事务
3、可以在catch 语句中显示地定义回滚语句,但默认失败就会回滚。
package edu.nwu.prepared;
/**
* @Author: 58378
* @CreateTime: 2020-02-11 03:46
* @Description:西北大学信科院709
*/
import edu.nuw.test.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TestTranscation2 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
// 关闭数据库的事务自动提交(相当于开启手动事务提交)
conn.setAutoCommit(false);
String sql1 = "update users set PASSWORD = '0000' where name = '小波'";
st = conn.prepareStatement(sql1);
st.executeUpdate();
int x = 1 / 0; // 报错(会执行回滚操作!不执行sql2语句)
String sql2 = "update users set PASSWORD = '0000' where name = 'lisi'";
st = conn.prepareStatement(sql2);
st.executeUpdate();
//业务完毕,提交事务
conn.commit();
System.out.println("成功!");
} catch (SQLException e) {
// 若果失败,则默认回滚
// try {
// conn.rollback(); // 如果失败则回滚事务
// } catch (SQLException e1) {
// e1.printStackTrace();
// }
e.printStackTrace();
} finally {
JdbcUtils.release(conn, st, rs);
}
}
}
------
输出:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at edu.nwu.prepared.TestTranscation2.main(TestTranscation2.java:31)
查看数据表中的变动情况:(未发生变动)
9、数据库连接池
常规JDBC操作流程:
值得注意的是: 连接、释放是十分浪费系统资源(增加系统的运作开销,需要通过网络认证等等)的操作。
由此,我们引入了池化技术: 对于线程,内存,oracle的连接对象等等,这些都是资源,程序中当你创建一个线程或者在堆上申请一块内存时,都涉及到很多系统调用,也是非常消耗CPU的,如果你的程序需要很多类似的工作线程或者需要频繁的申请释放小块内存,如果没有在这方面进行优化,那很有可能这部分代码将会成为影响你整个程序性能的瓶颈。
数据库连接池:
- 数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。 一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的性能低下。
- 数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并将这些连接组成一个连接池(简单说:在一个“池”里放了好多半成品的数据库联接对象),由应用程序动态地对池中的连接进行申请、使用和释放(归还给连接池)。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。
- 连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。通过使用连接池,将大大提高程序运行效率,同时,我们可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
数据库连接池究竟优化了什么?
1、不使用连接池执行Mysql语句的时候的执行流程:
不使用数据库连接池的步骤:
- TCP建立连接的三次握手
- MySQL认证的三次握手
- 真正的SQL执行(我们真正需要执行的功能!)
- MySQL的关闭
- TCP的四次握手关闭
结论:可以看到,为了执行一条SQL,却多了非常多网络交互。
优点:实现简单
缺点:
- 网络IO较多
- 数据库的负载较高
- 响应时间较长及QPS较低
- 应用频繁的创建连接和关闭连接,导致临时对象较多,GC频繁
- 在关闭连接后,会出现大量TIME_WAIT 的TCP状态(在2个MSL之后关闭)
2、使用数据库连接池后我们操作SQL语句的流程:
注意:第一次访问的时候,需要建立连接。 但是之后的访问,均会复用之前创建的连接,直接执行SQL语句(这样就能极大地减少网络交互的开销)。
优点:
- 较少了网络开销
- 系统的性能会有一个实质的提升
- 没了麻烦的TIME_WAIT状态
连接池参数:
- 最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费;
- 最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求将被加入到等待队列中,这会影响之后的数据库操作。
编写连接池的核心:实现一个接口:DataSource(参考后面的示例)
开源连接池实现(拿来即用)
DBCP
CP30
Druid:阿里巴巴
注意:使用了这些数据库连接池以后,我们在项目开发中就不需要编写连接数据库的代码了!
CP30:
需要用的 jar 包:
- c3p0-0.9.5.5
- mchange-commons-java-0.2.19
1、编写配置表:
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!--
C3P0的缺省(默认)配置,
如果在代码中“ComboPooledDataSource ds = new ComboPooledDataSource();”这样写就表示使用的是C3P0的缺省(默认)配置信息来创建数据源
-->
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcStudy?useUnicode=true&characterEncoding=utf8&useSSL=true</property>
<property name="user">root</property>
<property name="password">123456</property>
<property name="acquireIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">20</property>
</default-config>
<!--
C3P0的命名配置,
如果在代码中“ComboPooledDataSource ds = new ComboPooledDataSource("MySQL");”这样写就表示使用的是name是MySQL的配置信息来创建数据源
-->
<named-config name="MySQL">
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcStudy?useUnicode=true&characterEncoding=utf8&useSSL=true</property>
<property name="user">root</property>
<property name="password">123456</property>
<property name="acquireIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">20</property>
</named-config>
</c3p0-config>
2、构建工具了类:
package com.kuang.lesson05.utils;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class JdbcUtils_C3P0 {
private static ComboPooledDataSource dataSource = null;
static {
try{
// 代码版配置
// dataSource = new ComboPooledDataSource();
// dataSource.setDriverClass();
// dataSource.setUser();
// dataSource.setPassword();
// dataSource.setJdbcUrl();
//
// dataSource.setMaxPoolSize();
// dataSource.setMinPoolSize();
//创建数据源 工厂模式 --> 创建
dataSource = new ComboPooledDataSource("MySQL"); //配置文件写法
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection(); //从数据源中获取连接
}
//释放连接资源
public static void release(Connection conn, Statement st, ResultSet rs){
if (rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st!=null){
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
3、使用示例:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
public class TestC3P0 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
try {
conn = JdbcUtils_C3P0.getConnection(); //原来是自己的实现的,现在用别人的连接池来实现的
// 区别
// 使用? 占位符代替参数
String sql = "insert into users(id,`NAME`,`PASSWORD`,`email`,`birthday`) values(?,?,?,?,?)";
st = conn.prepareStatement(sql); //预编译SQL,先写sql,然后不执行
// 手动给参数赋值
st.setInt(1,5); //id
st.setString(2,"zhengjiaxiang");
st.setString(3,"1232112");
st.setString(4,"224734673@qq.com");
// 注意点: sql.Date 数据库 java.sql.Date()
// util.Date Java new Date().getTime() 获得时间戳
st.setDate(5,new java.sql.Date(new Date().getTime()));
//执行
int i = st.executeUpdate();
if (i>0){
System.out.println("插入成功!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils_C3P0.release(conn,st,null);
}
}
}
------------
输出:
二月 11, 2020 4:59:41 上午 com.mchange.v2.log.MLog
信息: MLog clients using java 1.4+ standard logging.
二月 11, 2020 4:59:41 上午 com.mchange.v2.c3p0.C3P0Registry
信息: Initializing c3p0-0.9.5.5 [built 11-December-2019 22:18:33 -0800; debug? true; trace: 10]
二月 11, 2020 4:59:41 上午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource
信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 5, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> MySQL, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 1hge0ywa8dkgd8agq7pzt|6659c656, idleConnectionTestPeriod -> 0, initialPoolSize -> 10, jdbcUrl -> jdbc:mysql://localhost:3306/jdbcStudy?useUnicode=true&characterEncoding=utf8&useSSL=true, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 20, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 5, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
插入成功!
DBCP
需要用到的 jar 包:
- commons-dbcp-1.4
- commons-pool-1.6
1、编写配置文件:
#连接设置 这里面的名字,是DBCP数据源中定义好的
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcStudy?useUnicode=true&characterEncoding=utf8&useSSL=true
username=root
password=123456
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=50
#<!-- 最大空闲连接 -->
maxIdle=20
#<!-- 最小空闲连接 -->
minIdle=5
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=UTF8
#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true
#driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=
#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
2、构建使用的工具类:
package com.kuang.lesson05.utils;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils_DBCP {
private static DataSource dataSource = null;
static {
try{
InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
Properties properties = new Properties();
properties.load(in);
//创建数据源 工厂模式 --> 创建
dataSource = BasicDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection(); //从数据源中获取连接
}
//释放连接资源
public static void release(Connection conn, Statement st, ResultSet rs){
if (rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st!=null){
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
3、使用示例:
package com.kuang.lesson05;
import com.kuang.lesson02.utils.JdbcUtils;
import com.kuang.lesson05.utils.JdbcUtils_DBCP;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
public class TestDBCP {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
try {
conn = JdbcUtils_DBCP.getConnection();
// 区别
// 使用? 占位符代替参数
String sql = "insert into users(id,`NAME`,`PASSWORD`,`email`,`birthday`) values(?,?,?,?,?)";
st = conn.prepareStatement(sql); //预编译SQL,先写sql,然后不执行
// 手动给参数赋值
st.setInt(1,4); //id
st.setString(2,"zhengjiaxiang");
st.setString(3,"888888");
st.setString(4,"247346731@qq.com");
// 注意点: sql.Date 数据库 java.sql.Date()
// util.Date Java new Date().getTime() 获得时间戳
st.setDate(5,new java.sql.Date(new Date().getTime()));
//执行
int i = st.executeUpdate();
if (i>0){
System.out.println("插入成功!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils_DBCP.release(conn,st,null);
}
}
}
----------
输出:
插入成功!
结论
无论使用什么数据源,本质还是一样的,DataSource接口不会变,方法就不会变。
Apache 家族中常用的相关组件一览表: