JDBC(本质,配置环境变量,JDBC编程六步,类加载注册,sql注入,事务问题,封装工具类,悲观锁,乐观锁)

news/2024/5/18 22:43:39 标签: jdbc, java, mysql

JDBC

2021.5.21
依然跟着动力节点杜老师学!!!

1、什么是JDBC?

Java DataBase Connectivity
在java语言中编写sql语句,对mysql数据库中的数据进行CRUD操作。

2、JDBC相关的类库在哪里?

java.sql.*;

3、JDBC本质上是一堆什么呢?

JDBC实际上是SUN公司制定好的一套接口,纯interface
java.sql.*;
这个包下都是JDBC的接口,SUN公司制定的!
JDBC是体现“接口作用”的非常经典的例子。
JDBC降低了耦合度,提高了扩展力。
对于java程序员来说,不需要关心数据库是哪个品牌。只要面向JDBC接口编程就行!

JDBC整个程序的结构当中有三波人????

	第一波:SUN公司,负责制定JDBC接口。这些接口已经写好了,在java.sql.*;

	第二波:java.sql.*下面的所有接口都要有实现类,这些实现类是数据库厂家编写的。
				我们连接的是mysql数据库,mysql数据库厂家的实现类在哪里呢?
					mysql-connector-java-5.1.23-bin.jar
					jar包中很多.class字节码文件,这是mysql数据库厂家写的接口实现!

					注意:如果连接的是oracle数据库,你需要从网上下载oracle的jar包。

					mysql-connector-java-5.1.23-bin.jar 这个jar包有一个专业的术语,
					大家记住就行:mysql的驱动。

					如果是oracle的jar,被称为oracle的驱动。
	
	第三波:我们java程序员,面向JDBC接口写代码就行!
java">模拟JDBC本质
// JDBC实际上是SUN公司制定好的一套接口,纯interface
public interface JDBC{

	// 获取数据库连接的一个方法
	void getConnection();
}

// mysql数据库厂家对SUN公司制定的JDBC接口进行了实现
// 这个代码只能由mysql厂家来编写,因为只有mysql厂家才知道mysql的秘密
public class MySQL implements JDBC{

	//获取连接
	public void getConnection(){
		// 这个大括号中的代码是mysql厂家写的
		System.out.println("通过mysql数据库的小秘密,连接了mysql数据库");
	}
}


// oracle数据库厂家对SUN公司制定的JDBC接口进行了实现
// 这个代码只能由oracle厂家来编写,因为只有oracle厂家才知道oracle的秘密
public class Oracle implements JDBC{

	//获取连接
	public void getConnection(){
		// 这个大括号中的代码是oracle厂家写的
		System.out.println("通过oracle数据库的小秘密,连接了oracle数据库");
	}
}

// 这就是我们,我们面向JDBC接口调用方法连接数据库
// 我们不需要关心底层是什么数据库
// 因为JDBC接口的出现让程序降低了耦合度
// 我们java程序员写一套JDBC程序,可以连接任何一套数据库

public class JavaProgrammer{
	public static void main(String[] args){
		// 面向JDBC接口写代码
		JDBC jdbc = new Oracle();
		//JDBC jdbc = new MySQL();

		// 连接数据库
		// 调用方法的时候,面向JDBC接口调用,JDBC接口中有什么方法,就调用什么方法
		jdbc.getConnection();

		// ...以下几百行代码都是面向JDBC接口调用的,不需要修改
		// ....
	}
}

4、JDBC开发之前的准备工作?

mysql的驱动jar包,需要配置到classpath当中吗?
	mysql-connector-java-5.1.23-bin.jar里是字节码,是class文件。
	Java虚拟机的类加载器会去加载class文件,类加载器怎么能够找到这些class文件呢?
		classpath没有配置的情况下,默认从当前路径下加载class。
		classpath如果配置死了,例如:classpath=D:\abc,则表示固定只从d:\abc目录下找class

		classpath=.;D:\course\04-JDBC\resources\MySql Connector Java 5.1.23\mysql-connector-java-5.1.23-bin.jar

		. 代表什么?当前路径。
		以上的classpath什么意思?
			类加载器从当前路径下加载class,如果当前路径下没找到,
			则去D:\course\04-JDBC\resources\MySql Connector Java 5.1.23\mysql-connector-java-5.1.23-bin.jar
			找class文件。

	jar包需要解压吗?
		不需要解压,java虚拟机的类加载器有这个能力找到class文件。

5、JDBC编程六步

1.注册驱动
	通知java程序我们即将要连接的是哪个品牌的数据库
	
2.获取数据库连接
	java进程和mysql进程,两个进程之间的通道开启了
	java进程可能在北京,mysql进程可能在上海
	
