JDBC批处理插入更新MySQL Oracle

时间:2020-02-23 14:35:18  来源:igfitidea点击:

今天,我们将研究MySQL和MySQL数据库中的JDBC Batch插入和更新示例。
有时我们需要对数据库运行类似类型的批量查询。
例如,将数据从CSV文件加载到关系数据库表。

众所周知,我们可以选择使用Statement或者PreparedStatement执行查询。
除此之外,JDBC提供了批处理功能,通过该功能,我们可以一次性完成数据库的大量查询。

JDBC批处理

JDBC批处理语句通过Statement和PreparedStatement的addBatch()和executeBatch()方法进行处理。
本教程旨在提供有关MySQL和Oracle数据库的JDBC Batch插入示例的详细信息。

我们将研究不同的程序,因此我们有一个结构如下图的项目。

注意,我在项目构建路径中有MySQL和Oracle DB JDBC Driver jar,以便我们可以在MySQL和Oracle DB两者上运行应用程序。

首先,为我们的测试程序创建一个简单的表格。
我们将运行大量的JDBC插入查询,并使用不同的方法查看性能。

--Oracle DB
CREATE TABLE Employee (
empId NUMBER NOT NULL,
name varchar2(10) DEFAULT NULL,
PRIMARY KEY (empId)
);

