JDBC

JDBC


1.JDCBC简介
1.1简介

​ JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。

​ JDBC可以在各种平台上使用Java,如Windows,Mac OS和各种版本的UNIX。

JDBC库包括与数据库使用相关的API。

  • 连接数据库。
  • 创建SQL或MySQL语句。
  • 在数据库中执行SQL或MySQL查询。
  • 查看和修改生成的记录。
1.2JDBC体系结构

JDBC API支持用于数据库访问的两层和三层处理模型,但通常,JDBC体系结构由两层组成:

  • JDBC:提供了应用程序到数据库连接规范。
  • JDBC驱动程序: 连接数据库的驱动程序的实现。

JDBC API使用驱动程序管理器和特定于数据库的驱动程序来提供与异构数据库的透明连接。

1.3JDBC核心组件

DriverManager: 此类管理数据库驱动程序列表。使用通信协议将来自java应用程序的连接请求与适当的数据库驱动程序匹配。

Driver:此接口处理与数据库服务器的通信,我们很少会直接与Driver对象进行交互。而是使用DriverManager对象来管理这种类型的对象。

Connection:该接口具有用于连接数据库的所有方法。连接对象表示通信上下文,数据库的所有通信仅通过连接对象。

Statement:使用从此接口创建的对象将SQL语句提交到数据库。除了执行存储过程之外,一些派生接口还接受参数。

ResultSet:**在使用Statement对象执行SQL查询后,这些对象保存从数据库检索的数据。它作为一个迭代器,允许我们移动其数据。

SQLException:此类处理数据库应用程序中发生的任何异常。

2.JDBC相关的SQL语法

​ SQL 是一种标准化的语言,它允许你在数据库上执行操作,如创建项目,查询内容,更新内容,并删除条目等操作。

​ Create, Read, Update, and Delete 通常称为CRUD操作。

对数据库进行操作:

CREATE DATABASE语句用于创建新的数据库:

SQL> CREATE DATABASE DATABASE_NAME;

DROP DATABASE语句用于删除现有数据库:

SQL> DROP DATABASE DATABASE_NAME;
对表进行操作:

CREATE TABLE语句用于创建新表。语法是 -

SQL> CREATE TABLE Employees
(
   id INT NOT NULL,
   age INT NOT NULL,
   first VARCHAR(255),
   last VARCHAR(255),
   PRIMARY KEY ( id )
);

DROP TABLE语句用于删除现有表。

SQL> DROP TABLE table_name;
对表的内容进行操作:

INSERT的语法类似于以下内容,其中column1,column2等表示要显示在相应列中的新数据

SQL> INSERT INTO table_name VALUES (column1, column2, ...);

SELECT语句用于从数据库中检索数据。SELECT的语法是 -

SQL> SELECT column_name, column_name, ...
     FROM table_name
     WHERE conditions;

UPDATE语句用于更新数据。

SQL> UPDATE table_name
     SET column_name = value, column_name = value, ...
     WHERE conditions;

DELETE语句用于从表中删除数据。

SQL> DELETE FROM table_name WHERE conditions;

WHERE子句可以使用比较运算符,例如=,!=,<,>,<=和> =,以及BETWEEN和LIKE运算符。

3.JDBC使用的基本步骤

构建JDBC应用程序涉及以下六个步骤:

  • 导入JDBC驱动包:需要下载包含数据库编程所需的JDBC的jar包。
  • 注册JDBC驱动程序:要求初始化驱动程序,以便可以打开与数据库的通信通道。
  • 创建连接:需要使用*DriverManager.getConnection()*方法创建一个Connection对象,该对象表示与数据库的物理连接。
  • 执行查询:需要使用类型为Statement的对象来构建和提交SQL语句到数据库。
  • 从结果集中提取数据:需要使用相应的*ResultSet.getXXX()*方法从结果集中检索数据。
  • 释放资源:需要明确地关闭所有数据库资源,而不依赖于JVM的垃圾收集。
(1)导入jar包

​ 在项目下创建lib目录,把mysql的jdbc包放入此目录,并添加依赖关系。(根据实际情况进行配置,此处不再赘述)

(2)注册驱动

​ 第一种方式(推荐写法):Class.forName()

​ 注册驱动程序最常见的方法是使用Java的Class.forName()方法,将驱动程序的类文件动态加载到内存中,并将其自动注册

