微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

01_JDBC

JDBC主要内容如下

学习需要用到的jar包
链接:https://pan.baidu.com/s/17ICR-S_ZZtAZlJQdQxRmqA
提取码:djc1
复制这段内容后打开百度网盘手机App,操作更方便哦

1. JDBC概述

序言:数据 持久化

  • 持久化(persistence): 把数据保存到可掉电式存储设备中以供之后使用。大多数情况下,特别是企业级应用, 数据持久化意味着将内存中的数据保存到硬盘上加以” 固化”,而持久化的实现过程大多通过各种 关系数据库来完成。
  • 持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中。

Java 中的数据存储技术

  • 在Java中,数据库存取技术可分为如下几类:
  • JDBC 直接访问数据库
  • JDO技术
  • 第三方O/R工具,如Hibernate, mybatis 等
  • JDBC是java访问数据库的基石,JDO, Hibernate等只是更好
    的封装了JDBC。

JDBC 基础

  • JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的sql 数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准java类库,(java.sql,javax.sql)使用这个类库可以以一种标准的方法、方便地访问数据库资源
  • JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些
    细节问题。
  • JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC 驱动程序数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。

在这里插入图片描述

JDBC 体系结构

  • JDBC接口(API)包括两个层次:
    • 面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行sql语句,获得结果)。
    • 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用。

JDBC API

  • JDBC API 是一系列的接口,它使得应用程序能够进行数据库联接,执行sql语句,并且得到返回结果。

在这里插入图片描述

JDBC 程序访问数据库步骤

在这里插入图片描述

2. 获取数据库连接

Driver 接口

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

在这里插入图片描述

加载与注册 JDBC 驱动

  • 方式一:加载 JDBC 驱动需调用 Class 类的静态方法 forName(),向其传递要加载的 JDBC 驱动的类名
    • Class.forName(“com.MysqL.jdbc.Driver”);
  • 方式二:DriverManager 类是驱动程序管理器类,负责管理驱动程序
    • DriverManager.registerDriver(com.MysqL.jdbc.Driver);
    • 通常不用显式调用 DriverManager 类的 registerDriver() 方法注册驱动程序类的实例,因为 Driver 接口的驱动程序类包含了静态代码块,在这个静态代码块中,会调用 DriverManager.registerDriver() 方法注册自身的一个实例

建立接 连接(Connection)

  • 可以调用 DriverManager 类的 getConnection() 方法建立到数据库的连接
  • User,password可以用“属性名=属性值”方式告诉数据库
  • JDBC URL 用于标识一个注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。
  • JDBC URL的标准由三部分组成,各部分间用冒号分隔。
    • jdbc: 子协议: 子名称
    • 协议:JDBC URL中的协议总是jdbc
    • 子协议:子协议用于标识一个数据库驱动程序
    • 名称:一种标识数据库方法。子名称可以依不同的子协议而变化,用子名称的目的是为
      了定位数据库提供足够的信息。包含 主机名(对应服务端的ip地址)

几种常用数据库的JDBC URL

在这里插入图片描述

  • 对于 Oracle 数据库连接,采用如下形式:
    • jdbc:oracle:thin:@localhost:1521:xxx
  • 对于 sqlServer 数据库连接,采用如下形式:
    • jdbc:microsoft:sqlserver//localhost:1433; DatabaseName=sid
  • 对于 MysqL 数据库连接,采用如下形式:
    • jdbc:MysqL://localhost:3306/xxx
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.sqlException;
import java.util.Properties;

import org.junit.Test;

public class ConnectionTest {

	// 方式一:
	@Test
	public void testConnection1() throws sqlException {
		// 获取Driver实现类对象
		Driver driver = new com.MysqL.jdbc.Driver();

		// url:http://localhost:8080/gmall/keyboard.jpg
		// jdbc:MysqL:协议
		// localhost:ip地址
		// 3306:MysqL的端口号
		// test:test数据库
		String url = "jdbc:MysqL://localhost:3306/test";
		// 将用户名和密码封装在Properties中
		Properties info = new Properties();
		info.setProperty("user", "root");
		info.setProperty("password", "abc123");

		Connection conn = driver.connect(url, info);

		System.out.println(conn);
	}

	// 方式二:对方式一的迭代:在如下的程序中不出现第三方的api,使得程序具有更好的可移植性
	@Test
	public void testConnection2() throws Exception {
		// 1.获取Driver实现类对象:使用反射
		Class clazz = Class.forName("com.MysqL.jdbc.Driver");
		Driver driver = (Driver) clazz.newInstance();

		// 2.提供要连接的数据库
		String url = "jdbc:MysqL://localhost:3306/test";

		// 3.提供连接需要的用户名和密码
		Properties info = new Properties();
		info.setProperty("user", "root");
		info.setProperty("password", "abc123");

		// 4.获取连接
		Connection conn = driver.connect(url, info);
		System.out.println(conn);

	}

	// 方式三:使用DriverManager替换Driver
	@Test
	public void testConnection3() throws Exception {
		// 1.获取Driver实现类的对象
		Class clazz = Class.forName("com.MysqL.jdbc.Driver");
		Driver driver = (Driver) clazz.newInstance();

		// 2.提供另外三个连接的基本信息:
		String url = "jdbc:MysqL://localhost:3306/test";
		String user = "root";
		String password = "abc123";

		// 注册驱动
		DriverManager.registerDriver(driver);

		// 获取连接
		Connection conn = DriverManager.getConnection(url, user, password);
		System.out.println(conn);
	}

	// 方式四:可以只是加载驱动,不用显示注册驱动过了。
	@Test
	public void testConnection4() throws Exception {
		// 1.提供三个连接的基本信息:
		String url = "jdbc:MysqL://localhost:3306/test";
		String user = "root";
		String password = "abc123";
		
		// 2.加载Driver
		Class.forName("com.MysqL.jdbc.Driver");
		//相较于方式三,可以省略如下的操作:
//		Driver driver = (Driver) clazz.newInstance();
//		// 注册驱动
//		DriverManager.registerDriver(driver);
		//为什么可以省略上述操作呢?
		/*
		 * 在MysqL的Driver实现类中,声明了如下的操作:
		 * static {
				try {
					java.sql.DriverManager.registerDriver(new Driver());
				} catch (sqlException E) {
					throw new RuntimeException("Can't register driver!");
				}
			}
		 */

		// 3.获取连接
		Connection conn = DriverManager.getConnection(url, user, password);
		System.out.println(conn);
	}
	//方式五(final版):将数据库连接需要的4个基本信息声明在配置文件中,通过读取配置文件的方式,获取连接
	/*
	 * 此种方式的好处?
	 * 1.实现了数据与代码的分离。实现了解耦
	 * 2.如果需要修改配置文件信息,可以避免程序重新打包。
	 */
	@Test
	public void getConnection5() throws Exception{
		
		//1.读取配置文件中的4个基本信息
		InputStream is = ConnectionTest.class.getClassLoader().getResourceAsstream("jdbc.properties");
		
		Properties pros = new Properties();
		pros.load(is);
		
		String user = pros.getProperty("user");
		String password = pros.getProperty("password");
		String url = pros.getProperty("url");
		String driverClass = pros.getProperty("driverClass");
		
		//2.加载驱动
		Class.forName(driverClass);
		
		//3.获取连接
		Connection conn = DriverManager.getConnection(url, user, password);
		System.out.println(conn);
		
		
	}

}

