JDBC-Java数据库连接

JDBC-Java数据库连接

    • 1、JDBC是什么?
    • 2、JDBC的本质?
    • 3、JDBC开发前的准备工作: 先从官网下载对应的驱动jar包,然后将其配置到环境变量classpath当中
    • 4、JDBC编程六步(背会)
    • 5、手写JDBC
    • 6、使用IDEA工具 开发JDBC
    • 7、Statement 和 PreparedStatement
    • 8、JDBC 事务机制
    • 9、JDBC工具类的封装
    • 10、拓展:悲观锁(行级锁)和乐观锁

1、JDBC是什么?

  Java DataBase Connectivity(Java数据库连接)

2、JDBC的本质?

JDBC是SUN公司制定的一套接口(interface)
	java.sql.*; (这个软件包下有很多接口。)

接口都有调用者和实现者。
面向接口调用、面向接口写实现类,这都属于面向接口编程。

为什么要面向接口编程?
	解耦合:降低程序的耦合度,提高程序的扩展力。
	多态机制就是非常典型的:面向抽象编程。(不要面向具体编程)
		建议:
			Animal a = new Cat();
			Animal a = new Dog();
			// 喂养的方法
			public void feed(Animal a){ // 面向父类型编程。
			
			}
		不建议:
			Dog d = new Dog();
			Cat c = new Cat();

思考:为什么SUN制定一套JDBC接口呢?
	因为每一个数据库的底层实现原理都不一样。
	Oracle数据库有自己的原理。
	MySQL数据库也有自己的原理。
	MS SqlServer数据库也有自己的原理。
	....
	每一个数据库产品都有自己独特的实现原理。

JDBC的本质到底是什么?
	一套接口。

JDBC-Java数据库连接_第1张图片

3、JDBC开发前的准备工作: 先从官网下载对应的驱动jar包,然后将其配置到环境变量classpath当中

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

  前面的“.;”必须要有!
  “."代表的是当前路径,以他开头,是为了程序运行时,让他在当前路径去寻找额外的一些资源,比如说,你自己写的一些类。
  ";"是起着分割的作用,如果在前面的目录中没有找到想要,那么会去很分号后面的目录中查找,就这样一级一级的找下去,
  直到classpath末尾,如果还没有找到,就报异常!
   classpath= . ; 路径1 ; 路径2 ; 路径3 …
  
  以上的配置是针对于文本编辑器(例如记事本)的方式开发。
  如果使用IDEA等开发工具的时候,不需要配置以上的环境变量。IDEA有自己的配置方式。

4、JDBC编程六步(背会)

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

  • 第二步:获取连接
        表示JVM的进程和数据库进程之间的通道打开了,这属于进程之间的通信,重量级的,使用完之后一定要关闭通道。

  • 第三步:获取数据库操作对象
        专门执行sql语句的对象

  • 第四步:执行SQL语句
        DQL DML…

  • 第五步:处理查询结果集
        只有当第四步执行的是select语句的时候,才有这第五步 处理查询结果集。

  • 第六步:释放资源
        使用完资源之后一定要关闭资源。Java和数据库属于进程间的通信,开启之后一定要关闭。

5、手写JDBC

  5.1、JDBC编程6步 之 DML(增删改)
    【JDBCTest01.java】+【JDBCTest02.java】

import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.Statement;

/*
    JDBC编程 6步
	JDBC 完成 insert

	注:这里是用记事本写的,不是用java开发工具
 */