3.获取数据库操作对象
	这个对象很重要,用这个对象执行SQL的
	
4.执行SQL语句
	执行CRUD操作
	
5.处理查询结果集
	如果第四步是select语句,才有这个第五步
	
6.释放资源
	关闭所有的资源(因为JDBC毕竟是进程之间的通信,会占用很多资源,需要关闭)
java">/*
	JDBC编程六步

		1.注册驱动

		2.获取数据库连接

		3.获取数据库操作对象

		4.执行SQL语句

		5.处理查询结果集

		6.释放资源
*/
import java.sql.DriverManager;
import java.sql.Driver;
import java.sql.SQLException;
import java.sql.Connection; // 连接接口interface
import java.sql.Statement;

public class JDBCTest01{
	public static void main(String[] args){

		Connection conn = null;
		Statement stmt = null;

		try{
			//1.注册驱动
			// com.mysql.jdbc.Driver是mysql数据库厂家写的,实现了java.sql.Driver接口
			// 如果是oracle数据库的,类名就不一样了,oracle.jdbc.driver.OracleDriver
			//com.mysql.jdbc.Driver() 实现 java.sql.Driver接口
			Driver driver = new com.mysql.jdbc.Driver(); // mysql
			// Driver driver = new oracle.jdbc.driver.OracleDriver(); // oracle的
			DriverManager.registerDriver(driver);

			//2.获取数据库连接
			/*
				什么是URL、
					统一资源定位符
				任何一个URL都包括
					协议+IP地址+端口号port+资源名
					http://192.168.100.2:8888/abc

				协议
					是一个提前规定好的数据传输格式,通信协议有很多:http、https...
					在传送数据之前,提前商量好数据传送的格式
					这样对方接受到数据之后,就会按照这个格式去解析,拿到有价值的数据

				IP地址
					网络当中定位某台计算机的

				PORT端口号
					定位这台计算机上某个服务的

				资源名
					这个服务下的某个资源

				jdbc:mysql://			这是java程序和mysql数据库通信的协议
				localhost				这是本机IP地址,本机IP地址还可以写成:127.0.0.1
				3306					mysql数据库端口号
				bjpowernode				mysql数据库的名称

				jdbc:mysql://192.168.111.123:3306/abc

				如果是oracle数据库的话:
					oracle:jdbc:thin:@localhost:1521:bjpowernode
					oracle:jdbc:thin:@    这是oracle和java的通信协议
					localhost			  本机IP地址
					1521				  oracleĬ默认端口
					bjpowernode			  oracle中数据库的名字
				
				localhost和127.0.0.1都是本机IP地址。死记硬背

			*/
			String url = "jdbc:mysql://localhost:3306/bjpowernode";
			String user = "root";
			String password = "123456";
			conn = DriverManager.getConnection(url,user,password);

			//输出连接对象的内存地址
			// com.mysql.jdbc.JDBC4Connection@2aaf7cc2
			// com.mysql.jdbc.JDBC4Connection类实现了ava.sql.Connection接口
			// 实际上我们后续的开发不需要关心底层具体是哪个对象,因为面向接口编程
			//System.out.println(conn);

			// 3.获取数据库操作对象
			stmt = conn.createStatement();
			//System.out.println(stmt);

			// 通过一个连接对象Connection是可以创建多个Statement对象的
			//Statement stmt2 = conn.createStatement();
			//System.out.println(stmt2);

			// 4.执行SQL语句
			// insert delete update
			/*
			String insertSql = "insert into dept(deptno,dname,loc) values(50, '销售部', '北京')";
			// Statement 接口中的 executeUpdate方法专门来执行DML语句的
			// 该方法的返回值表示:影响了数据库表中的总记录条数!
			int count = stmt.executeUpdate(insertSql);
			System.out.println(count); // 1
			*/

			/*
			String updateSql = "update dept set dname = '人事部', loc = '天津' where deptno = 50";
			int count = stmt.executeUpdate(updateSql);
			System.out.println(count);  // 1
			*/

			String deleteSql = "delete from dept where deptno = 50";
			int count = stmt.executeUpdate(deleteSql);
			System.out.println(count);


		}catch(SQLException e){
			e.printStackTrace();
		} finally{
			// 6.释放资源,先释放Statement,再释放Connection
			// 分别进行try..catch处理
			// 放在finally中关闭
			if(stmt != null){
				try{
					stmt.close();
				}catch(SQLException e){
					e.printStackTrace();
				}
			}
			if(conn != null){
				try{
					conn.close();
				}catch(SQLException e){
					e.printStackTrace();
				}
			}
		}
		
	}
}
java">/*
	处理查询结果集

	提醒一下:
		JDBC中所有的下标都是从1开始的
*/
import java.sql.*;