3. 使用Statement操作数据表的弊端

访问数据库

  • 数据库连接被用于向数据库服务器发送命令和 sql 语句,在连接建立后,需要对数据库进行访问,执行 sql 语句
  • 在 java.sql 包中有 3 个接口分别定义了对数据库调用的不同方式:
  • Statement
    • PreparedStatement
      • CallableStatement

sql 注入攻击

  • sql 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 sql 语句段或命令(如:SELECT user,password FROM user_table WHERE user=‘a’ OR 1 = ’ AND password =’ OR ‘1’ = ‘1’) ,从而利用系统的 sql 引擎完成恶意行为的做法
  • 对于 Java 而言,要防范 sql 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了

4. 使用PreparedStatement

1.PreparedStatement作为Statement的子接口

  • 可以通过调用 Connection 对象的 preparedStatement() 方法获取PreparedStatement 对象
  • PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的sql 语句
  • PreparedStatement 对象所代表的 sql 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数. setXxx() 方法有两个参数,第一个参数是要设置的 sql 语句中的参数的索引(从 1 开始),第二个是设置的 sql 语句中的参数的值

2.PreparedStatement vs Statement

  • 代码的可读性和可维护性.
  • PreparedStatement 能最大可能提高性能
    • DBServer会对 预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
    • 在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,
      没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存.这样每执
      行一次都要对传入的语句编译一次.
    • (语法检查,语义检查,翻译成二进制命令,缓存)
  • PreparedStatement 可以防止 sql 注入

对比案例

jdbc.properties

username=root
password=root
url=jdbc:MysqL://localhost:3306/db3?rewriteBatchedStatements=true
driverClass=com.MysqL.jdbc.Driver

User

public class User {

	private String user;
	private String password;

	public User() {
	}

	public User(String user, String password) {
		super();
		this.user = user;
		this.password = password;
	}

	@Override
	public String toString() {
		return "User [user=" + user + ", password=" + password + "]";
	}

	public String getUser() {
		return user;
	}

	public void setUser(String user) {
		this.user = user;
	}

	public String getpassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

}


StatementTest

import java.io.InputStream;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.sqlException;
import java.sql.Statement;
import java.util.Properties;
import java.util.Scanner;

import org.junit.Test;

public class StatementTest {

	// 使用Statement的弊端:需要拼写sql语句,并且存在sql注入的问题
	//如何避免出现sql注入:只要用 PreparedStatement(从Statement扩展而来) 取代 Statement
	@Test
	public void testLogin() {
		Scanner scanner = new Scanner(system.in);
		
		System.out.print("请输入用户名:");
		String user = scanner.nextLine();
		System.out.print("请输入密码:");
		String password = scanner.nextLine();
		//SELECT user,password FROM user_table WHERE user = '1'  AND password = '1' or '1' = '1'
		String sql = "SELECT user,password FROM user_table WHERE user = '"+ user +"' AND password = '"+ password +"'";
		User returnUser = get(sql,User.class);
		if(returnUser != null){
			System.out.println("登录成功");
		}else{
			System.out.println("用户名不存在或密码错误");
		}
	}

	// 使用Statement实现对数据表的查询操作
	public <T> T get(String sql, Class<T> clazz) {
		T t = null;

		Connection conn = null;
		Statement st = null;
		ResultSet rs = null;
		try {
			// 1.加载配置文件
			InputStream is = StatementTest.class.getClassLoader().getResourceAsstream("jdbc.properties");
			Properties pros = new Properties();
			pros.load(is);

			// 2.读取配置信息
			String user = pros.getProperty("user");
			String password = pros.getProperty("password");
			String url = pros.getProperty("url");
			String driverClass = pros.getProperty("driverClass");

			// 3.加载驱动
			Class.forName(driverClass);

			// 4.获取连接
			conn = DriverManager.getConnection(url, user, password);

			st = conn.createStatement();

			rs = st.executeQuery(sql);

			// 获取结果集的元数据
			ResultSetMetaData rsmd = rs.getMetaData();

			// 获取结果集的列数
			int columnCount = rsmd.getColumnCount();

			if (rs.next()) {

				t = clazz.newInstance();

				for (int i = 0; i < columnCount; i++) {
					// //1. 获取列的名称
					// String columnName = rsmd.getColumnName(i+1);

					// 1. 获取列的别名
					String columnName = rsmd.getColumnLabel(i + 1);

					// 2. 根据列名获取对应数据表中的数据
					Object columnVal = rs.getobject(columnName);

					// 3. 将数据表中得到的数据,封装进对象
					Field field = clazz.getDeclaredField(columnName);
					field.setAccessible(true);
					field.set(t, columnVal);
				}
				return t;
			}
		} catch (Exception e) {
			e.printstacktrace();
		} finally {
			// 关闭资源
			if (rs != null) {
				try {
					rs.close();
				} catch (sqlException e) {
					e.printstacktrace();
				}
			}
			if (st != null) {
				try {
					st.close();
				} catch (sqlException e) {
					e.printstacktrace();
				}
			}

			if (conn != null) {
				try {
					conn.close();
				} catch (sqlException e) {
					e.printstacktrace();
				}
			}
		}

		return null;
	}

}


PreparedStatementTest

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.Scanner;

import org.junit.Test;

import com.atguigu3.util.JDBCUtils;

/**
 * 
 * @Description 演示使用PreparedStatement替换Statement,解决sql注入问题
 * @version 
 * 除了解决Statement的拼串、sql问题之外,PreparedStatement还有哪些好处呢?
 * 1.PreparedStatement操作Blob的数据,而Statement做不到。
 * 2.PreparedStatement可以实现更高效的批量操作。
 *
 */
public class PreparedStatementTest {
	@Test
	public void testLogin() {
		Scanner scanner = new Scanner(system.in);
		
		System.out.print("请输入用户名:");
		String user = scanner.nextLine();
		System.out.print("请输入密码:");
		String password = scanner.nextLine();
		//SELECT user,password FROM user_table WHERE user = '1' or ' AND password = '=1 or '1' = '1'
		String sql = "SELECT user,password FROM user_table WHERE user = ? and password = ?";
		User returnUser = getInstance(User.class,sql,user,password);
		if(returnUser != null){
			System.out.println("登录成功");
		}else{
			System.out.println("用户名不存在或密码错误");
		}
	}
	
