此笔记是 B站 动力结点 老杜 老师的课程笔记

可能您学了MySQL数据库之后有疑问了,我操作数据库都是在命令行里,终端里控制的数据库,那我怎么讲数据库应用到网站上呢。这里就讲解释,数据库可以用Java程序控制,即JDBC,当然数据库也可以由python(模块包pymysql)等控制。这些编程语言本身就可以做游戏和网站,在登录或执行其他操作时就会调用数据库。

JDBC

Java DataBase Connectivity(Java语言连接数据库)

本质是:由SUN公司制定的 一套 接口(interface)(属于面向接口编程)
java.sql.*;

可以在api参考文档中看的内容

Java.sql包

接口 用途
Connection接口 获取连接 getConnection()
Driver接口 获取连接DriverManager()
ResultSet接口 执行DQL语句(select)
Statement接口 获取数据库操作对象 createStatement()

JDBC的六个步骤(背下来)

  • 注册驱动
    (作用:告诉Java程序,即将连接的是哪个品牌的数据库)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import java.sql.Driver;
    import java.sql.DriverManager;

    // 方法一
    Driver driver = new com.mysql.jdbc.Driver; //多态,父类型引用指向子类型对象
    DriverManager.registerDriver(driver);
    // 记得try catch受检异常

    //方法二(类加载时会调用,使用反射机制)(常用)
    Class.forName("com.mysql.jdbc.Driver"); //固定写法,加载驱动,反射机制
  • 建立连接
    (表示JVM的进程和数据库进程之间的通道打开了,属于进程间通信,使用完一定要关闭)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import java.sql.Connection;

    // 写在try catch内

    // url:统一资源定位符(网络中某个资源的绝对路径)
    // jdbc:mysql://表示协议,通信协议是通信之前就提前定好的数据传送格式
    // localhost或者127.0.0.1表示IP地址
    // 3306表示MySQL端口号
    // useUnicode=true(支持中文编码)
    // characterEncoding=utf8(设置中文字符集utf8)
    // useSSL=true(使用安全的连接)
    //jbdc:mysql:// 协议
    //127.0.0.1 IP地址
    //3306 端口
    //具体数据库实例名
    String url="jdbc:mysql://localhost:3306/数据库名?useUnicode=true&characterEncoding=utf8&useSSL=true";
    String user="root";
    String password="123456";
    Connection connection = DriverManager.getConnection(url,user,password); // 数据库连接对象
  • 获得数据库操作对象
    (专门执行sql语句的对象)

    1
    2
    3
    import java.sql.Statement;
    // 写在try catch内
    Statement statement = connection.createStatement();
  • 执行SQL语句
    statement.execteUpdate()执行DML语句(insert、delete、update)
    statement.excuteQuery()执行DQL语句(select)
    (主要执行DQL和DML等)

    1
    2
    3
    4
    5
    6
    // 写在try catch内

    String sql = "INSERT TNTO 表名(字段名1,字段名2) VALUES(值1,值2)";
    // 专门执行DML语句的(INSERT、DELETE、UPDATE)
    // 返回值count是影响数据库中的记录条数
    int count = statement.executeUpdate(sql);
  • 处理查询集
    (只有当第4步执行的是SELECT语句时,才有这第5步)
    select才是查询结果
    查询结果集

  • 释放资源
    (使用完资源后一定要关闭,Java和数据库属于进程间通信,一定要关闭)

    1
    2
    3
    4
    5
    6
    7
    // 为了保证资源一定释放,在try...catch的finally中关闭资源,并且   遵循从小到大依次关闭。
    if(statement!=null){
    statement.close();
    }
    if(connection!=null){
    connection.close();
    }

整体代码

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
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

