JDBC&连接池
今日内容介绍
jdbc:可以使用java语言操作数据库(发送sql语句,接受处理结果)
- 四天之内需要记住.
连接池:存放连接的池子
一 JDBC
1 概述
Java 数据库连接(Java DataBase Connectivity)
- 作用
通过Java语言操作数据库- 本质
其实是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包(接口的实现类)。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FnjpbBsu-1620723841175)(04.jdbc&连接池.assets/image-20200612092238490.png)]
小结:
- jdbc是一套操作关系型数据库的接口
- 由数据库厂商提供这套接口的实现类(叫做驱动)
- 我们只需要掌握这套接口中的方法,操作那种数据库就导入那种数据库的驱动即可
2 快速入门
需求
使用jdbc技术往day02数据库的account表中插入一条记录
步骤分析
-
创建一个模块(java模块或者web模块),导入mysql的驱动(昨天资料中)
-
编写代码
- 获取连接
- 编写sql
- 获取sql执行对象(执行者)
- 使用执行者执行sql语句,接受返回值
- 处理返回值
- 释放资源
代码实现
package com.itheima.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class A_HelloJdbc {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
//0.注册驱动的操作,但是可以省略
//方式1:DriverManager.registerDriver(new Driver());
//方式2:使用反射机制
Class.forName("com.mysql.jdbc.Driver");
//1.获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day02", "root", "1234");
//2.编写sql
String sql = "insert into account values(null,'jack',1000)";
//3.创建sql语句的执行者
Statement st = conn.createStatement();
//4.执行sql,接受返回值
int i = st.executeUpdate(sql);
//5.处理返回值
if(i==1){
System.out.println("success");
}else{
System.out.println("fail");
}
//6.释放资源
st.close();
conn.close();
}
}
3 API介绍
都在jdk的api中,都在java.sql包或者javax.sql包下
- Driver:驱动 一般看不到 – 接口
- DriverManager:驱动管理者 --类
- 注册驱动:我们省略了
- 获取连接
- Connection:连接类 – 接口
- 创建sql的执行者
- 控制事务
- Statement:sql语句执行类 – 接口
- 执行dml语句:executeUpdate(sql) 返回值: int类型(影响的行数)
- 执行dql语句:executeQuery(sql) 返回值: ResultSet类型(结果集)
- ResultSet:查询出来的数据集合(结果集) – 接口
- 判断是否有查询结果.若有的话需要获取每行中的数据.
DriverManager:
主要作用:
- 注册驱动
- 获取连接
常用方法:
方法名 | 作用 |
---|---|
static void registerDriver(Driver 驱动对象) --了解 | 注册驱动 |
static Connection getConnection(连接路径,数据库用户名,数据库密码) | 获取连接 |
注册驱动-了解
翻看mysql的驱动类源码,发现里面有一个静态代码块,其中的逻辑是:注册驱动.静态代码块的执行时机就是加载类对象的时候就会执行.我们只需要将Driver类加载到内存就可以了.可以使用反射机制
- 方式1: Class.forName(“com.mysql.jdbc.Driver”);
- 更好的解耦合,推荐用这种方式.
- 方式2: 类名.class;
- 方式3: 对象.getClass();
从jdk1.5开始,不注册驱动也可以,jdk底层会帮我们自动的注册驱动.有些程序开发的比较早,使用的jdk比较老,为了兼容它们,所以最好留着注册驱动.
获取连接
static Connection getConnection(url,username,pwd):获取连接
- url:目标数据库的url
- 格式:
jdbc:子协议://ip地址或者域名:端口号/数据库名字
例如:
jdbc:mysql://localhost:3306/day02
- 若连接的本机的3306端口号可以简写为
jdbc:mysql:///day02
连接上也可以添加参数,例如下面的参数可以解决存储数据出现乱码问题,一般不用
?useUnicode=true&characterEncoding=utf-8
username:数据库的用户名
password:数据库的密码
Connection
数据库连接
主要作用:
- 获取语句执行对象
- 控制事务
常用方法:
方法名 | 作用 |
---|---|
Statement createStatement() | 创建statement语句执行对象 |
PreparedStatement prepareStatement(sql语句) | 创建PreparedStatement预编译语句执行对象 |
setAutoCommit(布尔值) | 设置事务是否自动提交,若设置为false就是手动开启事务 |
commit() | 提交事务 |
rollback() | 回滚事务 |
Statement
语句执行对象
主要作用:
- 执行sql语句
常用方法:
方法名 | 作用 |
---|---|
int executeUpdate(sql) | 主要用来执行dml语句,增删改,返回值为:影响的行数 |
ResultSet executeQuery(sql) | 用来执行dql语句,查询操作,返回值为:查询到的结果集 |
ResultSet
查询语句执行后的结果集
常用的方法:
方法名 | 作用 |
---|---|
boolean next() | 向下移动指针,若有数据的话返回true,且移动指针;若无数据的话返回false |
xxx getXxx(列名或者列号) | 获取指定列或者指定字段名的数据 |
getXxx(): xxx指的数据类型
- 若获取的字符串 就有getString方法
- 若获取的int 就有getInt方法
- …
- 通用的方法 getObject()方法
若数据库类型为int的话,可不可以使用getString方法获取? 可以
String id = rs.getString(“id”);
4 CRUD操作
package com.itheima.jdbc;
import org.junit.Test;
import java.sql.*;
public class B_CrudDemo {
@Test
public void testQuery() throws ClassNotFoundException, SQLException {
//0.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//1.获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day02","root","1234");
//2.编写sql
String sql = "select * from account";
//3.创建sql执行者
Statement st = conn.createStatement();
//4.执行sql,接受返回值
ResultSet rs = st.executeQuery(sql);
//5.处理返回值
while (rs.next()) {
//获取id
int id = rs.getInt("id");
//获取name
String name = rs.getString(2);
//获取金额
String money = rs.getString("money");
System.out.print(id+"\t"+name+"\t"+money);
System.out.println();
}
//6.释放资源
rs.close();
st.close();
conn.close();
}
@Test
public void testUpdate() throws ClassNotFoundException, SQLException {
//0.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//1.获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql:///day02","root","1234");
//2.编写sql
String sql = "update account set name = '鹏飞' ,money = 10000 where id = 1";
//3.创建sql执行者
Statement st = conn.createStatement();
//4.执行sql,接受返回值
int i = st.executeUpdate(sql);
//5.处理返回值
if (i==1){
System.out.println("success");
}else {
System.out.println("fail");
}
//6.释放资源
st.close();
conn.close();
}
}
抽取工具类(不用写)
每次操作的时候,都注册了驱动、获取了连接和释放了资源.这些操作都是一样的.我们可以将这些操作抽取到一个工具类中 ,例如:JdbcUtils1
工具类有如下操作:
- 注册驱动:只需要注册一次,放入到静态代码块中
- 获取连接:每次操作都需要获取连接
- 释放资源:每次操作都需要释放资源
package com.itheima.utils;
import java.sql.*;
public class JdbcUtils1 {
//1.注册驱动,只需要一次,static
static{
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
//静默处理
}
}
//2.获取连接 因为调用者需要用conn创建其他的对象.
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql:///day02","root","1234");
}
//3.释放资源
public static void release(Connection conn, Statement st, ResultSet rs){
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
}
//可以让垃圾回收器更快的回收对象
rs = null;
}
if (st != null) {
try {
st.close();
} catch (SQLException e) {
}
//可以让垃圾回收器更快的回收对象
st = null;
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
}
//可以让垃圾回收器更快的回收对象
conn = null;
}
}
}
package com.itheima.jdbc;
import com.itheima.utils.JdbcUtils1;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class C_CrudDemo_TestUtils {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//1.获取连接
conn = JdbcUtils1.getConnection();
//2.编写sql
String sql = "delete from account where id = 2";
//3.获取语句执行者
st = conn.createStatement();
//4.执行sql,接受返回值
int i = st.executeUpdate(sql);
//5.处理返回值
if (i==1){
System.out.println("success");
}else {
System.out.println("fail");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//6.释放资源
JdbcUtils1.release(conn,st,rs);
}
}
}
5 事务操作
api
Connection的方法
一件事情(业务)属于一个事务操作.业务代码写在service层,事务要加载service层上
service中一个方法就使用一个业务. service中获取Connection连接对象控制事务,dao层也需要使用Connection操作数据库.service中的Connection和dao中的Connection是同一个吗?必须是.
怎么就可以将service中的connection传递给dao层? 可以使用传参方案.
service中方法逻辑
- 获取连接,开启事务
- 执行所有的操作,若操作涉及到dao,务必要把service中connection传递给dao层
- 当所有的操作都成功的时候,需要提交事务
- 当任何一个操作失败的时候,需要回滚事务
- 最后无论如何都需要释放Connection
转账案例
步骤分析:
- 创建AccountDao类.编写转出方法(扣钱)和转入(加钱)方法
- transferIn(连接,收款人,金额) 转入方法
- transferOut(连接,汇款人,金额) 转出方法
- 创建AccountService类,编写转账方法(transfer),参数有:汇款人,收款人,金额
- 开启事务
- 调用dao中的扣钱的操作
- 调用dao中的加钱的操作
- 都成功提交事务
- 若有一个失败就回滚事务
- 释放connection
- 测试转账
代码实现:
package com.itheima.jdbc.d_account.dao;
import com.itheima.utils.JdbcUtils1;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
public class AccountDao {
//扣钱
public int transferOut(Connection conn,String fromUser,int money){
Statement st = null;
try {
//1.编写sql
String sql = "update account set money = money - "+money+" where name = '"+fromUser+"'";
//2.获取语句执行者
st = conn.createStatement();
//3.执行sql,接受返回值
int i = st.executeUpdate(sql);
//5,返回 返回值
return i;
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtils1.release(null,st,null);
}
return 0;
}
//加钱
public int transferIn(Connection conn,String toUser,int money){
Statement st = null;
try {
//1.编写sql
String sql = "update account set money = money + "+money+" where name = '"+toUser+"'";
//2.获取语句执行者
st = conn.createStatement();
//3.执行sql,接受返回值
int i = st.executeUpdate(sql);
//5,返回 返回值
return i;
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtils1.release(null,st,null);
}
return 0;
}
}
package com.itheima.jdbc.d_account.service;
import com.itheima.jdbc.d_account.dao.AccountDao;
import com.itheima.utils.JdbcUtils1;
import java.sql.Connection;
import java.sql.SQLException;
public class AccountService {
AccountDao accountDao = new AccountDao();
public void transfer(String fromUser,String toUser,int money) throws Exception {
Connection conn = null;
try {
//1.获取conn 开启事务
conn = JdbcUtils1.getConnection();
conn.setAutoCommit(false);
//2.调用dao的扣钱操作
int i = accountDao.transferOut(conn, fromUser, money);
if (i!=1) {
throw new RuntimeException("转出失败");
}
//3,调用dao的加钱操作
i = accountDao.transferIn(conn,toUser,money);
if (i!=1) {
throw new RuntimeException("转入失败");
}
//4.没有问题,提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
//5.有问题,回滚事务
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
//6.通知调用者,转账失败
throw e;
} finally {
//6.都需要释放conn
JdbcUtils1.release(conn,null,null);
}
}
}
测试代码
package com.itheima.jdbc.d_account;
import com.itheima.jdbc.d_account.service.AccountService;
public class MoNiZhuanZhang {
public static void main(String[] args){
AccountService accountService = new AccountService();
try {
accountService.transfer("tom","jackson",100);
System.out.println("success");
} catch (Exception e) {
e.printStackTrace();
System.out.println("fail");
}
}
}
6 案例:用户登录
需求
通过java控制台模拟,用户输入账号和密码,实现登录网站功能
通过控制台接受用户名和密码,判断数据库中有无记录,若有记录,打印用户的信息,若无记录则打印"查无此人"
user表
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) DEFAULT NULL,
`password` varchar(32) DEFAULT NULL,
`sex` varchar(2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
INSERT INTO USER VALUES(NULL,'tom','123','男'),(NULL,'rose','abc','女');
分析:
- 使用scanner接受控制台输入的内容
- 使用jdbc技术去数据库中完成查询操作
步骤分析:
- 创建一个Scanner对象
- 提示用户输入用户名和密码,且接受
- 使用jdbc去查询数据库中的记录
- 获取连接
- 编写sql
- 创建语句执行者
- 执行查询语句,接受返回值
- 判断是否有记录
- 释放资源
代码实现:
public class E_LoginDemo {
public static void main(String[] args) {
//1.创建scanner对象
Scanner sc = new Scanner(System.in);
//2.提示用户输入用户名和密码,且接受
System.out.println("请输入用户名");
String username = sc.nextLine();
System.out.println("请输入密码");
String password = sc.nextLine();
//3.使用jdbc操作数据库
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//1. 获取连接
conn = JdbcUtils1.getConnection();
//2. 编写sql
String sql = "select * from user where username = '"+username+"' and password = '"+password+"'";
//3. 创建语句执行者
st = conn.createStatement();
//4. 执行查询语句,接受返回值
rs = st.executeQuery(sql);
//5. 判断是否有记录
if (rs.next()) {
System.out.println(rs.getString("username") + ":欢迎回来 , 性别:"+rs.getString("sex"));
}else{
System.out.println("用户名或密码错误");
}
} catch (SQLException e) {
e.printStackTrace();
System.out.println("服务器正在升级,请稍后再试");
} finally {
JdbcUtils1.release(conn,st,rs);
}
}
}
二 PreparedStatement
上面案例中,输入用户名的时候 输入:tom'#
,密码输入任何值都可以登陆.
1 概述
SQL注入问题
用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义,以上问题称为SQL注入。
解决sql注入问题
我们不能让用户输入的信息和SQL语句进行字符串拼接。需要使用PreparedSatement对象解决SQL注入。
2 使用步骤
-
编写sql的时候,使用?作为占位符,替换sql中的参数数据
之前的sql:
select * from user where username = ‘"+username+"’ and password = ‘"+pwd+"’
现在的sql:
select * from user where username = ? and password = ?
-
创建的预编译的语句执行者 PreparedStatement
之前的对象:
Statement st = conn.createStatement();
现在的对象
PreparedStatement st = conn.prepareStatement(sql);
-
给语句设置参数,替代?部分
st.setXxx(第几个问号,实际值):Xxx指的是数据类型
例如:
st.setString(1,“tom”);
st.setInt(1,10);
-
执行sql的时候要使用PreparedStatement的无参方法执行sql
之前执行sql:
st.executeUpdate(sql) 或者 st.executeQuery(sql)
现在使用无参方法:
st.executeUpdate()或者st.executeQuery()
3 优化登录案例
public class D_LoginPlusDemo {
public static void main(String[] args) {
//1. 创建一个Scanner对象
Scanner sc = new Scanner(System.in);
//2. 提示用户输入用户名和密码,且接受
System.out.println("请输入用户名");
String username = sc.nextLine();
System.out.println("请输入密码");
String pwd = sc.nextLine();
//3. 使用jdbc去查询数据库中的记录
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
// 1. 获取连接
conn = JdbcUtils1.getConnection();
// 2. 编写sql
String sql = "select * from user where username = ? and password = ?";
// 3. 创建语句执行者
st = conn.prepareStatement(sql);
// 4. 设置参数
st.setString(1,username);
st.setString(2,pwd);
// 5. 执行查询语句,接受返回值
rs = st.executeQuery();
// 5. 判断是否有记录
if (rs.next()) {
System.out.println("欢迎回来:"+rs.getString("username")+", 性别:"+rs.getString("sex"));
}else{
System.out.println("查无此人");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6. 释放资源
JdbcUtils1.release(conn,st,rs);
}
}
}
4 CRUD
PreparedStatement使用的步骤
- 获取连接
- 编写sql
- 创建预编译sql执行者
- 给sql设置参数
- 执行sql,接受结果
- 处理结果
- 释放资源
package com.itheima.jdbc;
import com.itheima.utils.JdbcUtils1;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class F_PP_CrudDemo {
@Test
public void testInsert() {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
//1.获取连接
conn = JdbcUtils1.getConnection();
//2.编写sql
String sql = "insert into user values (null,?,?,?)";
//3.创建预编译sql执行者
st = conn.prepareStatement(sql);
//4.设置参数
st.setString(1,"jack");
st.setString(2,"qwe");
st.setString(3,"男");
//5.执行sql,接受返回值
System.out.println(st.executeUpdate());
} catch (Exception e) {
e.printStackTrace();
} finally {
//7.释放资源
JdbcUtils1.release(conn,st,rs);
}
}
@Test
public void testUpdate() {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
//1.获取连接
conn = JdbcUtils1.getConnection();
//2.编写sql
String sql = "update user set username = ?,password = ? ,sex = ? where id = ?";
//3.创建预编译sql执行者
st = conn.prepareStatement(sql);
//4.设置参数
st.setString(1,"jackson");
st.setString(2,"asd");
st.setString(3,"女");
st.setString(4,"5");
//5.执行sql,接受返回值
System.out.println(st.executeUpdate());
//6.处理结果
} catch (Exception e) {
e.printStackTrace();
} finally {
//7.释放资源
JdbcUtils1.release(conn,st,rs);
}
}
@Test
public void testFind() {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
//1.获取连接
conn = JdbcUtils1.getConnection();
//2.编写sql
String sql = "select * from user order by id desc limit ?,?";
//3.创建预编译sql执行者
st = conn.prepareStatement(sql);
//4.设置参数
//st.setString(1,"username");
st.setInt(1,0);
st.setInt(2,2);
//5.执行sql,接受返回值
rs = st.executeQuery();
//6.处理结果
while (rs.next()) {
System.out.println(rs.getString("id") + "==" + rs.getString("username"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//7.释放资源
JdbcUtils1.release(conn,st,rs);
}
}
}
order by 后面不能使用 ? ,使用了也没有生效
5 优点
- PreparedStatement要比Statement更加安全,没有sql注入问题
- PreparedStatement要比Statement执行效率高.
每条sql语句执行之前也需要编译,编译的动作是sql服务器自己完成的.
若使用PreparedStatement之后,sql语句可以进行预编译,执行的时候只是参数不同而已,sql不需要重复编译
注意:
1. PreparedStatement是Statement的子类 2. 执行sql的时候使用的方法是自己的无参方法.否则就会报错
三 连接池
1 概述
连接池其实就是一个容器(集合),存放数据库连接的容器。
当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。
优点
- 节约资源,减轻服务器压力
- 提高连接复用性,用户访问高效
规范
Java为数据库连接池提供了公共的接口: javax.sql.DataSource ,各个厂商去实现这套接口,提供jar包。
- 从连接池中获取连接 : 调用连接池对象的 getConnection() 方法
- 归还连接 : 调用连接的close()方法
常用的连接池技术
- C3P0:数据库连接池技术,使用它的开源项目有Hibernate、Spring等。
- Druid:(德鲁伊)阿里巴巴提供的数据库连接池技术,是目前最好的数据库连接池。
- HikariCP:小日本开发的连接池技术,称之为效率最高的一款连接池,springboot整合的就是hikaricp连接池
其他的连接池(了解)
- boneCP:数据库连接池技术,体积小速度快。
- DBCP:Apache提供的数据库连接池技术。
2 Druid连接池
前提:
导入jar包(druid.jar和mysql驱动)
硬编码方式:(掌握,框架时候用)
- 创建DruidDataSource
- 设置连接池的四个基本参数(url,驱动,数据库用户名和密码)
- 还可以设置其他的参数(例如:连接池初始化连接的数量,连接的最大数量,连接的最大空闲数量,获取连接的超时时间等等)
- 获取连接
- 使用连接
- 归还连接
public class DruidDemo {
@Test
//硬编码方式
public void test1() throws SQLException {
//1. 创建DruidDataSource
DruidDataSource ds = new DruidDataSource();
//2. 设置连接池的四个基本参数(url,驱动,数据库用户名和密码)
ds.setUrl("jdbc:mysql:///day02");
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUsername("root");
ds.setPassword("1234");
//3. 还可以设置其他的参数(例如:连接池初始化连接的数量,连接的最大数量,连接的最大空闲数量,获取连接的超时时间等等)
//连接池初始化连接的数量
ds.setInitialSize(5);
//连接的最大数量
ds.setMaxActive(10);
//获取连接的超时时间,若超出这个时间会报错
ds.setMaxWait(3000);
//4. 获取连接
DruidPooledConnection conn = ds.getConnection();
//5. 使用连接
System.out.println(conn);
//6. 归还连接
conn.close();
}
@Test
//硬编码方式
/* 当获取的连接超出了最大数量的时候,程序就会等待一段时间(3000),
在这段时间中有人归还了连接,直接拿过来用.一致没有人归还就会报错
*/
public void test2() throws SQLException {
//1. 创建DruidDataSource
DruidDataSource ds = new DruidDataSource();
//2. 设置连接池的四个基本参数(url,驱动,数据库用户名和密码)
ds.setUrl("jdbc:mysql:///day02");
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUsername("root");
ds.setPassword("1234");
//3. 还可以设置其他的参数(例如:连接池初始化连接的数量,连接的最大数量,连接的最大空闲数量,获取连接的超时时间等等)
//连接池初始化连接的数量
ds.setInitialSize(5);
//连接的最大数量
ds.setMaxActive(10);
//获取连接的超时时间,若超出这个时间会报错
ds.setMaxWait(3000);
for (int i = 1; i <= 11 ; i++) {
//4. 获取连接
DruidPooledConnection conn = ds.getConnection();
//5. 使用连接
System.out.println(conn);
//归还一个连接
if (i%7==0) {
conn.close();
}
}
//6. 归还连接
//conn.close();
}
}
配置文件方式:
步骤:
- 在properties文件中声明连接池的属性(属性不能乱写)
- 在程序中,加载properties文件
- 使用DruidDatasourceFactory的静态方法创建一个连接池
- 通过连接池获取连接
- 使用连接
- 释放连接
@Test
//配置文件的方式
public void test3() throws Exception {
//1.创建连接池对象
//a.加载配置文件
InputStream is = TestDruid.class.getClassLoader().getResourceAsStream("druid.properties");
//b.创建配置对象
Properties prop = new Properties();
prop.load(is);
//c.使用工厂类的静态方式创建连接池对象
DataSource ds = DruidDataSourceFactory.createDataSource(prop);
//2.获取连接,使用连接
Connection conn = ds.getConnection();
System.out.println(conn);
//3.归还连接
conn.close();
}
3 抽取工具类
使用连接池技术获取连接
- 可以将连接池的4个基本参数放入一个db.properties
- 在工具类中声明一个连接池成员变量
- 在静态代码块中加载properties文件,初始化连接池对象
- 编写getConnection方法
- 从连接池中获取一个连接池返回即可
- 编写释放资源的方法(拷贝上午写的代码)
- 编写获取连接池的方法(目前对我们来说没有用,年后学习框架的时候用的上)
package com.itheima.utils;
import com.alibaba.druid.pool.DruidDataSource;
import javax.sql.DataSource;
import java.sql.*;
import java.util.ResourceBundle;
public class JdbcUtils {
//1.声明连接池对象
private static DruidDataSource ds = null;
//使用静态代码块完成连接池的初始化操作
static {
ds = new DruidDataSource();
//使用ResourceBundle类加载db.properties文件,注意不需要写文件的后缀名
ResourceBundle bundle = ResourceBundle.getBundle("db");
//设置4个基本的参数
ds.setDriverClassName(bundle.getString("jdbc. driver"));
ds.setUrl(bundle.getString("jdbc.url"));
ds.setUsername(bundle.getString("jdbc.username"));
ds.setPassword(bundle.getString("jdbc.password"));
}
//0,获取连接池方法,框架的时候采用
public static DataSource getDS(){
return ds;
}
//2.获取连接 因为调用者需要用conn创建其他的对象.
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//3.释放资源
public static void release(Connection conn, Statement st, ResultSet rs){
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
}
//可以让垃圾回收器更快的回收对象
rs = null;
}
if (st != null) {
try {
st.close();
} catch (SQLException e) {
}
//可以让垃圾回收器更快的回收对象
st = null;
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
}
//可以让垃圾回收器更快的回收对象
conn = null;
}
}
}
4.扩展:C3P0
一般使用的配置文件方式.
要求:
- 配置文件的名字必须叫: c3p0.properties或者c3p0-config.xml
- 配置文件的路径必须放在src目录下
- 创建连接池对象 new ComboPooledDataSource();
导入c3p0的jar包和mysql驱动包
public class C3P0Demo {
@Test
public void test1() throws SQLException {
//创建连接池
DataSource ds = new ComboPooledDataSource();
//获取连接
Connection conn = ds.getConnection();
//使用连接
System.out.println(conn);
//释放连接
conn.close();
}
@Test
public void test2() throws SQLException {
//创建连接池 使用其他命名配置
/*
若找不到指定的配置,还会使用默认的配置
*/
//DataSource ds = new ComboPooledDataSource("otherc3p0");
DataSource ds = new ComboPooledDataSource("otherc3p0111");
//获取连接
Connection conn = ds.getConnection();
//使用连接
System.out.println(conn);
//释放连接
conn.close();
}
}
四 总结
jdbc
牢牢的记住:jdbc是一套使用java语言操作关系型数据库的api,一套规范.使用jdbc的时候必须导入数据库的驱动
使用PreparedStatement步骤:
- 获取连接
- 编写sql
- 创建sql的预编译执行者
- 设置参数
- 执行sql(无参方法),接受返回值
- 处理返回值
- 释放资源
上面这个步骤,需要大家记住3天,3天之后大家就可以忘记了,
连接池(数据源)
连接的容器,在项目初始化的时候可以初始化一些连接对象放入容器中.获取连接的时候调用连接池的getConnection方法.若连接不用了就需要归还连接池(调用连接的close方法)
getConnection();
常见的连接池:
- 课上讲的是druid连接池
- 创建连接池对象:new DruidDataSource(); 设置基本参数即可使用
作业
1.使用duird抽取工具类
- 创建连接池对象
- 获取连接池
- 获取连接
- 释放资源
2.使用PreparedStatement完成账户的crud操作
3.完成转账案例,若有时间和精力的话改造成web方式
4.完成登陆案例,若有时间和精力的话改造成三层架构方式
5.若有时间和精力的话,可以把之前用户的crud操作(list)改成数据库方式