public class JDBCTest02{
	public static void main(String[] args){
		Connection conn = null;
		Statement stmt = null;
		ResultSet rs = null;
		try{
			// 1.注册驱动
			DriverManager.registerDriver(new com.mysql.jdbc.Driver());
			// 2.获取连接
			conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","123456");
			// 3.获取数据库操作对象
			stmt = conn.createStatement();
			// 4.执行SQL语句
			// JDBC当中的SQL语句不需要以";"结尾
			String sql = "select empno,ename as hehe,sal from emp order by sal desc";
			// 执行查询语句是这个方法,executeQuery
			// ResultSet就是查询结果集对象,查询的结果都在这个对象当中
			rs = stmt.executeQuery(sql);

			// 5.处理查询结果集
			// 目前没什么好处理的,直接遍历输出
			/*
				ResultSet对象中有以下数据:
				+-------+--------+---------+
				| empno | ename  | sal     |
				+-------+--------+---------+
				|  7839 | KING   | 5500.00 |
				|  7788 | SCOTT  | 3300.00 |
				|  7902 | FORD   | 3300.00 |
				|  7566 | JONES  | 3272.50 |
				|  7698 | BLAKE  | 3135.00 |
				|  7782 | CLARK  | 2695.00 |
				|  7499 | ALLEN  | 1760.00 |
				|  7844 | TURNER | 1650.00 |
				|  7934 | MILLER | 1430.00 |
				|  7654 | MARTIN | 1375.00 |
				|  7521 | WARD   | 1375.00 |
				|  7876 | ADAMS  | 1210.00 |
				|  7900 | JAMES  | 1045.00 |
				|  7369 | SMITH  |  880.00 |
				+-------+--------+---------+
				调用ResultSet接口中相应的方法遍历查询结果集
			*/
			/*
			boolean has = rs.next(); //光标向前移动一位
			if(has){
				// 条件成立表示光标指向的行有记录
				// 取当前行的第一个值
				String empno = rs.getString(1); // 注意:getString()这个方法不管底层数据库表中是什么类型,统一都以String形式返回
				// 取当前行的第二个值
				String ename = rs.getString(2);
				// 取当前行的第三个值
				String sal = rs.getString(3);
				System.out.println(empno + "," + ename + "," + sal);
			}

			has = rs.next();
			if(has){
				String empno = rs.getString(1);
				String ename = rs.getString(2);
				String sal = rs.getString(3);
				System.out.println(empno + "," + ename + "," + sal);
			}
			*/

			while(rs.next()){
				// 根据下标来取值的,并且都是以String类型取出来的!
				/*
				String empno = rs.getString(1);
				String ename = rs.getString(2);
				String sal = rs.getString(3);
				System.out.println(empno + "," + ename + "," + sal);
				*/

				// 根据下标来取值,以特定类型取出!!!
				/*
				int empno = rs.getInt(1);
				String ename = rs.getString(2);
				double sal = rs.getDouble(3);
				System.out.println(empno + "," + ename + "," + (sal + 100));
				*/

				// 根据查询结果的列名可以取吗?
				// 以后这种方式是常用的,健壮。
				int empno = rs.getInt("empno"); // empno并不是字段的名称,是查询结果的列名!!!
				//String ename = rs.getString("ename");
				String ename = rs.getString("hehe"); //是查询结果的列名,不是表格中的字段名
				double sal = rs.getDouble("sal");
				System.out.println(empno + "," + ename + "," + (sal + 100));

			}

		}catch(SQLException e){
			e.printStackTrace();
		} finally{
			// 6.释放资源
			//先关闭ResultSet,再关闭Statement,最后关闭Connection
			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();
				}
			}
		}
	}
}

next()遍历结果集的原理
在这里插入图片描述

6、注册驱动的第二种方式

java">package com.bjpowernode.jdbc;

import java.sql.*;

/*
注册驱动的第二种方式:类加载注册

mysql的厂家写的类:
这个类中有静态代码块,要让这个静态代码块执行,需要让类加载
class com.mysql.jdbc.Driver {
    static {
		try {
			java.sql.DriverManager.registerDriver(new Driver());
		} catch (SQLException E) {
			throw new RuntimeException("Can't register driver!");
		}
	}
}
 */
public class JDBCTest04 {
    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            // 1、注册驱动
            // oracle数据库: Class.forName("oracle.jdbc.driver.OracleDriver");
            Class.forName("com.mysql.jdbc.Driver");
            // 2、获取连接
            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode","root","123456");
            // 3、获取数据库操作对象
            stmt = conn.createStatement();
            // 4、执行SQL语句
            String sql = "select a.ename as '员工',b.ename as '领导' from emp a left join emp b on a.mgr = b.empno";
            rs = stmt.executeQuery(sql);
            // 5、处理查询结果集
            while(rs.next()){
                String ename = rs.getString("员工");
                String lname = rs.getString("领导");
                System.out.println(ename + "," + lname);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6、释放资源
            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();
                }
            }
        }
    }
}

