JDBC(Java DataBase Connectivity)

数据持久化: 数据持久化就是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称。数据模型可以是任何数据结构或对象模型,存储模型可以是关系模型、xml、二进制流等。数据持久化意味着将内存中的数据保存到硬盘加以“固化”。

好处:

使用数据持久化有以下好处:

  1. 程序代码重用性强,即使更换数据库,只需要更改配置文件,不必重写程序代码。
  2. 业务逻辑代码可读性强,在代码中不会有大量的SQL语言,提高程序的可读性。
  3. 持久化技术可以自动优化,以减少对数据库的访问量,提高程序运行效率。

数据持久化对象的基本操作有:保存、更新、删除、查询等。

在Java中,数据库存储技术可分为以下几类

  1. JDBC(Java DataBase Connectivity)直接访问数据库
  2. JDO技术
  3. 第三方O/R工具,如:hibernate、ibatis(mybatis)等

JDBC是Java访问数据库的基石,JDO、Hibernate、ibatis等只是更好的封装了JDBC。它独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),可以连接任何提供了JDBC驱动程序的数据库系统,为访问不同的数据库提供了一种统一的途径。

JDBC接口(API)包含两个层次:

  1. 面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)
  2. 面向数据库的API:Java Driver API,供应开发商开发数据库驱动程序用

JDBC驱动程序:各个数据库厂商根据JDBC的规范制作的JDBC实现类的类库

JDBC驱动程序的类型:

  1. JDBC-ODBC桥
  2. 部分本地API部分Java的驱动程序
  3. JDBC网络存Java驱动程序
  4. 本地协议的纯Java驱动程序

本地协议的纯Java驱动程序(重要)

这种类型的驱动程序完全使用Java编写,通过与数据库建立Socket连接,采用具体与厂商的网络协议把JDBC调用转换为直接连接的网络调用

Driver接口Java.sql.Driver接口是所有JDBC驱动程序需要实现的接口。这个接口是提供数据库厂商使用的,不同的数据库厂商提供不同的实现。在程序中不需要直接去访问实现了Driver接口的类,而是由驱动程序管理器类(java.sql.DriverManager)去调用这些Driver实现。

DriverManager接口:DriverManager是驱动的管理类。它可以通过重载getConnection()的方法获取数据库连接,较为方便;还可以同时管理多个驱动程序:若注册了多个数据库连接,则调用 getconnection()方法时传入不同的参数,即返回不同的数据库连接

操作数据库——步骤:

  1. 注冊驱动 (仅仅做一次)
  2. 建立连接(Connection)
  3. 创建运行SQL的语句(Statement)
  4. 运行语句
  5. 处理运行结果(ResultSet)
  6. 释放资源
package com.sardine.jdbc;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.Properties;
import org.junit.jupiter.api.Test;

class JDBCUtil {

	/***
	 * Driver简单连接
	 * @throws SQLException
	 */
	@Test
	public void test() throws SQLException {
		// 1、准备连接数据库的字符串
		String url = "jdbc:mysql://127.0.0.1:3306/user";
		String user = "";
		String password = "";
		String driverClass = "com.mysql.jdbc.Driver";
		// 2.创建Driver实现类对象
		Driver driver = new com.mysql.jdbc.Driver();
		// 此处可以用反射机制创建,该方法充分解耦
		//Driver driver = (Driver)Class.forName(driverClass).newInstance();
		Properties info = new Properties();
		info.put("user", user);
		info.put("password", password);
		// 3.调用Driver的connect接口,连接数据库
		Connection connection = driver.connect(url, info);
		System.out.println(connection);
		// 4.关闭连接
		connection.close();
	}

}
package com.sardine.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import org.junit.jupiter.api.Test;

class JDBCUtil {