/*JDBC编程六步*/
public class JDBCTest1 {
public static void main(String[] args) throws SQLException {
Statement stmt=null;
Connection conn=null;
try {

//1.注册驱动
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
//2.获取连接
/*url:统一资源定位符(网络中某个资源的绝对路径)
URL包含哪几部分:协议、IP、PORT、资源名
http://是通信协议 什么是协议?有什么用?
通信协议是通信之前就提前定好的数据传送格式,数据包具体怎么传数据,提前定好了格式
182.61.200.7是服务器IP地址
80 是服务器上软件的端口
index.html是服务器上某个资源名
*/
String url = "jdbc:mysql://localhost:3306/MySql?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT";
String user ="root";
String password="111111";
conn=DriverManager.getConnection(url,user,password);
System.out.println("数据库连接的对象 =" +conn);
//3.获取数据库操作对象(statement专门执行sql语句的)
stmt= conn.createStatement();
//4.执行sql
String sql="insert into dept(deptno,dname,loc) values(50,'人事部','北京')";
//专门执行DML语句(insert、delete、update)
//返回值是‘影响数据库中的记录条数’
int count=stmt.executeUpdate(sql);
System.out.println(count==1?"保存成功":"保存失败");//insert保存一条语句

// 5.处理查询结果集

}catch (SQLException e){
e.printStackTrace();
}finally {
//6.释放资源(为了保证资源一定释放,在finally语句块中关闭资源)
//并且要遵循从小到大依次关闭
//分别对其try...catch
try {
if (stmt!=null){
stmt.close();
}
}catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn!=null){
conn.close();
}
}catch (SQLException e){
e.printStackTrace();
}
}
}
}

将连接数据库的所有信息配置到配置文件中

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
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ResourceBundle;

/*
* 实际开发中不建议把连接数据库的信息写死到Java程序中。
* */
public class JDBCTest4 {
//将连接数据库的所有信息配置到配置文件中
public static void main(String[] args) throws SQLException{
//使用资源绑定器绑定属性配置文件
ResourceBundle bundle=ResourceBundle.getBundle("jdbc");
String driver=bundle.getString("driver");
String url=bundle.getString("url");
String user=bundle.getString("user");
String password=bundle.getString("password");
Connection conn=null;
Statement stmt=null;
try {
//1.注册驱动
Class.forName(driver);
//2.获取连接
conn=DriverManager.getConnection(url,user,password);
//3.获取数据库操作对象
stmt=conn.createStatement();
//4.执行SQL语句
String sql="delete from dept where deptno=40";
int count=stmt.executeUpdate(sql);
System.out.println(count==1 ?"删除成功":"删除失败");
}catch (SQLException | ClassNotFoundException e){
e.printStackTrace();
}finally {
//6.释放资源
if (stmt!=null){
try {
stmt.close();
}catch (SQLException e){
e.printStackTrace();
}
}
if (conn!=null){
try {
conn.close();
}catch (SQLException e){
e.printStackTrace();
}
}
}
}
}

处理查询结果集

使用ResultSet的next()方法
如果该位置有数据就返回true,所以用while循环

取数据用getString()方法
注意,不论数据库中的数据类型是什么最好都以String的形式取出
但是可以用其他的类型double就用getDouble()….(需要取出结果计算的情况下)不过还是要和数据库中的类型一致

JDBC中所有下标都是从1开始。不是从0开始

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
import java.sql.*;
import java.util.ResourceBundle;