	/**
	 * 
	 * @Description 针对于不同的表的通用的查询操作,返回表中的一条记录
	 * @param clazz
	 * @param sql
	 * @param args
	 * @return
	 */
	public <T> T getInstance(Class<T> clazz,String sql, Object... args) {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			InputStream is = PreparedStatementTest.class.getClassLoader().getResourceAsstream("java语言高级/O92_JDBC/O5_JDBC登录案例练习/jdbc.properties");
            Properties properties = new Properties();
            properties.load(is);

            String username = properties.getProperty("username");
            String password = properties.getProperty("password");
            String url = properties.getProperty("url");
            String driverClass = properties.getProperty("driverClass");

            Class.forName(driverClass);

            conn= DriverManager.getConnection(url, username, password);

			ps = conn.prepareStatement(sql);
			for (int i = 0; i < args.length; i++) {
				ps.setobject(i + 1, args[i]);
			}

			rs = ps.executeQuery();
			// 获取结果集的元数据 :ResultSetMetaData
			ResultSetMetaData rsmd = rs.getMetaData();
			// 通过ResultSetMetaData获取结果集中的列数
			int columnCount = rsmd.getColumnCount();

			if (rs.next()) {
				T t = clazz.newInstance();
				// 处理结果集一行数据中的每一个列
				for (int i = 0; i < columnCount; i++) {
					// 获取列值
					Object columValue = rs.getobject(i + 1);

					// 获取每个列的列名
					// String columnName = rsmd.getColumnName(i + 1);
					String columnLabel = rsmd.getColumnLabel(i + 1);

					// 给t对象指定的columnName属性,赋值为columValue:通过反射
					Field field = clazz.getDeclaredField(columnLabel);
					field.setAccessible(true);
					field.set(t, columValue);
				}
				return t;
			}
		} catch (Exception e) {
			e.printstacktrace();
		} finally {
            // 关闭资源
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (sqlException e) {
                    e.printstacktrace();
                }
            }
            if (statement != null) {
                try {
                    statement.close();
                } catch (sqlException e) {
                    e.printstacktrace();
                }
            }

            if (connection != null) {
                try {
                    connection.close();
                } catch (sqlException e) {
                    e.printstacktrace();
                }
            }
        }

		return null;
	}
}

连接数据库、操作表的步骤

在这里插入图片描述

下面使用的JDBCUtils 工具类

/**
 * 
 * @Description 操作数据库的工具类
 *
 */
public class JDBCUtils {
	
	/**
	 * 
	 * @Description 获取数据库的连接
	 */
	public static Connection getConnection() throws Exception {
		// 1.读取配置文件中的4个基本信息
		InputStream is = ClassLoader.getSystemClassLoader().getResourceAsstream("jdbc.properties");

		Properties pros = new Properties();
		pros.load(is);

		String user = pros.getProperty("user");
		String password = pros.getProperty("password");
		String url = pros.getProperty("url");
		String driverClass = pros.getProperty("driverClass");

		// 2.加载驱动
		Class.forName(driverClass);

		// 3.获取连接
		Connection conn = DriverManager.getConnection(url, user, password);
		return conn;
	}
	/**
	 * 
	 * @Description 关闭连接和Statement的操作
	 * @param conn
	 * @param ps
	 */
	public static void closeResource(Connection conn,Statement ps){
		try {
			if(ps != null)
				ps.close();
		} catch (sqlException e) {
			e.printstacktrace();
		}
		try {
			if(conn != null)
				conn.close();
		} catch (sqlException e) {
			e.printstacktrace();
		}
	}
	/**
	 * 
	 * @Description 关闭资源操作
	 * @param conn
	 * @param ps
	 * @param rs
	 */
	public static void closeResource(Connection conn,Statement ps,ResultSet rs){
		try {
			if(ps != null)
				ps.close();
		} catch (sqlException e) {
			e.printstacktrace();
		}
		try {
			if(conn != null)
				conn.close();
		} catch (sqlException e) {
			e.printstacktrace();
		}
		try {
			if(rs != null)
				rs.close();
		} catch (sqlException e) {
			e.printstacktrace();
		}
	}
}

3.通用方法(通用的查询

在这里插入图片描述

//通用的查询
	public <T> T getInstance(Class<T> clazz,String sql,Object ... args) throws Exception{
		Connection conn = JDBCUtils.getConnection();
		PreparedStatement ps = conn.prepareStatement(sql);
		for(int i = 0;i < args.length;i++){
			ps.setobject(i + 1, args[i]);
		}
		
		ResultSet rs = ps.executeQuery();
		ResultSetMetaData rsmd = rs.getMetaData();
		int columnCount = rsmd.getColumnCount();
		if(rs.next()){
			T t = clazz.newInstance();
			for(int i = 0;i < columnCount;i++){
				Object columnVal = rs.getobject(i + 1);
				String columnLabel = rsmd.getColumnLabel(i + 1);
				
				Field field = clazz.getDeclaredField(columnLabel);
				field.setAccessible(true);
				field.set(t, columnVal);
				
			}
			return t;
		}
		
		JDBCUtils.close(rs, ps, conn);
		
		return null;
	}

4.通用的删除方法

// 删除操作,返回是否有数据被删除
	public static boolean delete(String sql, Object... args) {
		// 1.获取数据库的连接
		Connection conn = null;
		// 2.获取一个PreparedStatement的对象
		PreparedStatement ps = null;
		try {
			conn = JDBCUtils.getConnection();
			ps = conn.prepareStatement(sql);
			for (int i = 0; i < args.length; i++) {
				ps.setobject(i + 1, args[i]);
			}
			// 3.通过PreparedStatement的对象执行sql语句
			int i = ps.executeUpdate();
			if (i > 0) {
				return true;
			} else {
				return false;
			}
		} catch (Exception e) {
			// Todo Auto-generated catch block
			e.printstacktrace();
		} finally {
			// 4.关闭操作
			JDBCUtils.close(null, ps, conn);
		}
		return false;
	}

5.ResultSet结果集

  • 通过调用 PreparedStatement 对象的 excuteQuery() 方法创建该对象
  • ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集,ResultSet 接口由数据库厂商实现
  • ResultSet 对象维护了一个指向当前数据行的游标,初始的时候,游标在第一行之前,可以通过ResultSet 对象的 next() 方法移动到下一行
  • ResultSet 接口的常用方法
    • boolean next()
    • getString()

在这里插入图片描述


关于ResultSet

  1. 查询需要调用Prepared Statement 的 executeQuery() 方法查询结果是一个 ResultSet对象

  2. 关于 ResultSet:代表结果集

    • ResultSet: 结果集. 封装了使用 JDBC 进行查询的结果.
    • 调用 PreparedStatement 对象的 executeQuery() 可以得到结果集.
    • ResultSet 返回的实际上就是一张数据表. 有一个指针指向数据表的第一条记录的前面.
  3. 可以调用 next() 方法检测下一行是否有效. 若有效该方法返回 true, 且指针下移. 相当于Iterator 对象的 hasNext() 和 next() 方法的结合体

  4. 当指针指向一行时, 可以通过调用 getXxx(int index) 或 getXxx(int columnName) 获取每一
    列的值.

    • 例如: getInt(1), getString(“name”)
  5. ResultSet 当然也需要进行关闭.

6.ResultSetMetaData结果集,ResultSet 的 getMetaData() 方法即可获取

  • 可用于获取关于 ResultSet 对象中列的类型和属性信息的对象
  • ResultSetMetaData Meta = rs.getMetaData();
    • getColumnName(int column):获取指定列的名称
    • getColumnLabel(int column):获取指定列的别名
    • getColumnCount():返回当前 ResultSet 对象中的列数。
    • getColumnTypeName(int column):检索指定列的数据库特定的类型名称
    • getColumndisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。
    • isNullable(int column):指示指定列中的值是否可以为 null。
    • isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。

在这里插入图片描述

7.向数据表中插入、读取大数据:BLOB字段

  • LOB ,即Large Objects (大对象), 是用来存储大量的二进制和文本数据的一种数据类型一个LOB字段可存储可多达4GB的数据)。
  • LOB 分为两种类型:内部LOB和外部LOB。
    • 内部LOB将数据以字节流的形式存储在数据库的内部。因而,内部LOB的许多操作都可以参与事务,也可以像处理普通数据一样对其进行备份和恢复操作。Oracle支持三种类型的内部LOB:
      • BLOB(二进制数据)
      • CLOB(单字节字符数据)
      • NCLOB(多字节字符数据)
    • CLOB和NCLOB类型适用于存储超长的文本数据,BLOB 字段适用于存储大量的二进制数据,如图像、视频、音频,文件等。
    • 目前只支持一种外部LOB类型,即BFILE类型。在数据库内,该类型仅存储数据在操作系统中的位置信息,而数据的实体以外部文件的形式存在于操作系统的文件系统中。因而,该类型所表示的数据是只读的,不参与事务。该类型可帮助用户管理大量的由外部程序访问的文件

