文章目录
10、JDBC(重点)
java操作数据库
10.1、数据库驱动
驱动:声卡、显卡、数据库
我们的程序会通过 数据库驱动,和数据库打交道!
10.2、JDBC
SUN公司为了简化 开发人员的(对数据库的统一)操作,提供了一个(java操作数据库的)规范 ,俗称 JDBC
用这个驱动去连数据库的
这些规范的实现由具体的厂商去做~
对于开发人员来说,我们只需要掌握JDBC接口的操作即可
编写这个需要两个包:
java.sql
javas.sql
还需要导入一个数据库驱动包 :
https://mvnrepository.com/artifact/mysql/mysql-connector-java
驱动下载:
https://downloads.mysql.com/archives/c-j/
10.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,'zhangsan','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');
1、创建一个普通项目
2、导入数据库驱动
3、编写测试代码
package com.yin.lesson01;
import java.sql.*;
public class JdbcFirstDemo01 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1.加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.用户信息和url
//连接第一个参数用?
// useUnicode=true:支持中文编码
// characterEncoding=utf8:设置字符集为utf8
// useSSL=true:使用安全的连接
// serverTimezone=UTC:MySQL8.0以后加上时区
String url = "jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC";
String username ="root";
String password ="123456";
//3.连接成功,数据库对象
// DriverManager:驱动管理
// getConnection:获得连接
// connection:代表数据库
Connection connection = DriverManager.getConnection(url, username, password);
//4.执行sql的对象
// Statement:执行sql的对象
Statement statement = connection.createStatement();
//5.执行sql的对象 去执行sql ,可能存在结果,查看返回结果
String sql ="select * from users";
//execute() :执行
//executeQuery():查询
//executeUpdate():更新(增删改)
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("================================");
}
//6.释放连接
//先开后关
resultSet.close();
statement.close();
connection.close();
}
}
id=1
name=zhangsan
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
================================
进程已结束,退出代码为 0
package com.kuang.lesson01;
import java.sql.*;
// 我的第一个JDBC程序
public class jdbcFirstDemo {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 1.加载驱动
// DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
Class.forName("com.mysql.cj.jdbc.Driver"); // 固定写法,加载驱动
// 2.用户信息和url
// 连接数据库了
// 连接第一个参数用:?
// 这个三个参数的意思:?useUnicode=true&charcaterEncoding=utf8&useSSL=true
// ?useUnicode=true 支持中文编码
// charcaterEncoding=utf8 设置它得字符集为utf-8
// useSSL=true 使用安全的链接 // &serverTimezone=GMT%2B8
String url = "jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&charcaterEncoding=utf8&useSSL=true&serverTimezone=UTC ";
String username = "root";
String password = "123456";
// 3.连接成功,返回数据库对象 Connection 代表数据库
Connection connection = DriverManager.getConnection(url, username, password);
/* connection.rollback(); //设置回滚
connection.commit(); //提交
connection.setAutoCommit();//自动提交*/
// 4.执行SQL的对象 Statement 执行SQL的对象
Statement statement = connection.createStatement();
/*statement.executeQuery(); //查询操作 返回 结果集ResultSet
statement.execute(); //执行任何SQL
statement.executeUpdate(); // 更新、插入、删除。都使用这个,返回一个受影响的行数*/
// 5.执行SQL的对象 去 执行SQL ,一般执行完SQL可能存在结果,查看返回结果
//
String sql = "select * from users";
// execute() :执行
// executeQuery() :查询
// executeUpdate() :更新 所有的删除和插入都叫做更新,
ResultSet resultSet = statement.executeQuery(sql); // 返回一个结果集
// 获得指定的数据类型
/* resultSet.getObject();//在不知道列类型的情况下使用
// 如果知道列的类型就使用指定的类型
resultSet.getString();
resultSet.getInt();
resultSet.getFloat();
resultSet.getDate();*/
/*// 遍历,指针
resultSet.beforeFirst(); //移动到最前面
resultSet.afterLast();//移动到最后面
resultSet.next();//移动到下一个数据
resultSet.previous();//移动到前一行
resultSet.absolute(row);//移动到指定行*/
// 查看返回的东西
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("================================");
}
// 6.释放连接
resultSet.close();
statement.close();
connection.close();//耗资源,用完关掉
Statement st = connection.createStatement();
// String sql ="delete from u";
}
}
步骤总结:
1、加载驱动 Class.forName(“com.mysql.cj.jdbc.Driver”); // 固定写法,加载驱动
2、连接数据库 DriverManager
3、获取执行sql的对象 statement (以后写了方法后,只用往这里传一个sql就可以)
4、获得返回的结果集(resultSet) 查询才有结果集 ,剩下的是受影响的行数
5、释放连接
解释 常用的对象
DriverManager(驱动管理)
// 1.加载驱动
//DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
Class.forName("com.mysql.cj.jdbc.Driver"); // 固定写法,加载驱动
Connection connection = DriverManager.getConnection(url, username, password); //推荐使用这种
//connection 代表数据库
//作用?
//数据库设置自动提交
//事务提交
//事务回滚
//代表数据库能做的一些事情
connection.rollback(); //设置回滚
connection.commit(); //提交
connection.setAutoCommit();//自动提交
URL(地址)
//?useUnicode=true 支持中文编码
//charcaterEncoding=utf8 设置它得字符集为utf-8
//useSSL=true 使用安全的链接
String url = "jdbc:mysql://localhost:3306/com/jdbcstudy?useUnicode=true&charcaterEncoding=utf8&useSSL=false&serverTimezone=UTC";
// mysql 默认 3306
//String url = "jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC";
//公式:// 协议://主机地址:端口号/数据库名?参数1&参数2&参数3
// oralce 默认 -- 1521
// oralce(写法)
// jdbc:oracle:thin:@localhost:1521:sid
Statement:执行SQL的对象 、PrepareStatement :执行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();
....
遍历,next()指针
//[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
resultSet.beforeFirst(); //移动到最前面
resultSet.afterLast();//移动到最后面
resultSet.next();//移动到下一个数据 常用
resultSet.previous();//移动到前一行
resultSet.absolute(row);//移动到指定行
释放资源(必须做)
连接占用内存!
// 释放连接
resultSet.close();
statement.close();
connection.close();//耗资源,用完关掉
10.4、statement对象
Jdbc中的statement对象用于向数据库发送SQL语句,想完成对数据库的增删改查,只需要通过这个对象向数据库发送增删改查语句即可。
Statement对象的executeUpdate方法,用于向数据库发送增、删、改的sql语句,
executeUpdate执行完后,将会返回一个行数(即增删改语句导致了数据库几行数据发生了变化)。
Statement.executeQuery方法用于向数据库发送查询语句,
executeQuery方法返回代表查询结果的RseultSet对象。
CRUD操作-creat 增
使用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 = "updat 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对象中
}
代码实现 增删改查
1、提取工具类
db.properties
driver=com.mysql.cj.jdbc.Driver
#url=jdbc:mysql://localhost:3306/:端口号,这样就连接数据库了
url=jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&charcaterEncoding=utf8&useSSL=true&serverTimezone=UTC
username=root
password=123456
读取配置文件
package com.yin.lesson02.utils;
//工具类
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
//读取配置文件
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 {
// getClassLoader:获得类加载器
// getResourceAsStream:拿到他的资源,获取具体资源,资源名字文件名
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
//把流读到 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);
}
//释放连接资源
// Connection:数据库连接对象
// Statement:执行操作sql的对象
// ResultSet:返回的结果集
public static void release(Connection con, 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 ( con != null){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
2、编写增删改的方法,
都使用executeUpdate
增
package com.kuang.lesson02;
import com.kuang.lesson02.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
// 插入一条数据
// 增
public class TextInsert {
public static void main(String[] args) {
// 获取连接
// 把 Connection 拿到最上面去
Connection conn = null;
Statement st = null;
ResultSet rs = null;
//环绕方式快捷键:Ctrl + Alt + T
try {
conn = JdbcUtils.getConnection();//1.获取数据库连接
// 2、通过连接去创建 Statement 对象
st= conn.createStatement();//获得SQL的执行对象
// 3、编写一个要执行SQL的命令
String sql="insert into users(id,`NAME`,`PASSWORD`,`email`,`birthday`)" +
"values (4,'xiaoyin','12345','123456@qq.com','2001-04-06')";
// 4、 通过这个执行对象去执行SQL
int i = st.executeUpdate(sql);
if (i>0){
System.out.println("插入成功!");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {//在这个地方释放
// 5、最后把资源释放掉
JdbcUtils.release(conn,st,rs);
}
}
}
删
package com.kuang.lesson02;
import com.kuang.lesson02.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TextDelete {
// 删
public static void main(String[] args) {
// 获取连接
// 把 Connection 拿到最上面去
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();//1.获取数据库连接
// 2、通过连接去创建 Statement 对象
st= conn.createStatement();//获得SQL的执行对象
// 3、编写一个要执行SQL的命令
String sql=" delete from users where id = 4";
// 4、 通过这个执行对象去执行SQL
int i = st.executeUpdate(sql);
if (i>0){
System.out.println("删除成功!");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {//在这个地方释放
// 5、最后把资源释放掉
JdbcUtils.release(conn,st,rs);
}
}
}
改
package com.kuang.lesson02;
import com.kuang.lesson02.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TestUpdate {
// 更
public static void main(String[] args) {
// 获取连接
// 把 Connection 拿到最上面去
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();//1.获取数据库连接
// 2、通过连接去创建 Statement 对象
st= conn.createStatement();//获得SQL的执行对象
// 3、编写一个要执行SQL的命令
String sql="update users set `NAME`='xaioming',`email`='123456@qq.com' where id =1";
// 4、 通过这个执行对象去执行SQL
int i = st.executeUpdate(sql);
if (i>0){
System.out.println("更新成功!");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {//在这个地方释放
// 5、最后把资源释放掉
JdbcUtils.release(conn,st,rs);
}
}
}
3、查询
executeQuery
package com.kuang.lesson02;
import com.kuang.lesson02.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
// 查
public class TestSelect {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs= null;
// 1.获取连接
try {
conn= JdbcUtils.getConnection();
// 获取一个执行对象
st= conn.createStatement();
// 写SQL
// 输出全部的
// String sql = "select *from users ";
String sql = "select *from users where id=1";
// 会返回一个结果集,执行一下
rs= st.executeQuery(sql); //查询完毕会返回一个结果集
// 打印
if (rs.next()){
// 只会输出一号用户的名字
System.out.println(rs.getString("NAME"));
System.out.println(rs.getString("password"));
System.out.println("===================");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
//释放资源
finally {
JdbcUtils.release(conn,st,rs);
}
}
}
SQL注入的问题
什么叫SQL注入?
sql 存在漏洞,会被攻击导致数据泄露,本质:SQL会被拼接 ,有or的原因!
package com.yin.lesson02;
import com.yin.lesson02.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
//SQL注入问题
public class SQLInjection {
public static void main(String[] args) {
//login("xiaoying","123456"); 正常登录
//SQL注入!
login("'or'1=1"," 'or'1=1");//有技巧的输入
}
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` = 'xiaoying' 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.getObject("name"));
System.out.println(rs.getObject("password"));
System.out.println("=============");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(conn,st,rs);
}
}
}
xiaoying
123456
=============
lisi
123456
=============
wangwu
123456
=============
xiaiobai
123456
=============
进程已结束,退出代码为 0
10.5、PreparedStatement对象
它和 statement对象 本质的区别是:
PreparedStatement对象可以防止SQL注入,并且效率更高!
**PreparedStatement本质:**把传递进来的参数当做字符
代码实现
步骤
- 获取连接 获取数据库的连接对象
- 写sql执行的命令
- 通过连接创建 prepareStatement 对象 (操作sql的对象)
- 手动给参数赋值赋值 (参数的下标,具体的值)
- 执行sql
- 打印出来
- 关闭连接
1、新增
package com.yin.lesson03;
import com.yin.lesson02.utils.JdbcUtils;
import java.sql.Connection;
import java.util.Date;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TestInsert {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
try {
//1.获取连接 获取数据库的连接对象
conn = JdbcUtils.getConnection();
// 和Statement的区别:
// 使用? 占位符替代参数
//2.写sql执行的命令
String sql = "insert into users(id,`NAME`,`PASSWORD`,`email`,`birthday`) value (?,?,?,?,?)";
//3.通过连接创建 prepareStatement 对象 (操作sql的对象)
st = conn.prepareStatement(sql); //预编译SQL,先写sql,先不执行
//4.手动给参数赋值赋值
st.setInt(1,5);
st.setString(2,"xiaoming");
st.setString(3,"123456");
st.setString(4,"1455272942@qq.com");
// 注意点:
// sql.Date: 数据库的 java.sql.Date()
// util.Date: Java的 new Date().getTime() 获得时间戳
st.setDate(5,new java.sql.Date(new Date().getTime()));
//5.执行sql
int i = st.executeUpdate();
if (i>0){
System.out.println("插入成功");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(conn,st,null);
}
}
}
插入成功
2、删除
package com.yin.lesson03;
import com.yin.lesson02.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
//删除
public class TestDelete {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
//1.获取数据库的连接
try {
conn = JdbcUtils.getConnection();
//2.编写sql代码
String sql = "delete from users where id = ?";
//3.创建 PreparedStatement 操作数据库对象
st = conn.prepareStatement(sql);
//4.手动给参数赋值
st.setInt(1,5);
//5.执行sql
int i = st.executeUpdate();
if (i>0){
System.out.println("删除成功!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//6.关闭连接
JdbcUtils.release(conn,st,null);
}
}
}
删除成功
3、更新
package com.yin.lesson03;
import com.yin.lesson02.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
//修改
//1.获取数据库连接
//2.编写sql语句
//3.创建执行数据库的对象 PreparedStatement
//4.手动赋值
//5.执行sql
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` = ?,`email`= ? where id = ?";
st = conn.prepareStatement(sql);
st.setString(1,"xiaobai");
st.setString(2,"1455272942@qq.com");
st.setInt(3,1);
int i = st.executeUpdate();
if (i>0){
System.out.println("修改成功!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(conn,st,null);
}
}
}
修改成功!
4、查询
package com.yin.lesson03;
import com.yin.lesson02.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
//查
/*
1.获取数据库连接
2.编写sql的命令
3.创建操作数据库的对象 PreparedStatement
4.给数据库参数赋值 (参数的下标,具体的值)
5.执行sql
6.关闭连接
*/
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 = ?";
st = conn.prepareStatement(sql);
st.setInt(1,1);
rs = st.executeQuery();
while (rs.next()){
System.out.println(rs.getInt("id"));
System.out.println(rs.getString("name"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(conn,st,rs);
}
}
}
1
xiaobai
5、防止SQL注入
package com.yin.lesson03;
import com.yin.lesson02.utils.JdbcUtils;
import java.sql.*;
/*
步骤:
1.获得数据库连接
2.编写sql代码
3.创建操作数据库的类 PreparedStatement
4.给数据库参数赋值
5.执行sql
6.打印出来
7.关闭连接
*/
public class SQLInjection {
public static void main(String[] args) {
//正常登录
login("wangwu","123456");
//SQL注入!
// login("'or'1=1"," 'or'1=1");//有技巧的输入
}
//登录功能
public static void login(String username ,String password){
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
String sql = "select * from users where `name` =? and `password` = ?" ;
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"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(conn,st,rs);
}
}
}
wangwu
123456
10.7、使用IDEA连接数据库
2、连接成功后选择数据库
查看数据库内容:
双击数据库
更新数据
编写sql代码
注意
时区
8.0以后的版本把serverTimezone设置为:
- Asia/Shanghai
- UTC
MySQL版本
- 8.0版本使用:com.mysal.cj.jdbc.Driver
- 5.0版本使用:com.mysql.jdbc.Driver
如果版本不对,导致连接失败修改位置
路径
不要乱改
10.8、事务
要么都成功,要么都失败
ACID原则
原子性:要么都成功,要么都失败
一致性:总数不变
隔离性:多个进程互补干扰
持久性:一旦提交不可逆,持久化到数据库了
隔离性的问题:
脏度:一个事务读取了另一个没有提交的事务
不可重复读:在同一个事务内,重复读取表中的数据,表数据发生了改变
虚度(幻读):在一个事务内,读取到了别人插入的数据,导致前后读出的结果不一致(隔离性问题)
转账
代码实现
1、开启事务 : conn.setAutoCommit(false);
2、一组业务执行完毕,提交事务 :****conn.commit();
3、可以在catch 语句中显示的定义 回滚语句,但默认失败就会回滚 conn.rollback();
成功的
package com.yin.lesson04;
import com.yin.lesson02.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
//模拟转账! 成功的
public class TestTransaction1 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
// 关闭数据库的自动提交,会自动开启事务
conn.setAutoCommit(false);// 开启事务
// 执行sql
// 模拟执行成功的
String sql1 = "Update account set money = money - 100 where name = 'A'";
st = conn.prepareStatement(sql1);
st.executeUpdate();
String sql2 = "Update account set money = money + 100 where name = 'B'";
st = conn.prepareStatement(sql2);
st.executeUpdate();
//事务完毕,提交事务
conn.commit();
System.out.println("成功");
} catch (SQLException e) {
try {
//失败了回滚(恢复)
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} finally {
JdbcUtils.release(conn,st,rs);
}
}
}
失败的
package com.yin.lesson04;
import com.yin.lesson02.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
//模拟转账! 失败的
public class TestTransaction2 {
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 account set money = money - 100 where name = 'A'";
st = conn.prepareStatement(sql1);
st.executeUpdate();
int x = 1/0; // 报错
String sql2 = "Update account set money = money - 100 where name = 'B'";
st = conn.prepareStatement(sql2);
st.executeUpdate();
//事务完毕,提交事务
conn.commit();
System.out.println("成功");
} catch (SQLException e) {
try {
//失败了回滚 :回到最初的样子
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} finally {
JdbcUtils.release(conn,st,rs);
}
}
}
10.9、数据库连接池
数据库连接 – 执行完毕 – 到 释放
连接-–> 释放 是十分浪费系统资源的
池化技术: 准备一些预先的资源,过来就连接预先准备好的
- 讲解:我们每次去连接,去执行10个SQL,需要每次连接释放十次,预留下来五个接口
举例假设:银行
没有池化技术:
- ----开门 – 服务(自己)---- 关门 — 很麻烦
有池化技术:
- -----开门 ---- 业务员(等待)------服务 ----- 关门(服务器关闭)
涉及到的问题:一个银行需要几个业务员?
-
按照常用连接数设置:最小连接数!
- 最小连接数如:10
-
一般情况下设置:最大连接数!
- 最大连接数如:15 业务最高承载上限
-
假设15个员工都被用了,需要排队等待!
-
等待这15个人有人用完了在可以再用!
-
等待超时:
- 假设等待超过了100ms,断开
编写连接池
只需要实现一个接口 DataSource
对于它的实现类,世界上比较知名的有:
开源数据源实现(拿来即用)
DBCP(开源组织实现的)
C3P0(开源组织实现的)
Druid:(阿里巴巴的)
**作用:**使用了这些数据库连接池之后,我们在项目开发中就不需要编写连接数据库的代码了!
DBCP
需要用到的 jar 包
-
commons-dbcp-1.4
-
commons-pool-1.6
创建 :dbcpconfig.properties 配置文件
#连接设置 这里面的名字,是DBCP数据源定义好的
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=GMT&useUnicode=true&charcaterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai
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
创建:工具类
package com.yin.lesson05;
import com.yin.lesson02.utils.JdbcUtils;
import com.yin.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 {
//1.获取连接 获取数据库的连接对象
conn = JdbcUtils_DBCP.getConnection();
// 和Statement的区别:
// 使用? 占位符替代参数
//2.写sql执行的命令
String sql = "insert into users(id,`NAME`,`PASSWORD`,`email`,`birthday`) value (?,?,?,?,?)";
//3.通过连接创建 prepareStatement 对象 (操作sql的对象)
st = conn.prepareStatement(sql); //预编译SQL,先写sql,先不执行
//4.手动给参数赋值赋值
st.setInt(1,5);
st.setString(2,"xiaoming");
st.setString(3,"123456");
st.setString(4,"1455272942@qq.com");
// 注意点:
// sql.Date: 数据库的 java.sql.Date()
// util.Date: Java的 new Date().getTime() 获得时间戳
st.setDate(5,new java.sql.Date(new Date().getTime()));
//5.执行sql
int i = st.executeUpdate();
if (i>0){
System.out.println("插入成功");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils_DBCP.release(conn,st,null);
}
}
}
插入类
package com.yin.lesson05.utils;
import com.yin.lesson02.utils.JdbcUtils;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.nio.file.attribute.BasicFileAttributes;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils_DBCP {
// 数据源
private static DataSource dataSource = null;
static {
try {
// getClassLoader():获得类加载器
// getResourceAsStream():拿到他的资源,获取具体资源,资源名字文件名
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
//把流读到 Properties 里
Properties properties = new Properties();
// 把流加载进来
properties.load(in);
// 创建数据源
// BasicDataSourceFactory:工厂模式 --> 创建对象的
// 返回数据源
dataSource = BasicDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接 数据源里边自带连接
//简写
public static Connection getConnection() throws SQLException {
return dataSource.getConnection(); // 从数据源中获取连接
}
//释放连接资源
// Connection:数据库连接对象
// Statement:执行操作sql的对象
// ResultSet:返回的结果集
public static void release(Connection con, 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 ( con != null){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
C3P0
需要用到的 jar 包
-
c3p0-0.9.5.5
-
mchange-commons-java-0.2.19
结论
无论使用什么数据源,本质还是一样的
DataSource 接口不会变,工具类中的方法就不会变
// 获取连接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();//从数据源中获取连接 getConnection:接口的方法
}
Druid
- stringboot学习