try {
   Class.forName("com.mysql.jdbc.Driver");
   Class.forName("com.mysql.cj.jdbc.Driver");//JDBC8.0版本之后使用
}
catch(ClassNotFoundException ex) {
   System.out.println("Error: unable to load driver class!");
   System.exit(1);
}

​ 第二种方式:是使用静态DriverManager.registerDriver()方法。

try {
   Driver myDriver = new com.mysql.jdbc.Driver();
   DriverManager.registerDriver( myDriver );
}
catch(ClassNotFoundException ex) {
   System.out.println("Error: unable to load driver class!");
   System.exit(1);
}

 * 1>驱动程序注册两次
 * 2>java程序依赖mysql驱动包
(3)获取连接

​ <1>数据库URL配置

​ 加载驱动程序后,可以使用DriverManager.getConnection()方法建立连接。为了方便参考,这里列出三个重载的DriverManager.getConnection()方法 :

  • getConnection(String url)
  • getConnection(String url,Properties prop)
  • getConnection(String url,String user,String password)

RDBMSJDBC驱动程序名称连接字符串格式
MySQL的com.mysql.jdbc.Driver**jdbc:mysql://**hostname:3306 / databaseName
ORACLEoracle.jdbc.driver.OracleDriverjdbc:oracle:thin:@ hostname:port Number:databaseName
DB2COM.ibm.db2.jdbc.net.DB2Driver**jdbc:db2:**hostname:port Number / databaseName
SYBASEcom.sybase.jdbc.SybDriverjdbc:sybase:Tds: hostname:port Number / databaseName

MySql8.0版本后进行了一些更新

​ JDBC驱动名称更新为com.mysql.cj.jdbc.Driver

​ 连接字符串格式更新为jdbc:mysql://localhost:3306/数据库名称?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone = GMT

​ <2>创建数据库连接对象

/**
 *此处以我使用的版本进行演示
 * driverclass--com.mysql.cj.jdbc.Driver
 * url--jdbc:jdbc:mysql://localhost:3306/数据库名称?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone = GMT
 */

String URL = "jdbc:jdbc:mysql://localhost:3306/数据库名称?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone = GMT";
String USER = "数据库用户名";
String PASS = "数据库密码";
Connection conn = DriverManager.getConnection(URL, USER, PASS);
(4)执行查询(暂不处理,下一节内容)
(5)处理结果(暂不处理,下一节内容)
(6)关闭数据库连接

​ 为确保连接关闭,可以在代码中提供一个“finally”块,在finally块中执行关闭数据库的操作,因为finally块总是执行,不管是否发生异常。

​ 要关闭上面打开的连接,应该调用close()方法如下 :-

conn.close();
4.使用JDBC执行SQL语句

​ 一旦获得了连接,我们可以与数据库进行交互。JDBC Statement和PreparedStatement接口定义了能够发送SQL命令并从数据库接收数据的方法和属性。

接口使用
Statement用于对数据库进行通用访问。在运行时使用静态SQL语句时很有用。Statement接口不能接受参数。
PreparedStatement(推荐使用)当您计划多次使用SQL语句时使用。PreparedStatement接口在运行时接受输入参数。
4.1Statement

创建语句对象

​ 在使用Statement对象执行SQL语句之前,需要使用Connection对象的createStatement()方法创建一个,如下例所示:

Statement stmt = null;
try {
   stmt = conn.createStatement( );
   . . .
}
catch (SQLException e) {
   . . .
}
finally {
   . . .
}

创建Statement对象后,可以使用它来执行一个SQL语句,其中有三个常用执行方法:

  • boolean execute(String SQL):如果可以检索到ResultSet对象,则返回一个布尔值true; 否则返回false。使用此方法执行SQL DDL语句或需要使用真正的动态SQL时。
  • int executeUpdate(String SQL):返回受SQL语句执行影响的行数。使用此方法执行预期会影响多个行的SQL语句,例如INSERT,UPDATE或DELETE语句。
  • ResultSet executeQuery(String SQL):返回一个ResultSet对象。当需要获得结果集时,请使用此方法,就像使用SELECT语句一样。

关闭Statement对象

​ 就像我们关闭一个Connection对象以保存数据库资源一样,由于同样的原因,还应该关闭Statement对象。