MysqL BLOB

  • MysqL中,BLOB是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据。
  • MysqL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是等同的)
类型大小(单位:字节)
TinyBlob最大为 255
Blob最大为65K
MediumBlob最大为16M
LongBlob最大为4G
  • 实际使用中根据需要存入的数据大小定义不同的BLOB类型。需要注意的是:如果存储的文件过大,数据库性能会下降。

步 骤

  • 向数据表中插入大数据类型
String sql = "INSERT INTO customer(name, email, birth, photo) VALUES(?, ?, ?, ?)";
conn = JDBCUtil.getConnection();
ps = conn.preparedStatement(sql);
ps.setString(1, "LDH");
ps.setString(2, "LDH@163.com");
ps.setDate(3, new Date(new java.util.Date().getTime()));
// 填充 Blob  类型的数据
ps.setBlob(4, new FileInputStream("abcd.jpg"));
ps.executeUpdate();
  • 从数据表中读取大数据类型
String sql = "SELECT id, name, email, birth, photo FROM customer WHERE id = ?";
conn = getConnection();
ps = conn.prepareStatement(sql);
ps.setInt(1, 8);
rs = ps.executeQuery();
if(rs.next()){
	Integer id = rs.getInt(1);
	String name = rs.getString(2);
	String email = rs.getString(3);
	Date birth = rs.getDate(4);
	Customer cust = new Customer(id, name, email, birth);
	System.out.println(cust);
	
	Blob photo = rs.getBlob(5);
	InputStream is = photo.getBinaryStream();
	OutputStream os = new FileOutputStream("c.jpg");
	byte [] buffer = new byte[1024];
	int len = 0;
	while((len = is.read(buffer)) != -1){
		os.write(buffer, 0, len);
	}
}

8.使用PreparedStatement实现批量插入

  • 当需要成批插入或者更新记录时。可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率
  • JDBC的批量处理语句包括下面两个方法
    • addBatch(String):添加需要批量处理的sql语句或是参数;
    • executeBatch():执行批量处理语句;
    • clearBatch():清空缓存的数据
  • 通常我们会遇到两种批量执行sql语句的情况:
    • 多条sql语句的批量处理;
    • 一个sql语句的批量传参;

一个sql语句的批量传参
优化1: :
  使用PreparedStatement替代Statement
优化2: :
  ① 使用 addBatch() / executeBatch() / clearBatch()
  ② ?rewriteBatchedStatements=true&useServerPrepStmts=false
  ③ 使用更新的MysqL 驱动:mysql-connector-java-5.1.37-bin.jar
优化3:
  Connection 的 setAutoCommit(false) / commit()

案例
实现批量数据的操作

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.sqlException;

import org.junit.Test;

import com.util.JDBCUtils;

/*
 * 使用PreparedStatement实现批量数据的操作
 * 
 * update、delete本身就具有批量操作的效果。
 * 此时的批量操作,主要指的是批量插入。使用PreparedStatement如何实现更高效的批量插入?
 * 
 * 题目:向goods表中插入20000条数据
 * CREATE TABLE goods(
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(25)
   );
 * 方式一:使用Statement
 * Connection conn = JDBCUtils.getConnection();
 * Statement st = conn.createStatement();
 * for(int i = 1;i <= 20000;i++){
 * 		String sql = "insert into goods(name)values('name_" + i + "')";
 * 		st.execute(sql);
 * }
 * 
 */
public class InsertTest {
	//批量插入的方式二:使用PreparedStatement
	@Test
	public void testInsert1() {
		Connection conn = null;
		PreparedStatement ps = null;
		try {
			
			long start = System.currentTimeMillis();
			
			conn = JDBCUtils.getConnection();
			String sql = "insert into goods(name)values(?)";
			ps = conn.prepareStatement(sql);
			for(int i = 1;i <= 20000;i++){
				ps.setobject(1, "name_" + i);
				
				ps.execute();
			}
			
			long end = System.currentTimeMillis();
			
			System.out.println("花费的时间为:" + (end - start));//20000:83065
		} catch (Exception e) {
			e.printstacktrace();
		}finally{
			JDBCUtils.closeResource(conn, ps);
			
		}
		
	}
	
	/*
	 * 批量插入的方式三:
	 * 1.addBatch()、executeBatch()、clearBatch()
	 * 2.MysqL服务器认是关闭批处理的,我们需要通过一个参数,让MysqL开启批处理的支持。
	 * 		 ?rewriteBatchedStatements=true 写在配置文件的url后面
	 * 3.使用更新的MysqL 驱动:mysql-connector-java-5.1.37-bin.jar
	 */
	@Test
	public void testInsert2() {
		Connection conn = null;
		PreparedStatement ps = null;
		try {
			
			long start = System.currentTimeMillis();
			
			conn = JDBCUtils.getConnection();
			String sql = "insert into goods(name)values(?)";
			ps = conn.prepareStatement(sql);
			for(int i = 1;i <= 1000000;i++){
				ps.setobject(1, "name_" + i);
				
				//1."攒"sql
				ps.addBatch();
				
				if(i % 500 == 0){
					//2.执行batch
					ps.executeBatch();
					
					//3.清空batch
					ps.clearBatch();
				}
				
			}
			
			long end = System.currentTimeMillis();
			
			System.out.println("花费的时间为:" + (end - start));//20000:83065 -- 565
		} catch (Exception e) {								//1000000:16086
			e.printstacktrace();
		}finally{
			JDBCUtils.closeResource(conn, ps);
			
		}
		
	}
	
