JDBC基础知识
1.JDBC概述
概念:
全称 Java Database Connectivity (Java数据库连接)
使用Java语言操作关系型数据库的一套API
-- 总结: 一套用来操作数据库的接口
本质:
Sun公司定义的一套操作所有关系型数据库的规则,即接口
各个数据库厂商去实现这套接口,提供数据库驱动jar包
使用JDBC编程,真正执行的代码是驱动jar包中的实现类
好处:
同一套API可以操作不同的关系型数据库
可以随时替换底层数据库,访问数据库的Java代码基本不变
2.JDBC入门了解
2.1操作流程
1. 编写Java代码
2. Java代码将SQL发送到MySQL服务端
3. MySQL服务端接收到SQL语句并执行该SQL语句
4. 将SQL语句执行的结果返回给Java代码
2.2编写代码
1. 创建工程,导入驱动jar包
mysql-connector-java-5.1.48.jar
2. 注册驱动(MySQL 5 之后版本驱动包可不写)
Class.forName("com.mysql.jdbc.Driver");
3. 获取连接
Connection conn = DriverManager.getConnection(url, username, password);
-- Java代码发送SQL给MySQL服务端,需要先建立连接
4.定义SQL语句
String sql = "UPDATE ……";
5.获取执行SQL对象
Statement stmt = conn.createStatement();
-- Statement 是 SQL执行对象,用来执行SQL语句
6.执行SQL
stmt.executeUpdate(sql);
7.处理返回结果
-- 执行SQL后返回int类型数值,代表更改的数据行
int count = stmt.executeUpdate(sql);
System.out.println(count);
8.释放资源
stmt.close();
conn.close();
2.2.1案例练习
数据表准备:
-- 初始化account表SQL
DROP TABLE IF EXISTS account;
-- 创建账户表
CREATE TABLE account(
id int PRIMARY KEY auto_increment,
name varchar(10),
money double(10,2)
);
-- 添加数据
INSERT INTO account(name,money) values('张三',1000),('李四',1000);
创建测试类JDBCDemo,导入驱动包后,右键选择 Add as Library……选项
JDBCDemo.java 内容:
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 = "1234";
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.1DriverManager(驱动管理类)
1. 注册驱动
实现语句: Class.forName("com.mysql.jdbc.Driver");
2. 获取数据库连接
String url = "……";
String username = "……";
String password = "……";
url: 连接路径
语法格式:
jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值1&参数键值2……
-- 细节:
如果连接本机mysql服务器,并且服务端口为3306默认端口,可简写为:jdbc:mysql///数据库名称?参数键值对
配置useSSL = false 参数,禁用安全连接方式,解决警告提示
username : 连接数据库使用用户名
password : 数据库连接密码
3.2Connection(数据库连接对象)
作用:
1. 获取执行SQL的对象
2. 管理事务
1. 获取执行对象
普通执行SQL对象: Statement createtatement()
-- 该方法会被入侵者SQL注入
防止SQL注入: PrepareStatement prepareStatement(sql)
-- 该方法通过将符号转译的方法,避免了SQL注入
2. 执行存储过程的对象
CallableStatement prepareCall(sql)
-- 该方法是用来执行存储过程的,而存储过程在MySQL中不常用
3.2.1事务管理
开启事务:
BEGIN;
提交事务:
COMMIT;
回滚事务:
ROLLBACK;
Connection接口定义相对应的方法:
开启事务:
-- 由于MySQL默认自动提交事务,所以首先要更改掉提交状态
conn.setAutoCommit(false);
提交事务:
conn.commit();
回滚事务:
conn.rollback();
3.2.2案例演示
public class JDBCDemo3_Connection {
public static void main(String[] args) throws Exception {
//1. 注册驱动
//Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "1234";
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/catch中
try {
// 开启事务,手动更改提交状态
conn.setAutoCommit(false);
//5. 执行sql
int count1 = stmt.executeUpdate(sql1);//受影响的行数
//6. 处理结果
System.out.println(count1);
int i = 3/0; // 此处会出错
//5. 执行sql
int count2 = stmt.executeUpdate(sql2);//受影响的行数
//6. 处理结果
System.out.println(count2);
// ============提交事务==========
//程序运行到此处,说明没有出现任何问题,则需求提交事务
conn.commit();
} catch (Exception e) {
// ============回滚事务==========
//程序在出现异常时会执行到这个地方,此时就需要回滚事务
conn.rollback();
e.printStackTrace();
}
//7. 释放资源
stmt.close();
conn.close();
}
}
3.3Statement(执行SQL语句)
作用:
用来执行SQL语句,针对不通过类型的SQL语句使用方法也不一样
1. 执行DDL、DML语句:
statement.executeUpdate("sql语句");
// 增删改库,表相关信息
2. 执行DQL语句:
statement.executeQuery("sql语句");
// 查询表相关信息
// 返回的是ResultSet 相关操作请查看3.4内容
3.3.1案例演示
执行DML语句:
@Test
// Test 单元测试, 需要导入junit-4.12.jar,hamcrest-core-1.3.jar两个驱动包
public void testDML() throws Exception {
//1. 注册驱动
//Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 定义sql
String sql = "update account set money = 3000 where id = 1";
//4. 获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//5. 执行sql
int count = stmt.executeUpdate(sql);//执行完DML语句,受影响的行数
//6. 处理结果
//System.out.println(count);
if(count > 0){
System.out.println("修改成功~");
}else{
System.out.println("修改失败~");
}
//7. 释放资源
stmt.close();
conn.close();
}
执行DQL语句:
@Test
public void testDDL() throws Exception {
//1. 注册驱动
//Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 定义sql
String sql = "drop database db2";
//4. 获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//5. 执行sql
int count = stmt.executeUpdate(sql);//执行完DDL语句,可能是0
//6. 处理结果
System.out.println(count);
//7. 释放资源
stmt.close();
conn.close();
}
3.4ResultSet(结果集对象)
作用:
封装了SQL查询语句的结果
执行DQL语句后就会返回ResultSet对象
String sql = "SELECT ……";
ResultSet rs = stmt.executeQuery(sql);
//返回的rs是一个对象,直接打印返回结果是地址值
查询结果方法:
1. boolean next() (参考迭代器)
// 判断当前行是否为有效行(是否存有数据),并将光标从当前位置向后移动一行
// 一般使用while遍历
2. xxx getXxx(参数) (获取数据)
xxx : 数据类型,如 int getInt(参数); String getString(参数)
/*
参数:
int 类型的参数 : 表示列的编号,从1开始,代表第一列
String 类型的参数 : 一般写列名,代表x列
*/
3.4.1案例演示
// 这里采用String参数方法
@Test
public void resultSet() throws SQLException {
String url = "jdbc:mysql://127.0.0.1:3306/db_jdbc";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
//3.sql语句
String sql = "select * from stu";
Statement statement = conn.createStatement();
ResultSet rs = statement.executeQuery(sql);
//4. 遍历rs,获取相应数据内容
while (rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
String money = rs.getString("money");
System.out.println(id + " " + name + " " + money);
}
}
3.5案例练习
需求:
查询account账户表数据,封装为Account对象中,并且存储到ArrayList集合中
案例演示:
//首先根据account数据表中拥有元素,创建Account类,包含id,name,money等信息
@Test
public void testResultSet2() throws Exception {
//1. 注册驱动
//Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 定义sql
String sql = "select * from account";
//4. 获取statement对象
Statement stmt = conn.createStatement();
//5. 执行sql
ResultSet rs = stmt.executeQuery(sql);
// 创建集合
List<Account> list = new ArrayList<>();
// 创建 Account 对象
Account account = null;
/*
1.account作用域更广
2.不需要创建多个Account参数,只需赋值即可
*/
// 6.1 光标向下移动一行,并且判断当前行是否有数据
while (rs.next()){
account = new Account();
//6.2 获取数据 getXxx()
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.6***PreparedStatement
//上边没有打错, conn.prepareStatement(sql)返回对象时PreparedaStatement类对象
作用:
预编译SQL语句并执行,预防SQL注入问题
3.6.1模拟SQL注入问题
@Test
public void testLogin() throws Exception {
//获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
// 接收用户输入 用户名和密码
String name = "sjdljfld";
String pwd = "' or '1' = '1";
String sql = "select * from tb_user where username = '"+name+"' and password = '" + pwd + "'";
/*
sql语句会变成:
select * from tb_user where username = '" + name + "' and password = '' or '1' = '1';
将 username and password 判断
更改为
username 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("登录失败~");
}
//7. 释放资源
rs.close();
stmt.close();
conn.close();
}
3.6.2获取PreparedStatement对象
// SQL语句中的参数值,使用?占位符替代
String sql = "select * from user where username = ? and password = ?";
// 通过Connection对象获取,并传入对应的sql语句
PreparedStatement pstmt = conn.prepareStatement(sql);
设置参数值:
pstmt.setXxx(参数1,参数2) : 给?赋值
Xxx : 数据类型; 如 setInt(参数1,参数2)
参数:
参数1: ? 的位置编号,从1开始
参数2: ? 的值
执行SQL语句:
1.执行DDL语句和DML语句
executeUpdate();
2.执行DQL语句
executeQuery();
//获取PreparedStatement对象时已经对SQL语句进行预编译,所以这两个方法不需要传递SQL语句
3.6.3使用PreparedStatement改进
@Test
public void testPreparedStatement() throws Exception {
//2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
// 接收用户输入 用户名和密码
String name = "zhangsan";
String pwd = "' or '1' = '1";
// 定义sql
String sql = "select * from tb_user where username = ? and password = ?";
// 获取pstmt对象
PreparedStatement pstmt = conn.prepareStatement(sql);
// 设置?的值
pstmt.setString(1,name);
pstmt.setString(2,pwd);
// 执行sql
ResultSet rs = pstmt.executeQuery();
// 判断登录是否成功
if(rs.next()){
System.out.println("登录成功~");
}else{
System.out.println("登录失败~");
}
//7. 释放资源
rs.close();
pstmt.close();
conn.close();
}
3.6.4原理
PreparedStatement好处:
1.预编译SQL 性能更高
2.防止SQL注入 将敏感字符进行转义
简单来说:
/*
select * from tb_user where username = '" + name + "' and password = '' or '1' = '1';
将 username and password 判断
更改为
username and password or 1 = 1 恒成立判断
*/
PreparedStatement提交到MySQL服务器时会成为如下语句:
select * from tb_user where username = '" + name + "' and password = '\' or \'1\' = \'1';
将多余 ' 转义为 \',所以不会注入成功
4.数据库连接池
概念:
数据库连接池是个容器,负责分配,管理数据库连接
允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个
释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏
好处:
资源重用
提升系统响应速度
避免数据库连接遗漏
避免数据库连接泄露(设定连接最大值)
4.1数据库连接池实现
标准接口:
DataSource
由第三方组织实现此接口,该接口提供了获取连接的功能
Connection getConnection()
//调用getConnection(),返回Connection类对象
后续将不会通过DriverManager对象获取Connection对象,而通过连接池获取
常见数据库连接池
DBCP
C3P0
Druid
我们现在使用更多的是Druid,它的性能比其他两个会好一些。
Druid(德鲁伊)
Druid连接池是阿里巴巴开源的数据库连接池项目
功能强大,性能优秀,是Java语言最好的数据库连接池之一
4.2Druid使用
使用步骤:
1.导入druid-1.1.12.jar
2.定义配置文件
3.加载配置文件
4.获取数据库连接池对象
5.获取连接
编写配置文件:
-- 注册驱动
driverClassName=com.mysql.jdbc.Driver
-- 连接目标数据库
url=jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true
username=root
password=1234
# 初始化连接数量
initialSize=5
# 最大连接数
maxActive=10
# 最大等待时间
maxWait=3000
使用Druid的代码如下
/**
* 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"));
// properties文件路径不正确使用上述输出语句进行查看
}
}
5.JDBC案例练习
5.1环境准备
5.1.1数据库准备
-- 删除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);
5.1.2实体类准备
/**
* 品牌
* 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 +
'}';
}
}
5.2查询所有
@Test
public void testSelectAll() throws Exception {
//加载文件
Properties properties = new Properties();
properties.load(new FileInputStream("F:\\JavaWeb\\day03\\src\\druid.properties"));
//获取数据池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//获取数据库连接Connection
Connection conn = dataSource.getConnection();
//sql语句
String sql = "select * from tb_brand;";
//获取PreparedStatement对象
PreparedStatement pstmt = conn.prepareStatement(sql);
//执行sql语句
ResultSet rs = pstmt.executeQuery();
//定义Brand对象,准备List集合存入Brand对象
Brand brand = null;
List<Brand> brandList = 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);
brandList.add(brand);
}
System.out.println(brandList);
//释放资源
rs.close();
pstmt.close();
conn.close();
}
5.3添加数据
@Test
public void testAdd() throws Exception {
//加载文件
Properties properties = new Properties();
properties.load(new FileInputStream("F:\\JavaWeb\\day03\\src\\druid.properties"));
//获取数据池对象
//创建连接 connection
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection conn = dataSource.getConnection();
String sql = "insert into tu_brand(brand_name,company_name,ordered,description,status) values(?,?,?,?,?)";
//预编译
PreparedStatement pstmt = conn.prepareStatement(sql);
//写入元素
pstmt.setString(1,"香飘飘");
pstmt.setString(2,"香飘飘奶茶公司");
pstmt.setInt(3,3000);
pstmt.setString(4,"一年卖老多了");
pstmt.setInt(5,1);
//执行SQL
int count = pstmt.executeUpdate();
System.out.println(count > 0);
pstmt.close();
conn.close();
}
5.4修改数据
@Test
public void testUpdate() throws Exception {
//加载文件
Properties properties = new Properties();
properties.load(new FileInputStream("F:\\JavaWeb\\day03\\src\\druid.properties"));
//获取数据池对象
//创建连接 connection
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection conn = dataSource.getConnection();
String sql = "update tu_brand set brand_name = ? ,company_name = ?,ordered = ?,description = ?,status = ? where id = ?";
//预编译
PreparedStatement pstmt = conn.prepareStatement(sql);
//写入元素
pstmt.setString(1,"香飘飘");
pstmt.setString(2,"香飘飘奶茶公司");
pstmt.setInt(3,3000);
pstmt.setString(4,"绕地球呢");
pstmt.setInt(5,1);
pstmt.setInt(6,4);
//执行SQL
int count = pstmt.executeUpdate();
System.out.println(count > 0);
pstmt.close();
conn.close();
}
5.5删除数据
@Test
public void testDel() throws Exception {
//加载文件
Properties properties = new Properties();
properties.load(new FileInputStream("F:\\JavaWeb\\day03\\src\\druid.properties"));
//获取数据池对象
//创建连接 connection
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection conn = dataSource.getConnection();
String sql = "delete from tu_brand where id = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1,4);
//执行SQL
int count = pstmt.executeUpdate();
System.out.println(count > 0);
pstmt.close();
conn.close();
}