​ 一个简单的调用close()方法将执行该作业。如果先关闭Connection对象,它也会关闭Statement对象。但是,应始终显式关闭Statement对象,以确保正确清理。

Statement stmt = null;
try {
   stmt = conn.createStatement( );
   . . .
}
catch (SQLException e) {
   . . .
}
finally {
   stmt.close();
}
SQL注入

​ 就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。比如先前的很多影视网站泄露VIP会员密码大多就是通过WEB表单递交查询字符暴出的,这类表单特别容易受到SQL注入式攻击。

4.2PreparedStatement

​ 该PreparedStatement的接口扩展了Statement接口,它提供了一个通用的Statement对象,并且提供了两个附加功能:1.预编译,效率高 2 .安全,避免SQL注入

​ 此语句可以动态地提供参数。

PreparedStatement pstmt = null;
try {
   String SQL = "Update Employees SET age = ? WHERE id = ?";
   pstmt = conn.prepareStatement(SQL);
   . . .
}
catch (SQLException e) {
   . . .
}
finally {
   . . .
}

​ JDBC中的所有参数都由【?】符号,这被称为参数标记。在执行SQL语句之前,必须为每个参数提供值。

​ 所述的setXXX()方法将值绑定到所述参数,其中XXX代表要绑定到输入参数的值的Java数据类型。如果忘记提供值,将收到一个SQLException。

​ 每个参数标记由其顺序位置引用。第一个标记表示位置1,下一个位置2等等。该方法与Java数组索引不同,从0开始。

关闭PreparedStatement对象

​ 就像关闭Statement对象一样,由于同样的原因,还应该关闭PreparedStatement对象。

​ 一个简单的调用close()方法将执行该作业。如果先关闭Connection对象,它也会关闭PreparedStatement对象。但是,应始终显式关闭PreparedStatement对象,以确保正确清理。

PreparedStatement pstmt = null;
try {
   String SQL = "Update Employees SET age = ? WHERE id = ?";
   pstmt = conn.prepareStatement(SQL);
   . . .
}
catch (SQLException e) {
   . . .
}
finally {
   pstmt.close();
}
4.3JDBC批处理

​ Statement接口可以简单实现在Java编译器中处理单条SQL语句,但也会造成恶意表单注入。PrepareStatement接口解决了Statement恶意表单注入的安全问题,并且扩展了Statement接口的功能,实现了SQL语句预编译的效果,但是仍然只能一次处理单条SQL语句,但实际操作中会遇到大量SQL语句执行的情况,此时单条语句进行注入就无法满足需求,于是出现了批处理技术:

​ 批量处理允许将相关的SQL语句分组到批处理中,并通过对数据库的一次调用提交它们。当需要一次向数据库发送多个SQL语句时,可以减少连接数据库的开销,从而提高性能。

(1)Statement批处理

以下是使用语句对象的批处理的典型步骤(步骤3和步骤5在数据库事务处会进行详细介绍)

  • 1 注册驱动获取连接
  • 2 使用createStatement ()方法创建Statement对象。
  • 3 使用setAutoCommit ()将auto-commit设置为false 。(可选)
  • 4 使用addBatch ()方法在创建的语句对象上添加可能使用的SQL语句到批处理中。
  • 5 在创建的语句对象上使用executeBatch ()方法执行所有SQL语句。
  • 6 使用commit ()方法提交所有更改。(可选)
  • 7 释放资源
// Create statement object
Statement stmt = conn.createStatement();

// Set auto-commit to false
conn.setAutoCommit(false);

// Create SQL statement
String SQL = "INSERT INTO Employees (id, first, last, age) VALUES(200,'Zia', 'Ali', 30)";
// Add above SQL statement in the batch.
stmt.addBatch(SQL);

// Create one more SQL statement
String SQL = "INSERT INTO Employees (id, first, last, age) VALUES(201,'Raj', 'Kumar', 35)";
// Add above SQL statement in the batch.
stmt.addBatch(SQL);

// Create one more SQL statement
String SQL = "UPDATE Employees SET age = 35 WHERE id = 100";
// Add above SQL statement in the batch.
stmt.addBatch(SQL);

// Create an int[] to hold returned values
int[] count = stmt.executeBatch();

//Explicitly commit statements to apply changes
conn.commit();
(2)PrepareStatement批处理