	//批量插入的方式四:设置连接不允许自动提交数据
	@Test
	public void testInsert3() {
		Connection conn = null;
		PreparedStatement ps = null;
		try {
			
			long start = System.currentTimeMillis();
			
			conn = JDBCUtils.getConnection();
			
			//设置不允许自动提交数据
			conn.setAutoCommit(false);
			
			String sql = "insert into goods(name)values(?)";
			ps = conn.prepareStatement(sql);
			for(int i = 1;i <= 1000000;i++){
				ps.setobject(1, "name_" + i);
				
				//1."攒"sql
				ps.addBatch();
				
				if(i % 500 == 0){
					//2.执行batch
					ps.executeBatch();
					
					//3.清空batch
					ps.clearBatch();
				}
				
			}
			
			//提交数据
			conn.commit();
			
			long end = System.currentTimeMillis();
			
			System.out.println("花费的时间为:" + (end - start));//20000:83065 -- 565
		} catch (Exception e) {								//1000000:16086 -- 5114
			e.printstacktrace();
		}finally{
			JDBCUtils.closeResource(conn, ps);
			
		}
		
	}
}

测试使用PreparedStatement操作Blob类型的数据

package com.atguigu5.blob;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.sqlException;

import org.junit.Test;

import com.atguigu3.bean.Customer;
import com.atguigu3.util.JDBCUtils;

/**
 * 
 * @Description 测试使用PreparedStatement操作Blob类型的数据
 *
 */
public class BlobTest {
	
	//向数据表customers中插入Blob类型的字段
	@Test
	public void testInsert() throws Exception{
		Connection conn = JDBCUtils.getConnection();
		String sql = "insert into customers(name,email,birth,photo)values(?,?,?,?)";
		
		PreparedStatement ps = conn.prepareStatement(sql);
		
		ps.setobject(1,"钢铁侠");
		ps.setobject(2, "yuan@qq.com");
		ps.setobject(3,"1992-09-08");
		FileInputStream is = new FileInputStream(new File("girl.jpg"));
		ps.setBlob(4, is);
		
		ps.execute();
		
		JDBCUtils.closeResource(conn, ps);
		
	}
	
	//查询数据表customers中Blob类型的字段
	@Test
	public void testQuery(){
		Connection conn = null;
		PreparedStatement ps = null;
		InputStream is = null;
		FileOutputStream fos = null;
		ResultSet rs = null;
		try {
			conn = JDBCUtils.getConnection();
			String sql = "select id,name,email,birth,photo from customers where id = ?";
			ps = conn.prepareStatement(sql);
			ps.setInt(1, 21);
			rs = ps.executeQuery();
			if(rs.next()){
	//			方式一:
	//			int id = rs.getInt(1);
	//			String name = rs.getString(2);
	//			String email = rs.getString(3);
	//			Date birth = rs.getDate(4);
				//方式二:
				int id = rs.getInt("id");
				String name = rs.getString("name");
				String email = rs.getString("email");
				Date birth = rs.getDate("birth");
				
				Customer cust = new Customer(id, name, email, birth);
				System.out.println(cust);
				
				//将Blob类型的字段下载下来,以文件的方式保存在本地
				Blob photo = rs.getBlob("photo");
				is = photo.getBinaryStream();
				fos = new FileOutputStream("zhangyuhao.jpg");
				byte[] buffer = new byte[1024];
				int len;
				while((len = is.read(buffer)) != -1){
					fos.write(buffer, 0, len);
				}
				
			}
		} catch (Exception e) {
			e.printstacktrace();
		}finally{
			
			try {
				if(is != null)
					is.close();
			} catch (IOException e) {
				e.printstacktrace();
			}
			
			try {
				if(fos != null)
					fos.close();
			} catch (IOException e) {
				e.printstacktrace();
			}
			
			JDBCUtils.closeResource(conn, ps, rs);
		}
		
		
	}
	
}

5. 数据库事务

1.什么是事务

  • 事务: 一组逻辑操作单元, 使数据从一种状态变换到另一种状态。
  • 事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都交被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。
  • 为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。

2.JDBC 事务处理

  • 一个连接对象被创建时,认情况下是自动提交事务:每次执行一个sql 语句时,如果执行成功,就会向数据库自动提交,而不能回滚
  • 为了让多个 sql 语句作为一个事务执行:
    • 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
    • 在所有的 sql 语句都成功执行后,调用 commit(); 方法提交事务
    • 在出现异常时,调用 rollback(); 方法回滚事务
    • 若此时 Connection 没有被关闭, 则需要恢复其自动提交状态

数据库事务使用的过程