public class JDBCTest5 {
//处理查询结果集
public static void main(String[] args) {
Connection conn=null;
Statement stmt=null;
ResultSet rs=null;
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/MySql?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT"
,"root","111111");
//3.获取数据库操作对象
stmt=conn.createStatement();
//4.执行sql
String sql="select empno,ename,sal from emp";
//int excuteUpdate(insert/delete/update)
//ResultSet excuteQuery(select)
rs=stmt.executeQuery(sql);//专门执行DQL语句的方法
//5.处理查询结果集
/* boolean flag1=rs.next();
// System.out.println(flag1); true
if (flag1){
//光标指向的行有数据
//取数据
//getString()方法的特点是:不管数据库中的数据是什么类型的,都按string形式取出
//1,2,3 说的是第几列
String empno=rs.getString(1);//JDBC所有下标从1开始
String ename=rs.getString(2);
String sal=rs.getString(3);
System.out.println(empno+","+ename+","+sal);
}*/
while (rs.next()){
/* String empno=rs.getString(1);//JDBC所有下标从1开始
String ename=rs.getString(2);
String sal=rs.getString(3);
System.out.println(empno+","+ename+","+sal);*/


//这个不是以列的下标获取,以列的名字获取,(健壮性)
//除了可以以String类型取出之外,还可以以特定了类型取出
//int getInt...
String empno=rs.getString("empno");
String ename=rs.getString("ename"); //假如有 AS a 则以a查询,因为是rs是结果集,根据查询时是否重名来填入
String sal=rs.getString("sal");
System.out.println(empno+","+ename+","+sal);
}

}catch (Exception e){
e.printStackTrace();
}finally {
//释放资源
if (rs!=null){
try {
rs.close();
}catch (SQLException e){
e.printStackTrace();
}
}
if (stmt!=null){
try {
stmt.close();
}catch (SQLException e){
e.printStackTrace();
}
}
if (conn!=null){
try {
conn.close();
}catch (SQLException e){
e.printStackTrace();
}
}
}

}
}

模拟用户登录,发现SQL注入现象

导致SQL注入的根本原因:

用户输入的信息中含有sql语句的关键字,并且这些关
导致sql语句的原意被扭曲,进而达到sql注入
可能还会说删除等操作,会严重破坏数据库

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
95
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

/**
* @author 张永锐
* @aim 模拟用户登录功能的实现
* 业务描述:
* 1.程序运行时,提供一个输入的入口,可以让用户输入用户名和密码
* 2.用户输入后提交信息,Java程序收集到用户信息
* 3.Java程序连接数据库验证用户名和密码是否合法
* 4.演示结果
* 数据准备;
* 设计表,PowerDesigner
*/

/**
* !程序问题:
* 如此输入:
* 用户名:ddd(表中没有)
* ?密码:nuanqibazao' or'1'='1 --> or '1' = '1'是恒成立的,所以会登录成功,导致注入的主要原因
* 登录成功
* SQL注入(安全隐患,要修改)(黑客常用)
* !SQL注入的根本原因:
* 用户输入的信息中含有SQL语句的关键字,并且这些关键字参与了sql语句中的编译过程,导致sql语句的原意被扭曲
*/
public class Test07_login_ {
public static void main(String[] args) {
//初始化一个界面
Map<String,String> userLoginInfo = initUI();
//验证用户名和密码
boolean loginSuccess = login(userLoginInfo);
//显示结果
System.out.println(loginSuccess? "登录成功" : "登录失败");
}

/**
* 初始化一个用户姐界面
* @return 返回用户输入的用户名和密码
*/
public static Map<String,String> initUI(){
Scanner s = new Scanner(System.in);
System.out.print("用户名:");
String loginName = s.nextLine();
System.out.print("密码:");
String loginPwd = s.nextLine();
Map<String,String> userLoginInfo = new HashMap<>();
userLoginInfo.put("loginName",loginName);
userLoginInfo.put("loginPwd",loginPwd);
return userLoginInfo;
}

/**
* 用户登录判断
* @param userLoginInfo 用户登录信息
* @return 是否成功
*/
@SuppressWarnings({"all"})
public static boolean login(Map<String,String> userLoginInfo){
boolean is_longin = false;
//JDBC代码
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try{
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String passwd = "123456";
conn = DriverManager.getConnection(url,user,passwd);
stmt = conn.createStatement();
String sql = "select * from t_user where loginName = '" + userLoginInfo.get("loginName")
+ "' and loginPwd = '" + userLoginInfo.get("loginPwd") + "'";
rs = stmt.executeQuery(sql);
if(rs.next()){
is_longin =true;
}
} catch(SQLException | ClassNotFoundException e){
e.printStackTrace();
} finally {
try{
if(stmt != null){
stmt.close();
}
if(conn != null){
conn.close();
}
} catch (SQLException e){
e.printStackTrace();
}
}
return is_longin;
}
}