以下是使用语句对象的批处理的典型步骤(步骤4和步骤7在数据库事务处会进行详细介绍)

  • 1 注册驱动获取连接
  • 2 使用占位符创建SQL语句。
  • 3 使用prepareStatement ()方法创建PrepareStatement对象。
  • 4 使用setAutoCommit ()将auto-commit设置为false 。(可选)
  • 5 使用addBatch ()方法在创建的语句对象上添加可能使用的SQL语句到批处理中。
  • 6 在创建的语句对象上使用executeBatch ()方法执行所有SQL语句。
  • 7 最后,使用commit ()方法提交所有更改。(可选)
  • 8 释放资源
// Create SQL statement
String SQL = "INSERT INTO Employees (id, first, last, age) VALUES(?, ?, ?, ?)";

// Create PrepareStatement object
PreparedStatemen pstmt = conn.prepareStatement(SQL);

//Set auto-commit to false
conn.setAutoCommit(false);

// Set the variables
pstmt.setInt( 1, 400 );
pstmt.setString( 2, "Pappu" );
pstmt.setString( 3, "Singh" );
pstmt.setInt( 4, 33 );
// Add it to the batch
pstmt.addBatch();

// Set the variables
pstmt.setInt( 1, 401 );
pstmt.setString( 2, "Pawan" );
pstmt.setString( 3, "Singh" );
pstmt.setInt( 4, 31 );
// Add it to the batch
pstmt.addBatch();

//add more batches

//Create an int[] to hold returned values
int[] count = stmt.executeBatch();

//Explicitly commit statements to apply changes
conn.commit();
(3)Statment批处理和PrepareStatment批处理区别:

​ <1>Statment批处理可以添加不同Sql语句,而PrepareStatment只能添加一种sql语句
​ <2>PrepareStatment效率比Statment高,而且更安全。

4.4 ResultSet

​ SELECT语句是从数据库中选择行并在结果集中查看行的标准方法。该java.sql.ResultSet中的接口表示结果集数据库查询。

​ ResultSet对象维护指向结果集中当前行的游标。术语“结果集”是指包含在ResultSet对象中的行和列数据。我们应该知道在cmd或者图形化界面下执行查询语句返回的是一个二维的数据表格。那么如果JDBC需要拿到查询结果数据,就必须遍历表格。JDBC给执行查询结果指定了一个返回结果集的类型ResultSet ,在ResultSet中有一个指向行的光标。光标默认的起始位置不是从实际数据开始,而是从表格的表头开始。我们可以通过结果集对象的next()方法进行行遍历,通过结果集对象的getString(i)【此处i表示具体的列数】或getString(“列名”)方法进行列遍历,从而获得查询结果集的具体内容。

ResultSet 对象具有指向其当前数据行的指针。最初,指针被置于第一行之前。指针可以方便我们对结果集进行遍历。默认的 ResultSet 对象不可更新,仅有一个向前移动的指针。因此,只能迭代它一次,并且只能按从第一行到最后一行的顺序进行。

如果没有指定任何ResultSet类型,将自动获得一个TYPE_FORWARD_ONLY。

类型描述
ResultSet.TYPE_FORWARD_ONLY光标只能在结果集中向前移动。
ResultSet.TYPE_SCROLL_INSENSITIVE光标可以向前和向后滚动,结果集对创建结果集后发生的数据库的其他更改不敏感。
ResultSet.TYPE_SCROLL_SENSITIVE。光标可以向前和向后滚动,结果集对创建结果集之后发生的其他数据库所做的更改敏感。
5.使用JDBC操作二进制数据

​ PreparedStatement对象可以使用输入和输出流来提供参数数据。这使您可以将整个文件放入可以保存大值的数据库列,例如Text和BLOB数据类型。

有以下方法可用于流式传输数据 -

  • setAsciiStream():此方法用于提供大的ASCII值。
  • setCharacterStream():此方法用于提供大型UNICODE值。
  • setBinaryStream():此方法用于提供较大的二进制值。

setXXXStream()方法除了参数占位符之外还需要额外的参数,文件大小。

考虑我们要将XML文件XML_Data.xml上传到数据库表中。这是XML文件的内容 -

案例:将文件放入数据库
<?xml version="1.0" encoding="UTF-8"?>
<Employee>
  <id>100</id>
  <first></first>
  <last>无极</last>
  <Salary>10000</Salary>
  <Dob>18-08-1978</Dob>