public void testJDBCTransaction() {
Connection conn = null;
try {
	// 1. 获取数据库连接
	conn = JDBCUtils.getConnection();
	// 2. 开启事务
	conn.setAutoCommit(false);
	// 3. 进行数据库操作
	// 4. 若没有异常,则提交事务
	conn.commit();
} catch (Exception e) {
	e.printstacktrace();
// 5. 若有异常,则回滚事务
try {
	conn.rollback();
} catch (sqlException e1) {
	e1.printstacktrace();
} finally {
	JDBCUtils.close(null, null, conn); }
}

案例

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.sqlException;

import org.junit.Test;

import com.util.JDBCUtils;
/*
 * 1.什么叫数据库事务?
 * 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
 * 		> 一组逻辑操作单元:一个或多个DML操作。
 * 
 * 2.事务处理的原则:保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。
 * 当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存
 * 下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。
 * 
 * 3.数据一旦提交,就不可回滚
 * 
 * 4.哪些操作会导致数据的自动提交?
 * 		>DDL操作一旦执行,都会自动提交。
 * 			>set autocommit = false 对DDL操作失效
 * 		>DML认情况下,一旦执行,就会自动提交。
 * 			>我们可以通过set autocommit = false的方式取消DML操作的自动提交。
 * 		>认在关闭连接时,会自动的提交数据
 */
public class TransactionTest {
	
	//******************未考虑数据库事务情况下的转账操作**************************
	/*
	 * 针对于数据表user_table来说:
	 * AA用户给BB用户转账100
	 * 
	 * update user_table set balance = balance - 100 where user = 'AA';
	 * update user_table set balance = balance + 100 where user = 'BB';
	 */
	@Test
	public void testUpdate(){
		
		String sql1 = "update user_table set balance = balance - 100 where user = ?";
		update(sql1, "AA");
		
		//模拟网络异常
		System.out.println(10 / 0);
		
		String sql2 = "update user_table set balance = balance + 100 where user = ?";
		update(sql2, "BB");
		
		System.out.println("转账成功");
	}

	// 通用的增删改操作---version 1.0
	public int update(String sql, Object... args) {// sql中占位符的个数与可变形参的长度相同!
		Connection conn = null;
		PreparedStatement ps = null;
		try {
			// 1.获取数据库的连接
			conn = JDBCUtils.getConnection();
			// 2.预编译sql语句,返回PreparedStatement的实例
			ps = conn.prepareStatement(sql);
			// 3.填充占位符
			for (int i = 0; i < args.length; i++) {
				ps.setobject(i + 1, args[i]);// 小心参数声明错误!!
			}
			// 4.执行
			return ps.executeUpdate();
		} catch (Exception e) {
			e.printstacktrace();
		} finally {
			
			//修改其为自动提交数据
			//主要针对于使用数据库连接池的使用
			try {
				conn.setAutoCommit(true);
			} catch (sqlException e) {
				e.printstacktrace();
			}
			
			// 5.资源的关闭
			JDBCUtils.closeResource(conn, ps);

		}
		return 0;

	}
	
	//********************考虑数据库事务后的转账操作*********************
	
	@Test
	public void testUpdateWithTx() {
		Connection conn = null;
		try {
			conn = JDBCUtils.getConnection();
			System.out.println(conn.getAutoCommit());//true
			//1.取消数据的自动提交
			conn.setAutoCommit(false);
			
			String sql1 = "update user_table set balance = balance - 100 where user = ?";
			update(conn,sql1, "AA");
			
			//模拟网络异常
			System.out.println(10 / 0);
			
			String sql2 = "update user_table set balance = balance + 100 where user = ?";
			update(conn,sql2, "BB");
			
			System.out.println("转账成功");
			
			//2.提交数据
			conn.commit();
			
		} catch (Exception e) {
			e.printstacktrace();
			//3.回滚数据
			try {
				conn.rollback();
			} catch (sqlException e1) {
				e1.printstacktrace();
			}
		}finally{
			
			JDBCUtils.closeResource(conn, null);
		}
		
	}
	
	// 通用的增删改操作---version 2.0 (考虑上事务)
	public int update(Connection conn,String sql, Object... args) {// sql中占位符的个数与可变形参的长度相同!
		PreparedStatement ps = null;
		try {
			// 1.预编译sql语句,返回PreparedStatement的实例
			ps = conn.prepareStatement(sql);
			// 2.填充占位符
			for (int i = 0; i < args.length; i++) {
				ps.setobject(i + 1, args[i]);// 小心参数声明错误!!
			}
			// 3.执行
			return ps.executeUpdate();
		} catch (Exception e) {
			e.printstacktrace();
		} finally {
			// 4.资源的关闭
			JDBCUtils.closeResource(null, ps);

		}
		return 0;

	}
	
	//*****************************************************
	@Test
	public void testTransactionSelect() throws Exception{
		
		Connection conn = JDBCUtils.getConnection();
		//获取当前连接的隔离级别
		System.out.println(conn.getTransactionIsolation());
		//设置数据库的隔离级别:
		conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
		//取消自动提交数据
		conn.setAutoCommit(false);
		
		String sql = "select user,password,balance from user_table where user = ?";
		User user = getInstance(conn, User.class, sql, "CC");
		
		System.out.println(user);
		
	}
	
	@Test
	public void testTransactionUpdate() throws Exception{
		Connection conn = JDBCUtils.getConnection();
		
		//取消自动提交数据
		conn.setAutoCommit(false);
		String sql = "update user_table set balance = ? where user = ?";
		update(conn, sql, 5000,"CC");
		
		Thread.sleep(15000);
		System.out.println("修改结束");
	}
	
	//通用的查询操作,用于返回数据表中的一条记录(version 2.0:考虑上事务)
	public <T> T getInstance(Connection conn,Class<T> clazz,String sql, Object... args) {
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			
			ps = conn.prepareStatement(sql);
			for (int i = 0; i < args.length; i++) {
				ps.setobject(i + 1, args[i]);
			}

			rs = ps.executeQuery();
			// 获取结果集的元数据 :ResultSetMetaData
			ResultSetMetaData rsmd = rs.getMetaData();
			// 通过ResultSetMetaData获取结果集中的列数
			int columnCount = rsmd.getColumnCount();

			if (rs.next()) {
				T t = clazz.newInstance();
				// 处理结果集一行数据中的每一个列
				for (int i = 0; i < columnCount; i++) {
					// 获取列值
					Object columValue = rs.getobject(i + 1);

					// 获取每个列的列名
					// String columnName = rsmd.getColumnName(i + 1);
					String columnLabel = rsmd.getColumnLabel(i + 1);

					// 给t对象指定的columnName属性,赋值为columValue:通过反射
					Field field = clazz.getDeclaredField(columnLabel);
					field.setAccessible(true);
					field.set(t, columValue);
				}
				return t;
			}
		} catch (Exception e) {
			e.printstacktrace();
		} finally {
			JDBCUtils.closeResource(null, ps, rs);

		}

		return null;
	}
	
}

3.数据库事务

  • 事务的ACID(acid)属性
    1. 原子性(Atomicity)
      原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
    2. 一致性(Consistency)
      事务必须使数据库一个一致性状态变换到另外一个一致性状态。
    3. 隔离性(Isolation)
      事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
    4. 持久性(Durability)
      持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响

数据库事务

  • 以第一个 DML 语句的执行作为开始
  • 以下面的其中之一作为结束:
    • COMMIT 或 或 ROLLBACK 语句
    • DDL 或 DCL 语句(自动提交)
    • 用户会话正常结束
    • 系统异常终了
DDL: 
   Data DeFinition Language 数据定义语言(用来定义数据库结构): create table; alter table; drop table; create index; drop index
DCL: 
   Data Control Language 数据控制语言(用来控制数据库的访问):grant; revoke; commit;rollback; lock;
DML: 
   Data Manipulation Language 数据操纵语言(用来查询与更新记录): insert; update; delete


COMMIT 和ROLLBACK 语句的优点

使用COMMIT 和 ROLLBACK语句,我们可以:

  • 确保数据完整性。
  • 数据改变被提交之前预览。
  • 将逻辑上相关的操作分组。
数据完整性:
存储 在数据库中的所有数据值均处于正确的状态。如果数据库中存储有不正确的数据值,则该数据库称为已丧失数据完整性。
数据库 采用多种方法来保证数据完整性,包括外键、束约、规则和触发器。

提交或回滚前的数据状态

  • 改变前的数据状态是可以恢复的
  • 执行 DML 操作的用户可以通过 SELECT 语句查询提交或回滚之前的修正
  • 其他用户不能看到当前用户所做的改变,直到当前用户结束事务。
  • DML语句所涉及到的行被锁定, 其他用户不能操作。

提交后的数据状态

  • 数据的改变已经被保存到数据库中。
  • 改变前的数据已经丢失。
  • 所有用户可以看到结果。
  • 锁被释放,其他用户可以操作涉及到的数据。

附:数据库 的隔离级别

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

    • 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
    • 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
    • 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
  • 数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。

  • 一个事务与其他事务隔离的程度称为隔离级别. 数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。

  • 数据库提供的 4 种事务隔离级别:

    在这里插入图片描述

  • Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE。
    Oracle 认的事务隔离级别为: READ COMMITED

  • MysqL 支持 4 种事务隔离级别. MysqL 认的事务隔离级别为:
    REPEATABLE READ

附:在 MysqL 中设置隔离级别

  • 每启动一个 MysqL 程序, 就会获得一个单独的数据库连接. 每个数据库连接都
    一个全局变量 @@tx_isolation, 表示当前的事务隔离级别.
  • 查看当前的隔离级别: SELECT @@tx_isolation;
  • 设置当前 MysqL 连接的隔离级别:
    • set transaction isolation level read committed;
  • 设置数据库系统的全局的隔离级别:
    • set global transaction isolation level read committed;
      SET autocommit = 0; 禁止操作自动提交