7、连接数据库的信息统一写到属性配置文件中。

java">(db.properties 属性配置文件)

#######mysql connectivity configuration###############
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/bjpowernode
user=root
password=123456

#######oracle connectivity configuration###############
#driver=oracle.jdbc.driver.OracleDriver
#url=jdbc:oracle:thin:@localhost:1521:orcl
#user=scott
#password=tiger



package com.bjpowernode.jdbc;

import java.sql.*;
import java.util.ResourceBundle;

/*
思想:
    将连接数据库的可变化的4条信息写到配置文件中。
    以后想连接其他数据库的时候,可以直接修改配置文件,不用修改java程序。
    四个信息是什么?
        driver
        url
        user
        password
 */
public class JDBCTest05 {
    public static void main(String[] args) {

        //资源绑定器
        ResourceBundle bundle = ResourceBundle.getBundle("resources/db");
        // 通过属性配置文件拿到信息
        String driver = bundle.getString("driver");
        String url = bundle.getString("url");
        String user = bundle.getString("user");
        String password = bundle.getString("password");

        System.out.println(driver);
        System.out.println(url);
        System.out.println(user);
        System.out.println(password);

        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            // 1、注册驱动
            Class.forName(driver);
            // 2、获取连接
            conn = DriverManager.getConnection(url,user,password);
            // 3、获取数据库操作对象
            stmt = conn.createStatement();
            // 4、执行SQL语句
            String sql = "select a.ename as '员工',b.ename as '领导' from emp a left join emp b on a.mgr = b.empno";
            rs = stmt.executeQuery(sql);
            // 5、处理查询结果集
            while(rs.next()){
                String ename = rs.getString("员工");
                String lname = rs.getString("领导");
                System.out.println(ename + "," + lname);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6、释放资源
            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();
                }
            }
        }
    }
}

8、实现一个登录功能。

第一步:提供一个输入的界面,可以让用户输入用户名和密码。

第二步:底层数据库当中需要有一张用户表,用户表中存储了用户信息。
java">drop table if exists t_user;
create table t_user(
    id int primary key auto_increment,
    login_name varchar(255) unique,
    login_pwd varchar(255),
    real_name varchar(255)
);
insert into t_user(login_name, login_pwd, real_name) values('admin','123','管理员');
insert into t_user(login_name, login_pwd, real_name) values('zhangsan','123','张三');
select * from t_user;

第三步:当java程序接收到用户名和密码的时候,连接数据库验证用户名和密码。
验证通过,表示登录成功,验证失败,表示登录失败。
登录发生sql注入
java">import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

/*
模拟用户登录

用户名:fdsa
密码:fdsa' or '1'='1
select * from t_user where login_name = 'fdsa' and login_pwd = 'fdsa' or '1'='1'
登录成功

以上随意输入一个用户名和密码,登录成功了,这种现象被称为SQL注入现象!

导致SQL注入的根本原因是什么?怎么解决?
    导致SQL注入的根本原因是:用户不是一般的用户,用户是懂得程序的,输入的用户名信息以及密码信息中
    含有SQL语句的关键字,这个SQL语句的关键字和底层的SQL语句进行“字符串拼接”,导致原SQL语句的含义
    被扭曲了。最最最最最最主要的原因是:用户提供的信息参与了SQL语句的编译。

    主要因素是:这个程序是先进行的字符串的拼接,然后再进行SQL语句的编译,正好被注入。
 */
public class Mysql {
    public static void main(String[] args) {

        // 初始化一个界面,可以让用户输入用户名和密码
        Map<String,String> userLoginInfo = initUI();

        // 连接数据库验证用户名和密码是否正确
        boolean ok = checkNameAndPwd(userLoginInfo.get("loginName"), userLoginInfo.get("loginPwd"));

        System.out.println(ok ? "登录成功" : "登录失败");
    }