</Employee>
public class JDBCExample {
   // JDBC driver name and database URL
   static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";  
   static final String DB_URL = "jdbc:jdbc:mysql://localhost:3306/数据库名称?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone = GMT";

   //  Database credentials
   static final String USER = "username";
   static final String PASS = "password";
   
   public static void main(String[] args) {
   Connection conn = null;
   PreparedStatement pstmt = null;
   Statement stmt = null;
   ResultSet rs = null;
   try{
      // Register JDBC driver
      Class.forName("com.mysql.cj.jdbc.Driver");
      // Open a connection
      System.out.println("Connecting to database...");
      conn = DriverManager.getConnection(DB_URL,USER,PASS);

      //Create a Statement object and build table
      stmt = conn.createStatement();
      createXMLTable(stmt);

      //Open a FileInputStream
      File f = new File("XML_Data.xml");
      long fileLength = f.length();
      FileInputStream fis = new FileInputStream(f);

      //Create PreparedStatement and stream data
      String SQL = "INSERT INTO XML_Data VALUES (?,?)";
      pstmt = conn.prepareStatement(SQL);
      pstmt.setInt(1,100);
      pstmt.setAsciiStream(2,fis,(int)fileLength);
      pstmt.execute();

      //Close input stream
      fis.close();

      // Do a query to get the row
      SQL = "SELECT Data FROM XML_Data WHERE id=100";
      rs = stmt.executeQuery (SQL);
      // Get the first row
      if (rs.next ()){
         //Retrieve data from input stream
         InputStream xmlInputStream = rs.getAsciiStream (1);
         int c;
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
         while (( c = xmlInputStream.read ()) != -1)
            bos.write(c);
         //Print results
         System.out.println(bos.toString());
      }
      // Clean-up environment
      rs.close();
      stmt.close();
      pstmt.close();
      conn.close();
   }catch(SQLException se){
      //Handle errors for JDBC
      se.printStackTrace();
   }catch(Exception e){
      //Handle errors for Class.forName
      e.printStackTrace();
   }finally{
      //finally block used to close resources
      try{
         if(stmt!=null)
            stmt.close();
      }catch(SQLException se2){
      }// nothing we can do
      try{
         if(pstmt!=null)
            pstmt.close();
      }catch(SQLException se2){
      }// nothing we can do
      try{
         if(conn!=null)
            conn.close();
      }catch(SQLException se){
         se.printStackTrace();
      }//end finally try
   }//end try
   System.out.println("Goodbye!");
}//end main

public static void createXMLTable(Statement stmt) 
   throws SQLException{
   System.out.println("Creating XML_Data table..." );
   //Create SQL Statement
   String streamingDataSql = "CREATE TABLE XML_Data " +
                             "(id INTEGER, Data TEXT)";
   //Drop table first if it exists.
   try{
      stmt.executeUpdate("DROP TABLE IF EXISTS XML_Data");
      //Build table.
   	  stmt.executeUpdate(streamingDataSql);
   }catch(SQLException se){
   }// do nothing
  
}//end createXMLTable
}//end JDBCExample
案例:把图片放入数据库
/**
 * 把图片放入数据库
 * @author wgy
 *
 */
public class Demo4 {
	public static void main(String[] args) throws Exception{
		//write();
		read();
	}
	public static void write() throws Exception{
		Class.forName("com.mysql.cj.jdbc.Driver");
		String url="jdbc:jdbc:mysql://localhost:3306/数据库名称?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone = GMT";
		Connection conn=DriverManager.getConnection(url, "root", "root");
		PreparedStatement pstat=conn.prepareStatement("insert into bigdata2(id,img) values(?,?)");
		FileInputStream fis=new FileInputStream("图片所在路径");
		pstat.setInt(1, 1);
		pstat.setBinaryStream(2, fis);
		int count=pstat.executeUpdate();
		System.out.println(count);
		pstat.close();
		conn.close();
	}
	public static void read() throws Exception{
		Class.forName("com.mysql.cj.jdbc.Driver");
		String url="jdbc:jdbc:mysql://localhost:3306/数据库名称?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone = GMT";
		Connection conn=DriverManager.getConnection(url, "用户名", "密码");
		PreparedStatement pstat=conn.prepareStatement("select * from bigdata2 where id=1");
	
		ResultSet rs=pstat.executeQuery();
		if(rs.next()) {
			int id=rs.getInt("id");
			System.out.println(id);
			//处理图片
			InputStream is=rs.getBinaryStream("img");
			FileOutputStream fos=new FileOutputStream("存储图片的路径");
			byte[] buf=new byte[1024];
			int len=0;
			while((len=is.read(buf))!=-1) {
				fos.write(buf,0,len);
			}
			fos.close();
			is.close();
			
		}
		rs.close();
		pstat.close();
		conn.close();
		System.out.println("读取完成");
	}
}
6.数据库事务
6.1事务概述