6.提供通用的DAO

import java.sql.Connection;
import java.sql.Date;
import java.util.List;

import com.bean.Customer;

/*
 * 此接口用于规范针对于customers表的常用操作
 */
public interface CustomerDAO {
	/**
	 * 
	 * @Description 将cust对象添加数据库中
	 * @param conn
	 * @param cust
	 */
	void insert(Connection conn,Customer cust);
	/**
	 * 
	 * @Description 针对指定的id,删除表中的一条记录
	 * @param conn
	 * @param id
	 */
	void deleteById(Connection conn,int id);
	/**
	 * 
	 * @Description 针对内存中的cust对象,去修改数据表中指定的记录

	 * @param conn
	 * @param cust
	 */
	void update(Connection conn,Customer cust);
	/**
	 * 
	 * @Description 针对指定的id查询得到对应的Customer对象
	 * @param conn
	 * @param id
	 */
	Customer getCustomerById(Connection conn,int id);
	/**
	 * 
	 * @Description 查询表中的所有记录构成的集合
	 * @param conn
	 * @return
	 */
	List<Customer> getAll(Connection conn);
	/**
	 * 
	 * @Description 返回数据表中的数据的条目数
	 * @param conn
	 * @return
	 */
	Long getCount(Connection conn);
	
	/**
	 * 
	 * @Description 返回数据表中最大的生日
	 * @param conn
	 * @return
	 */
	Date getMaxBirth(Connection conn);
	
}	

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.sqlException;
import java.util.ArrayList;
import java.util.List;

import com.atguigu1.util.JDBCUtils;

/*
 * DAO: data(base) access object
 * 封装了针对于数据表的通用的操作
 */
public abstract class BaseDAO<T> {
	
	private Class<T> clazz = null;

//	public BaseDAO(){
//
//	}

	{
		//获取当前BaseDAO的子类继承的父类中的泛型
		Type genericSuperclass = this.getClass().getGenericSuperclass();
		ParameterizedType paramType = (ParameterizedType) genericSuperclass;

		Type[] typeArguments = paramType.getActualTypeArguments();//获取父类的泛型参数
		clazz = (Class<T>) typeArguments[0];//泛型的第一个参数

	}
	
	
	// 通用的增删改操作---version 2.0 (考虑上事务)
	public int update(Connection conn, String sql, Object... args) {// sql中占位符的个数与可变形参的长度相同!
		PreparedStatement ps = null;
		try {
			// 1.预编译sql语句,返回PreparedStatement的实例
			ps = conn.prepareStatement(sql);
			// 2.填充占位符
			for (int i = 0; i < args.length; i++) {
				ps.setobject(i + 1, args[i]);// 小心参数声明错误!!
			}
			// 3.执行
			return ps.executeUpdate();
		} catch (Exception e) {
			e.printstacktrace();
		} finally {
			// 4.资源的关闭
			JDBCUtils.closeResource(null, ps);

		}
		return 0;

	}

	// 通用的查询操作,用于返回数据表中的一条记录(version 2.0:考虑上事务)
	public T getInstance(Connection conn, String sql, Object... args) {
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {

			ps = conn.prepareStatement(sql);
			for (int i = 0; i < args.length; i++) {
				ps.setobject(i + 1, args[i]);
			}

			rs = ps.executeQuery();
			// 获取结果集的元数据 :ResultSetMetaData
			ResultSetMetaData rsmd = rs.getMetaData();
			// 通过ResultSetMetaData获取结果集中的列数
			int columnCount = rsmd.getColumnCount();

			if (rs.next()) {
				T t = clazz.newInstance();
				// 处理结果集一行数据中的每一个列
				for (int i = 0; i < columnCount; i++) {
					// 获取列值
					Object columValue = rs.getobject(i + 1);

					// 获取每个列的列名
					// String columnName = rsmd.getColumnName(i + 1);
					String columnLabel = rsmd.getColumnLabel(i + 1);

					// 给t对象指定的columnName属性,赋值为columValue:通过反射
					Field field = clazz.getDeclaredField(columnLabel);
					field.setAccessible(true);
					field.set(t, columValue);
				}
				return t;
			}
		} catch (Exception e) {
			e.printstacktrace();
		} finally {
			JDBCUtils.closeResource(null, ps, rs);

		}

		return null;
	}
	// 通用的查询操作,用于返回数据表中的多条记录构成的集合(version 2.0:考虑上事务)
	public List<T> getForList(Connection conn, String sql, Object... args) {
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {

			ps = conn.prepareStatement(sql);
			for (int i = 0; i < args.length; i++) {
				ps.setobject(i + 1, args[i]);
			}

			rs = ps.executeQuery();
			// 获取结果集的元数据 :ResultSetMetaData
			ResultSetMetaData rsmd = rs.getMetaData();
			// 通过ResultSetMetaData获取结果集中的列数
			int columnCount = rsmd.getColumnCount();
			// 创建集合对象
			ArrayList<T> list = new ArrayList<T>();
			while (rs.next()) {
				T t = clazz.newInstance();
				// 处理结果集一行数据中的每一个列:给t对象指定的属性赋值
				for (int i = 0; i < columnCount; i++) {
					// 获取列值
					Object columValue = rs.getobject(i + 1);

					// 获取每个列的列名
					// String columnName = rsmd.getColumnName(i + 1);
					String columnLabel = rsmd.getColumnLabel(i + 1);

					// 给t对象指定的columnName属性,赋值为columValue:通过反射
					Field field = clazz.getDeclaredField(columnLabel);
					field.setAccessible(true);
					field.set(t, columValue);
				}
				list.add(t);
			}

			return list;
		} catch (Exception e) {
			e.printstacktrace();
		} finally {
			JDBCUtils.closeResource(null, ps, rs);

		}

		return null;
	}
	//用于查询特殊值的通用的方法
	public <E> E getValue(Connection conn,String sql,Object...args){
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			ps = conn.prepareStatement(sql);
			for(int i = 0;i < args.length;i++){
				ps.setobject(i + 1, args[i]);
				
			}
			
			rs = ps.executeQuery();
			if(rs.next()){
				return (E) rs.getobject(1);
			}
		} catch (sqlException e) {
			e.printstacktrace();
		}finally{
			JDBCUtils.closeResource(null, ps, rs);
			
		}
		return null;
		
	}	
}

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.Date;
import java.util.List;

import com.bean.Customer;

public class CustomerDAOImpl extends BaseDAO<Customer> implements CustomerDAO{
	
	
	@Override
	public void insert(Connection conn, Customer cust) {
		String sql = "insert into customers(name,email,birth)values(?,?,?)";
		update(conn, sql,cust.getName(),cust.getEmail(),cust.getBirth());
	}

	@Override
	public void deleteById(Connection conn, int id) {
		String sql = "delete from customers where id = ?";
		update(conn, sql, id);
	}

	@Override
	public void update(Connection conn, Customer cust) {
		String sql = "update customers set name = ?,email = ?,birth = ? where id = ?";
		update(conn, sql,cust.getName(),cust.getEmail(),cust.getBirth(),cust.getId());
	}