	/***
	 * DriverManager连接
	 * @throws SQLException
	 */
	@Test
	public void test() throws Exception{
		// 1、准备连接数据库的字符串
		String url = "jdbc:mysql://127.0.0.1:3306/user";
		String user = "";
		String password = "";
		String driverClass = "com.mysql.jdbc.Driver";
		// 2、加载数据库驱动程序(注册驱动 对应的Driver实现类中有注册驱动的静态代码块)
		/***
		  * 实际上注册驱动时,应写成 DriverManager.registerDriver((Driver)Class.forName(driverClass).newInstance());
		  * 但是 java.mysql.jdbc 已经在静态代码块中封装了注册方法:java.sql.DriverManager.registerDriver(new Driver());
		  * 所以只需要加载驱动程序加载即可
		 */
		Class.forName(driverClass);
		// 3、调用DriverManager的getConnection接口,连接数据库
		Connection connection = DriverManager.getConnection(url, user, password);
		System.out.println(connection);
		// 4.关闭连接
		connection.close();
	}
}

注册驱动方式:

  1.  Class.forName(“com.mysql.jdbc.Driver”);(推荐这样的方式,不会对详细的驱动类产生依赖)
  2. DriverManager.registerDriver(com.mysql.jdbc.Driver);(会对详细的驱动类产生依赖)
  3. System.setProperty(“jdbc.drivers”, “driver1:driver2”);(尽管不会对详细的驱动类产生依赖;但注冊不太方便。所以非常少使用)

建立连接(Connection):

通过Connection建立连接,Connection是一个接口类。其功能是与数据库进行连接(会话)。

几种常用数据库的JDBC url:

  1. mysql:jdbc:mysql//localhost:3306/user
  2. oracle:jdbc:oracle:thin:@localhost:1521:user
  3. SQLServer:jdbc:microsoft:sqlserver//localhost:1433;DataBaseName=user

创建运行SQL的语句(Statement):

通过Connection的createStantement()方法获取Statement对象

executeQuery(Stringsql),该方法用于运行实现查询功能的sql语句。返回类型为ResultSet(结果集)。

executeUpdate(Stringsql),该方法用于运行实现增、删、改功能的sql语句,返回类型为int,即受影响的行数。

ResultSet对象:

ResultSet:结果集,封装了使用JDBC进行查询的结果集。

ResultSet返回的实际就是一张数据表,有一个指针指向数据表的第一行前面。可以调用next()方法检测下一行是否有效,若有效方法返回true,且指针下移。相当于Iterator对象的hashNext()和next()结合体。

当指针定位到一行时,可以通过调用getXxx(columnIndex)或getXxx(columnLabel)获取每一列的值,列索引从1开始。例如:getInt(1)、getString("name")。

Statement接口类还派生出两个接口类PreparedStatementCallableStatement,这两个接口类对象为我们提供了更加强大的数据访问功能。

PreparedStatement:能够对SQL语句进行预编译,这样防止了   SQL注入 提高了安全性。预编译结果能够存储在PreparedStatement对象中。当多次运行SQL语句时能够提高效率。作为Statement的子类,PreparedStatement继承了Statement的全部函数。

	PreparedStatement ps = connection.prepareStatement( "select * from user where id = ? and name = ?");
	ps.setInt(1, 1);
	ps.setString(2, "集团管理员");
	ResultSet set = ps.executeQuery();
	while(set.next()) {
		System.out.println(set.getString("name"));
	}

CallableStatement:CallableStatement类继承了PreparedStatement类,他主要用于运行SQL存储过程。在JDBC中运行SQL存储过程须要转义。

对数据库中存储过程的调用是CallableStatement对象所含的内容。有两种形式:1:形式带结果参数;2:形式不带结果参数。结果参数是一种输出参数(存储过程中的输出OUT参数),是存储过程的返回值。两种形式都有带有数量可变的输入、输出、输入和输出的参数。用问号做占位符。

  1. 形式带结果参数语法格式:{ ? = call 存储过程名[(?, ?, ?, ...)]};
  2. 形式不带结果参数语法格式:{ call 存储过程名[(?, ?, ?, ...)]};PS方括号里面的内容可有可无。