public class JDBCTest01 {
    public static void main(String[] args) {
		Connection conn = null;
		Statement stmt = null;
        try {
            //1、注册驱动
			 //多态,父类型引用指向子类型对象。
			//另外这里的执行前提是已经在classpath中配置了mysql的驱动jar包,否则编译时会报错 “错误: 程序包com.mysql.jdbc不存在”
            Driver driver = new com.mysql.jdbc.Driver();
            DriverManager.registerDriver(driver);

            //2、获取连接
			/*
				url:统一资源定位符(网络中某个资源的绝对路径)
				
				url包括哪几部分?
					协议
					IP
					PORT(端口)
					资源名

				https://www.baidu.com/ 这就是url
					在dos窗口,C:\Users\Administrator>ping www.baidu.com,可以看到百度IP为14.215.177.38
					现也可以这样访问百度:	http://14.215.177.38:80/index.html

				http://14.215.177.38:80/index.html
					http://————通信协议
					14.215.177.38————服务器IP地址
					80————服务器上软件的端口
					index.html————服务器上面的某个资源名
				
				jdbc:mysql://127.0.0.1:3306/zt
					jdbc:mysql:// ————协议
					127.0.0.1 ————IP地址
					3306 ————数据库端口号
					zt ————具体的数据库名
				
				说明:localhost和127.0.0.1 都是本机IP

				什么是通信协议?有什么用?
					通信协议是通信之前就提前定好的数据传送格式。
					数据包具体怎么传数据,格式提前定好的。
			*/
			//String url = "jdbc:mysql://localhost:3306/zt"; 
            String url = "jdbc:mysql://127.0.0.1:3306/zt";//最后的为数据库名字
            String user = "root";
            String password = "123";
            conn = DriverManager.getConnection(url,user,password);
            
			System.out.println("数据库连接对象:"+conn); //数据库连接对象:com.mysql.jdbc.JDBC4Connection@41cf53f9
			//可以知道该对象的类名为com.mysql.jdbc.JDBC4Connection
			
			//3、获取数据库操作对象
			stmt = conn.createStatement();  

			//4、执行SQL语句
			String sql = "insert into dept(deptno,dname,loc) values(50,'人事部','北京')"; //注意:jdbc中的sql语句不需要写分号结尾
			
			// executeUpdate:专门执行DML语句的(DML:数据操作语言,对表当中的数据进行增删改insert、delete、update)
			//返回值是“影响数据库中记录条数”
			//executeQuery:专门执行DQL语句的(DQL:数据查询语言,对表当中的数据进行查询select)
			//返回值是“单个 ResultSet 对象。”
			int count = stmt.executeUpdate(sql);
			System.out.println(count == 1 ? "添加成功" : "添加失败"); 
			
			//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();
			}	
		}
    }
}
import java.sql.*;

/*
    JDBC 完成delete、update

	注:这里是用记事本写的,不是用java开发工具
 */
