目录
JDBC概述
JDBC概念
JDBC本质
JDBC好处
JDBC快速入门
JDBC API详解
DriverManager
Connection
Statement
ResultSet
PreparedStatement
数据库连接池
数据库连接池简介
数据库连接池实现
Driud使用
JDBC练习
JDBC概述
JDBC概念
JDBC 就是使用Java语言操作关系型数据库的一套API
全称:( Java DataBase Connectivity ) Java 数据库连接
需求:同一套Java代码操作不同的关系型数据库,sun公司就指定了一套标准接口(JDBC),JDBC中定义了所有操作关系型数据库的规则。接口是无法直接使用的,需要使用接口的实现类,而这套实现类(称之为:驱动)就由各自的数据库厂商给出。
JDBC本质
-
官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口
-
各个数据库厂商去实现这套接口,提供数据库驱动jar包
-
我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类
JDBC好处
-
各数据库厂商使用相同的接口,Java代码不需要针对不同数据库分别开发
-
可随时替换底层数据库,访问数据库的Java代码基本不变
以后编写操作数据库的代码只需要面向JDBC(接口),操作哪儿个关系型数据库就需要导入该数据库的驱动包,如需要操作MySQL数据库,就需要再项目中导入MySQL数据库的驱动包。如下图就是MySQL驱动包:
JDBC快速入门
通过Java操作数据库的流程:
- 第一步:编写Java代码
- 第二步:Java代码将SQL发送到MySQL服务端
- 第三步:MySQL服务端接收到SQL语句并执行该SQL语句
- 第四步:将SQL语句执行的结果返回给Java代码
步骤:
-
创建工程,导入驱动jar包
-
注册驱动
Class.forName("com.mysql.jdbc.Driver");
-
获取连接
Connection conn = DriverManager.getConnection(url, username, password);
Java代码需要发送SQL给MySQL服务端,就需要先建立连接
-
定义SQL语句
String sql = “update…” ;
-
获取执行SQL对象
执行SQL语句需要SQL执行对象,而这个执行对象就是Statement对象
Statement stmt = conn.createStatement();
-
执行SQL
stmt.executeUpdate(sql);
-
处理返回结果
-
释放资源
/*** 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 = "1234";Connection conn = DriverManager.getConnection(url, username, password);//3. 定义sqlString sql = "update account set money = 2000 where id = 1";//4. 获取执行sql的对象 StatementStatement stmt = conn.createStatement();//5. 执行sqlint count = stmt.executeUpdate(sql);//受影响的行数//6. 处理结果System.out.println(count);//7. 释放资源stmt.close();conn.close();}
}
JDBC API详解
DriverManager
DriverManager(驱动管理类)作用:
- 注册驱动
- 获取数据库连接
注册驱动:
MySQL提供的Driver类实现了注册驱动:
MySQL 5之后的驱动包,可以省略注册驱动的步骤,自动加载jar包中META-INF/services/java.sql.Driver文件中的驱动类
获取数据库连接:
参数:
1.url : 连接路径
语法:jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2…
示例:jdbc:mysql://127.0.0.1:3306/db1
细节:
-
如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称?参数键值对
-
配置 useSSL=false 参数,禁用安全连接方式,解决警告提示
2.user :用户名
3.password :密码
Connection
Connection(数据库连接对象)作用:
-
获取执行 SQL 的对象
-
管理事务
获取执行对象:
1.普通执行SQL对象
Statement createStatement()
2.预编译SQL的执行SQL对象:防止SQL注入(重点)
PreparedStatement prepareStatement(sql)
3.执行存储过程的对象(不常用)
CallableStatement prepareCall(sql)
管理事务:
MySQL事务管理的操作:
-
开启事务 : BEGIN; 或者 START TRANSACTION;
-
提交事务 : COMMIT;
-
回滚事务 : ROLLBACK;
MySQL默认是自动提交事务
JDBC事务管理的方法:
- 开启事务
参数autoCommit 表示是否自动提交事务,true表示自动提交事务,false表示手动提交事务。而开启事务需要将该参数设为为false。
- 提交事务
- 回滚事务
/*** JDBC API 详解:Connection*/
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. 定义sqlString sql1 = "update account set money = 3000 where id = 1";String sql2 = "update account set money = 3000 where id = 2";//4. 获取执行sql的对象 StatementStatement stmt = conn.createStatement();try {// ============开启事务==========conn.setAutoCommit(false);//5. 执行sqlint count1 = stmt.executeUpdate(sql1);//受影响的行数//6. 处理结果System.out.println(count1);int i = 3/0;//5. 执行sqlint count2 = stmt.executeUpdate(sql2);//受影响的行数//6. 处理结果System.out.println(count2);// ============提交事务==========//程序运行到此处,说明没有出现任何问题,则需求提交事务conn.commit();} catch (Exception e) {// ============回滚事务==========//程序在出现异常时会执行到这个地方,此时就需要回滚事务conn.rollback();e.printStackTrace();}//7. 释放资源stmt.close();conn.close();}
}
Statement
Statement对象的作用就是用来执行SQL语句。而针对不同类型的SQL语句使用的方法也不一样
- 执行DDL、DML语句
- 执行DQL语句
执行DML语句:
/*** 执行DML语句* @throws Exception*/
@Test
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. 定义sqlString sql = "update account set money = 3000 where id = 1";//4. 获取执行sql的对象 StatementStatement stmt = conn.createStatement();//5. 执行sqlint 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();
}
执行DDL语句:
/*** 执行DDL语句* @throws Exception*/
@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. 定义sqlString sql = "drop database db2";//4. 获取执行sql的对象 StatementStatement stmt = conn.createStatement();//5. 执行sqlint count = stmt.executeUpdate(sql);//执行完DDL语句,可能是0//6. 处理结果System.out.println(count);//7. 释放资源stmt.close();conn.close();
}
以后开发很少使用Java代码操作DDL语句
ResultSet
ResultSet(结果集对象)作用:
-
封装了SQL查询语句的结果
ResultSet
对象提供了操作查询结果数据的方法,如下:
1.boolean next()
-
将光标从当前位置向前移动一行
-
判断当前行是否为有效行
方法返回值说明:
-
true : 有效航,当前行有数据
-
false : 无效行,当前行没有数据
2.xxx getXxx(参数):获取数据
-
xxx : 数据类型;如: int getInt(参数) ;String getString(参数)
-
参数
-
int类型的参数:列的编号,从1开始
-
String类型的参数: 列的名称
-
如下图为执行SQL语句后的结果:
一开始光标指定于第一行前,如图所示红色箭头指向于表头行。当我们调用了 next()
方法后,光标就下移到第一行数据,并且方法返回true,此时就可以通过 getInt("id")
获取当前行id字段的值,也可以通过 getString("name")
获取当前行name字段的值。如果想获取下一行的数据,继续调用 next()
方法,以此类推。
/*** 执行DQL* @throws Exception*/
@Test
public void testResultSet() 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. 定义sqlString sql = "select * from account";//4. 获取statement对象Statement stmt = conn.createStatement();//5. 执行sqlResultSet rs = stmt.executeQuery(sql);//6. 处理结果,遍历rs中的所有数据// 6.1 光标向下移动一行,并且判断当前行是否有数据while (rs.next()){//6.2 获取数据 getXxx()int id = rs.getInt("id");String name = rs.getString("name");double money = rs.getDouble("money");System.out.println(id);System.out.println(name);System.out.println(money);System.out.println("--------------");}//7. 释放资源rs.close();stmt.close();conn.close();
}
PreparedStatement
PreparedStatement作用:
-
预编译SQL语句并执行:预防SQL注入问题
SQL注入:通过操作输入来修改事先定义好的SQL语句,用以达到执行代码对服务器进行攻击的方法
模拟SQL注入:
@Test
public void testLogin() 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 = "sjdljfld";String pwd = "' or '1' = '1";String sql = "select * from tb_user where username = '"+name+"' and password = '"+pwd+"'";// 获取stmt对象Statement stmt = conn.createStatement();// 执行sqlResultSet rs = stmt.executeQuery(sql);// 判断登录是否成功if(rs.next()){System.out.println("登录成功~");}else{System.out.println("登录失败~");}//7. 释放资源rs.close();stmt.close();conn.close();
}
拼接后的sql语句如下:
select * from tb_user where username = 'sjdljfld' and password = ''or '1' = '1'
从上面语句可以看出条件(先执行and,再执行or) username = 'sjdljfld' and password = ''
不管是否满足,而 or
后面的 '1' = '1'
是始终满足的,最终条件是成立的,就可以正常的进行登录了
使用PreparedStatement改进:
1.获取 PreparedStatement 对象
// SQL语句中的参数值,使用?占位符替代
String sql = "select * from user where username = ? and password = ?";
// 通过Connection对象获取,并传入对应的sql语句
PreparedStatement pstmt = conn.prepareStatement(sql);
2.设置参数值
PreparedStatement对象:setXxx(参数1,参数2):给 ? 赋值
-
Xxx:数据类型 ; 如 setInt (参数1,参数2)
-
参数:
-
参数1: ?的位置编号,从1 开始
-
参数2: ?的值
-
3.执行SQL语句
- executeUpdate(): 执行DDL语句和DML语句
- executeQuery():执行DQL语句
注意:调用这两个方法时不需要传递SQL语句,因为获取SQL语句执行对象时已经对SQL语句进行预编译了
@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";// 定义sqlString sql = "select * from tb_user where username = ? and password = ?";// 获取pstmt对象PreparedStatement pstmt = conn.prepareStatement(sql);// 设置?的值pstmt.setString(1,name);pstmt.setString(2,pwd);// 执行sqlResultSet rs = pstmt.executeQuery();// 判断登录是否成功if(rs.next()){System.out.println("登录成功~");}else{System.out.println("登录失败~");}//7. 释放资源rs.close();pstmt.close();conn.close();
}
PreparedStatement原理:
PreparedStatement 好处:
-
预编译SQL,性能更高
-
防止SQL注入:将敏感字符进行转义
Java操作数据库流程:
检查SQL和编译SQL花费的时间比执行SQL的时间还要长,如果我们只是重新设置参数,那么检查SQL语句和编译SQL语句将不需要重复执行,这样就提高了性能
1.开启预编译功能:useServerPrepStmts=true
2.配置MySQL执行日志(重启mysql服务后生效),在mysql配置文件(my.ini)中添加如下配置:
log-output=FILE
general-log=1
general_log_file="D:\mysql.log"
slow-query-log=1
slow_query_log_file="D:\mysql_slow.log"
long_query_time=2
/*** PreparedStatement原理* @throws Exception*/
@Test
public void testPreparedStatement2() throws Exception {//2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写// useServerPrepStmts=true 参数开启预编译功能String url = "jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true";String username = "root";String password = "1234";Connection conn = DriverManager.getConnection(url, username, password);// 接收用户输入 用户名和密码String name = "zhangsan";String pwd = "' or '1' = '1";// 定义sqlString sql = "select * from tb_user where username = ? and password = ?";// 获取pstmt对象PreparedStatement pstmt = conn.prepareStatement(sql);Thread.sleep(10000);// 设置?的值pstmt.setString(1,name);pstmt.setString(2,pwd);ResultSet rs = null;// 执行sqlrs = pstmt.executeQuery();// 设置?的值pstmt.setString(1,"aaa");pstmt.setString(2,"bbb");// 执行sqlrs = pstmt.executeQuery();// 判断登录是否成功if(rs.next()){System.out.println("登录成功~");}else{System.out.println("登录失败~");}//7. 释放资源rs.close();pstmt.close();conn.close();
}
日志如下:
上图中第三行中的 Prepare
是对SQL语句进行预编译,第四行和第五行是执行了两次SQL语句,而第二次执行前并没有对SQL进行预编译。
原理:
-
在获取PreparedStatement对象时,将sql语句发送给mysql服务器进行检查,编译(这些步骤很耗时)
-
执行时就不用再进行这些步骤了,速度更快
-
如果sql模板一样,则只需要进行一次检查、编译
数据库连接池
数据库连接池简介
数据库连接池是个容器,负责分配、管理数据库连接(Connection)
它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个
释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏
好处:
-
资源重用
-
提升系统响应速度
-
避免数据库连接遗漏
Connection对象的复用:
数据库连接池实现
标准接口:DataSource
官方(SUN) 提供的数据库连接池标准接口,由第三方组织实现此接口。该接口提供了获取连接的功能: Connection getConnection()
那么以后就不需要通过 DriverManager
对象获取 Connection
对象,而是通过连接池(DataSource)获取 Connection
对象。
常见的数据库连接池
-
DBCP
-
C3P0
-
Druid(Druid连接池是阿里巴巴开源的数据库连接池项目,功能强大,性能优秀,是Java语言最好的数据库连接池之一 )
使用更多的是Druid,它的性能比其他两个会好一些。
Driud使用
-
导入jar包 druid-1.1.12.jar
-
定义配置文件
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true
username=root
password=1234
# 初始化连接数量
initialSize=5
# 最大连接数
maxActive=10
# 最大等待时间
maxWait=3000
-
加载配置文件
-
获取数据库连接池对象
-
获取连接
/*** 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. 获取数据库连接 ConnectionConnection connection = dataSource.getConnection();System.out.println(connection); //获取到了连接后就可以继续做其他操作了//System.out.println(System.getProperty("user.dir"));}
}
JDBC练习
完成商品品牌数据的增删改查操作
-
查询:查询所有数据
-
添加:添加品牌
-
修改:根据id修改
-
删除:根据id删除
数据库表:
-- 删除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);
实体类Brand:
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;}@Overridepublic String toString() {return "Brand{" +"id=" + id +", brandName='" + brandName + '\'' +", companyName='" + companyName + '\'' +", ordered=" + ordered +", description='" + description + '\'' +", status=" + status +'}';}
}
查询所有:
/*** 查询所有* 1. SQL:select * from tb_brand;* 2. 参数:不需要* 3. 结果:List<Brand>*/@Test
public void testSelectAll() throws Exception {//1. 获取Connection//3. 加载配置文件Properties prop = new Properties();prop.load(new FileInputStream("jdbc-demo/src/druid.properties"));//4. 获取连接池对象DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);//5. 获取数据库连接 ConnectionConnection conn = dataSource.getConnection();//2. 定义SQLString sql = "select * from tb_brand;";//3. 获取pstmt对象PreparedStatement pstmt = conn.prepareStatement(sql);//4. 设置参数//5. 执行SQLResultSet 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);//7. 释放资源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//3. 加载配置文件Properties prop = new Properties();prop.load(new FileInputStream("jdbc-demo/src/druid.properties"));//4. 获取连接池对象DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);//5. 获取数据库连接 ConnectionConnection conn = dataSource.getConnection();//2. 定义SQLString 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. 执行SQLint count = pstmt.executeUpdate(); // 影响的行数//6. 处理结果System.out.println(count > 0);//7. 释放资源pstmt.close();conn.close();
}
修改数据:
/*** 修改* 1. SQL:update tb_brandset 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//3. 加载配置文件Properties prop = new Properties();prop.load(new FileInputStream("jdbc-demo/src/druid.properties"));//4. 获取连接池对象DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);//5. 获取数据库连接 ConnectionConnection conn = dataSource.getConnection();//2. 定义SQLString 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. 执行SQLint count = pstmt.executeUpdate(); // 影响的行数//6. 处理结果System.out.println(count > 0);//7. 释放资源pstmt.close();conn.close();
}
删除数据:
/*** 删除* 1. SQL:delete from tb_brand where id = ?* 2. 参数:需要,id* 3. 结果:boolean*/
@Test
public void testDeleteById() throws Exception {// 接收页面提交的参数int id = 4;//1. 获取Connection//3. 加载配置文件Properties prop = new Properties();prop.load(new FileInputStream("jdbc-demo/src/druid.properties"));//4. 获取连接池对象DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);//5. 获取数据库连接 ConnectionConnection conn = dataSource.getConnection();//2. 定义SQLString sql = " delete from tb_brand where id = ?";//3. 获取pstmt对象PreparedStatement pstmt = conn.prepareStatement(sql);//4. 设置参数pstmt.setInt(1,id);//5. 执行SQLint count = pstmt.executeUpdate(); // 影响的行数//6. 处理结果System.out.println(count > 0);//7. 释放资源pstmt.close();conn.close();
}