CallableStatement接口中常用的方法。

1:getInt(int parameterIndex)、getInt(String parameterName)、还有getString、getBigDecimal、getString、getDate、getURL等等都类似和PreparedStatement与Statement中的用法类似。

2:registerOutParameter(int parameterIndex, int sqlType):按顺序位置parameterIndex将OUT参数注册为JDBC类型sqlType。

3:wasNull():查询最后一个读取的OUT参数是否为SQL Null。

	Class.forName(driverClass);
	connection = DriverManager.getConnection(url, user, password);
			
	/***
	 * 调用无参存储过程
	  * 例sql:create procedure findAll_user() select * from user;
	*/
	// 通过存储过程名称连接
	CallableStatement callableStatement1 = connection.prepareCall("{call sp_name()}");
	set = callableStatement1.executeQuery();
	while (set.next()) {
		System.out.println(set.getString("name"));
	}
			
	/***
	 * 带参存储过程
	 * 例sql:create procedure insert_user(in myid int, in myname varchar(50)) insert into user(id,name) values(myid, myname);
	*/
	// 通过存储过程名称连接
	CallableStatement callableStatement2 = connection.prepareCall("{call insert_user(?,?)}");
	callableStatement2.setInt(1, 1002);
	callableStatement2.setString(2, "测试数据名称");
	int state = callableStatement2.executeUpdate();
	System.out.println(state);
			
	/***
	 *创建有输入输出参数的存储过程
	 * 例sql:create procedure getNameById(in cid int, out return_name varchar(50)) select name into return_name from user where id = cid;
	 * 注意:1、有输出变量的时候,要给输出变量进行注册  2、创建存储过程时,需要用into指定返回值,类似于sql中as
	*/
	// 通过存储过程名称连接
	CallableStatement callableStatement3 = connection.prepareCall("{call getNameById(?,?)}");
	callableStatement3.setInt(1, 1000);
	// 注册输出变量
	callableStatement3.registerOutParameter(2, Types.CHAR);
	boolean b = callableStatement3.execute();
	System.out.println(b);
	String name = callableStatement3.getString(2);
	System.out.println(name);

释放资源:

数据库资源不关闭,其占用的内存不会被释放,徒耗资源,影响系统。

注意:Connection、Statement都是应用程序和数据库服务器的连接资源,使用后一定要关闭。特别注意在异常时,要在finally语句块中关闭Connection和Statement对象。关闭顺序,先关闭后获取的,即先关闭Statement,在关闭Connection。

JDBC处理元数据

DatabaseMetaData:是描述数据库的元数据对象。可以从这个对象获得有关数据库管理系统的各种信息,包括数据库中的各个表,表中的各个列,数据类型,触发器,存储过程等各方面的信息。

ResultSetMetaData:是描述结果集的元数据对象。可以知道SQL语句中查询了哪些列,以及列的别名、值等基本信息。

JDBC利用ResultSetMetaData元数据反射编写通用的查询方法

  1. 利用SQL进行查询得到结果集(注意:返回的字段需与Bean字段相同,可以用 as 别名)
  2. 得到ResultSetMetaData对象
  3. 反射到实体Bean对象
package com.sardine.jdbc;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.junit.jupiter.api.Test;

class JDBCUtil {

	@Test
	public void test() {
		String studentSql = "select id,name,sex,birthday from student where id = ?";
		Student student = getBean(Student.class, studentSql, 1);
		System.out.println(student.getId());
		System.out.println(student.getName());
		System.out.println(student.getSex());
		System.out.println(student.getBirthday());
		
		String bookSql = "select id,book_name bookName,author,price from book where id = ?";
		Book book = getBean(Book.class, bookSql, 1);
		System.out.println(book.getId());
		System.out.println(book.getBookName());
		System.out.println(book.getAuthor());
		System.out.println(book.getPrice());
	}
	