--MySQL DB
CREATE TABLE `Employee` (
`empId` int(10) unsigned NOT NULL,
`name` varchar(10) DEFAULT NULL,
PRIMARY KEY (`empId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

我们将从属性文件中读取数据库配置详细信息,以便从一个数据库切换到另一个数据库是快速而简单的。

db.properties

#mysql DB properties
DB_DRIVER_CLASS=com.mysql.jdbc.Driver
DB_URL=jdbc:mysql://localhost:3306/UserDB
#DB_URL=jdbc:mysql://localhost:3306/UserDB?rewriteBatchedStatements=true
DB_USERNAME=hyman
DB_PASSWORD=hyman123

#Oracle DB Properties
#DB_DRIVER_CLASS=oracle.jdbc.driver.OracleDriver
#DB_URL=jdbc:oracle:thin:@localhost:1871:UserDB
#DB_USERNAME=scott
#DB_PASSWORD=tiger

在进入实际的JDBC批处理插入示例以将批量数据插入Employee表之前,让我们编写一个简单的实用程序类来获取数据库连接。

DBConnection.java

package com.theitroad.jdbc.batch;

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

public class DBConnection {

	public static Connection getConnection() {
		Properties props = new Properties();
		FileInputStream fis = null;
		Connection con = null;
		try {
			fis = new FileInputStream("db.properties");
			props.load(fis);

			//load the Driver Class
			Class.forName(props.getProperty("DB_DRIVER_CLASS"));

			//create the connection now
			con = DriverManager.getConnection(props.getProperty("DB_URL"),
					props.getProperty("DB_USERNAME"),
					props.getProperty("DB_PASSWORD"));
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return con;
	}
}

现在,让我们看一下可以为JDBC批处理插入示例采用的不同方法。

  • 使用Statement一次执行一个查询。

  • 使用PreparedStatement一次执行一个查询。
    JDBCPreparedStatement.java
    这种方法类似于使用Statement,但是PreparedStatement提供了性能上的好处,并且避免了SQL注入攻击。

  • 使用Statement Batch API进行批量处理。
    JDBCStatementBatch.java
    我们正在处理10,000条记录,批量为1000条记录。
    一旦达到批量大小,我们将执行它并继续处理剩余的查询。

  • 使用PreparedStatement Batch Processing API进行批量查询。
    JDBCPreparedStatementBatch.java

让我们看看我们的程序如何与MySQL数据库一起使用,我已经分别执行了多次,下表包含结果。

MySQL DBStatementPreparedStatementStatement BatchPreparedStatement Batch
Time Taken (ms)8256813071297019

当我查看响应时间时,我不确定是否正确,因为我期望批处理可以使响应时间有所改善。
因此,我在网上寻找了一些解释,并发现默认情况下,MySQL批处理的工作方式与无批处理类似。

为了获得MySQL中批处理的实际好处,我们在创建数据库连接时需要将rewriteBatchedStatements传递为TRUE。
在db.properties文件中查看上面的MySQL URL。

将" rewriteBatchedStatements"设置为" true"时,下表提供了相同程序的响应时间。

MySQL DBStatementPreparedStatementStatement BatchPreparedStatement Batch
Time Taken (ms)567655703716394

如您所见,rewriteBatchedStatements为true时,PreparedStatement批处理非常快。
因此,如果涉及大量批处理,则应使用此功能来加快处理速度。

Oracle批处理插入

当我针对Oracle数据库执行上述程序时,结果与MySQL处理结果一致,并且PreparedStatement Batch处理比任何其他方法都快得多。

JDBC批处理异常

让我们看看其中一个查询抛出异常的情况下批处理程序的行为。

JDBCBatchExceptions.java

package com.theitroad.jdbc.batch;

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

public class JDBCStatement {

	public static void main(String[] args) {
		
		Connection con = null;
		Statement stmt = null;
		
		try {
			con = DBConnection.getConnection();
			stmt = con.createStatement();
			
			long start = System.currentTimeMillis();
			for(int i =0; i<10000;i++){
				String query = "insert into Employee values ("+i+",'Name"+i+"')";
				stmt.execute(query);
			}
			System.out.println("Time Taken="+(System.currentTimeMillis()-start));
			
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			try {
				stmt.close();
				con.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}

}

当我为MySQL数据库执行上述程序时,出现以下异常,并且没有任何记录插入表中。

package com.theitroad.jdbc.batch;

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

public class JDBCPreparedStatement {

	public static void main(String[] args) {
		
		Connection con = null;
		PreparedStatement ps = null;
		String query = "insert into Employee (empId, name) values (?,?)";
		try {
			con = DBConnection.getConnection();
			ps = con.prepareStatement(query);
			
			long start = System.currentTimeMillis();
			for(int i =0; i<10000;i++){
				ps.setInt(1, i);
				ps.setString(2, "Name"+i);
				ps.executeUpdate();
			}
			System.out.println("Time Taken="+(System.currentTimeMillis()-start));
			
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			try {
				ps.close();
				con.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}

}

当对Oracle数据库执行相同的程序时,出现以下异常。

package com.theitroad.jdbc.batch;

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

public class JDBCStatementBatch {

	public static void main(String[] args) {
		
		Connection con = null;
		Statement stmt = null;
		
		try {
			con = DBConnection.getConnection();
			stmt = con.createStatement();
			
			long start = System.currentTimeMillis();
			for(int i =0; i<10000;i++){
				String query = "insert into Employee values ("+i+",'Name"+i+"')";
				stmt.addBatch(query);
				
				//execute and commit batch of 1000 queries
				if(i%1000 ==0) stmt.executeBatch();
			}
			//commit remaining queries in the batch
			stmt.executeBatch();
			
			System.out.println("Time Taken="+(System.currentTimeMillis()-start));
			
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			try {
				stmt.close();
				con.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}

}

但是异常之前的行已成功插入数据库。
尽管该异常清楚地说明了错误的根源,但它并没有告诉我们是哪个查询引起了问题。
因此,或者在添加数据进行批处理之前验证数据,或者使用JDBC事务管理以确保在发生异常的情况下插入所有记录或者不插入任何记录。

带有JDBC事务管理的相同程序如下所示。

JDBCBatchExceptions.java

package com.theitroad.jdbc.batch;

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

public class JDBCPreparedStatementBatch {

	public static void main(String[] args) {
		
		Connection con = null;
		PreparedStatement ps = null;
		String query = "insert into Employee (empId, name) values (?,?)";
		try {
			con = DBConnection.getConnection();
			ps = con.prepareStatement(query);
			
			long start = System.currentTimeMillis();
			for(int i =0; i<10000;i++){
				ps.setInt(1, i);
				ps.setString(2, "Name"+i);
				
				ps.addBatch();
				
				if(i%1000 == 0) ps.executeBatch();
			}
			ps.executeBatch();
			
			System.out.println("Time Taken="+(System.currentTimeMillis()-start));
			
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			try {
				ps.close();
				con.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}

}

如您所见,如果出现任何SQL异常,我将回滚事务。
如果批处理成功,那么我将明确提交事务。