0%

MySQL与Java

通过Java代码操作数据库。

目录 概述
IDEA 常用快捷键
Properties 类 构造方法,成员方法
JDBC JDBC 连接数据库,编写 JDBC 工具类
PreparedStatement 接口 SQL 注入问题,获取对象,成员方法
c3p0 连接池 数据库连接池,DataSource接口,编写 c3p0 连接池工具类
DBUtils QueryRunner类,ResultSetHandler接口,DbUtils工具类
事务操作 MySQL 事务操作,JDBC 事务操作,DBUtils 事务操作
案例:三层架构 持久层,持久层,表示层

IDEA

IntelliJ IDEA是一种商业化销售的Java集成开发环境(Integrated Development Environment,IDE)工具软件。

常用快捷键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
运行		Alt+Shift+F10
调试 Shift+F9
停止 Ctrl+F2

#编写代码
复制当前行 Ctrl+D
删除当前行 Ctrl+Y
移动代码(块 Ctrl+Shift+↑/↓
代码重构 Ctrl+Shift+Alt+T
格式化代码 Ctrl+Alt+L
包裹代码(if/elsetry/catch...) Ctrl+Alt+T
提取变量 Ctrl+Alt+V

#代码生成
for循环 fori+Tab
println sout+Tab
main psvm+Tab
查询模板 Ctrl+J
构造函数、toString、getter/setter、重写父类方法 Alt+Insert
user .for+Tab => for(User user:users)
user.getNum() .var+Tab => Date birthday = user.getNum()

#编辑
重命名 Shift+F6
以单词为单位前后移动 Ctrl+←/→
以代码块为单位前后移动 Ctrl+/和Ctrl+Shift+/
定位到错误处 F2和Shift+F2

#智能提示
基本提示 Ctrl+Space
按类型信息提示 Ctrl+Shift+Space
快速修复 Alt+Enter
自动补全末尾 Ctrl+Shift+Enter

#窗口切换
Project Alt+1
Editor Esc

#查找
打开类、文件 Ctrl+N和Ctrl+Shift+N
全局搜索 Shift+Shift
打开当前类的继承关系 Ctrl+H

Properties类

概述

Properties类继承于Hashtable,来表示一个持久的属性集。它使用键值结构存储数据,每个键及其对应值都是一个字符串。可以使用Properties类从文件中动态加载配置

构造方法

1
public Properties():创建一个空的属性列表

成员方法

1
2
3
4
public Object setProperty(String key, String value):保存一对属性
public String getProperty(String key):使用此属性列表中指定的键搜索属性值
public Set<String> stringPropertyNames():所有键的名称的集合
public void load(InputStream inStream):从字节输入流中读取键值对

基本的存储方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 基本的存储方法
public class PropertiesDemo {
public static void main(String[] args) throws IOException {
// 创建属性集对象
Properties properties = new Properties();
// 添加键值对元素
properties.setProperty("filename", "a.txt");
properties.setProperty("length", "209385038");
properties.setProperty("location", "D:\\a.txt");
// 打印属性集对象
System.out.println(properties);
// 通过键,获取属性值
System.out.println(properties.getProperty("filename"));
System.out.println(properties.getProperty("length"));
System.out.println(properties.getProperty("location"));
}
}

// 输出结果
{filename=abc.txt, length=89757, location=D:\a.txt}
abc.txt
89757
D:\a.txt

与流相关的方法

从字节输入流中读取键值对。

1
2
3
4
// 文件中的内容(必须是键值对形式,可以使用空格、等号、冒号等符号分隔)
filename=a.txt
length=209385038
location=D:\a.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 与流相关的方法
public class PropertiesDemo {
public static void main(String[] args) throws IOException {
// 创建属性集对象
Properties properties = new Properties();
// 加载文本中信息到属性集
properties.load(new FileInputStream("E:\\workspace\\IDEA\\MySQL\\abc.txt"));
// 遍历属性集,获取所有键的集合
Set<String> list = properties.stringPropertyNames();
// 遍历集合,打印键值对
for (String key : list) {
System.out.println(key + " " + properties.getProperty(key));
}
}
}

// 输出结果
filename a.txt
length 209385038
location D:a.txt

JDBC

概述

JDBC(Java DataBase Connectivity)

是一种用于执行SQL语句的Java API,是Java访问数据库的标准规范,由一组用Java语言编写的接口和类组成,可以为不同的关系型数据库提供统一访问。

驱动

是JDBC接口的实现,一般由数据库厂商提供,驱动实现不同设备之间进行通信。

JDBC规范(四个核心对象)

  • DriverManager:用于注册驱动
  • Connection: 表示与数据库创建的连接
  • Statement: 操作数据库sql语句的对象
  • ResultSet: 结果集(一张虚拟表)

JDBC连接数据库

开发步骤

  1. 导入mysql-connector-java-5.1.37-bin.jar包
  2. 注册驱动
  3. 获取连接对象
  4. 创建SQL语句
  5. 获取Statement对象来执行SQL语句
  6. 执行SQL语句,返回影响的行数或结果集
  7. 打印返回值或遍历打印结果集中的数据
  8. 关闭资源

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class JDBCDemo {
public static void main(String[] args) throws SQLException {
/*
注册驱动,不建议使用
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
原因:a.驱动被注册2次 b.强烈依赖数据库的驱动jar
*/
// DriverManager.registerDriver(new com.mysql.jdbc.Driver());
// 2.注册驱动,建议使用Class.forName("com.mysql.jdbc.Driver");
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

/*
3.获取连接对象
url格式:jdbc:mysql://ip地址:端口号(3306)/数据库名称(product)
username:数据库的用户名
password:数据库的密码
*/
Connection connection = DriverManager.getConnection("jdbc:mysql:///product", "root", "root");
// Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/product", "root", "root");

//4.创建SQL语句
String sql = "select * from product";

//5.获取Statement对象来执行SQL语句,存在SQL注入漏洞
Statement statement = connection.createStatement();
/*
6.执行SQL语句
int executeUpdate(String sql); --执行insert/update/delete语句,返回影响的行数,用int接收
ResultSet executeQuery(String sql); --执行select语句,返回结果集对象,用ResultSet接收
boolean execute(String sql); --仅当执行select并且有结果时才返回true,执行其他的语句返回false,一般不用
*/
ResultSet rs = statement.executeQuery(sql);

// 7.遍历打印结果集中的数据,类似于迭代器
while (rs.next()) {
String pid = rs.getString("pid");
String pname = rs.getString("pname");
double price = rs.getDouble("price");
String category_id = rs.getString("category_id");
System.out.println(pid + "," + pname + "," + price + "," + category_id);
}

// 8.关闭资源,先建立连接的后关闭
rs.close();
statement.close();
connection.close();
}
}

编写JDBC工具类

“获得数据库连接”操作,将在以后的增删改查所有功能中都存在,可以封装成JDBCUtils工具类,实现代码复用。

JDBCUtils工具类实现的功能

  1. 通过DataBase.conf文件配置连接
  2. 获取连接对象
  3. 关闭资源

配置文件中的内容

1
2
3
url=jdbc:mysql:///product
username=root
password=root

JDBC工具类中的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class JDBCUtils {
// 数据库连接配置,通过配置文件赋值
private static String url;
private static String username;
private static String password;

// 私有化构造方法,防止实例化
private JDBCUtils() {
}

// 在静态代码块中注册驱动,导入配置
// 静态代码块的特点:类只要被加载到内存,静态代码块就会执行,并且只执行一次
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

// 从DataBase.conf文件中动态加载配置
try {
Properties properties = new Properties();
FileInputStream fis = new FileInputStream("DataBase.conf");
properties.load(fis);
url = properties.get("url").toString();
username = properties.get("username").toString();
password = properties.get("password").toString();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}

// 获取连接对象
public static Connection getConnection() throws SQLException {
Connection connection = DriverManager.getConnection(url, username, password);
return connection;
}

// 关闭资源
public static void closeResource(Connection con, Statement st) throws SQLException {
st.close();
con.close();
}
public static void closeResource(Connection con, Statement st, ResultSet rs) throws SQLException {
rs.close();
st.close();
con.close();
}
}

PreparedStatement接口

SQL注入问题

用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的含义。

SQL注入案例

有登录案例SQL语句如下:

1
SELECT * FROM 用户表 WHERE NAME = 用户输入的用户名 AND PASSWORD = 用户输的密码;

当用户输入正确的账号与密码后,查询到了信息则让用户登录。但当用户输入的密码内容为'XXX' OR 'a'='a'时,真正执行的代码变为:

1
SELECT * FROM 用户表 WHERE NAME = 'XXX' AND PASSWORD ='XXX'  OR 'a'='a';

上述查询语句是永远可以查询出结果的,这便是SQL注入问题。为此,我们使用PreparedStatement来解决对应的问题。

概述

PreparedStatement接口表示预编译的 SQL 语句的对象,SQL 语句被预编译并存储在 PreparedStatement 对象中,然后可以使用此对象多次高效地执行该语句。

获取对象

通过调用Connection接口的prepareStatement(String sql)方法返回PreparedStatement对象。

成员方法

1
2
3
int executeUpdate():执行增删改,返回影响数据的行数
ResultSet executeQuery():执行 SQL 查询,并返回该查询生成的 ResultSet 结果集对象
boolean execute():执行任何种类的 SQL 语句,一般不用

特点

  • 性能高
  • 预编译sql语句
  • 能过滤掉用户输入的关键字

使用步骤

  1. 获取连接对象
  2. 创建sql语句,使用占位符?替换要传入的实际参数
  3. 获取预编译语句执行对象
  4. 设置实际参数
  5. 执行SQL语句
  6. 打印数据
  7. 关闭资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class PreparedStatementDemo {
public static void main(String[] args) throws SQLException {
// 1.通过JDBCUtils工具类获取连接对象
Connection con = JDBCUtils.getConnection();

// 2.创建SQL语句,使用占位符?替换要传入的实际参数
String sql = "select * from account where name = ? and password = ?";

// 3.获取预编译语句执行对象
PreparedStatement psm = con.prepareStatement(sql);

// 4.设置实际参数
psm.setString(1, "jack");
psm.setString(2, "123456");

// 5.执行SQL语句
ResultSet rs = psm.executeQuery();

// 6.打印结果集中的数据
if (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
double money = rs.getDouble("money");
String password = rs.getString("password");
System.out.println(id + "," + name + "," + money + "," + password);
}

// 7.关闭资源
JDBCUtils.closeResource(con, psm);
}
}

c3p0连接池

数据库连接池概述

数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。

连接池可以理解为存放多个连接的集合,使用连接池技术可以节省建立数据库连接耗费的资源和时间提高性能

常见的连接池:C3P0、DRUID。

DataSource接口

Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口。

编写c3p0连接池工具类

c3p0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。

c3p0连接池工具类实现的功能

  1. 创建连接池对象,通过src目录下的c3p0-config.xml文件配置连接
  2. 从池中获得一个连接
  3. 关闭资源

配置文件中的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<c3p0-config>
<!-- 使用默认的配置读取连接池对象 -->
<default-config>
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day03</property>
<property name="user">root</property>
<property name="password">root</property>

<!-- 连接池参数 -->
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">10</property>
<property name="checkoutTimeout">2000</property>
<property name="maxIdleTime">1000</property>
</default-config>
</c3p0-config>

c3p0连接池常用的配置参数:

参数 说明
initialPoolSize 初始连接数
maxPoolSize 最大连接数
checkoutTimeout 最大等待时间
maxIdleTime 最大空闲回收时间

c3p0连接池工具类中的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class C3P0Utils {
//私有化构造方法,防止实例化
private C3P0Utils() {
}

//创建C3P0连接池对象,通过c3p0-config.xml文件配置连接
private static DataSource ds = new ComboPooledDataSource();

//提供静态方法返回DataSource对象
public static DataSource getDataSource(){
return ds;
}

//从池中获得一个连接
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}

//关闭资源,其中的C3P0连接池重写的con.close()方法是将连接放回资源池
public static void closeReference(Connection con, Statement sm, ResultSet rs) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
sm.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void closeReference(Connection con, Statement sm) {
try {
sm.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

使用c3p0连接池工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
public class C3P0UtilsDemo {
/**
* 插入一条数据
* @throws SQLException
*/
@Test
public void insert() throws SQLException {
//获取连接池对象
Connection con = C3P0Utils.getConnection();
//创建SQL语句。字段名称需要在预编译前写好,不能通过?来添加
String sql="insert into product (pid,pname,price,category_id) values(?,?,?,?)";
//获取预编译语句执行对象
PreparedStatement psm=con.prepareStatement(sql);
//设置实际参数
// psm.setInt(1,null);
psm.setString(1,"p011");
psm.setString(2,"粗粮");
psm.setDouble(3,999);
psm.setString(4,"c004");
//执行SQL语句
int count=psm.executeUpdate();
System.out.println("影响了"+count+"条数据");
C3P0Utils.closeReference(con,psm);
}

/**
* 删除一条数据
* @throws SQLException
*/
@Test
public void delete() throws SQLException {
//获取连接池对象
Connection con = C3P0Utils.getConnection();
//创建SQL语句
String sql="delete from product where pname=?";
//获取预编译语句执行对象
PreparedStatement psm=con.prepareStatement(sql);
//设置实际参数
psm.setString(1,"水果");
//执行SQL语句
int count=psm.executeUpdate();
System.out.println("影响了"+count+"条数据");
C3P0Utils.closeReference(con,psm);
}

/**
* 查询数据
* @throws SQLException
*/
@Test
public void query() throws SQLException {
//获取连接池对象
Connection con = C3P0Utils.getConnection();
//创建SQL语句
String sql="select * from product where pname=?";
//获取预编译语句执行对象
PreparedStatement psm=con.prepareStatement(sql);
//设置实际参数
psm.setString(1,"联想");
//执行SQL语句
ResultSet rs=psm.executeQuery();
//循环打印结果集中的数据
while (rs.next()){
int pid = rs.getInt("pid");
String pname = rs.getString("pname");
double price = rs.getDouble("price");
String category_id = rs.getString("category_id");
System.out.println(pid+" "+pname+" "+price+" "+category_id);
}
C3P0Utils.closeReference(con,psm,rs);
}

/**
* 修改一条数据
* @throws SQLException
*/
@Test
public void update() throws SQLException {
//获取连接池对象
Connection con = C3P0Utils.getConnection();
//创建SQL语句
String sql="update product set price=? where pname=?";
//获取预编译语句执行对象
PreparedStatement psm=con.prepareStatement(sql);
//设置实际参数
psm.setDouble(1,5000);
psm.setString(2,"联想");
//执行SQL语句
int count=psm.executeUpdate();
System.out.println("影响了"+count+"条数据");
//关闭资源
C3P0Utils.closeReference(con,psm);
}
}

DBUtils

为了简化JDBC开发,我们采用Apache Commons组件的一个成员:DBUtils

DBUtils是JDBC的简化开发工具包,需要项目导入commons-dbutils-1.6.jar才能使用。

概述

DBUtils是java编程中的数据库操作实用工具,小巧简单实用。DBUtils封装了对JDBC的操作,简化了JDBC操作,可以少写代码。

Dbutils的三个核心功能

  • QueryRunner类:提供对sql语句操作的API
  • ResultSetHandler接口:用于定义select操作后,怎样封装结果集
  • DbUtils类:它是一个工具类,定义了关闭资源与事务处理的方法

QueryRunner

构造方法

1
2
QueryRunner(DataSource):创建核心类,【提供】数据源,内部自己维护Connection
QueryRunner():创建核心类,【不提供】数据源,在进行具体操作时,【需要】提供Connection

成员方法

1
2
3
4
update(String sql, Object ... params):执行DML语句,【不使用】Connection
update(Connection con, String sql, Object ... params):【使用】提供的Connection,完成DML语句
query(String sql, ResultSetHandler, Object ... params):执行DQL语句,并将查询结果封装到对象中
query(Connection con, String sql, ResultSetHandler, Object ... params):使用提供的Connection,执行DQL语句,并将查询结果封装到对象中

实现增删改

update(String sql, Object... params):用来完成表数据的增加、删除、更新操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class DBUtilsDemo {
/*
dbutil完成数据的增、删、改
qr.update("insert into product (pname,price,category_id) values(?,?,?)",params);
*/
//插入一条数据
@Test
public void insert() throws SQLException {
//获取QueryRunner对象
QueryRunner qr=new QueryRunner(C3P0Utils.getDataSource());

//调用方法执行SQL语句
// Object[] params={"思念水饺",23,"f001"};
// int count=qr.update("insert into product (pname,price,category_id) values(?,?,?)",params);
int count=qr.update("insert into product (pname,price,category_id) values(?,?,?)","思念水饺",23,"f001");
System.out.println("影响了"+count+"条数据");
}

//删除一条数据
@Test
public void delete() throws SQLException {
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
Object[] params={"思念水饺"};
int count=qr.update("delete from product where pname=?",params);
System.out.println("影响了"+count+"条数据");
}

//修改一条数据
@Test
public void update() throws SQLException {
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
Object[] params={4000,"联想"};
int count = qr.update("update product set price=? where pname=?", params);
System.out.println("影响了"+count+"条数据");
}
}

实现查询

query(String sql, ResultSetHandler<T> rsh, Object... params):用来完成表数据的查询操作

ResultSetHandler结果集

  • BeanHandler:将结果集中第一条记录封装到一个指定的javaBean中。
  • BeanListHandler:将结果集中每一条记录封装到指定的javaBean中,将这些javaBean在封装到List集合中
  • ScalarHandler:它是用于单数据。例如select count(*) from表操作。
  • ColumnListHandler:将结果集中指定字段的值,封装到一个List集合中

JavaBean

JavaBean就是一种(遵循一定规范的)类,在开发中常用于封装数据。具有如下特性:

  1. 需要实现接口:java.io.Serializable,通常可以省略这个步骤
  2. 提供私有字段:private 类型 字段名;
  3. 提供getter/setter方法
  4. 提供无参构造

POJO

POJO翻译为中文就是普通Java类,具有一部分JavaBean规范的类就可以称为POJO类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//需要定义一个Product的pojo类,存放Product对象
public class DBUtilsDemo {
/* 1.查询一个Product对象,使用BeanHandler
BeanHandler处理方式:
将数据表的结果集第一行数据,封装成JavaBean类的对象
构造方法:
BeanHandler(Class<T> type)
传递一个Class类型对象,将结果封装到哪个类的对象呢
ZhangWu类的Class对象
*/
@Test
public void queryProduct() throws SQLException {
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
Product product = qr.query("select * from product where pname=?",
new BeanHandler<>(Product.class), "联想"); //Product.class是字节码文件,使用了反射,后面讲
System.out.println(product);
}

@Test
/* 2.查询一个List<Product>,使用BeanListHandler
将数据表的每一行数据,封装成JavaBean类对象
多个JavaBean对象使用List集合存储
*/
public void queryList() throws SQLException {
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
List<Product> list = qr.query("select * from product",
new BeanListHandler<>(Product.class));
for (Product product : list) {
System.out.println(product.getPid()+" "+product.getPname()+" "+product.getPrice()+" "+product.getCategory_id());
}
}

/* 3.查询一列商品的名字List<String>,使用ColumnListHandler
ColumnListHandler处理方式:
将查询数据表结果集中的某一列数据,存储到List集合
如果不确定哪个列,也不确定数据类型,使用List<Object>
ColumnListHandler构造方法:
空参数:获取就是数据表的第一列
int参数:传递列的顺序编号
String参数:传递列名
创建对象,可以加入泛型,但是加入的数据类型要和查询的列类型一致
*/
@Test
public void queryColumn() throws SQLException {
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
List<Object> list = qr.query("select pname from product",
new ColumnListHandler());
for (Object pname : list) {
System.out.println(pname);
}
}

/* 4.查询一共有多少商品,使用ScalarHandler
处理单值查询结果,即执行select语句后,结果集只有1个
*/
@Test
public void queryCount() throws SQLException {
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
Long count = (Long)qr.query("select count(*) from product",
new ScalarHandler());
System.out.println("查询到了"+count+"条结果");
}
}

事务操作

概述

事务主要用于处理操作量大,复杂度高的数据。

  • 在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。
  • 事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。
  • 事务用来管理 insert,update,delete 语句

事务特性:ACID

一般来说,事务是必须满足4个条件(ACID):原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。

  • 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  • 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
  • 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
  • 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

并发访问问题

如果不考虑隔离性,事务存在三种并发访问问题:

  1. 脏读:一个事务读到了另一个事务未提交的数据
  2. 不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致
  3. 虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致

隔离级别:解决问题

数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。

  1. read uncommitted:读未提交,一个事务读到另一个事务没有提交的数据。
  • 存在:3个问题(脏读、不可重复读、虚读)
  • 解决:0个问题
  1. read committed 读已提交,一个事务读到另一个事务已经提交的数据。
  • 存在:2个问题(不可重复读、虚读)
  • 解决:1个问题(脏读)
  1. repeatable read:可重复读,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。
  • 存在:1个问题(虚读)
  • 解决:2个问题(脏读、不可重复读)
  1. serializable 串行化,同时只能执行一个事务,相当于事务中的单线程。
  • 存在:0个问题。
  • 解决:3个问题(脏读、不可重复读、虚读)

安全和性能对比

  • 安全性:serializable > repeatable read > read committed > read uncommitted
  • 性能 : serializable < repeatable read < read committed < read uncommitted

常见数据库的默认隔离级别

  • MySql:repeatable read
  • Oracle:read committed

查看MySQL的事务隔离级别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
--查看当前会话隔离级别
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+

--查看系统当前隔离级别
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+

--设置当前会话隔离级别
mysql> set session transaction isolatin level repeatable read;

--设置系统当前隔离级别
mysql> set global transaction isolation level repeatable read;

MySQL事务操作

SQL语句 描述
begin 或 start transaction 开启事务
commit 提交事务
rollback 回滚事务

MySQL中有两种方式可以进行事务的管理

  • 自动提交:执行一条 SQL 语句便提交一次事务,MySQL 默认开启。
  • 手动提交:–先显式开启事务,再执行SQL语句,最后提交或回滚。
1
2
3
4
5
6
7
8
9
10
11
--查看MySQL是否开启自动提交
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+

--改变MySQL的自动提交模式
SET AUTOCOMMIT=0; --禁止自动提交
SET AUTOCOMMIT=1; --开启自动提交
1
2
3
4
5
6
7
--先显式开启事务,再执行SQL语句,最后提交或回滚
begin;
update account set money=money-1000 where name='jack';
update account set money=money+1000 where name='rose';
commit;
--或者
rollback;

JDBC事务操作

Connection 对象的方法名 描述
conn.setAutoCommit(false) 开启事务
conn.commit() 提交事务
conn.rollback() 回滚事务

DBUtils事务操作

Connection对象的方法名 描述
conn.setAutoCommit(false) 开启事务
new QueryRunner() 创建核心类,不设置数据源(手动管理连接)
query(conn , sql , handler, params ) 或
update(conn, sql , params)
手动传递连接, 执行SQL语句CRUD
DbUtils.commitAndCloseQuietly(conn) 提交并关闭连接,不抛异常
DbUtils.rollbackAndCloseQuietly(conn) 回滚并关闭连接,不抛异常

事务总结

事务特性:ACID

  • 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生

  • 一致性(Consistency)事务前后数据的完整性必须保持一致

  • 隔离性(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离

  • 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

案例:三层架构

开发中,常使用分层思想

  • 不同的层次结构分配不同的解决过程,各个层次间组成严密的封闭系统
  • 不同层级结构彼此平等

分层的目的

  • 解耦
  • 可维护性
  • 可扩展性
  • 可重用性

不同层次,使用不同的包表示

  • 持久层:moe.sannaha.dao
  • 业务逻辑层:moe.sannaha.service
  • 表示层:moe.sannaha.web

其他包

  • javabean:moe.sannaha.domain
  • 工具:moe.sannaha.utils

配置文件

src/c3p0-config.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<c3p0-config>
<!-- 使用默认的配置读取连接池对象 -->
<default-config>
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/bank</property>
<property name="user">root</property>
<property name="password">root</property>

<!-- 连接池参数 -->
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">10</property>
<property name="checkoutTimeout">2000</property>
<property name="maxIdleTime">1000</property>
</default-config>
</c3p0-config>

c3p0连接池工具类

src/moe.sannaha.utils/C3P0Utils.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package moe.sannaha.utils;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

//c3p0工具类,提供静态方法返回DataSource对象和Connection对象
public class C3P0Utils {
//创建一个DataSource对象
private static DataSource ds=new ComboPooledDataSource();
//编写一个静态方法返回DataSource对象
public static DataSource getDataSource(){
return ds;
}
//编写一个静态方法获取Connection对象
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
}

pojo类

src/moe.sannaha.pojo/Account.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package moe.sannaha.pojo;
import java.util.Objects;

//Account类
public class Account {
private int id;
private String name;
private double money;
private String password;

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Account account = (Account) o;
return name.equals(account.name) &&
password.equals(account.password);
}

@Override
public int hashCode() {
return Objects.hash(name, password);
}

@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
", password='" + password + '\'' +
'}';
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

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;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public Account(int id, String name, double money, String password) {
this.id = id;
this.name = name;
this.money = money;
this.password = password;
}

public Account() {
}
}

持久层

src/moe.sannaha.dao/AccountDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package moe.sannaha.dao;
import moe.sannaha.utils.C3P0Utils;
import org.apache.commons.dbutils.QueryRunner;
import java.sql.Connection;
import java.sql.SQLException;

/*
持久层
AccountDao类,负责修改数据库,进行资金的转入和转出
*/
public class AccountDao {
//转入
public void moneyCome(Connection con,String name,double money) throws SQLException {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//执行SQL语句,修改数据
qr.update(con,"update account set money=money+? where name=?",money,name);
}
//转出
public void moneyGo(Connection con,String name,double money) throws SQLException {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//执行SQL语句,修改数据
qr.update(con,"update account set money=money-? where name=?",money,name);
}
}
src/moe.sannaha.dao/LoginDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package moe.sannaha.dao;
import moe.sannaha.pojo.Account;
import moe.sannaha.utils.C3P0Utils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import java.sql.SQLException;

/*
持久层
LoginDao类,负责查询数据库,返回Account类对象
*/
public class LoginDao {
//登录
public Account login(String name, String password) throws SQLException {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
//执行SQL语句,查询数据
Account account = qr.query("select * from account where name=? and password=?", new BeanHandler<Account>(Account.class), name, password);
return account;
}

业务逻辑层

src/moe.sannaha.service/AccountService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package moe.sannaha.service;
import moe.sannaha.dao.AccountDao;
import moe.sannaha.utils.C3P0Utils;
import org.apache.commons.dbutils.DbUtils;
import java.sql.Connection;
import java.sql.SQLException;

/*
业务逻辑层
AccountService类,负责调用转入和转出方法,实现转账功能
*/
public class AccountService {
public void transfer(String nameCome,String nameGo,double money) throws SQLException {
//实例化AccountDao对象
AccountDao ad = new AccountDao();
//使用C3P0工具类获取Connection对象
Connection con = C3P0Utils.getConnection();
//开启事务
con.setAutoCommit(false);
try {
//转出
ad.moneyGo(con,nameGo,money);
//转入
ad.moneyCome(con,nameCome,money);
//提交事务
DbUtils.commitAndCloseQuietly(con);
System.out.println("转账成功!");
} catch (Exception e) {
//转账出现异常,回滚事务
DbUtils.rollbackAndCloseQuietly(con);
System.out.println("转账失败!");
e.printStackTrace();
}
}
src/moe.sannaha.service/LoginService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package moe.sannaha.service;
import moe.sannaha.dao.LoginDao;
import moe.sannaha.pojo.Account;
import java.sql.SQLException;

/*
业务逻辑层
LoginService类,负责调用登录方法获取Account类对象,实现登录验证功能
*/
public class LoginService {
public boolean loginService(String name, String password) throws SQLException {
//实例化LoginDao类
LoginDao loginDao = new LoginDao();
//登录,获取Account对象
Account account=loginDao.login(name,password);
//判断Account是否为空
if(account==null){
System.out.println("登录失败,请重新登录!");
return false;
}else{
System.out.println("登录成功");
System.out.println("欢迎您,"+account.getName());
System.out.println("您的账户余额为:"+account.getMoney());
return true;
}
}
}

表示层

src/moe.sannaha.app/AccountApp.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package moe.sannaha.app;
import moe.sannaha.service.AccountService;
import moe.sannaha.service.LoginService;
import java.sql.SQLException;
import java.util.Scanner;

/*
表示层
AccountAPP类,程序的入口,供用户使用
feature:事先判断要转入的用户是否存在
*/
public class AccountApp {
public static void main(String[] args) throws SQLException {
while (true) {
//用户登录
Scanner sc = new Scanner(System.in);
System.out.println("登录程序执行\n请输入用户名:");
String name=sc.nextLine();
System.out.println("请输入6位数字密码:");
String password=sc.nextLine();
LoginService ls = new LoginService();
//登录成功,开始转账
if(ls.loginService(name,password)){
AccountService as = new AccountService();
System.out.println("转账程序执行\n请输入要转到的账户名:");
String nameCome=sc.nextLine();
System.out.println("请输入要转出的金额:");
Double money=Double.parseDouble(sc.nextLine());
as.transfer(nameCome,name,money);
break;
}
}
}
}