Statement和PreparedStatement对比

-Statement存在SQL注入问题;PreparedStatement解决了SQL注入问题
-Statement是编译一次执行一次;PreparedStatement是编译一次可执行N次;PreparedStatement执行效率较高一些
-PreparedStatement会在编译阶段做类型的安全检查

综上所述:PreparedStatement使用较多,极少数情况下使用Statement

什么情况下必须使用Statement?
业务方面要求必须支持SQL注入时。(PreparedStatement无法支持SQL注入)
Statement支持SQL注入,凡是业务方面需要进行sql语句拼接时,必须使用Statement
只需要给sql语句传值时使用:PreparedStatement

解决SQL注入问题

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
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

/**
* @author 张永锐
* @aim 再Test07的基础上解决SQL注入问题
* 使用PreparedStatement类 --》99%都用这个类,因为可以提前编译(效率高),避免SQL租入现象
* 1%是如果本身业务就需要SQL注入的情况,比如就是想升序或者降序查询
*/
public class Test08_solve_SQL_in {
public static void main(String[] args) {
//初始化一个界面
Map<String,String> userLoginInfo = initUI();
//验证用户名和密码
boolean loginSuccess = login(userLoginInfo);
//显示结果
System.out.println(loginSuccess? "登录成功" : "登录失败");
}

/**
* 初始化一个用户姐界面
* @return 返回用户输入的用户名和密码
*/
public static Map<String,String> initUI(){
Scanner s = new Scanner(System.in);
System.out.print("用户名:");
String loginName = s.nextLine();
System.out.print("密码:");
String loginPwd = s.nextLine();
Map<String,String> userLoginInfo = new HashMap<>();
userLoginInfo.put("loginName",loginName);
userLoginInfo.put("loginPwd",loginPwd);
return userLoginInfo;
}

/**
* 用户登录判断
* @param userLoginInfo 用户登录信息
* @return 是否成功
*/
@SuppressWarnings({"all"})
public static boolean login(Map<String,String> userLoginInfo){
boolean is_longin = false;
//JDBC代码
Connection conn = null;

//Statement stmt = null;
//!使用PreparedStatement (原本是Statement)
PreparedStatement ps = null;

ResultSet rs = null;
try{
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String passwd = "123456";
conn = DriverManager.getConnection(url,user,passwd);

//!第三步的时候就要传入sql语句,预先编译,就是要留问号(占位符),之后会替换,很智能
String sql = "select * from t_user where loginName = ? and loginPwd = ?";
ps = conn.prepareStatement(sql);

//!在JDBC中一切的下标都是从1开始
//ps.setInt(下标(第几个问号),值);可以灵活设置
//!很智能的是,如果是setInt等值,在编译的sql语句中就不会加引号,如果是字符串类型就会加引号(把整体引起来,就不会执行注入语句了)
ps.setString(1,userLoginInfo.get("loginName"));//!修改占位符的值
ps.setString(2,userLoginInfo.get("loginPwd"));

rs = ps.executeQuery();//!因为前面已经传入了sql预处理语句,所以不需要参数了
if(rs.next()){
is_longin =true;
}
} catch(SQLException | ClassNotFoundException e){
e.printStackTrace();
} finally {
try{
if(rs != null){
rs.close();
}
if(ps != null){
ps.close();
}
if(conn != null){
conn.close();
}
} catch (SQLException e){
e.printStackTrace();
}
}
return is_longin;
}
}

Statement 用处

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
import java.sql.*;
import java.util.Objects;
import java.util.Scanner;