    /**
     * 验证用户名和密码
     * @param loginName 登录名
     * @param loginPwd 登录密码
     * @return true表示登录成功,false表示登录失败
     */
    private static boolean checkNameAndPwd(String loginName, String loginPwd) {
        boolean ok = false; //默认是登录失败的!
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            //1、注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2、获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","123456");
            //3、获取数据库操作对象
            stmt = conn.createStatement();
            //4、执行SQL
            String sql = "select * from t_user where login_name = '" + loginName + "' and login_pwd = '" + loginPwd + "'";
            System.out.println(sql);
            // 程序执行到此处,才会将以上的sql语句发送到DBMS上。DBMS进行sql语句的编译。
            rs = stmt.executeQuery(sql);
            //5、处理查询结果集
            // 如果以上sql语句中用户名和密码是正确的,那么结果集最多也就查询出一条记录,所以以下不需要while循环,if就够了!
            if(rs.next()){
                ok = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //6、释放资源
            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();
                }
            }
        }
        return ok;
    }

    /**
     * 初始化界面,并且接收用户的输入。
     * @return 存储登录名和登录密码的Map集合。
     */
    private static Map<String,String> initUI() {
        System.out.println("欢迎使用该系统,请输入用户名和密码进行身份认证.");
        Scanner s = new Scanner(System.in);
        System.out.print("用户名:");
        String loginName = s.nextLine(); //接收用户输入,一次接收一行!
        System.out.print("密码:");
        String loginPwd = s.nextLine();

        //将用户名和密码放到Map集合中。
        Map<String,String> userLoginInfo = new HashMap<>();
        userLoginInfo.put("loginName",loginName);
        userLoginInfo.put("loginPwd",loginPwd);

        // 返回Map
        return userLoginInfo;
    }
}

解决sql注入
java">package com.bjpowernode.jdbc;

import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

/**
 * 怎么避免SQL注入?
 *  SQL注入的根本原因是:先进行了字符串的拼接,然后再进行的编译。
 *
 *  java.sql.Statement接口的特点:先进行字符串的拼接,然后再进行sql语句的编译。
 *     优点:使用Statement可以进行sql语句的拼接。
 *     缺点:因为拼接的存在,导致可能给不法分子机会。导致SQL注入。
 *
 *  java.sql.PreparedStatement接口的特点:先进行SQL语句的编译,然后再进行sql语句的传值。
 *     优点:避免SQL注入。
 *     缺点:没有办法进行sql语句的拼接,只能给sql语句传值。
 *
 *     PreparedStatement预编译的数据库操作对象。
 */
public class JDBCTest07 {
    public static void main(String[] args) {

        // 初始化一个界面,可以让用户输入用户名和密码
        Map<String,String> userLoginInfo = initUI();

        // 连接数据库验证用户名和密码是否正确
        boolean ok = checkNameAndPwd(userLoginInfo.get("loginName"), userLoginInfo.get("loginPwd"));

        System.out.println(ok ? "登录成功" : "登录失败");
    }

    /**
     * 验证用户名和密码
     * @param loginName 登录名
     * @param loginPwd 登录密码
     * @return true表示登录成功,false表示登录失败
     */
    private static boolean checkNameAndPwd(String loginName, String loginPwd) {
        boolean ok = false; //默认是登录失败的!
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        try {
            //1、注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2、获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","123456");
            //3、获取预编译的数据库操作对象
            // 注意:一个问号?是一个占位符,一个占位符只能接收一个“值/数据”
            // 占位符 ? 两边不能有单引号。 '?' 这样是错误的。
            String sql = "select * from t_user where login_name = ? and login_pwd = ?";
            stmt = conn.prepareStatement(sql); // 此时会发送sql给DBMS,进行sql语句的编译
            // 给占位符?传值
            // JDBC中所有下标都是从1开始。
            // 怎么解决SQL注入的:即使用户信息中有sql关键字,但是不参加编译就没事。
            // 如果需要的类型是int,调用setInt方法,传入的是一个数值
            stmt.setString(1, loginName); // 1代表第1个问号 ?
            stmt.setString(2, loginPwd); // 2代表第2个问号 ?
            //4、执行SQL
            rs = stmt.executeQuery(); //这个方法不需要将sql语句传递进去。不能是这样:rs = stmt.executeQuery(sql);
            //5、处理查询结果集
            if(rs.next()){
                ok = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //6、释放资源
            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();
                }
            }
        }
        return ok;
    }

    /**
     * 初始化界面,并且接收用户的输入。
     * @return 存储登录名和登录密码的Map集合。
     */
    private static Map<String,String> initUI() {
        System.out.println("欢迎使用该系统,请输入用户名和密码进行身份认证.");
        Scanner s = new Scanner(System.in);
        System.out.print("用户名:");
        String loginName = s.nextLine(); //接收用户输入,一次接收一行!
        System.out.print("密码:");
        String loginPwd = s.nextLine();

        //将用户名和密码放到Map集合中。
        Map<String,String> userLoginInfo = new HashMap<>();
        userLoginInfo.put("loginName",loginName);
        userLoginInfo.put("loginPwd",loginPwd);

        // 返回Map
        return userLoginInfo;
    }
}

9、Statement主要是进行sql语句的拼接。PreparedStatement可以避免sql注入,主要进行传值。

