1. JDBC简介
- JDBC概念:
- JDBC就是使用java语言操作关系型数据库的一套API
- 全称(Java DataBase Connectivity)Java数据库连接
- JDBC本质:
- 官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口
- 各个数据库厂商去实现这套接口,提供数据库驱动jar包
- 我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类
- JDBC好处:
- 各数据库厂商使用相同的接口,Java代码不需要针对不同数据库分别开发
- 可随时替换底层数据库,访问数据库的Java代码基本不变
2. 快速入门
步骤:
0. 创建工程,导入驱动jar包
- 注册驱动
Class.forName("com.mysql.jdbc,Driver");
- 获取连接
Connection conn = DriverManager getConnection(url,username,password);
- 定义SQL语句
String sql = "update...";
- 获取执行SQL对象
Statement stmt = conn.createStatement();
- 执行SQL
stmt exexuteUpdate(sql);
- 处理返回结果
- 释放资源
【示例】快速入门JDBC简单操作
package com.test.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
/**
* JDBC快速入门
*/
public class JDBCDemo {
public static void main(String[] args) throws Exception{
//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接
String url = "jdbc:mysql://127.0.0.1:3306/db1";
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url,username,password);
//3. 定义SQL语句
String sql = "UPDATE account SET money = 2000 WHERE id = 1";
//4. 获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//5. 执行sql
int count = stmt.executeUpdate(sql); //受影响的行数
//6. 处理结果
System.out.println(count);
//7. 释放资源
stmt.close();
conn.close();
}
}
3. JDBC API详解
3.1 DriverManager(驱动管理类)
3.1.1 作用一:注册驱动
Class.forName("com.mysql.jdbc.Driver");
Driver源码:
注意:
- MySQL5之后的驱动包,可以省略注册驱动的步骤
- 自动加载jar包中META-INF/services/java.sql.Driver文件中的驱动类
3.1.2 作用二:获取数据库连接
参数:
- url:连接路径
语法:jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2…
示例:jdbc:mysql://127.0.0.1:3306/db1
细节:
1.如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称?参数键值对,示例:jdbc:mysql:///db1
2.配置 useSSL=false 参数,禁用安全连接方式,解决警告提示,示例:jdbc:mysql://127.0.0.1:3306/db1?useSSL=false
- user:用户名
- password :密码
3.2 Connection(数据库连接对象)
3.2.1 作用一:获取执行SQL的对象
- 普通执行SQL对象: 标准代码中通过该方法获取的执行对象
Statement createStatement()
- 预编译SQL的执行SQL对象:防止SQL注入
PreparedStatement prepareStatement(sql)
- 执行存储过程的对象:获取的 CallableStatement 执行对象用来执行存储过程
CallableStatement prepareCall(sql)
3.2.1 作用二:事务管理
- MySQL事务管理
开启事务:START TRANSACTION;或者 BEGIN;
提交事务:COMMIT;
回滚事务:ROLLBACK;
MySQL默认自动提交事务
- JDBC事务管理:Connection接口定义了3个对应的方法
开启事务: setAutoCommit(boolean autoCommit):true为自动提交事务,false为手动提交事务,即为开启事务
提交事务: commit()
回滚事务: rollback()
【示例】Connection事务管理
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
/**
* JDBC快速入门 Connection
*/
public class JDBCDemo_Connection {
public static void main(String[] args) throws Exception{
//1. 注册驱动
//Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接
String url = "jdbc:mysql://127.0.0.1:3306/db1";
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url,username,password);
//3. 定义SQL语句
String sql1 = "UPDATE account SET money = 3000 WHERE id = 1";
String sql2 = "UPDATE account SET money = 3000 WHERE id = 2";
//4. 获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
try {
//开启事务
conn.setAutoCommit(false);
//5. 执行sql
int count1 = stmt.executeUpdate(sql1); //受影响的行数
//6. 处理结果
System.out.println(count1);
//int i = 3/0;
int count2 = stmt.executeUpdate(sql2);
System.out.println(count2);
//提交事务
conn.commit();
} catch (Exception e) {
//回滚事务
conn.rollback();
e.printStackTrace();
}
//7. 释放资源
stmt.close();
conn.close();
}
}
3.3 Statement
作用:执行SQL语句
- int executeUpdate(sql):执行DML、DDL语句
返回值:(1)DML语句影响的行数(2)DDL语句执行后,执行成功也可能返回0 - ResultSet executeQuery(sql):执行DQL语句
返回值:ResultSet结果集对象
3.4 ResultSet
3.4.1 作用:封装DQL查询语句的结果
ResultSet executeQuery(sql):执行DQL 语句,返回 ResultSet 对象
3.4.2 获取查询结果
-
boolean next():(1)将光标从当前位置向下移动一行(2)判断当前行是否为有效行
返回值:true : 有效行,当前行有数据;false : 无效行,当前行没有数据 -
xxx getXxx(参数):获取数据
xxx : 数据类型;如: int getInt(参数) ;String getString(参数)
参数:
int类型的参数:列的编号,从1开始
String类型的参数: 列的名称
3.4.3 使用步骤
- 游标向下移动一行,并判断该行是否有数据next()
- 获取数据:getXxx(参数)
//循环判断游标是否是最后一行末尾
while(rs.next){
//获取数据
rs.getXxx(参数);
}
【示例】
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
/**
* JDBC API ResultSet
*/
public class JDBCDemo_ResultSet {
public static void main(String[] args) throws Exception{
//1. 注册驱动
//Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接
String url = "jdbc:mysql://127.0.0.1:3306/db1";
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url,username,password);
//3. 定义SQL语句
String sql = "select * from account";
//4. 获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//5. 执行sql
ResultSet rs = stmt.executeQuery(sql); //受影响的行数
//6. 处理结果(遍历rs中的所有数据)
//光标向下移动一行,并且判断当前行是否有数据
while (rs.next()){
//获取数据
int id = rs.getInt(1);
String name = rs.getString(2);
double money = rs.getDouble(3);
System.out.println(id);
System.out.println(name);
System.out.println(money);
System.out.println("===================");
}
//7. 释放资源
rs.close();
stmt.close();
conn.close();
}
}
3.4.5 案例
需求:查询account账户表数据,封装为Acount对象中并且储存到ArrayList集合中
【示例】定义实体类Account
package com.test.pojo;
public class Account {
private int id;
private String name;
private double money;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
【示例】查询account账户表数据并封装为Acount对象中并且储存到ArrayList集合中
package com.test.jdbc;
import com.test.pojo.Account;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
/**
* JDBC API ResultSet
*/
public class JDBCDemo_ResultSet2 {
public static void main(String[] args) throws Exception{
//1. 注册驱动
//Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接
String url = "jdbc:mysql://127.0.0.1:3306/db1";
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url,username,password);
//3. 定义SQL语句
String sql = "select * from account";
//4. 获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//5. 执行sql
ResultSet rs = stmt.executeQuery(sql); //受影响的行数
//创建集合
List<Account> list = new ArrayList<>();
//6. 处理结果(遍历rs中的所有数据)
//光标向下移动一行,并且判断当前行是否有数据
while (rs.next()){
Account account = new Account();
//获取数据
int id = rs.getInt("id");
String name = rs.getString("name");
double money = rs.getDouble("money");
//赋值
account.setId(id);
account.setName(name);
account.setMoney(money);
//存入集合
list.add(account);
}
System.out.println(list);
//7. 释放资源
rs.close();
stmt.close();
conn.close();
}
}
3.5 PreparedStatement
作用:预编译SQL语句并执行,预防SQL注入问题。
SQL注入:通过操作输入来修改事先定义好的SQL语句,是以达到执行代码对服务器进行攻击的方法。
SELECT * FROM tb_user where username = 'safafeer' and password = '' or '1'='1'
【示例】演示SQL注入问题
package com.test.jdbc;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
/**
* 用户登录
*/
public class JDBCDemo_UserLogin {
/*@Test
public void testLongin() throws Exception{
// 获取连接
String url = "jdbc:mysql://127.0.0.1:3306/db1";
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url,username,password);
// 接收用户输入的用户名密码
String name = "zhangsan";
String pwd = "12";
String sql = "SELECT * FROM tb_user where username = '"+name+"' and password = '"+pwd+"'";
//获取stmt对象
Statement stmt = conn.createStatement();
//执行SQL
ResultSet rs = stmt.executeQuery(sql);
//判断登录是否成功
if (rs.next()){
System.out.println("登录成功!!");
}else{
System.out.println("登录失败!");
}
// 释放资源
rs.close();
stmt.close();
conn.close();
}*/
/**
* 演示SQL注入
* @throws Exception
*/
@Test
public void testLongin_Inject() throws Exception{
// 获取连接
String url = "jdbc:mysql://127.0.0.1:3306/db1";
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url,username,password);
// 接收用户输入的用户名密码
String name = "safafeer";
String pwd = "' or '1'='1";
String sql = "SELECT * FROM tb_user where username = '"+name+"' and password = '"+pwd+"'";
System.out.println(sql); //SELECT * FROM tb_user where username = 'safafeer' and password = '' or '1'='1'
//获取stmt对象
Statement stmt = conn.createStatement();
//执行SQL
ResultSet rs = stmt.executeQuery(sql);
//判断登录是否成功
if (rs.next()){
System.out.println("登录成功!!");
}else{
System.out.println("登录失败!");
}
// 释放资源
rs.close();
stmt.close();
conn.close();
}
}
3.5.1 作用:预编译SQL语句并执行,预防SQL注入问题
- 获取 PreparedStatement 对象
// SQL语句中的参数值,使用?占位符替代
String sql = "select * from user where username = ? and password = ?";
// 通过Connection对象获取,并传入对应的sql语句
PreparedStatement pstmt = conn.prepareStatement(sql);
- 设置参数值
PreparedStatement对象:setXxx(参数1,参数2):给 ? 赋值
Xxx:数据类型 ; 如 setInt (1,234)为设置第一个问号值为234
参数:
参数1: ?的位置编号,从1 开始
参数2: ?的值
- 执行SQL语句
executeUpdate();//executeQuery(); 不需要传递SQL语句
注意:
调用这两个方法时不需要传递SQL语句,因为获取SQL语句执行对象时已经对SQL语句进行预编译了。
3.5.2 PreparedStatement原理
PreparedStatement好处:
- 预编译SQL,性能更高
- 防止SQL注入:将敏感字符进行转义
PreparedStatement原理:
- 在获取PreparedStatement对象时,将sql语句发送给mysql服务器进行检查,编译(这些步骤很耗时)
- 执行时就不用再进行这些步骤了,速度更快
- 如果sql模板一样,则只需要进行一次检查、编译
预编译功能:
- PreparedStatement预编译功能开启:useServerPrepStmts=true
注意:在代码中编写url时需要加上以下参数。不开启预编译的情况下,PreparedStatement对象只是解决了SQL注入漏洞。
- 配置MySQL执行日志(重启mysql服务后生效)
log-output=FILE
general-log=1
general_log_file="D:\mysql.log"
slow-query-log=1
slow_query_log_file="D:\mysql_slow.log"
log_query_time=2
4. 数据库连接池
4.1 数据库连接池简介
- 数据库连接池是个容器,负责分配、管理数据库连接(Connection);
- 它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;
- 释放空闲时间超过“最大空闲时间”的数据库连接,从而避免因为没有释放数据库连接而引起的数据库连接遗漏
- 好处:
(1)资源重用
(2)提升系统响应速度
(3)避免数据库连接遗漏
连接池是在一开始就创建好了一些连接(Connection)对象存储起来。用户需要连接数据库时,不需要自己创建连接,而只需要从连接池中获取一个连接进行使用,使用完毕后再将连接对象归还给连接池;这样就可以起到资源重用,也节省了频繁创建连接销毁连接所花费的时间,从而提升了系统响应的速度。
4.2 数据库连接池实现
- 标准接口:DataSource
官方(SUN) 提供的数据库连接池标准接口,由第三方组织实现此接口。
功能:获取连接
Connection getConnection() - 常见的数据库连接池:DBCP、C3P0、Druid
- Druid:
Druid连接池是阿里巴巴开源的数据库连接池项目
功能强大,性能优秀,是Java语言最好的数据库连接池之一
4.3 Druid数据库连接池
Druid使用步骤:
-
导入jar包druid-1.1.12.jar
复制粘贴到模块目录下lib文件夹下,右键add as library,level选择module library -
定义配置文件
【示例】
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true
username=root
password=123456
# 初始化连接数量
initialSize=5
# 最大连接数
maxActive=10
# 最大等待时间
maxWait=3000
- 加载配置文件
Properties的load方法
扩展:获取路径:System.out.println(System.getProperty(“user.dir”));
- 获取数据库连接池对象
使用Druid连接池工厂类DruidDataSourceFactory的创建连接池方法createDataSource获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop); - 获取连接
【示例】
package com.test.druid;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
/**
* Druid数据库连接池演示
*/
public class DruidDemo {
public static void main(String[] args) throws Exception{
//1.导入jar包
//2. 定义配置文件
//3. 加载配置文件
Properties prop = new Properties();
prop.load(new FileInputStream("jdbc-demo/src/druid.properties"));
//4. 获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
//5. 获取数据库连接 Connection
Connection connection = dataSource.getConnection();
System.out.println(connection);
//System.out.println(System.getProperty("user.dir"));
}
}
5. JDBC练习
需求:完成商品品牌数据的增删改查操作
查询:查询所有数据; 添加:添加品牌; 修改:根据id修改; 删除:根据id删除
5.1 准备环境
数据库表 tb_brand:
-- 删除tb_brand表
drop table if exists tb_brand;
-- 创建tb_brand表
create table tb_brand (
-- id 主键
id int primary key auto_increment,
-- 品牌名称
brand_name varchar(20),
-- 企业名称
company_name varchar(20),
-- 排序字段
ordered int,
-- 描述信息
description varchar(100),
-- 状态:0:禁用 1:启用
status int
);
-- 添加数据
insert into tb_brand (brand_name, company_name, ordered, description, status)
values ('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0),
('华为', '华为技术有限公司', 100, '华为致力于把数字世界带入每个人、每个家庭、每个组织,构建万物互联的智能世界', 1),
('小米', '小米科技有限公司', 50, 'are you ok', 1);
SELECT * FROM tb_brand;
实体类Brand
package com.test.pojo;
/**
* 品牌
*
* alt+鼠标左键:整列编辑
* 在实体类中,基本数据类型建议使用对应的包装类型
*/
public class Brand {
// id 主键
private Integer id;
// 品牌名称
private String brandName;
// 企业名称
private String companyName;
// 排序字段
private Integer ordered;
// 描述信息
private String description;
// 状态:0:禁用 1:启用
private Integer status;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getBrandName() {
return brandName;
}
public void setBrandName(String brandName) {
this.brandName = brandName;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public Integer getOrdered() {
return ordered;
}
public void setOrdered(Integer ordered) {
this.ordered = ordered;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
@Override
public String toString() {
return "Brand{" +
"id=" + id +
", brandName='" + brandName + '\'' +
", companyName='" + companyName + '\'' +
", ordered=" + ordered +
", description='" + description + '\'' +
", status=" + status +
'}';
}
}
测试用例BrandTest
package com.test.example;
/**
* 品牌数据的增删改查操作
*/
public class BrandTest {
}
5.2 完成商品品牌数据的增删改查操作
查询:查询所有的数据
package com.test.example;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.test.pojo.Brand;
import org.junit.jupiter.api.Test;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* 品牌数据的增删改查操作
*/
public class BrandTest {
/**
* 查询所有
* 1.SQL: select * from tb_brand
* 2.参数:不需要
* 3.结果:List<Brand>
*/
@Test
public void testSelectAll() throws Exception{
//1. 获取连接Connection
//加载配置文件
Properties prop = new Properties();
prop.load(new FileInputStream("src/druid.properties"));
//获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
//获取数据库连接 Connection
Connection conn = dataSource.getConnection();
//2. 定义SQL
String sql = "SELECT * FROM tb_brand";
//3. 获取pstmt对象
PreparedStatement pstmt = conn.prepareStatement(sql);
//4. 设置参数
//5. 执行SQL
ResultSet rs = pstmt.executeQuery();
//6. 处理结果 List<Brand>:封装Brand对象,装在List集合
Brand brand = null;
List<Brand> brands = new ArrayList<>();
while (rs.next()){
//获取数据
int id = rs.getInt("id");
String brandName = rs.getString("brand_name");
String companyName = rs.getString("company_name");
int ordered = rs.getInt("ordered");
String description = rs.getString("description");
int status = rs.getInt("status");
//封装Brand对象
brand = new Brand();
brand.setId(id);
brand.setBrandName(brandName);
brand.setCompanyName(companyName);
brand.setOrdered(ordered);
brand.setDescription(description);
brand.setStatus(status);
//装载集合
brands.add(brand);
}
System.out.println(brands);
//释放资源
rs.close();
pstmt.close();
conn.close();
}
}
添加:添加品牌数据
/**
* 添加
* 1.SQL: insert into tb_brand(brand_name, company_name, ordered, description, status)values(?,?,?,?,?);
* 2.参数:需要,除了id之外的所有参数信息
* 3.结果:boolean
*/
@Test
public void testAdd() throws Exception{
//接收页面提交的参数
String brandName = "香飘飘";
String companyName = "香飘飘";
int ordered = 1;
String description = "绕地球一圈";
int status = 1;
//1. 获取连接Connection
//加载配置文件
Properties prop = new Properties();
prop.load(new FileInputStream("src/druid.properties"));
//获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
//获取数据库连接 Connection
Connection conn = dataSource.getConnection();
//2. 定义SQL
String sql = "insert into tb_brand(brand_name, company_name, ordered, description, status)values(?,?,?,?,?);";
//3. 获取pstmt对象
PreparedStatement pstmt = conn.prepareStatement(sql);
//4. 设置参数
pstmt.setString(1,brandName);
pstmt.setString(2,companyName);
pstmt.setInt(3,ordered);
pstmt.setString(4,description);
pstmt.setInt(5,status);
//5. 执行SQL
int count = pstmt.executeUpdate(); //影响的行数
//6. 处理结果
System.out.println(count > 0);
//7.释放资源
pstmt.close();
conn.close();
}
修改:根据id修改
/**
* 修改
* 1.SQL:
update tb_brand
set brand_name = ?,
company_name= ?,
ordered= ?,
description = ?,
status=?
where id= ?
* 2.参数:需要,所有数据
* 3.结果:boolean
*/
@Test
public void testUpdate() throws Exception{
//接收页面提交的参数
String brandName = "香飘飘";
String companyName = "香飘飘";
int ordered = 1000;
String description = "绕地球三圈";
int status = 1;
int id = 4;
//1. 获取连接Connection
//加载配置文件
Properties prop = new Properties();
prop.load(new FileInputStream("src/druid.properties"));
//获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
//获取数据库连接 Connection
Connection conn = dataSource.getConnection();
//2. 定义SQL
String sql = "update tb_brand\n" +
" set brand_name = ?,\n" +
" company_name= ?,\n" +
" ordered= ?,\n" +
" description = ?,\n" +
" status=?\n" +
" where id= ?";
//3. 获取pstmt对象
PreparedStatement pstmt = conn.prepareStatement(sql);
//4. 设置参数
pstmt.setString(1,brandName);
pstmt.setString(2,companyName);
pstmt.setInt(3,ordered);
pstmt.setString(4,description);
pstmt.setInt(5,status);
pstmt.setInt(6,id);
//5. 执行SQL
int count = pstmt.executeUpdate(); //影响的行数
//6. 处理结果
System.out.println(count > 0);
//7.释放资源
pstmt.close();
conn.close();
}
删除:根据id删除
/**
* 删除
* 1.SQL: delete from tb_brand where id = ?
* 2.参数:需要,除了id之外的所有参数信息
* 3.结果:boolean
*/
@Test
public void testDeleteById() throws Exception{
//接收页面提交的参数
int id = 4;
//1. 获取连接Connection
//加载配置文件
Properties prop = new Properties();
prop.load(new FileInputStream("src/druid.properties"));
//获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
//获取数据库连接 Connection
Connection conn = dataSource.getConnection();
//2. 定义SQL
String sql = "delete from tb_brand where id = ?";
//3. 获取pstmt对象
PreparedStatement pstmt = conn.prepareStatement(sql);
//4. 设置参数
pstmt.setInt(1,id);
//5. 执行SQL
int count = pstmt.executeUpdate(); //影响的行数
//6. 处理结果
System.out.println(count > 0);
//7.释放资源
pstmt.close();
conn.close();
}