public class JDBCTest02 {
    public static void main(String[] args) {
		Connection conn = null;
		Statement stmt = null;
        try {
            //1、注册驱动
            DriverManager.registerDriver(new com.mysql.jdbc.Driver());

            //2、获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/zt","root","123");
            
			//3、获取数据库操作对象
			stmt = conn.createStatement();  

			//4、执行SQL语句
			//String sql = "delete from dept where deptno = 50";
			String sql = "update dept set dname = '张三',loc = '黄土高坡'  where deptno = 40";
			int count = stmt.executeUpdate(sql);
			System.out.println(count == 1 ? "操作成功" : "操作失败"); 
			
			//5、处理查询结果集

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

  5.2、JDBC 注册驱动的第二种方式(这种方式常用)

import java.sql.*;
/*
    JDBC 注册驱动的另一种方式(这种方式常用)

	注:这里是用记事本写的,不是用java开发工具
 */
public class JDBCTest03 {
    public static void main(String[] args) {

        try {
            //1、注册驱动
			//这是注册驱动的第一种方式
            //DriverManager.registerDriver(new com.mysql.jdbc.Driver());
			
			//第二种方式:通过反射机制

			/*		在com.mysql.jdbc.Driver类里面,static静态代码块中有这一句:
			“java.sql.DriverManager.registerDriver(new Driver());”,这一行代码也就是注册驱动的第一种方式
			在反射机制中,要想只让一个类的静态代码块执行,可以使用:Class.forName("完整类名");
			也就是这注册驱动的第二种方式。

			为什么这种方式更常用?因为参数是一个字符串,可以写到xxx.properties配置文件当中
			以后只需要修改配置文件,不用修改java代码,就可以做到对不同类型数据库的连接,不只是mysql
			*/
			Class.forName("com.mysql.jdbc.Driver");

            //2、获取连接
            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/zt","root","123");
            
			System.out.println(conn); //com.mysql.jdbc.JDBC4Connection@41cf53f9
									 //com.mysql.jdbc.JDBC4Connection@3d646c37
			
        } catch (SQLException e) {
            e.printStackTrace();
        } catch(ClassNotFoundException e){
			e.printStackTrace();
		}
    }
}

  5.3、实际开发中,不建议把连接数据库的信息写死到java程序当中,建议将连接数据库的所有信息配置到xxx.properties配置文件当中
    【jdbc.properties】+【JDBCTest04.java】

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/zt
user=root
password=123
import java.sql.*;
import java.util.ResourceBundle;

public class JDBCTest04{
	public static void main(String[] args) {
		
		//使用资源绑定器绑定属性配置文件(在javase反射机制中学过)
		ResourceBundle bundle = ResourceBundle.getBundle("jdbc"); //jdbc.properties后缀不用写
		String driver = bundle.getString("driver"); //通过key来获取value		
		String url = bundle.getString("url");
		String user = bundle.getString("user");
		String pwd = bundle.getString("password");

		Connection conn = null;
		Statement stmt = null;
        try {
            //1、注册驱动
			Class.forName(driver);

            //2、获取连接
            conn = DriverManager.getConnection(url, user, pwd);
            
			//3、获取数据库操作对象
			stmt = conn.createStatement();  

			//4、执行SQL语句
			//String sql = "delete from dept where deptno = 50";
			String sql = "update dept set dname = '张三',loc = '黄土高坡'  where deptno = 40";
			int count = stmt.executeUpdate(sql);
			System.out.println(count == 1 ? "操作成功" : "操作失败"); 
			
			//5、处理查询结果集

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

  5.4、处理查询结果集 ————DQL(查)

import java.sql.*;

public class JDBCTest05{
	public static void main(String[] args){
		Connection conn = null;
		Statement stmt = null;
		ResultSet rs = null;

		try{
			//1、注册驱动
			Class.forName("com.mysql.jdbc.Driver");

			//2、获取连接
			conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/zt","root","123");

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

			//4、执行sql语句
			String sql = "select empno, ename as n, sal from emp";
			// executeUpdate(insert、delete、update),返回值是“影响数据库中记录条数”
			// executeQuery(select),返回值是“单个 ResultSet 对象。”
			rs = stmt.executeQuery(sql);

			//5、处理查询结果集
			while (rs.next()){  //返回值代表下一行是否有数据
				//取数据
				
				/*	
				//getString()方法的特点:不管数据库中的数据类型是什么,都以String的形式取出
				//方法内的参数,可以是列的下标,也可以是列名(重点注意:不是表中的列名,是查询结果集的列名)
				String no = rs.getString(1);   //第 1 列
				String name = rs.getString("n");  //重命名之后的列名
				String sal = rs.getString(3);

				System.out.println(no + "," + name + ","+ sal);
				*/

				//除了可以以String类型取出,还可以以特定的类型取出(但要“适可而止”,比如数据库中类型是字符串的,就不可能以int类型取出)
				int no = rs.getInt(1);   //数据库中empno类型本身就是int,那么取出int类型肯定可以
				//int name = rs.getInt(2); //报错:java.sql.sql异常:getInt()的值无效-“SMITH”
				String name = rs.getString(2);  //ename类型是varchar(可变长字符串)
				int sal = rs.getInt(3);  //sal类型是double ,就可以以int类型取出,当然也可以以double类型取出

				System.out.println(no + "," + name + ","+ (sal+100));//sal工资加100块钱
			}
			
			
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			//6、释放资源
			//遵循从小到大依次关闭
			if (rs != null){  //先释放查询结果集rs
				try{
					rs.close();
				}
				catch (Exception e){
					e.printStackTrace();
				}			
			}
			if (stmt != null){  //再释放数据库操作对象stmt
				try{
					stmt.close();
				}
				catch (Exception e){
					e.printStackTrace();
				}			
			}
			if (conn != null){ //最后释放数据库连接对象conn
				try{
					conn.close();
				}
				catch (Exception e){
					e.printStackTrace();
				}			
			}
		}
	}
}

JDBC-Java数据库连接_第2张图片

6、使用IDEA工具 开发JDBC

6.1、新建空的工程:
  File - New - Project - 选中左边最下面那个 Empty Project - Next -输入工程名字jdbc - Finish

6.2、在jdbc工程里面新建一个模块:
  File - New - Module - 就选默认的左边第一个 Java,中间最上面可以选择jdk版本(我选了8)
  - Next - 输入模块名字jdbc-test - Finish

6.3、将mysql的驱动jar包引入IDEA
  (相当于上面的用记事本写jdbc,运行前需要配置classpath环境变量)
  jar包位置:D:\MySql Connector Java 5.1.23\mysql-connector-java-5.1.23-bin.jar 引入IDEA

  File –> Project Structure,点左边第三个Libraries - 再点右边上面的+号,选择Java
  - 在弹出来的窗口里面,找到自己jar包的位置 - ok -选择模块- ok - 点Apply ,再点ok

6.4、实例:用户登录业务

0)前期准备:

		【安装PowerDesigner】
			在实际开发中,表的设计会使用专业的建模工具,我们这里安装一个建模工具: PowerDesigner
			使用PD工具来进行数据库表的设计。
		
		PowerDesigner和Navicat for MySQL的区别:
			(1)PowerDesigner 是做物理建模的,系统设计,设计数据库的,系统架构师用的
			(2)而Navicat for MySQL,是数据库和表都已经有了,
			      可以用Navicat for MySQL进行便捷的操作,系统架构师不一定会用这个
	

		使用PowerDesigner工具进行物理建模
			建一个user表,放用户登录信息
			完成之后,保存sql脚本(相关的sql语句)
		
		然后使用dos窗口,登录mysql,使用sql脚本完成表的创建和数据的导入
			登录mysql数据库管理系统  mysql -uroot -p333
			查看有哪些数据库 show databases; 
			使用zt数据库 use zt; 
			查看当前使用的数据库中有哪些表show tables;
			初始化数据 source F:\login-user.sql

1)编写代码,实现用户登录业务

package com.yuming.jdbc;

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

/*
实现功能:
    1、需求:模拟用户登录功能的实现
    2、业务描述:
        程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码
        用户输入用户名和密码之后,提交信息,java程序收集到用户信息
        Java程序连接数据库验证用户名和密码是否合法:
        合法:显示登录成功
        不合法:显示登录失败
    3、数据的准备:
        在实际开发中,表的设计会使用专业的建模工具,我们这里安装一一个建模工具: PowerDesigner
        使用PD工具来进行数据库表的设计。(参见login-user.sql脚本)
    4、当前程序存在的问题:
        ----------------------
        用户名:sdsdsa
        密码:cscs' or '1'='1
        登录成功
        ---------------------
        这种现象称为sql注入(安全隐患,黑客经常使用)
    5、导致sql注入的根本原因是什么?
        用户输入的信息中含有sql语句关键字,并且这些关键字参与sql语句的编译过程,
        导致sql语句的原意被扭曲,进而达到sql注入
 */
public class JDBCTest06 {
    public static void main(String[] args) {
        //初始化一个界面
        Map<String,String> userLoginInfo = initUI();
        //验证用户名和密码
        boolean loginSuccess = login(userLoginInfo);

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

    /**
     * 用户登录
     * @param userLoginInfo 用户登录信息
     * @return  true表示登录成功,false表示登录失败
     */
    private static boolean login(Map<String, String> userLoginInfo) {
       //打标记 (表示登录的状态)
        boolean loginSuccess = false ;

        //先拿到登录名和密码
        String username = userLoginInfo.get("user");//map集合,get方法,根据key获取value
        String password = userLoginInfo.get("pwd");

        //jdbc代码
        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/zt","root","123");
            //3、获取数据库操作对象
            stmt = conn.createStatement();

            //4、执行sql
            //String sql = "select * from t_user where loginName = 'xxx' and loginPwd = 'yyy'";
            //拼接字符串,将指定位置(上面的xxx和yyy)去掉,加上一对双引号,双引号内加一对加号,加号内写变量名
            //  "......xxxxx...."   ------>     "......" + 变量名 + "...."
            String sql = "select * from t_user where loginName = '"+username+"' and loginPwd = '"+password+"'";
            rs = stmt.executeQuery(sql);

            //5、处理查询结果集
            if (rs.next()){ //rs.next() == true,表示查询语句返回了结果,也就是说存在该用户
                //登录成功
                loginSuccess = true;
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException 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 loginSuccess;
    }

    /**
     * 初始化用户的界面
     * @return 用户输入的用户名和密码等登录信息
     */
    private static Map<String, String> initUI() {
        Scanner s = new Scanner(System.in);

        System.out.print("用户名:");
        String username = s.nextLine();

        System.out.print("密码:");
        String password = s.nextLine();

        Map<String,String> userLoginInfo = new HashMap<>();
        userLoginInfo.put("user",username);
        userLoginInfo.put("pwd",password);

        return userLoginInfo;
    }
}

2)关于SQL注入

		当前程序存在的问题:
			----------------------
			用户名:sdscdca
			密码:xscs' or '1'='1
			登录成功
			---------------------
		 这种现象称为sql注入(安全隐患,黑客经常使用)

3)导致sql注入的根本原因是什么?

		用户输入的信息中含有sql语句关键字,并且这些关键字参与sql语句的编译过程,
		导致sql语句的原意被扭曲,进而达到sql注入。

		可以看到下面两个sql语句,都会得到查询结果:
			select * from t_user where loginName = '正确用户名' and loginPwd = '正确密码' 
			select * from t_user where loginName = 'sdscdca'  and loginPwd = ' xscs' or '1'='1  '

4)解决sql注入问题:

		只要用户提供的信息不参与sql语句的编译过程,问题就解决了
		即使用户输入的信息中含有sql语句关键字,但是没有参与编译,不起作用。

		要想用户的信息不参与SQL语句的编译,就必须使用java.sql.PreparedStatement
		PreparedStatement接口继承了Statement
		PreparedStatement是属于 预编译的 数据库操作对象
		PreparedStatement的原理:预先对SQL语句的框架进行编译,然后再给SQL语句传“值”。
package com.yuming.jdbc;

import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/*
测试结果:
            用户名:sdsdsd
            密码:cscs' or '1'='1
            登录失败

            用户名:zhangsan
            密码:123
            登录成功
 */
public class JDBCTest07 {
    public static void main(String[] args) {
        //初始化一个界面
        Map<String,String> userLoginInfo = initUI();
        //验证用户名和密码
        boolean loginSuccess = login(userLoginInfo);

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

    /**
     * 用户登录
     * @param userLoginInfo 用户登录信息
     * @return  true表示登录成功,false表示登录失败
     */
    private static boolean login(Map<String, String> userLoginInfo) {
        //打标记 (表示登录的状态)
        boolean loginSuccess = false ;

        //先拿到登录名和密码
        String username = userLoginInfo.get("user");
        String password = userLoginInfo.get("pwd");

        //jdbc代码
        Connection conn = null;
        // Statement stmt = null;
        PreparedStatement pstmt = null; //这里使用PreparedStatement(预编译的数据库操作对象)!!!!!!!!!!!!!!!
        ResultSet rs = null;

        try {
            //1、注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2、获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/zt","root","123");

            /*
            //3、获取数据库操作对象
            stmt = conn.createStatement();

            //4、执行sql
            String sql = "select * from t_user where loginName = '"+username+"' and loginPwd = '"+password+"'";
            rs = stmt.executeQuery(sql);
            */

            //3、获取 预编译的 数据库操作对象   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            //SQL语句的框架。其中一个?表示一个占位符,一个?将来接收一个"值",注意:占位符不能加单引号括起来!!!!!
            String sql = "select * from t_user where loginName = ? and loginPwd = ?";
            //程序执行到此处,会发送SQL语句给DBMS,然后DBMS进行SQL语句的预先编译
            pstmt = conn.prepareStatement(sql);

            //给占位符?传值(第一个?下标是1,第二个?下标是2.。。。jdbc中所有下标都是从1开始)
            pstmt.setString(1,username);
            pstmt.setString(2,password);

            //4、执行sql
            rs = pstmt.executeQuery();  //注意,这里就不用在传sql语句了

            //5、处理查询结果集
            if (rs.next()){
                //登录成功
                loginSuccess = true;
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //6、释放资源
            if (rs != null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (pstmt != null){
                try {
                    pstmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return loginSuccess;
    }

    /**
     * 初始化用户的界面
     * @return 用户输入的用户名和密码等登录信息
     */
    private static Map<String, String> initUI() {
        Scanner s = new Scanner(System.in);

        System.out.print("用户名:");
        String username = s.nextLine();

        System.out.print("密码:");
        String password = s.nextLine();

        Map<String,String> userLoginInfo = new HashMap<>();
        userLoginInfo.put("user",username);
        userLoginInfo.put("pwd",password);

        return userLoginInfo;
    }
}

7、Statement 和 PreparedStatement

1、对比一下 Statement 和 PreparedStatement :
		1、Statement存在SQL注入问题——————PreparedStatement解决了SQL注入问题
	
		2、Statement是编译一次,执行一次——————PreparedStatement是编译一次,可执行n次。
	   		PreparedStatement 效率高一些。
	   
		3、PreparedStatement 会在编译阶段做类型的安全检查

	综上所述:PreparedStatement使用较多,只有极少情况下会需要使用Statement

2、什么情况下,必须使用Statement?
	业务方面,要求必须使用SQL注入的时候!Statement支持SQL注入。
	凡是业务方面要求是需要进行SQL拼接注入的,必须使用Statement
	
	演示 Statement的用途:【JDBCTest08.java】

3、PreparedStatement完成增删改:【JDBCTest09.java】
import java.sql.*;
import java.util.Scanner;
/*
	演示 Statement的用途
*/
public class JDBCTest08 {
    public static void main(String[] args) {
        //用户在控制台输入desc就是降序,输入asc就是升序

        Scanner s = new Scanner(System.in);
        System.out.println("输入asc或者desc,asc表示升序,desc表示降序");
        System.out.print("请输入:");
        String keyWords = s.next();

/*        //执行sql
        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/zt","root","123");
            //3 获取预编译的数据库操作对象
            String sql = "select ename from emp order by ename ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,keyWords); //问题在这里————》给占位符?传值的时候,因为是setString,所以默认给加单引号括起来了
            //4 执行sql语句
            rs = ps.executeQuery();
            //5 处理查询结果集
            while(rs.next()){
                System.out.println(rs.getString("ename"));
            }

            //运行之后,报异常了: mysql语法错误异常,第1行的''asc''附近的问题,它不应该被当成字符串有单引号引起来
            // com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException:
            // You have an error in your SQL syntax;
            // check the manual that corresponds to your MySQL server version for the right syntax to use near ''asc'' at line 1
            // 。。。

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException 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();
                }
            }
        }
        */

        //执行sql
        Connection conn = null;
        // PreparedStatement ps = null;
        Statement stmt = null;  //这里就必须使用Statement了!!!!!!!!!!!!!!
        ResultSet rs = null;
        try {
            // 1 注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 2 获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/zt","root","123");
            //3 获取 数据库操作对象
            stmt = conn.createStatement();

            //4 执行sql语句
            String sql = "select ename from emp order by ename " + keyWords;
            rs = stmt.executeQuery(sql);

            //5 处理查询结果集
            while(rs.next()){
                System.out.println(rs.getString("ename"));
            }

            //运行之后,就可以正确升序降序显示了

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException 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();
                }
            }
        }
    }
}
import java.sql.*;

/*
	PreparedStatement完成增删改 insert、delete、update
 */
public class JDBCTest09 {
    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/zt","root","123");

            //3 获取预编译的数据库操作对象
            /*
            //insert
            String sql = "insert into dept(deptno,dname,loc) values(?,?,?)";
            ps = conn.prepareStatement(sql);
            ps.setInt(1,60);
            ps.setString(2,"营销部");
            ps.setString(3,"广东");*/

            /*
            //update
            String sql = "update dept set dname=? , loc=? where deptno = 60 ";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"销售部");
            ps.setString(2,"上海");*/

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

            //4 执行sql语句
            //int count = ps.executeUpdate(sql);
            int count = ps.executeUpdate(); //注意,这里不能再传sql语句了
            System.out.println(count == 1 ? "操作成功" : "操作失败");

            //5 处理查询结果集

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException 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();
                }
            }
        }
    }
}

8、JDBC 事务机制

1、JDBC中的事务是自动提交的,什么是自动提交?
	只要执行任意一条DML语句,则自动提交一次。这是JDBc默认的事务行为。
	但是在实际的业务当中,通常都是N条DML语句共同联合才能完成的,必须
	保证他们这些DML语句在同一个事务中同时成功或者同时失败。

2、账户转账演示————jdbc事务控制
	(1) JDBC的事务 自动提交机制 的演示
		【JDBCTest10.java】

		JDBC的事务是自动提交的,但是我们有时候希望的不是自动提交,而是手动提交
	
	(2) 修改为手动提交机制
		重点三行代码:
		    conn.setAutoCommit(false);  //开启事务。将自动提交机制 修改为 手动提交
		    conn.commit();  //手动提交事务
		    conn.rollback(); //手动回滚事务

		【JDBCTest11.java】
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

//(1) JDBC的事务 自动提交机制 的演示

/*
    【账户转账演示——jdbc事务控制】
    业务需求:111账户 转账给112账户 2000元

    SQL脚本:
        drop table if exists t_act;
        create table t_act(
            actno int,
            balance double(7,2)
        );
        insert into t_act(actno,balance) values(111,12000);
        insert into t_act(actno,balance) values(112,0);
        commit;
        select * from t_act;

     1、JDBC的事务 自动提交机制 的演示

 */
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/zt","root","123");
            //3 获取预编译的数据库操作对象

            String sql = "update t_act set balance = ?  where actno = ?";
            ps = conn.prepareStatement(sql);

            //给?传值——111账户由12000变为10000
            ps.setDouble(1,10000);
            ps.setInt(2,111);
            int count = ps.executeUpdate();

            //制造一个空指针异常,使程序停止到这里
            String s = null;
            System.out.println(s.toString());
            //最后在数据库中看到,111账户减少了2000元,但是112账户依然是0 !!!转账失败!!!这是因为jdbc的事务自动提交机制!!!!!!但现在这里不是我们所希望的!!

            //给?传值 ——112账户增加2000
            ps.setDouble(1,2000);
            ps.setInt(2,112);
            //count = count + ps.executeUpdate();
            count += ps.executeUpdate();

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

        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            if (ps != null ){
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null ){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/*
(2)修改为手动提交机制:(运行之前,先把数据库中的111账户的余额修改回12000)

【重点三行代码】:
    conn.setAutoCommit(false);  //开启事务。将自动提交机制 修改为 手动提交
    conn.commit();  //手动提交事务
    conn.rollback(); //手动回滚事务
 */
public class JDBCTest11 {
    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/zt","root","123");

            //将自动提交机制 修改为 手动提交!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            conn.setAutoCommit(false);  //开启事务

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

            //给?传值——111账户由12000变为10000
            ps.setDouble(1,10000);
            ps.setInt(2,111);
            int count = ps.executeUpdate();

           /*
            //制造一个空指针异常,使程序停止到这里
            String s = null;
            System.out.println(s.toString());
            //第一次运行:在数据库中看到,111账户和112账户都没有发生变化 !!!!是我们所希望的!!!!nice!!!!!!!!!!!!!!!
            */
            //第二次运行:把这里的代码去掉之后,程序可以正常结束了,111账户减少了2000,112账户增加了2000,转账成功!!!!!!!!!!!

            //给?传值 ——112账户增加2000
            ps.setDouble(1,2000);
            ps.setInt(2,112);
            //count = count + ps.executeUpdate();
            count += ps.executeUpdate();

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

            //程序能够走到这里,说明以上程序没有异常,事务结束——手动提交数据:!!!!!!!!!!!!!!!!!!!!!
            conn.commit();  //手动提交事务

        }  catch (Exception e) {
            // 回滚事务!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            if (conn != null){
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }
            e.printStackTrace();
        } finally{
            if (ps != null ){
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null ){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

9、JDBC工具类的封装

   1) JDBC工具类的封装 【DBUtil.java】
   2) JDBC实现模糊查询【JDBCTest12.java】

package com.yuming.jdbc.utils;

import java.sql.*;

/**
 * JDBC工具类的封装:
 * JDBC工具类,简化JDBC编程
 */
public class DBUtil {

    /**
     * 工具类当中的构造方法都是私有的,为了防止别人new对象
     * 因为工具类当中的方法都是静态的,不需要new对象,直接采用类名调用: 类名.方法名
     */
    private DBUtil() {
    }

    //静态代码块,在类加载时执行,且只执行一次
    static {
        try {
            //注册驱动
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取数据库连接对象
     *
     * @return 连接对象
     * @throws SQLException
     */
    public static Connection getConnection() {
        Connection conn = null;
        try {
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/zt", "root", "123");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    /**
     * 释放资源
     *
     * @param conn 连接对象
     * @param stmt 数据库操作对象
     * @param rs   结果集
     */
    public static void close(Connection conn, Statement stmt, ResultSet rs) {
        //方法参数中选择Statement没有选择PreparedStatement,是因为Statement是父接口,更通用,
        // 即使未来传过来的是PreparedStatement,也会自动向下转型

        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();
            }
        }
    }
    //测试
    public static void main(String[] args) {
        Connection conn = DBUtil.getConnection();
        System.out.println(conn); //com.mysql.jdbc.JDBC4Connection@3eb07fd3 ----输出连接对象,说明测试成功
        DBUtil.close(conn,null,null);
    }
}
package com.yuming.jdbc;

import com.yuming.jdbc.utils.DBUtil;

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

/*
   这个程序两个任务:
    1、测试DBUtil是否好用
    2、JDBC实现模糊查询:找出名字里面,第二个字是A的
 */
public class JDBCTest12 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //1 注册驱动 + 2、获取连接
            conn = DBUtil.getConnection();

            //3 获取预编译的数据库连接对象

            /* String sql = "select ename from emp where ename like '_?%'"; //错误写法,占位符不能加单引号括起来
            ps = conn.prepareStatement(sql);
            ps.setString(1,"A");*/

            String sql = "select ename from emp where ename like ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"_A%");

            // 4 执行sql语句
            rs = ps.executeQuery();

            // 5 处理查询结果集
            while(rs.next()){
                System.out.println(rs.getString("ename"));
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally{
            //6、释放资源
            DBUtil.close(conn, ps, rs);
        }

    }
}
/*
WARD
MARTIN
JAMES
 */

10、拓展:悲观锁(行级锁)和乐观锁

  悲观锁和乐观锁的概念:
JDBC-Java数据库连接_第3张图片

  演示行级锁机制:

	【JDBCTest13.java】+【JDBCTest14.java】(其中引用到了上面的JDBC工具类)
	
	2个程序需要一起运行,这里使用断点,debug运行

	使用行级锁之后,对应的表中的记录会被锁住,只要该事务没有结束,其他事务就不可能
	对这些锁住的记录进行操作,直到该事务结束,行级锁也就没有了,其他事务才可以操作
package com.yuming.jdbc;

import com.yuming.jdbc.utils.DBUtil;

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

/*
第一个程序:
    这个程序开启一个事务,这个事务专门进行查询,并且使用行级锁/悲观锁,锁住相关的记录
 */
public class JDBCTest13 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //1 注册驱动 + 2、获取连接
            conn = DBUtil.getConnection();
            //将自动提交机制 修改为 手动提交
            conn.setAutoCommit(false);  //开启事务

            //3 获取预编译的数据库连接对象
            String sql = "select ename,job,sal from emp where job = ? for update"; //添加行级锁/悲观锁
            ps = conn.prepareStatement(sql);
            ps.setString(1,"MANAGER");

            // 4 执行sql语句
             rs = ps.executeQuery();

             // 5 处理查询结果集
            while(rs.next()){
                System.out.println(rs.getString("ename")+","
                        +rs.getString("job")+","+rs.getDouble("sal"));
            }

            //手动提交事务(事务结束)
            conn.commit();               //在这行代码最前面添加断点!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            /*
            因为添加断点,运行后,停在了这里,没有执行这行代码,就是没有提交,这个程序(事务)就没有结束,
            事务没有结束,由于emp表中,工作岗位为“MANAGER”的记录添加了悲观锁(行级锁),被锁住了,,,
            同时再去运行第二个程序,来修改这些记录,看看到底能不能锁住: 最终第二个程序一直卡住了,没有输出结果!!!说明这些记录确实被锁住了
            当结束这个(第一个)程序的时候,第二个程序的结果马上出来了,因为第一个程序结束了,就没有被锁了
            */

        } catch (SQLException e) {
            // 回滚事务(事务结束)
            if (conn != null){
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }
            e.printStackTrace();
        }finally{
            //6、释放资源
            DBUtil.close(conn, ps, rs);
        }
    }
}
package com.yuming.jdbc;

import com.yuming.jdbc.utils.DBUtil;

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

/*
第二个程序:
    这个程序负责修改被锁定的记录
 */
public class JDBCTest14 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //1 注册驱动 + 2、获取连接
            conn = DBUtil.getConnection();
            //将自动提交机制 修改为 手动提交
            conn.setAutoCommit(false);  //开启事务

            //3 获取预编译的数据库连接对象
            String sql = "update emp set sal = sal * 1.1 where job = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"MANAGER");

            // 4 执行sql语句
            int count = ps.executeUpdate();
            System.out.println(count);

            //手动提交事务(事务结束)
            conn.commit();
        } catch (SQLException e) {
            // 回滚事务(事务结束)
            if (conn != null){
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }
            e.printStackTrace();
        }finally{
            //6、释放资源
            DBUtil.close(conn, ps, null);
        }
    }
}

你可能感兴趣的:(JDBC,jdbc,java,数据库)