如果需要降序,用PreparedStatement可以吗
java">package com.bjpowernode.jdbc;

import java.sql.*;
import java.util.Scanner;

/**
 * 需求:用户在控制台上输入desc则降序,输入asc则升序
 * 思考一下:这个应该选择Statement还是PreparedStatement呢?
 *  选Statement,因为PreparedStatement只能传值,不能进行sql语句的拼接。
 *  这里传入的是'desc'一个字符串
 *  sql语句变成了:select ename,sal from emp order by sal ’desc‘  报错
 */
public class JDBCTest08 {
    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        System.out.print("请输入desc或asc【desc表示降序,asc表示升序】:");
        // 用户输入的
        String orderKey = s.next(); // desc

        // 先使用PreparedStatement
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // 1、注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 2、获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","123456");
            // 3、获取预编译的数据库操作对象
            String sql = "select ename,sal from emp order by sal ?";
            ps = conn.prepareStatement(sql);
            // 给?传值
            ps.setString(1, orderKey);
            // 4、执行sql语句
            rs = ps.executeQuery();
            while(rs.next()){
                String ename = rs.getString("ename");
                String sal = rs.getString("sal");
                System.out.println(ename + "," + sal);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

降序使用Statement
java">package com.bjpowernode.jdbc;

import java.sql.*;
import java.util.Scanner;
/*
使用Statement。
 */
public class JDBCTest09 {
    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        System.out.print("请输入desc或asc【desc表示降序,asc表示升序】:");
        // 用户输入的
        String orderKey = s.next(); // desc

        // 先使用PreparedStatement
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            // 1、注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 2、获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","123456");
            // 3、获取预编译的数据库操作对象
            stmt = conn.createStatement();
            // 4、执行sql语句
            String sql = "select ename,sal from emp order by sal " + orderKey;
            rs = stmt.executeQuery(sql);
            // 5、处理查询结果集
            while(rs.next()){
                String ename = rs.getString("ename");
                String sal = rs.getString("sal");
                System.out.println(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();
                }
            }
        }
    }
}

使用PreparedStatement进行增删改
java">package com.bjpowernode.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/*
使用PreparedStatement完成增删改。
 */
public class JDBCTest10 {
    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/bjpowernode","root","123456");
            // 3、获取预编译的数据库操作对象
            /*
            String sql = "insert into dept(deptno,dname,loc) values(?,?,?)";
            ps = conn.prepareStatement(sql);
            // 给?传值
            ps.setInt(1, 50);
            ps.setString(2, "销售部");
            ps.setString(3, "天津");
            */

            /*
            String sql = "update dept set dname = ?, loc = ? where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, "软件研发部");
            ps.setString(2, "北京");
            ps.setInt(3, 50);
            */

            String sql = "delete from dept where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setInt(1, 50);

            // 4、执行SQL语句
            int count = ps.executeUpdate(); // 这个方法不需要sql参数
            System.out.println(count);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6、释放资源
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

使用PreparedStatement做模糊查询
java">package com.bjpowernode.jdbc;

import java.sql.*;

/*
使用PreparedStatement做模糊查询
 */
public class JDBCTest11 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // 1、注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 2、获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","123456");
            // 3、获取预编译的数据库操作对象(找出名字中含有O的)
            /*String sql = "select ename from emp where ename like '%?%'"; //这样写错误的! ?不能写在''里面
            ps = conn.prepareStatement(sql);
            ps.setString(1, "O");*/

            // 重点是占位符该怎么写!!!
            String sql = "select ename from emp where ename like ?"; //这样是对的!
            ps = conn.prepareStatement(sql);
            ps.setString(1, "%O%");

            // 4、执行SQL语句
            rs = ps.executeQuery();
            while(rs.next()){
                System.out.println(rs.getString("ename"));
            }

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

10、JDBC的事务问题?

conn.setAutoCommit(false);
conn.commit();
conn.rollback();
java">package com.bjpowernode.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/*
JDBC默认情况下对事务是怎么处理的?
    模拟一下银行账户转账操作,A账户20000,B账户0。
    A账户向B账户转账10000元。
    从A账户减去10000,向B账户加上10000.
    必须同时成功,或者同时失败!

    转账是需要:执行两条update语句的。

JDBC默认情况下支持自动提交:
    什么叫做自动提交呢?
        只要执行一条DML语句就自动提交一次。
        (所以这个程序在关闭自动提交机制之前,
        在出现异常或者睡眠之前就会提交,导致A账户总额变成10000, 而后面给B账户的转账无法执行)

在实际开发中必须将JDBC的自动提交机制关闭掉,改成手动提交。
当一个完整的事务结束之后,再提交。
    conn.setAutoCommit(false); 关闭自动提交机制。
    conn.commit(); 手动提交
    conn.rollback(); 手动回滚
 */
public class JDBCTest12 {
    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/bjpowernode","root","123456");
            // 开启事务:将自动提交机制关闭掉。
            conn.setAutoCommit(false);