	@Override
	public Customer getCustomerById(Connection conn, int id) {
		String sql = "select id,name,email,birth from customers where id = ?";
		Customer customer = getInstance(conn, sql,id);
		return customer;
	}

	@Override
	public List<Customer> getAll(Connection conn) {
		String sql = "select id,name,email,birth from customers";
		List<Customer> list = getForList(conn, sql);
		return list;
	}

	@Override
	public Long getCount(Connection conn) {
		String sql = "select count(*) from customers";
		return getValue(conn, sql);
	}

	@Override
	public Date getMaxBirth(Connection conn) {
		String sql = "select max(birth) from customers";
		return getValue(conn, sql);
	}

}

7. 数据库连接池

1.JDBC 数据库连接池的必要性

  • 在使用开发基于数据库的web程序时, 传统的模式基本是按以下步骤:
  • 这种模式开发,存在的问题:
    • 普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。 数用 据库的连接资源并没有得到很好的重复利用.若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
    • 对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库
    • 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。

2.数据库连接池(connection pool)

  • 解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
  • 数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
  • 数据库连接池负责分配、管理和释放数据库连接,它 允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个
  • 数据库连接池在初始化时将创建一定数量数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

3.数据库连接池的工作原理

在这里插入图片描述

4.数据库连接池技术的优点

  • 资源重用
    • 由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
  • 更快的系统反应速度
  • 数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间
  • 新的资源分配手段
    • 对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源
  • 统一的连接管理,避免数据库连接泄露
    • 在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露

5.两种开源的数据库 连接池

  • JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:
  • DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池
  • DataSource 用来取代DriverManager 来获取Connection ,获取速度快,同时可以大幅度提高数据库访问速度

C3P0数据库连接池

在这里插入图片描述

DBCP数据库连接池

DBCP 数据源

  • DBCP 是 Apache 软件基金组织下的开源连接池实现,该连接池依赖该组织下的另一个开源系统:Common-pool. 如需使用该连接池实现,应在系统中增加如下两个 jar 文件
    • Commons-dbcp.jar:连接池的实现
    • Commons-pool.jar:连接池实现的依赖库
  • Tomcat 的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。

DBCP 数据源使用范例

在这里插入图片描述

c3p0案例

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

	<named-config name="hellc3p0">
		<!-- 提供获取连接的4个基本信息 -->
		<property name="driverClass">com.MysqL.jdbc.Driver</property>
		<property name="jdbcUrl">jdbc:MysqL:///test</property>
		<property name="user">root</property>
		<property name="password">abc123</property>
		
		<!-- 进行数据库连接池管理的基本信息 -->
		<!-- 当数据库连接池中的连接数不够时,c3p0一次性向数据库服务器申请的连接数 -->
		<property name="acquireIncrement">5</property>
		<!-- c3p0数据库连接池中初始化时的连接数 -->
		<property name="initialPoolSize">10</property>
		<!-- c3p0数据库连接池维护的最少连接数 -->
		<property name="minPoolSize">10</property>
		<!-- c3p0数据库连接池维护的最多的连接数 -->
		<property name="maxPoolSize">100</property>
		<!-- c3p0数据库连接池最多维护的Statement的个数 -->
		<property name="maxStatements">50</property>
		<!-- 每个连接中可以最多使用的Statement的个数 -->
		<property name="maxStatementsPerConnection">2</property>

	</named-config>
</c3p0-config>
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.sqlException;

import org.junit.Test;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.mchange.v2.c3p0.DataSources;

public class C3P0Test {
	//方式一:
	@Test
	public void testGetConnection() throws Exception{
		//获取c3p0数据库连接池
		ComboPooledDataSource cpds = new ComboPooledDataSource();
		cpds.setDriverClass( "com.MysqL.jdbc.Driver" ); 
		cpds.setJdbcUrl( "jdbc:MysqL://localhost:3306/test" );
		cpds.setUser("root");                                  
		cpds.setPassword("abc123"); 
		//通过设置相关的参数,对数据库连接池进行管理:
		//设置初始时数据库连接池中的连接数
		cpds.setinitialPoolSize(10);
		
		Connection conn = cpds.getConnection();
		System.out.println(conn);
		
		//销毁c3p0数据库连接池
//		DataSources.destroy( cpds );
	}
	//方式二:使用配置文件
	@Test
	public void testGetConnection1() throws sqlException{
		ComboPooledDataSource cpds = new ComboPooledDataSource("hellc3p0");
		Connection conn = cpds.getConnection();
		System.out.println(conn);
	}
}

dbcpTest案例

driverClassName=com.MysqL.jdbc.Driver
url=jdbc:MysqL:///test
username=root
password=abc123

initialSize=10
import java.io.File;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.sqlException;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.junit.Test;

public class DBCPTest {
	/**
	 * 
	 * @Description 测试DBCP的数据库连接池技术
	 * @author shkstart
	 * @throws sqlException 
	 * @date 下午3:20:50
	 */
	//方式一:不推荐
	@Test
	public void testGetConnection() throws sqlException{
		//创建了DBCP的数据库连接池
		BasicDataSource source = new BasicDataSource();
		
		//设置基本信息
		source.setDriverClassName("com.MysqL.jdbc.Driver");
		source.setUrl("jdbc:MysqL:///test");
		source.setUsername("root");
		source.setPassword("abc123");
		
		//还可以设置其他涉及数据库连接池管理的相关属性:
		source.setinitialSize(10);
		source.setMaxActive(10);
		//。。。
		
		Connection conn = source.getConnection();
		System.out.println(conn);
	}
	
	//方式二:推荐:使用配置文件
	@Test
	public void testGetConnection1() throws Exception{
		Properties pros = new Properties();
		
		//方式1:
//		InputStream is = ClassLoader.getSystemClassLoader().getResourceAsstream("dbcp.properties");
		//方式2:
		FileInputStream is = new FileInputStream(new File("src/dbcp.properties"));
		
		
		pros.load(is);
		DataSource source = BasicDataSourceFactory.createDataSource(pros);
		
		Connection conn = source.getConnection();
		System.out.println(conn);
	}
	
	
}

Druid案例

url=jdbc:MysqL:///test
username=root
password=abc123
driverClassName=com.MysqL.jdbc.Driver

initialSize=10
maxActive=10
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;

import javax.sql.DataSource;

import org.junit.Test;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;

public class DruidTest {
	
	@Test
	public void getConnection() throws Exception{

		Properties pros = new Properties();

		InputStream is = ClassLoader.getSystemClassLoader().getResourceAsstream("druid.properties");

		pros.load(is);

		DataSource source = DruidDataSourceFactory.createDataSource(pros);
		Connection conn = source.getConnection();
		System.out.println(conn);

	}
}

8. dbutils工具类

将常用的操作数据库的JDBC的类和方法集合在一起,就是dbutils.

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

BeanHandler:把结果集转为一个 Bean
BeanListHandler:把结果集转为一个 Bean 的集合
MapHandler:把结果集转为一个 Map
MapListHandler:把结果集转为一个 Map 的 List
ScalarHandler:把结果集转为一个类型的数据返回, 该类型通常指 String 或其它 8 种基本数据类型.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