​ 一组要么同时执行成功,要么同时失败的SQL语句。是数据库操作的一个不能分割执行单元。

​ 数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。

事务开始于

  • 连接到数据库上,并执行一条DML语句insert、update或delete
  • 前一个事务结束后,又输入了另一条DML语句

事务结束于

  • 执行commit或rollback语句。
  • 执行一条DDL语句,例如create table语句,在这种情况下,会自动执行commit语句。
  • 执行一条DDL语句,例如grant语句,在这种情况下,会自动执行commit。
  • 断开与数据库的连接
  • 执行了一条DML语句,该语句却失败了,在这种情况中,会为这个无效的DML语句执行rollback语句。
6.2 事务的四大特点

(ACID)

  • Atomicity(原子性)

表示一个事务内的所有操作是一个整体,要么全部成功,要么全部失败

  • Consistency(一致性)

表示一个事务内有一个操作失败时,所有的更改过的数据都必须回滚到修改前状态

  • Isolation(隔离性)

事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。

  • Durability(持久性)

持久性事务完成之后,它对于系统的影响是永久性的。

案例演示
CREATE TABLE account(
	id INT PRIMARY KEY,
	NAME VARCHAR(20) NOT NULL,
	money DOUBLE(10,2)
)
public static void main(String[] args) {
		Connection connection=null;
		PreparedStatement pstat1=null;
		PreparedStatement pstat2=null;
		
		//1注册驱动
		try {
			Class.forName("com.mysql.cj.jdbc.Driver");
			//2获取连接
			connection=DriverManager.getConnection("jdbc:jdbc:mysql://localhost:3306/数据库名称?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone = GMT");
			//3创建命令
			//3.1开启事务 ,设置事务自动提交为false
			connection.setAutoCommit(false);
			pstat1=connection.prepareStatement("update account set money=money-1000 where name='张莎强'");
			pstat1.executeUpdate();
			//int c=10/0;
			pstat2=connection.prepareStatement("update account set money=money+1000 where name='小苍'");
			pstat2.executeUpdate();
			
			System.out.println("转账成功...");
			//3.2提交事务
			connection.commit();
			
		} catch (Exception e) {
			System.out.println("出现异常");
			try {
				connection.rollback();//出现问题,要回滚(撤销事务做过的修改)
				connection.commit();//可加也不不加
			} catch (SQLException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		}finally {
			if(pstat1!=null){
				try {
					pstat1.close();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			if(pstat2!=null){
				try {
					pstat2.close();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			if(connection!=null){
				try {
					connection.close();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
Mysql支持的事务语句
#开启事务
START TRANSACTION;     # connection.setAutoCommit(false);
UPDATE account SET money=money-1000 WHERE id=1;
UPDATE account SET money=money+1000 WHERE id=2;
#提交事务
COMMIT;#connection.commit();
#回滚
ROLLBACK; #connection.rollback();
6.3事务隔离级别

SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。

Read Uncommitted(读取未提交内容)

​ 在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。

Read Committed(读取提交内容)

​ 这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别出现不可重复读(Nonrepeatable Read)问题,因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。

**Repeatable Read **可重读

​ 这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读(Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻读” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。

Serializable 可串行化
​ 这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。效率最低的。

​ 这四种隔离级别采取不同的锁类型来实现,若读取的是同一个数据的话,就容易发生问题。

例如:

​   脏读(Drity Read):某个事务已更新一份数据未提交前,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。

​ 演示案例:

#修改事务的隔离级别:
SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]
#查看事务隔离级别
SELECT @@tx_isolation;
#5.7.20版本后使用此语句查询
SELECT @@transaction_isolation;

​   不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。

​ 演示案例:

# A方 买家 本伟
SELECT @@transaction_isolation;
START TRANSACTION;
UPDATE account SET money=money-2000 WHERE id=1;
UPDATE account SET money=money+2000 WHERE id=2;
COMMIT;
ROLLBACK;

START TRANSACTION;
UPDATE account SET money=money+1000 WHERE id=2;
COMMIT;





# B方  卖家  郑帅
#(修改隔离级别)
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
#查看隔离级别
SELECT @@transaction_isolation;
SELECT *FROM account;
#发货
#修改隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT *FROM account;

#不可重复读

START TRANSACTION;
	SELECT SUM(money) FROM account;
	SELECT SUM(money) FROM account;
	SELECT SUM(money) FROM account;
COMMIT;	

#再次修改隔离级别

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;


START TRANSACTION;
	SELECT SUM(money) FROM account;
	SELECT SUM(money) FROM account;
	SELECT SUM(money) FROM account;
COMMIT;	


​   幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。但是InnoDB存储引擎通过多版本并发控制机制解决了该问题。

6.4 JDBC中事务应用

JDBC连接默认处于自动提交模式,则每个SQL语句在完成后都会提交到数据库。

事务使您能够控制是否和何时更改应用于数据库。它将单个SQL语句或一组SQL语句视为一个逻辑单元,如果任何语句失败,则整个事务将失败。

要启用手动事务支持,而不是JDBC驱动程序默认使用的自动提交模式,请使用Connection对象的setAutoCommit()方法。如果将boolean false传递给setAutoCommit(),则关闭自动提交。我们可以传递一个布尔值true来重新打开它。

(1) 事务的提交和回滚

​ 完成更改后,我们要提交更改,然后在连接对象上调用**commit()**方法,如下所示:

conn.commit( );

否则,要使用连接名为conn的数据库回滚更新,请使用以下代码 -

conn.rollback( );
try{
   //开启事务
   conn.setAutoCommit(false);
   Statement stmt = conn.createStatement();
   
   String SQL = "INSERT INTO Employees VALUES (106, 20, 'Rita', 'Tez')";
   stmt.executeUpdate(SQL);  
   //有可能出现异常
   String SQL = "INSERT IN Employees VALUES (107, 22, 'Sita', 'Singh')";
   stmt.executeUpdate(SQL);
   // 没有错误提交
   conn.commit();
}catch(SQLException se){
   //出现错误回滚
   conn.rollback();
   conn.commit();
}
(2) Savepoint

​ 新的JDBC 3.0 Savepoint接口为您提供了额外的事务控制。

​ 设置保存点时,可以在事务中定义逻辑回滚点。如果通过保存点发生错误,则可以使用回滚方法来撤消所有更改或仅保存在保存点之后所做的更改。

​ Connection对象有两种新的方法来帮助管理保存点 -

  • setSavepoint(String savepointName):定义新的保存点。它还返回一个Savepoint对象。
  • releaseSavepoint(Savepoint savepointName):删除保存点。请注意,它需要一个Savepoint对象作为参数。此对象通常是由setSavepoint()方法生成的保存点。
public static void main(String[] args) {
		Connection conn = null;
		Statement stmt = null;
		Savepoint savepoint1 = null;
		try {
			Class.forName("com.mysql.cj.jdbc.Driver");
			conn = DriverManager.getConnection("jdbc:jdbc:mysql://localhost:3306/数据库名称?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone = GMT", "用户名", "密码");
			// Assume a valid connection object conn
			conn.setAutoCommit(false);
			stmt = conn.createStatement();

			// set a Savepoint
			String SQL = "INSERT INTO Employees VALUES (106, 20, 'Rita', 'Tez');";
			stmt.executeUpdate(SQL);
			savepoint1 = conn.setSavepoint("Savepoint1");
			// Submit a malformed SQL statement that breaks
			SQL = "INSERT IN Employees VALUES (107, 22, 'Sita', 'Tez')";
			stmt.executeUpdate(SQL);
			// If there is no error, commit the changes.
			conn.commit();
			System.out.println("执行成功");

		} catch (Exception se) {
			// If there is any error.
			try {
				se.printStackTrace();
				conn.rollback(savepoint1);
				conn.commit();
				System.out.println("回滚");
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值