事务
如果一个包含多个步骤的业务操作,被事务管理,那么这些操作要么同时成功,要么同时失败
案例:张三给李四转账500元
- 查询张三账户余额是否大于500
- 张三账户 金额 -500
- 李四账户 金额 +500
若被事务管理,则1,2,3当作统一整体,其中任意一个步骤出现异常,事务将进行
- 步骤:
- 开启事务:start transaction
- 回滚事务:rollback
- 提交事务:commit
-- 创建账户表
CREATE TABLE account(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(10),
balance DOUBLE
);
-- 添加数据
INSERT INTO account (name, balance) VALUE ('zhangsan', 1000),('lisi',1000);
# 张三给李四转账500 未添加事务
UPDATE account SET balance - 500 WHERE name = 'zhangsan';
UPDATE account SET balance + 500 WHERE name = 'lisi';
# 开启事务
START TRANSACTION;
UPDATE account SET balance - 500 WHERE name = 'zhangsan';
出错了。。。
UPDATE account SET balance + 500 WHERE name = 'lisi';
-- 发现问题,回滚事务
ROLLBACK;
-- 操作正常,提交事务
COMMIT;
## 查询默认提交方式
SELECT @@autocommit; -- 1:自动提交 0:手动提交
## 修改默认提交方式
SET @@autocommit = 0;
UPDATE account SET balance = 30;
MySQL数据库中事务默认自动提交,Oracle数据库默认手动提交
- 一条DML(增删改)语句会自动提交一次事务
- 事务提交的两种方式:自动(MySQL就是自动提交),手动(需要先开启事务,再提交)
- 修改事务的默认提交方式:set @@autocommit = 0/1;
事务的四大特征
- 原子性:是不可分割的最小操作单位,要么同时成功,要么同时失败
- 持久性:当事务提交或回滚后,数据库会持久化的保存数据
- 隔离性:多个事务之间,相互独立
- 一致性:事务操作前后,数据总量不变
事务的隔离级别
概念:多个事务之间是隔离的,相互独立的。但是多个事务操作同一批数据,则会发生一些问题,设置不同的隔离级别就可以解决问题
-
存在问题:
- 脏读:一个事务读取到另一个事务中没有提交的数据
- 不可重复读(虚读):再同一个事务中,两次读取到的数据不一样
- 幻读:一个事务操作(DML)数据表中所有记录,另一个事务添加了一条数据,则第一个事务查询不到自己的修改
-
隔离级别:
- read uncommitted:读未提交
- 产生的问题:脏读,不可重复读,幻读
- read committed:读已提交(Oracle默认)
- 产生的问题:不可重复读,幻读
- repeatable read:可重复度(MySQL默认)
- 产生的问题:幻读
- serializable:串行化
- 可以解决所有问题
注意: 隔离级别从小到大安全性越来越高,但是效率越来越低
数组库设置隔离级别:
-
select @@tx_isolation;
查询隔离级别 -
set global transaction isolation level 级别字符串;
设置数据库隔离级别
- read uncommitted:读未提交
SQL分类:
- DDL:操作数据库和表
- DML:增删改表中数据
- DQL:查询表中的数据
- DCL:用户管理,授权
DBA:数据库管理员
DCL:
-
管理用户
-
添加用户
CREATE USER '用户名'@'主机名'IDENTIFIED BY '密码';
-
删除用户
DROP USER '用户名'@'主机名';
-
修改用户密码
-
UPDATE USER SET PASSWORD = PASSWORD('新密码') WHERE USER = '用户名';
password需要强转,它有自己的算法规则 SET PASSWORD FOR '用户名'@'主机名' = PASSWORD('新密码')
MySQL中忘记了root的密码?
cmd(需要管理员权限) --> net stop mysql 停止MySQL服务
使用无验证方式启动MySQL服务:
mysql --skip-grant-tables
-
打开新cmd窗口直接输入mysql回车即可登录,修改mysql表中root账户密码
use mysql;
update user set password = password('新密码') where user = 'root'
任务管理器手动停止mysqld.exe进程服务
打开MySQL服务即可使用新密码登录
-
-
查询用户
- 切换到mysql数据库:
use mysql;
- 查询user表:
select * from USER;
- 通配符:%表示可以在任意主机使用用户登录数据库
- 切换到mysql数据库:
-
-
授权
查询权限:
SHOW GRANTS FOR '用户名'@'主机名';
-
授予权限:
GRANT 权限列表 ON 数据库名.表明 TO '用户名'@'主机名';
给zimo用户授予所有权限,在任意数据库任意表:
GRANT ALL ON *.* TO 'zimo'@'localhost';
撤销权限:
REVOKE 权限列表 ON 数据库名.表明 FROM '用户名'@'主机名';
JDBC
概念:Java DataBase Connectivity Java数据库连接,Java语言操作数据库
期望:使用统一的一套Java代码,可以操作所有关系型数据库
sun公司实现了这个规则(接口)。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类
步骤:
- 导入驱动jar包
- 注册驱动
- 获取数据库的连接对象Connection
- 定义SQL语句
- 获取执行SQL语句的对象Statement
- 执行SQL,接收返回结果
- 处理结果
- 释放资源
package com.zimo._08_jdbc;
import java.sql.*;
public class JdbcDemo1 {
public static void main(String[] args) throws Exception {
// 1.导入MySQL jar包
// 2.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 3.获取数据库连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?serverTimezone=UTC", "root", "123456");
// Connection conn = DriverManager.getConnection("jdbc:mysql:///test?serverTimezone=UTC", "root", "123456");
// 4.定义sql语句
String sql = "CREATE TABLE account(id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(10),balance DOUBLE);";
// 5.获取执行sql的对象 statement
Statement st = conn.createStatement();
// 6.执行sql
st.addBatch(sql);
int[] ints = st.executeBatch();
// 7.处理结果
for (int anInt : ints) {
System.out.println(anInt);
}
// 8.释放资源
st.close();
conn.close();
}
}
详解各个对象
-
DriverManager:驱动管理对象
-
注册驱动:
static void registerDriver(Driver driver)
注册与给定的驱动程序DriverManager写代码使用:
Class.forName("com.mysql.cj.jdbc.Driver");
在
com.mysql.cj.jdbc.Driver
类中存在静态代码块java.sql.DriverManager.registerDriver(new Driver());
注意:MySQL5之后的驱动jar包可以省略注册驱动的步骤
-
获取数据库连接:
static Connection getConnection(String url, String user, String password)
-
url:指定连接的路径
MySQL连接方式:
jdbc:mysql://ip地址(或域名):端口号/数据库名称
细节:如果是本机的MySQL服务器,并且MySQL服务默认端口是3306,可以简写为
jdbc:mysql:///数据库名称
user:用户名
password:密码
-
-
-
Connection:数据库连接对象
-
获取执行sql的对象:
Statement createStatement()
PreparedStatement prepareStatement(String sql)
-
管理事务:
- 开启事务:
void setAutoCommit(boolean autoCommit)
设置参数false,即开启事务 - 提交事务:
commit()
- 回滚事务:
rollback()
- 开启事务:
-
-
Statement:执行静态sql的对象
-
boolean execute(String sql)
:可以执行任意sql -
int executeUpdate(String sql)
:执行DML(insert、update、delete)语句、DDL(create,alert,drop)语句,返回受影响的行数 -
ResultSet executeQuery(String sql)
:执行DQL(select)语句
String sql = "CREATE TABLE account(id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(10),balance DOUBLE);"; int rs = st.executeUpdate(sql); String sql = "select * from account;"; ResultSet resultSet = st.executeQuery(sql);
-
-
ResultSet:结果集对象,封装查询结果对象
-
boolean next()
:游标向下移动一行,返回true说明有数据 -
getXxx(参数)
:获取数据- Xxx代表数据类型(Int,String)
- 参数:
- int代表列的编号从1开始
- Sting代表列名称
while(resultSet.next()) { System.out.println(resultSet.getString("name") + resultSet.getInt("balance")); }
-
-
PreparedStatement:执行动态sql的对象(是Statement的子类)
- sql注入问题:在拼接sql时,有一些特殊关键字参与字符串凭借,会造成安全性问题
- 输入用户随便,输入密码:
'a' or 'a' = 'a'
- sql:
select * from user where username = "abcdefadg" and password = 'a' or 'a' = 'a'
- 输入用户随便,输入密码:
- 解决sql注入问题:使用PreparedStatement对象来解决
- 预编译的SQL:参数使用?作为占位符
- 导入驱动jar包
- 注册驱动
- 获取数据库连接对象Connection
- 定义sql:注意,使用占位符?作为参数
- 获取执行sql语句的对象PreparedStatement
Connection.prepareStatement(String sql)
- 给?占位符赋值
setXxx(参数1,参数2) 参数1:?所在位置(从1开始) 参数2:设置的值
- 执行sql,接收返回结果,不需要传递sql语句
- 处理结果
- 释放资源
- sql注入问题:在拼接sql时,有一些特殊关键字参与字符串凭借,会造成安全性问题
抽取JDBC工具类:JDBCUtils
- 目的:简化书写
- 分析:
- 抽取注册驱动
- 抽取一个方法获取连接对象
- 需求:不想传参(麻烦),还保证工具类的通用性
- 解决:配置文件
- 抽取一个方法释放资源
url=jdbc:mysql://localhost:3306/test
user=root
password=123456
driver=com.mysql.cj.jdbc.Driver
package com.zimo._09_JDBCUtils;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.sql.*;
import java.util.Properties;
/**
* JDBC工具类
*
* @author liuweifeng
* @version v0.1 by 2020-07-29 14:00:00
*/
public class JdbcUtils {
private static String url;
private static String user;
private static String password;
private static String driver;
/**
* 读取配置文件,只执行一次。使用静态代码块
*/
static {
try {
// 读取资源文件,获取值
// 1.创建Properties集合类
Properties properties = new Properties();
// 2.加载文件
// 2.1先获取src路径下的文件的方式------ClassLoader
ClassLoader classLoader = JdbcUtils.class.getClassLoader();
URL resource = classLoader.getResource("com/zimo/_09_JDBCUtils/jdbc.properties");
String path = resource.getPath();
properties.load(new FileReader(path));
// 3.获取数据,赋值
url = properties.getProperty("url");
user = properties.getProperty("user");
password = properties.getProperty("password");
driver = properties.getProperty("driver");
// 4.注册驱动
Class.forName(driver);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取连接
*
* @return 连接对象
*/
public static Connection getConnection() throws SQLException {
String s = url + "?serverTimezone=UTC";
return DriverManager.getConnection(s, user, password);
}
/**
* 释放资源
*
* @Param statement
* @Param connection
*/
public static void close(Statement statement, Connection connection) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 释放资源
*
* @Param resultSet
* @Param statement
* @Param connection
*/
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws SQLException {
// 演示JDBC工具类
Connection connection = JdbcUtils.getConnection();
// 定义sql语句
String sql = "select * from account;";
// 获取执行sql的对象 statement
Statement st = connection.createStatement();
// 执行sql
ResultSet resultSet = st.executeQuery(sql);
// 处理结果
boolean b = true;
while((b = resultSet.next()) != false) {
System.out.println(resultSet.getString("name") + resultSet.getInt("balance"));
}
// 关闭资源
JdbcUtils.close(resultSet, st, connection);
}
}
案例:登录验证
# 创建一张user表
CREATE TABLE USER(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32),
password VARCHAR(32)
);
# 插入数据
INSERT INTO USER VALUE(NULL,'zimo','123456');
INSERT INTO USER VALUE(NULL,'root','123456');
SELECT * FROM USER;
package com.zimo._09_JDBCUtils.LoginDemo;
import com.zimo._09_JDBCUtils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
/**
* 练习:
* 1.通过键盘录入用户名和密码
* 2.判断用户是否登录成功
*/
public class Login {
/**
* 登录方法
* @return Boolean res
*/
public boolean login(String user, String pass) throws SQLException {
if (user == null || pass == null){
return false;
}
// 连接数据库判断是否登录成功
Connection connection = JdbcUtils.getConnection();
Statement statement = connection.createStatement();
String sql = "select * from user where username = '" + user + "' and password = '" + pass + "'";
ResultSet resultSet = statement.executeQuery(sql);
return resultSet.next();
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名");
String name = sc.nextLine().trim();
System.out.println("请输入密码");
String pass = sc.nextLine().trim();
boolean res = false;
try {
res = new Login().login(name, pass);
} catch (SQLException e) {
e.printStackTrace();
}
if (res){
System.out.println("success");
}else {
System.out.println("false");
}
}
}
JDBC管理事务——转账
- 在执行SQL之前开启事务
- 当所有SQL都执行完提交事务
- 在catch中回滚事务
package com.zimo._09_JDBCUtils.AutoCommit;
import com.zimo._09_JDBCUtils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCAutoCommit {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement1 = null;
PreparedStatement preparedStatement2 = null;
try {
// 1.获取连接
connection = JdbcUtils.getConnection();
// 6_1.开启事务
connection.setAutoCommit(false);
// 2.定义sql
String sql1 = "update account set balance = balance - ? where name = ?";
String sql2 = "update account set balance = balance + ? where name = ?";
// 3.获取执行sql对象
preparedStatement1 = connection.prepareStatement(sql1);
preparedStatement2 = connection.prepareStatement(sql2);
// 4.设置参数
preparedStatement1.setInt(1, 500);
preparedStatement2.setInt(1, 500);
preparedStatement1.setString(2, "zimo");
preparedStatement2.setString(2, "mike");
// 5.执行sql
preparedStatement1.execute();
// 如果这里发生异常就会导致金额出问题,所以需要开启事务
preparedStatement2.execute();
// 6_2.事务提交
connection.commit();
} catch (Exception e) {
// 6_3.事务回滚
try {
if (connection != null) connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
}finally {
JdbcUtils.close(preparedStatement1, connection);
JdbcUtils.close(preparedStatement2, null);
}
}
}
数据库连接池
-
概念:其实就是一个容器(集合),存放数据库连接的容器。
当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器
-
好处:
- 节约资源
- 用户访问高校
-
实现:
- 标准接口:DataSource java.sql包下的
- 方法:
- 获取连接:
getConnection()
- 归还连接:
close()
如果连接对象Connection是从连接池中获取,那么调用Connection.close()方法则是归还连接,而不是关闭
- 获取连接:
- 方法:
- 一般我们不去实现它,由数据库厂商来实现
- C3P0:数据库连接池技术
- Druid:(德鲁伊)数据库连接池实现技术,由阿里巴巴提供的
- 标准接口:DataSource java.sql包下的
C3P0连接池
- C3P0使用步骤:
- 导入jar包(两个):c3p0.jar 和依赖包 mchange-commons-java.jar
- 定义配置文件:
- 名称:c3p0.properties 或者 c3p0-config.xml
- 路径:默认类路径classpath下,可以直接放在src目录下
- 创建核心对象:数据库连接池对象ComboPooledDataSource
- 获取连接对象:
getConnection()
com.mysql.cj.jdbc.Driver
jdbc:mysql://localhost:3306/test
root
123456
5
10
3000
com.mysql.cj.jdbc.Driver
jdbc:mysql://localhost:3306/test
root
123456
5
8
1000
public static void main(String args[]){
// 创建数据库连接池对象——使用默认配置
DataSource ds = new ComboPooledDataSource();
// 创建数据库连接池对象——使用指定名称的配置
DataSource ds = new ComboPooledDataSource("otherc3p0");
// 获取连接对象
Connection conn = ds.getConnection();
}
Druid连接池(阿里巴巴提供的)
- Druid使用步骤:
- 导入jar包 druid.jar
- 定义配置文件:
- 是properties形式的
- 可以叫任意名称,可以放在任意目录下
- 加载配置文件properties
- 获取数据库连接池对象:通过工厂类来获取 DruidDataSourceFactory
- 获取连接:getConnection
dirverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/test
username=root
password=123456
initialSize=5
maxActive=10
maxWait=3000
public static void main(String args[]){
// 加载配置文件
Properties pro = new Properties();
InputStream in = DruidDemo.class.getClassLoader().gerResourceAsStream("druid.properties");
pro.load(is);
// 获取连接对象
DataSource ds =DruidDataSourceFactory.createDataSource(pro);
// 获取连接对象
Connection conn = ds.getConnection();
}
- 定义Druid工具类
- 定义一个类JDBCUtils
- 提供静态代码块加载配置文件,初始化连接池
- 提供方法
- 获取连接方法:通过数据库连接池获取连接
- 释放资源
- 获取连接池的方法
package com.zimo.JDBCDruidUtils;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.zimo._09_JDBCUtils.JdbcUtils;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* Druid连接池的工具类
*
* @author liuweifeng
* @version v0.1 by 2020-07-30 11:48:57
*/
public class DruidUtils {
private static DataSource dataSource;
static {
try {
// 1.加载配置文件
Properties pro = new Properties();
pro.load(DruidUtils.class.getClassLoader().getResourceAsStream("com/zimo/JDBCDruidUtils/druid.properties"));
// 2.获取DataSource
dataSource = DruidDataSourceFactory.createDataSource(pro);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取连接对象
*/
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
/**
* 释放资源
*/
public static void close(Statement statement, Connection connection) {
close(null,statement, connection);
}
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
if (null != resultSet){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != statement){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != connection) try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 获取连接池对象
*/
public static DataSource getDataSource(){
if (dataSource != null) return dataSource;
return null;
}
}
测试连接池工具
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/test?rewriteBatchedStatements=true&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&failOverReadOnly=false&serverTimezone=UTC
username=root
password=123456
initialSize=5
maxActive=10
maxWait=3000
maxIdle=8
minIdle=3
package com.zimo.JDBCDruidUtils;
import com.zimo.JDBCDruidUtils.DruidUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCDruidDemo {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
// 1.获取连接
connection = DruidUtils.getConnection();
// 2.定义sql
String sql = "insert into account values(null, ?, ?)";
// 3.获取PreparedStatement
preparedStatement = connection.prepareStatement(sql);
// 4.设置占位符
preparedStatement.setString(1, "zhangsan");
preparedStatement.setDouble(2,10086);
// 5.执行sql
int i = preparedStatement.executeUpdate();
System.out.println(i);
} catch (SQLException e) {
e.printStackTrace();
}finally {
DruidUtils.close(preparedStatement, connection);
}
}
}
JDBCTemplete
Spring框架对JDBC的简单封装。提供了一个JDBCTemplate对象简化JDBC的开发
-
步骤:
导入jar包
-
创建JDBCTemplate对象,依赖数据源DataSource
JdbcTemplate template = new JdbcTemplate(dataSource);
-
调用JdbcTemplate的方法来完成CRUD的操作
-
update()
:执行DML语句。增、删、改语句 -
query()
:将查询结果集封装成JavaBean对象-
BeanPropertyRowMapper
:该方法可以直接返回一个JavaBean的自动封装(User.class)
-
-
queryForMap()
:将查询结果集封装成Map集合- 注意:该结果集的长度只能为1
-
queryForList()
:将查询结果集封装成list集合- 注意:将每一条记录封装成一个Map然后将Map集合装入List集合中
-
queryForObject)
:将查询结果集封装成对象- 注意:一般用于集合函数的查询
-
package com.zimo.JDBCTemplate;
import com.zimo.JDBCDruidUtils.DruidUtils;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.*;
public class JDBCTemplateDemo {
public static void main(String[] args) {
// 1.导入jar包
// 2.创建JDBCTemplate对象
JdbcTemplate template = new JdbcTemplate(DruidUtils.getDataSource());
// 3.调用方法
String sql = "update account set balance = 1000001 where id = ?";
template.update(sql, 1);
List