/**
* @author 张永锐
*/
public class Test09_desc_and_asc {
public static void main(String[] args) {
//todo 用户输入选择
Scanner s = new Scanner(System.in);
System.out.println("输入1升序,输入2降序");
String keyWord = null;
keyWord = s.nextLine();
if(Objects.equals(keyWord, "1")){
keyWord = "asc";
}else if(Objects.equals(keyWord, "2")){
keyWord = "desc";
}
//todo jdbc
//!在这里使用PreparedStatement不能实现功能
// Connection conn = null;
// PreparedStatement ps = null;
// ResultSet rs = null;
// try{
// Class.forName("com.mysql.jdbc.Driver");
// conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8","root","123456");
// String sql = "select name,english from student order by ?";
// ps = conn.prepareStatement(sql);
//
// ps.setString(1,keyWord);
//
// rs = ps.executeQuery();
//
// while(rs.next()){
// System.out.println(rs.getString("name") + " " + rs.getString("english"));
// }
// } catch(ClassNotFoundException | SQLException e){
// e.printStackTrace();
// } finally {
// try{
// if(rs != null){
// rs.close();
// }
// if(ps != null){
// ps.close();
// }
// if(conn != null){
// conn.close();
// }
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try{
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8","root","123456");
stmt = conn.createStatement();

String sql = "select name,english from student order by english " + keyWord;

rs = stmt.executeQuery(sql);

while (rs.next()){
System.out.println(rs.getString("name") + " " + rs.getString("english"));
}
} catch(ClassNotFoundException | SQLException e){
e.printStackTrace();
} finally {
try{
if(rs != null){
rs.close();
}
if(stmt != null){
stmt.close();
}
if(conn != null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}

使用PreparedStatement完成增删改

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
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
* @author 张永锐
*/
public class Test10_c_d_u {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
//1.
Class.forName("com.mysql.jdbc.Driver");

//2.
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");

//3.
// 增:
// String sql_i = "insert into t_vip2 values (?,?)";
// ps = conn.prepareStatement(sql_i);
// 删:
// String sql_d = "delete from t_vip2 where id = ?";
// ps = conn.prepareStatement(sql_d);
// 改:
String sql_u = "update t_vip2 set name = ? where id = ?";
ps = conn.prepareStatement(sql_u);
//4.
// 增:
// ps.setInt(1,3);
// ps.setString(2,"smith");
// 删:
// ps.setInt(1,3);
// 改:
ps.setString(1,"zhangsan");
ps.setInt(2,2);
ps.executeUpdate();

} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
//6.
try{
if(ps != null){
ps.close();
}
if(conn != null){
conn.close();
}
} catch(SQLException e){
e.printStackTrace();
}
}
}
}

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import java.sql.*;

/**
* @author 张永锐
* @aim 自己写一个封装方法,将JDBC的功能封装成函数
*/
public class DBUtils {

//1.使用静态代码块直接加载第一步注册驱动
static {
try{
Class.forName("com.mysql.jdbc.Driver");
} catch(ClassNotFoundException e){
e.printStackTrace();
}
}

//2.获取数据库连接(传入3个参数)
public static Connection getConnection(String url,String user,String passwd){
Connection conn = null;
try {
conn = DriverManager.getConnection(url, user, passwd);
} catch (SQLException e){
e.printStackTrace();
}
return conn;
}
//2.无参类型,默认调用
public static Connection getConnection() {
Connection conn = null;
try {
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String passwd = "123456";
conn = DriverManager.getConnection(url, user, passwd);
} catch (SQLException e){
e.printStackTrace();
}
return conn;
}

//6.释放资源 3个
public static void close(Connection conn, Statement stmt, ResultSet rs){
try{
if(rs != null){
rs.close();
}
if(stmt != null){
stmt.close();
}
if(conn != null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
//没有使用结果查询集,只释放两个资源
public static void close(Connection conn, Statement stmt){
try{
if(stmt != null){
stmt.close();
}
if(conn != null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}

事务机制

主要代码

1
2
3
conn.setAutoCommit(false);//关闭自动提交机制(打开事务提交机制)
conn.commit();//事务提交
conn.rollback();//事务回滚

注意是连接对象的操作函数

整体代码

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
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.ResultSet;

/**
* @author 张永锐
* @aim 用事务机制完成银行转账
* 任务:
* 1.实现银行转账机制(账户111将钱转给222,转10000.0)
* 2.使用自制的DBUtils工具类
* 实现:
* 1.先查询两个账户的价格
* 2.将111中的钱(10000.0)转给222
* 1.111中的钱少10000.0
* 2.222中的钱增加10000.0
* 3.再次查询结果
*? 本代码的重点 conn.setAutoCommit(false);//关闭自动提交,相当于开启事务
*? conn.commit();//事务提交
*? conn.rollback();//事务回滚
*/
public class Test12_transaction {
public static void main(String[] args) throws SQLException {
Connection conn = DBUtils.getConnection();//1,2
ResultSet rs;
String sql = "select * from t_act where actno = ? or actno = ?";
PreparedStatement ps = conn.prepareStatement(sql);
//todo 给ps赋值
ps.setInt(1,111);
ps.setInt(2,222);
rs = ps.executeQuery();
//todo 处理查询结果集
System.out.println("操作前查询结果:");
while(rs.next()){
System.out.println("actno: " + rs.getString("actno") +" balance: " + rs.getString("balance"));
}

//todo 转账操作
String sql2 = "update t_act set balance = balance + ? where actno = ? and balance >= ?";
ps = conn.prepareStatement(sql2);
//!关闭JDBC的自动提交 , 相当于开启事务(start transaction;)
conn.setAutoCommit(false);//*关闭自动提交
//todo 让111的钱减少
//todo 给ps赋值
ps.setDouble(1,-10000.0);//?减少就是加负的那么多嘛
ps.setInt(2,111);//?操作的是111的账户
ps.setDouble(3,10000.0);//?转账的用户的钱必须要大于转周的钱
int count = ps.executeUpdate();
//todo 让222的钱增加
//todo 再次给ps赋值
ps.setDouble(1,10000.0);
ps.setInt(2,222);
ps.setDouble(3,-10000000.0);//?收钱的账户不对钱有要求
count += ps.executeUpdate();
if(count == 2){
conn.commit();//!事务提交的方法
System.out.println("转账成功");
} else {
conn.rollback();//!事务回滚的方法
System.out.println("转账失败");
}
//todo 再次查询结果集
String sql3 = "select * from t_act where actno = ? or actno = ?";
ps = conn.prepareStatement(sql3);
ps.setInt(1,111);
ps.setInt(2,222);
rs = ps.executeQuery();
while(rs.next()){
System.out.println("actno: " + rs.getString("actno") +" balance: " + rs.getString("balance"));
}
//todo 释放资源
DBUtils.close(conn,ps,rs);
}
}

模糊查询

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
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
* @author 张永锐
* @aim 实现模糊查询
*/
public class Test13_like {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DBUtils.getConnection();
String sql = "select * from student where name like ?";
ps = conn.prepareStatement(sql);
ps.setString(1,"z%");//查询以z开头的名字
rs = ps.executeQuery();
while(rs.next()){
System.out.println(rs.getString("id") + " " +rs.getString("name")
+ " " + rs.getString("chinese") + " " + rs.getString("english")
+ " " + rs.getString("math"));
}
} catch (SQLException e){
e.printStackTrace();
} finally {
DBUtils.close(conn,ps,rs);
}
}
}

悲观锁与乐观锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
行级锁(又叫悲观锁):
select ename,job,sal from emp where job = 'MANAGER';
+-------+---------+---------+
| ename | job | sal |
+-------+---------+---------+
| JONES | MANAGER | 3272.50 |
| BLAKE | MANAGER | 3135.00 |
| CLARK | MANAGER | 2695.00 |
+-------+---------+---------+

select ename,job,sal from emp where job = 'MANAGER' for update;
select语句后加 for update 就是行级锁
代表在当前事务结束之前,其他事务都无法修改这一行数据
上面select语句表示:那三行数据(其实是对应emp表中的三行数据)都无法修改

乐观锁:
多线程并发 都可以对这行数据修改