	private <T> T getBean(Class<T> clazz, String sql, Object ... args){

		T entity = null;
		
		Connection connection = null;
		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;
		try {
			// 1.连接数据库资源
			connection = getConnection();
			
			// 2.获取 ResultSet 对象
			preparedStatement = connection.prepareStatement(sql);
			
			// 3.添加参数
			if(null != args) {
				for(int i = 0; i < args.length; i++) {
					preparedStatement.setObject(i + 1, args[i]);
				}
			}
			resultSet = preparedStatement.executeQuery();
			
			// 4.创建 ResultSetMetaData 对象 
			ResultSetMetaData rsmd = preparedStatement.getMetaData();
			
			Map<String, Object> map = new HashMap<String, Object>();
			while(resultSet.next()) {
				for(int i = 0; i < rsmd.getColumnCount(); i++) {
					/***
					 *  此处区分获取字段和字段值所用的对象不是同一个
					 */
					
					// 获取 sql 字段名称
					String columnLable = rsmd.getColumnLabel(i + 1);
					// 获取 sql 字段名称对应值
					Object columnValue = resultSet.getString(columnLable);
					map.put(columnLable, columnValue);
				}
			}
			
			// 5.若 Map 不为空集, 利用反射创建 clazz 对应的对象
			if(null != map){
				entity = clazz.newInstance();
				//6. 遍历 Map 对象, 利用反射为 Class 对象的对应的属性赋值.(也可以使用BeanUtils工具类) 
				for(Map.Entry<String, Object> entry: map.entrySet()){
					String fieldName = entry.getKey();
					Object value = entry.getValue();
					// 带条件id查询,每次只存在1条数据
					setFieldValue(entity, fieldName, value);
				}
			}
		}catch (Exception e) {
			e.printStackTrace();
		}finally {
			// 释放资源
			releaseDB(resultSet, preparedStatement, connection);
		}
		return entity;
	}
	
	/***
	 * 获取数据库连接
	 * @return
	 * @throws Exception
	 */
	private Connection getConnection() throws Exception {
		String url = "jdbc:mysql://127.0.0.1:3306/localhost_database";
		String user = "";
		String password = "";
		String driverClass = "com.mysql.jdbc.Driver";
		Class.forName(driverClass);
		Connection connection = DriverManager.getConnection(url, user, password);
		return connection;
	}
	