            // 3、获取预编译的数据库操作对象
            String sql = "update t_act set balance = ? where actno = ?";
            ps = conn.prepareStatement(sql);

            //给?传值
            ps.setDouble(1, 10000);
            ps.setString(2, "A");
            int count = ps.executeUpdate(); //更新成功之后表示更新1条,返回1

            //Thread.sleep(1000 * 20);
            // 模拟异常!!!!
            String s = null;
            s.toString();

            // 给?传值
            ps.setDouble(1, 10000);
            ps.setString(2, "B");
            count += ps.executeUpdate(); //再次更新1条再返回1

            System.out.println(count == 2 ? "转账成功" : "转账失败!");

            // 代码能够执行到此处,说明上面的代码没有出现任何异常,表示都成功了,手动提交。
            // 手动提交,事务结束。
            conn.commit();

        } catch (Exception e) {
            // 出现异常的话,为了保险起见,这里要回滚!
            try {
                if (conn != null) {
                    conn.rollback();
                }
            } catch (SQLException ex) {
                ex.printStackTrace();
            }

            e.printStackTrace();
        } finally {
            // 6、释放资源
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

11.为了便于以后的开发,封装一个JDBC工具类

java">package com.bjpowernode.jdbc.utils;

import java.sql.*;
import java.util.ResourceBundle;

/**
 * 数据库工具类,便于JDBC的代码编写。
 */
public class DBUtil {

    // 工具类中的构造方法一般都是私有化的,为什么?
    // 构造方法私有化是为了防止new对象,为什么要防止new对象?
    // 因为工具类中的方法都是静态的,不需要new对象,直接使用“类名.”的方式调用。
    // Suppresses default constructor, ensuring non-instantiability.
    private DBUtil(){}

    // 类加载时绑定属性资源文件,静态变量
    private static ResourceBundle bundle = ResourceBundle.getBundle("resources/db");

    // 注册驱动
    static {
        try {
            Class.forName(bundle.getString("driver"));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取数据库连接对象
     * @return 新的连接对象
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        String url = bundle.getString("url");
        String user = bundle.getString("user");
        String password = bundle.getString("password");
        Connection conn = DriverManager.getConnection(url,user,password);
        return conn;
    }

    /**
     * 释放资源
     * @param conn 连接对象
     * @param stmt 数据库操作对象
     * @param rs 查询结果集
     */
    public static void close(Connection conn, Statement stmt, ResultSet rs){
        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();
            }
        }
    }

}

测试工具类
java">package com.bjpowernode.jdbc;

import com.bjpowernode.jdbc.utils.DBUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/*
测试DBUtil工具类。
 */
public class JDBCTest13 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // 获取连接
            conn = DBUtil.getConnection();
            // 获取预编译的数据库操作对象
            String sql = "select ename,sal from emp where ename like ?";
            ps = conn.prepareStatement(sql);
            // 给?传值
            ps.setString(1, "A%");
            // 执行sql
            rs = ps.executeQuery();
            // 处理结果集
            while(rs.next()){
                System.out.println(rs.getString("ename") + "," + rs.getDouble("sal"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            DBUtil.close(conn, ps, rs);

            // 如果没有结果集对象,调用这个方法的时候第三个参数传null。
            //DBUtil.close(conn, ps, null);
        }
    }
}

关于DQL语句的悲观锁?

悲观锁和乐观锁在后面解释
对于一个DQL语句来说,末尾是可以添加这样一个关键之的:for update

select ename,sal from emp where job = 'MANAGER' for update
这句话的含义是:
	在本次事务的执行过程中,job = ‘MANAGER’的记录被查询,
	这些记录在我查询的过程中,任何人,任何事务都不能对这些记录进行修改操作,直到我当前事务结束。
	
	这种机制被称为:行级锁机制(又称为悲观锁)

在mysql中:
	当使用select...where...for update...时, mysql进行row lock 还是table lock
	取决于是否能使用索引(例如主键,unique约束的字段),能则为行锁,否则为表锁未查询到数据则无锁。
	而使用‘<>’,'like'等操作时,索引会失效,自然进行的是table lock
所以慎用 for update
整个表锁住的时候会导致性能降低,谨慎使用
使用for update时,最好是锁主键值,或者右unique约束的字段,锁别的字段可能会导致整个表锁住


乐观锁(提了一下)
是多个线程可以对当前记录进行修改,但是有个版本号

在这里插入图片描述

模拟悲观锁

两个程序进行模拟,锁Manager是进行的表锁,锁empno是行锁

java">package com.bjpowernode.jdbc;

import com.bjpowernode.jdbc.utils.DBUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/*
在当前事务中对job='MANAGER'的记录进行查询并锁定,使用行级锁机制。
 */
public class JDBCTest14 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // 获取连接
            conn = DBUtil.getConnection();
            // 开启事务
            conn.setAutoCommit(false);
            // 执行
            String sql = "select ename,sal from emp where empno = ? for update";
            ps = conn.prepareStatement(sql);
            ps.setInt(1, 7369);
            rs = ps.executeQuery();
            while(rs.next()){
                System.out.println(rs.getString("ename") + "," + rs.getDouble("sal"));
            }