	/***
	 * 释放资源
	 * @param resultSet
	 * @param statement
	 * @param connection
	 */
	private void releaseDB(ResultSet resultSet, Statement statement, Connection connection) {
		if(null != resultSet){
			try {
				resultSet.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		if(null != statement){
			try {
				statement.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		if(null != connection){
			try {
				connection.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}

	/******************************************************************************/
	
	/**
	 * 循环向上转型, 获取对象的 DeclaredField
	 * @param object
	 * @param filedName
	 * @return
	 */
	private Field getDeclaredField(Object object, String filedName){
		
		for(Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()){
			try {
				return superClass.getDeclaredField(filedName);
			} catch (NoSuchFieldException e) {
				//Field 不在当前类定义, 继续向上转型
			}
		}
		return null;
	}
	
	/**
	 * 使 filed 变为可访问
	 * @param field
	 */
	private void makeAccessible(Field field){
		if(!Modifier.isPublic(field.getModifiers())){
			field.setAccessible(true);
		}
	}
	
	/***
	 * 设置对象属性值
	 * @param object
	 * @param fieldName
	 * @param value
	 */
	private void setFieldValue(Object object, String fieldName, Object value){
		Field field = getDeclaredField(object,fieldName);
		if (field == null)
			throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
		makeAccessible(field);
		try {
			// 需注意,反射时编译器不会进行自动装/拆箱
			if(field.getType() == Long.class) {
				field.set(object, Long.valueOf(value.toString()));
			}
			if(field.getType() == Integer.class) {
				field.set(object, Integer.valueOf(value.toString()));
			}
			if(field.getType() == Date.class) {
				field.set(object, new Date());
			}
			if(field.getType() == String.class) {
				field.set(object, value.toString());
			}
			if(field.getType() == BigDecimal.class) {
				field.set(object, new BigDecimal(value.toString()));
			}
		} catch (IllegalAccessException e) {
			e.getMessage();
		}
	}
	
}

JDBC获取插入记录的主键

// prepareStatement的第二个参数为 autoGeneratedKeys,返回主键
preparedStatement = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
// 必须执行,才可以获取key
preparedStatement.executeUpdate();
// 通过getGeneratedKeys()方法获取包含了新生成的主键值
// 如果使用getGeneratedKeys()方法,那么resultSet中只有一列 GENERATED_KEY,用于存放新生成的主键值
resultSet = preparedStatement.getGeneratedKeys();

JDBC中Date类型数据

创建Date型数据:new java.sql.Date(new java.util.Date().getTime())

获取Date型数据:resultSet.getDate();

JDBC查询LOB(Large Objects-大对象,用来存储大量的二进制和文本数据的一种数据类型)

  1. Oracle
    1. BOLO(二进制数据,适合用于存储大量的二进制数据,如图像,视频,音频,文件等)
    2. CLOB(单字节字符数据,用于存储超长的文本数据)
    3. NCLOB (多字节字符数据,用于存储超长的文本数据)
  2. Mysql
    1. TinyBlob(二进制大型对象,最大255)
    2. Blob(二进制大型对象,最大65k)
    3. MediumBlob(二进制大型对象,最大16M)
    4. LongBlob(二进制大型对象,最大4G)
	/***
	 * bolb insert和update
	 */
	@Test
	public void test() {
		Connection connection = null;
		PreparedStatement insertPreparedStatement = null;
		PreparedStatement selectPreparedStatement = null;
		ResultSet insertResultSet = null;
		ResultSet selecttResultSet = null;
		try {
			// 1.连接数据库资源
			connection = getConnection();
			
			// 插入BLOB类型的数据必须使用 PreparedStatement:因为BOLB类型数据无法使用字符串拼接
			String insertSql = "insert into student(id,name,sex,birthday,picture) values(?,?,?,?,?)";	
			// prepareStatement的第二个参数为 autoGeneratedKeys,返回主键
			insertPreparedStatement = connection.prepareStatement(insertSql,Statement.RETURN_GENERATED_KEYS);
			insertPreparedStatement.setObject(1, 1);
			insertPreparedStatement.setObject(2, "the_sardine");
			insertPreparedStatement.setObject(3, 1);
			// 时间类型数据格式
			insertPreparedStatement.setObject(4, new java.sql.Date(new java.util.Date().getTime()));
			// 将数据转换为InputStream流对象存入
			InputStream in = new FileInputStream("C:\\Users\\极光.jpg");
			insertPreparedStatement.setObject(5, in);
			insertPreparedStatement.executeUpdate();
			insertResultSet = insertPreparedStatement.getGeneratedKeys();
			int id = 0;
			while (insertResultSet.next()) {
				id = insertResultSet.getInt(1);
			}
			
			String selectSql = "select id,name,sex,birthday,picture from student where id = ?";
			selectPreparedStatement = connection.prepareStatement(selectSql);
			selectPreparedStatement.setObject(1, 1);
			selecttResultSet = selectPreparedStatement.executeQuery();
			while(selecttResultSet.next()) {
				// 获取id
				int s_id = selecttResultSet.getInt(1);
				// 获取name
				String s_name = selecttResultSet.getString(2);
				// 获取性别
				int s_sex = selecttResultSet.getInt(3);
				// 获取生日
				java.sql.Date s_birthday = selecttResultSet.getDate(4);
				System.out.println(s_id);
				System.out.println(s_name);
				System.out.println(s_sex);
				System.out.println(s_birthday);
				// 调用 Blob 的 getBinaryStream()方法得到输入流,在使用IO操作即可。
				Blob s_picture = selecttResultSet.getBlob(5);
				InputStream sin = s_picture.getBinaryStream();
				OutputStream out = new FileOutputStream("C:\\Users\\极光1.jpg");
				byte[] buffer = new byte[1024];
				int len = 0;
				while((len = sin.read(buffer)) != -1) {
					out.write(buffer, 0, len);
				}
				out.close();
				sin.close();
			}
			
		}catch (Exception e) {
			e.getMessage();
		}finally {
			// 释放资源
			releaseDB(resultSet, preparedStatement, connection);
		}
	}

SQL注入攻击:利用某些系统没有对用户输入的数据进行充分检查,而在用户输入数据中注入非法的SQL语句段或命令,从而利用系统的SQL引擎完成恶意行为的做法。对于Java而言,要防范SQL注入,只要用PreparedStatement取代Statement即可。

例:

String url = "SELECT * from user where username = "+ username +" AND password =" + password;

-- username = "A' OR password ="   
-- password = "OR '1' = '1'";
-- 这样 sql变为 SELECT * from user where username = 'A' OR password = 'AND PASSWORD = ' OR '1' = '1'

-- 但是使用 PreparedStatement 时,?不会直接赋值,所以就防止了SQL注入
-- SELECT * from user where username = ? AND password = ?

JDBC事务管理

具体步骤:

  1. 开始事务:取消Connection的默认提交行为
  2. 如果事务的操作都成功,则提交事务:connection.commit();
  3. 回滚事务:若出现异常,则在catch块中回滚事务,connection.rollback();

注意:多个操作,每个操作使用的是自己的单独连接,则无法保证事务

对于同时运行的多个事务,当这些事务访问数据库中相同数据时,如果没有采取必要的隔离机制,就会导致各种并发问题:

  1. 脏读:对于两个事务T1、T2,T1读取了已经被T2更新但还没有被提交的字段之后,若T2回滚。T1读取的内容就是临时且无效的(必须要避免的)。
  2. 不可重复读:对于两个事务T1、T2,T1读取了一个字段,然后T2更新了改字段之后,T1在次读取同一字段,值就不同了。
  3. 幻读:对于两个事务T1、T2,T1从一个表中读取了一个字段,然后T2在该表中插入了一些新的行之后,如果T1在次读取同一个表,就会多出几行。

数据库提供4中隔离级别:

  1. READ UNCOMMITTTED(读取提交数据):允许事务读取未被其他事务提交的变更。脏读、不可重复的和幻读都会出现。
  2. READ COMMITED(读已提交数据):只允许事务读取已经被其他事务提交的变更。可以避免脏读,但不可重复读和幻读问题依然存在。
  3. REPEATABLE READ(可重复读):确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读。但幻读问题仍然存在。
  4. SERIALIZABLE(串行化):确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,所有并发问题都可以避免,但性能十分低下。

Oracle 支持2种事务级别:READ COMMITED、SERIALIZABLE。Oracle默认事务隔离级别为:READ COMMITED。

Mysql 支持4种事务级别,默认的事务级别为:REPEATABLE READ。

Mysql中相关操作:

  1. Mysql 查看事物隔离级别的sql:select @@tx_isolation;
  2. Mysql 设置隔离级别的sql:set tx_isolation='事物隔离级别名称';
  3. Mysql 设置当前连接的隔离级别:set transaction isolation level '事物隔离级别名称';
  4. Mysql 设置数据库系统的全局隔离级别:set global transaction  isolation level '事物隔离级别名称';
  5. 在JDBC中,可以通过 connection.setTransactionIsolation(int level),来设置事务级别。在调用Connection对象的setAutoCommit(false)方法之前。

JDBC批处理

Statement < PreparedStatement(预编译)

JDBC的批处理语句包括下面两个方法:

  1. addBatch(String):需要添加批量处理的SQL语句或是参数
  2. executeBath():执行批量处理命令
  3. clearBatch():清除批处理命令

两种批量执行SQL语句的情况:

  1. 多条SQL语句的批处理
  2. 一条SQL语句的批量传参
connection = getConnection();
statement = connection.createStatement();
String sql1 = "insert into user(id,name) values(1,'zhangsan')";
String sql2 = "insert into user(id,name) values(2,'lisi')";
String sql3 = "insert into user(id,name) values(3,'wangwu')";
String sql4 = "insert into user(id,name) values(4,'xiaoming')";
String sql5 = "update user set name='gacl' where id=1";
String sql6 = "insert into user(id,name) values(5,'adai')";
String sql7 = "delete from user where id=2";
statement.addBatch(sql1);
statement.addBatch(sql2);
statement.addBatch(sql3);
statement.addBatch(sql4);
statement.addBatch(sql5);
statement.addBatch(sql6);
statement.addBatch(sql7);
//执行批处理SQL语句
statement.executeBatch();
//清除批处理命令
statement.clearBatch();

 Statement.addBatch(sql)方式实现批处理的优缺点:

  • 优点:可以向数据库发送多条不同的sql语句。
  • 缺点:SQL语句没有预编译。
connection = getConnection();
String sql = "insert into user(id,name) values(?,?)";
preparedStatement = connection.prepareStatement(sql);
for(int i = 0; i < 1000000; i++){ 
	preparedStatement.setInt(1, i + 1);
	preparedStatement.setString(2, "name_" + i);
	// “积攒”sql( preparedStatement 会自动预编译sql,所以此处无须填写sql)
	preparedStatement.addBatch();
	// 当积攒到一定程度,统一执行,并清空之前积攒的sql
	if((i + 1) % 300 == 0){
		preparedStatement.executeBatch();
		preparedStatement.clearBatch();
	}
}
// 若总条数不是批量数值的整倍数,则还需要在额外执行异常
if(1000000 % 300 != 0){
	preparedStatement.executeBatch();
	preparedStatement.clearBatch();
}

PreparedStatement.addBatch()方式实现批处理的优缺点:

  • 优点:发送的是预编译后的SQL语句,执行效率高。
  • 缺点:只能应用在SQL语句相同,但参数不同的批处理中。因此此种形式的批处理经常用于在同一个表中批量插入数据,或批量更新表的数据。

JDBC数据库连接池

JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:

  1. DBCP 数据库连接池
  2. C3P0 数据库连接池

DBCP 数据源

 DBCP 是 Apache 软件基金组织下的开源连接池实现,该连接池依赖该组织下的另一个开源系统:Common-pool.如需使用该连接池实现,应在系统中增加如下两个 jar文件:

  1. Commons-dbcp.jar:连接池的实现
  2. Commons-pool.jar:连接池实现的依赖库
// 1、创建DBCP数据源实例
final BasicDataSource dataSource = new BasicDataSource();

// 2、为数据源实例指定必须的属性
dataSource.setUsername("root");
dataSource.setPassword("");
dataSource.setUrl("jdbc:mysql:///jdbc?useSSL=false");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");

// 3、指定数据源的一些可选的属性.
// 1). 指定数据库连接池中初始化连接数的个数
dataSource.setInitialSize(5);
// 2). 指定最大的连接数: 同一时刻可以同时向数据库申请的连接数
dataSource.setMaxActive(5);
// 3). 指定小连接数: 在数据库连接池中保存的最少的空闲连接的数量
dataSource.setMinIdle(2);
// 4).等待数据库连接池分配连接的最长时间. 单位为毫秒. 超出该时间将抛出异常.
dataSource.setMaxWait(1000 * 5);

// 4、从数据源中获取数据库连接
Connection connection = dataSource.getConnection();
/**
 *  使用配置文件的方式加载 DBCP数据源实例
 * 1. 加载 dbcp 的 properties 配置文件: 配置文件中的键需要来自 BasicDataSource 的属性. 
 * 2. 调用BasicDataSourceFactory 的 createDataSource 方法创建 DataSource 实例 
 * 3. 从DataSource 实例中获取数据库连接.
 */
Properties properties = new Properties();
InputStream inStream = JDBCUtil.class.getClassLoader().getResourceAsStream("dbcp.properties");
properties.load(inStream);
DataSource dataSource = BasicDataSourceFactory.createDataSource(properties);
System.out.println(dataSource.getConnection());

/**
 * 文档数据
 */
#username=root
#password=password
#driverClassName=com.mysql.jdbc.Driver
#url=jdbc:mysql://localhost:3306/local_database?useUnicode=true&characterEncoding=UTF-#8&useSSL=false
#initialSize=10
#maxActive=50
#minIdle=5
#maxWait=5000

C3P0 数据源

增加如下两个 jar文件:

  1. cc3p0-0.9.2.jar

  2. mchange-commons-java-0.2.2.jar
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver"); // loads the jdbc driver
cpds.setJdbcUrl("jdbc:mysql:///jdbc?useSSL=false");
cpds.setUser("root");
cpds.setPassword("");
System.out.println(cpds.getConnection());

XML配置C3P0数据源

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>

    <named-config name="helloc3p0">
        
        <!-- 指定连接数据源的基本属性 -->
        <property name="user">root</property>
        <property name="password"></property>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql:///jdbc?useSSL=false</property>
        
        <!-- 若数据库中连接数不足时, 一次向数据库服务器申请多少个连接 -->
        <property name="acquireIncrement">5</property>
        <!-- 初始化数据库连接池时连接的数量 -->
        <property name="initialPoolSize">5</property>
        <!-- 数据库连接池中的最小的数据库连接数 -->
        <property name="minPoolSize">5</property>
        <!-- 数据库连接池中的最大的数据库连接数 -->
        <property name="maxPoolSize">10</property>

        <!-- C3P0 数据库连接池可以维护的 Statement 的个数 -->
        <property name="maxStatements">20</property>
        <!-- 每个连接同时可以使用的 Statement 对象的个数 -->
        <property name="maxStatementsPerConnection">5</property>
    
    </named-config>
        
</c3p0-config>
    /**
     * 1. 创建 c3p0-config.xml 文件, 参考帮助文档中 Appendix B: Configuation Files 的内容 
     * 2. 创建 ComboPooledDataSource 实例; 
     *    DataSource dataSource = new ComboPooledDataSource("helloc3p0"); 
     * 3. 从 DataSource 实例中获取数据库连接.
     */
    @Test
    public void testC3poWithConfigFile() throws Exception {
        DataSource dataSource = new ComboPooledDataSource("helloc3p0");
        System.out.println(dataSource.getConnection());
        ComboPooledDataSource comboPooledDataSource = (ComboPooledDataSource) dataSource;
        System.out.println(comboPooledDataSource.getMaxStatements());
    }

dbcp和c3p0不同之处:

dbcpc3p0
spring组织推荐使用Hibernate组织推荐使用
强制关闭连接或者数据库重启后无法自动重连强制关闭连接或者数据库重启可以自动连接
没有自动的去回收空闲连接的功能自动回收空闲的功能
DBCP提供最大连接数

c3p0提供最大空闲时间

dbcp并没用相应功能c3p0可以控制数据源加载的prepareedstatement数量,并且可以设置帮助线程的数量来提升JDBC操作速度

获取驱动jar包技巧

一般我们本机上都装有数据库(mysql、oracle),那么找到你安装数据库的目录(以mysql为例)

这里面就有JDBC(Connector J 8.0)驱动,版本还和本机的相同,点进Connector J 8.0目录,其中就有一个jar包

官网下载:官网中的JDBC下载页面的链接

进入页面后选择平台

不要点击下面的Go to Download Page,直接点击上图中的 Looking for previous GA versions?

选择一个tar压缩包或者 zip压缩包下载,上面的是tar压缩包,下面的是zip。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值