            //睡眠
            Thread.sleep(1000 * 20);

            // 提交事务
            conn.commit();
        } catch (SQLException e) {
            // 回滚事务
            if(conn != null){
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(conn, ps, rs);
        }
    }
}

java">package com.bjpowernode.jdbc;

import com.bjpowernode.jdbc.utils.DBUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/*
在这个事务当中对job='MANAGER'的记录进行update操作。
 */
public class JDBCTest15 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            conn = DBUtil.getConnection();
            String sql = "update emp set sal = sal * 10 where empno = ?";
            ps = conn.prepareStatement(sql);
            ps.setInt(1, 7698);
            int count = ps.executeUpdate();
            System.out.println("更新了"+count+"条");
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(conn, ps, null);
        }
    }
}


http://www.niftyadmin.cn/n/1389339.html

相关文章

Spring Boot 配置logback

一&#xff1a;官网 logback的官方网站&#xff1a; http://logback.qos.ch 二&#xff1a;logback的配置文件 Logback 配置文件的语法非常灵活。正因为灵活&#xff0c;所以无法用 DTD 或 XML schema 进行定义。尽管如此&#xff0c;可以这样描述配置文件的基本结构&#xff1…

ISAP算法对 Dinic算法的改进

ISAP算法对 Dinic算法的改进&#xff1a;   在刘汝佳图论的开头引言里面&#xff0c;就指出了&#xff0c;算法的本身细节优化&#xff0c;是比较复杂的&#xff0c;这些高质量的图论算法是无数优秀算法设计师的智慧结晶。 如果一时半会理解不清楚&#xff0c;也是正常的。但…

[置顶]c# 设计模式(1)一 创建型

为了方便阅读&#xff0c;我把一篇设计模式的资料拆分开发&#xff0c;分为三个大的部分&#xff0c;如下&#xff1a;Factory Abstract FactoryBuilderPrototypeSingleton 名称Factory Method结构 意图定义一个用于创建对象的接口&#xff0c;让子类决定实例化哪一个类。Fact…

Nginx代理显实真实IP的解决

Nginx 使用总结Nginx x 2Nginx 是目前比较流行的 Web 服务器(当然它还能做其他的事情),我是因为使用 Tornado 开发 Web 应用程序而开始使用它的. 本文主要是我的使用过程中一些记录.nginx 命令用法选项释义-h帮助-s signal向主线程发送一个信号以控制其行为, signal 值可以是 s…

Spring Boot 本地jar包

一&#xff1a;问题场景 在开发过程中&#xff0c;我们有时会遇到Maven中央库没有的jar包&#xff0c;比如公司自己开发的而又没有放入公司私库的jar包&#xff0c;这时就不能通过三围在pom中直接引入。那么怎么在项目中引入这些本地或第三方jar包呢&#xff1f; 二&#xff1a…

LeetCode1707. 与数组中元素的最大异或值 (字典树)/ 990. 等式方程的可满足性(并查集)

1707. 与数组中元素的最大异或值 2021.5.23 每日一题&#xff0c;又是异或&#xff0c;今天准备参加人生第一次周赛哈哈 题目描述 给你一个由非负整数组成的数组 nums 。另有一个查询数组 queries &#xff0c;其中 queries[i] [xi, mi] 。第 i 个查询的答案是 xi 和任何 n…

分享两个好用的nosql GUI工具

redis: 真是千呼万唤始出来&#xff0c;终于有了redis gui 谢谢作者&#xff0c;但中文乱码问题貌似还没有解决呀&#xff01; https://github.com/caoxinyu/RedisClient mongoDB: MongoVUE这个还是很完善的 http://www.mongovue.com/

数据仓库的关键特征

按照W.H.Inmon 这位数据仓库权威的说法&#xff0c;“数据仓库是一个面向主题的、集成的、时变的、非易失的数据集合&#xff0c;支持管理部门的决策过程”。这个简短而又全面的定义指出了表明数据仓库主要特征的四个关键词&#xff1a;面向主题的、集成的、时变的、非易失的&a…