JDBC:数据的持久化

Java全栈

一、JDBC概述

1、 数据的持久化

持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用。

2、JDBC(Java Database Connectivity)的理解:

JDBC是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口

JDBC是SUN公司提供的一套API,使用这套API可以实现对具体数据库的操作。

(获取连接、关闭连接、DML、DDL、DCL)

3、图示理解

好处:

  • 从开发程序员的角度:不需要关注具体的数据库的细节
  • 数据库厂商:只需要提供标准的具体实现。
4、数据库驱动

数据库厂商针对于JDBC这套接口,提供的具体实现类的集合。

5、面向接口编程的思想:
  • JDBC是sun公司提供一套用于数据库操作的接口
  • 不同的数据库厂商,需要针对这套接口,提供不同实现
  • 不同的实现的集合,即为不同数据库的驱动
6、JDBC程序编写步骤

二、数据库的连接

public static void main(String[] args) throws Exception {
    // 1.提供三个连接的基本信息:
    String url = "jdbc:mysql://localhost:3306/text?serverTimezone=GMT%2B8";
    String user = "root";
    String password = "123456";

    // 2.加载Driver
    Class.forName("com.mysql.cj.jdbc.Driver");

    // 3.获取连接
    Connection conn = DriverManager.getConnection(url, user, password);
    System.out.println(conn);
}
/**
* 
* @Description 获取数据库的连接
* @date 上午9:11:23
* @return
* @throws Exception
*/
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;
}

其中,配置文件【jdbc.properties】:此配置文件声明在工程的src下

user=root
password=123456
url=jdbc:mysql://localhost:3306/db2?serverTimezone=GMT%2B8
dricerClass=com.mysql.cj.jdbc.Driver

三、Statement使用的弊端

1、拼串操作繁琐

String sql = "insert into customers(name,email,birth) values('" + name +"','"+email+"','"+birth+"')";

2、SQL注入问题

SELECT user,password FROM user_table WHERE user = '1' or ' AND password = '=1 or '1' = '1'

3、其他问题:
  • Statement 没办法操作 Blob 类型变量
  • Statement 实现批量插入时,效率较低
// 使用Statement的弊端:需要拼写sql语句,并且存在SQL注入的问题
// 如何避免出现sql注入:只要用 PreparedStatement(从Statement扩展而来) 取代 Statement
public static void main(String[] args) {
    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 = '"+ user +"' AND password = '"+ password +"'";
    User returnUser = get(sql,User.class);
    if(returnUser != null){
        System.out.println("登录成功");
    }else{
        System.out.println("用户名不存在或密码错误");
    }
}

// 使用Statement实现对数据表的查询操作
public static  <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.getColumnLabel(i + 1);
                // 2. 根据列名获取对应数据表中的数据
                Object columnVal = rs.getObject(columnName);

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

四、PreparedStatement的使用

1、PreparedStatement的理解

① PreparedStatement是Statement的子接口

② 预编译SQL语句

③ 可以解决Statement的sql注入问题,拼串问题

2、通用的增、删、改的方法(version 1.0)
/**
 * @Description 针对于不同表的通用的增删改操作
 * @param clazz
 * @param sql
 * @param args sql中占位符的个数与可变形参的长度相同!
 */
public static void update(String sql,Object ...args){
	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++){
			//占位符位置从1开始,小心参数声明错误!!
			ps.setObject(i + 1, args[i]);
		}
		//4.执行
		ps.execute();
	} catch (Exception e) {
		e.printStackTrace();
	}finally{
		//5.资源的关闭
		JDBCUtils.closeResource(conn, ps);
	}
}
3、通用的查询操作(version 1.0)
/**
 * @Description 针对于不同的表的通用的查询操作,返回表中的多条记录
 * @param clazz 
 * @param sql
 * @param args
 * @return
 */
public static <T> List<T> getForList(Class<T> clazz,String sql, Object... args){
	Connection conn = null;
	PreparedStatement ps = null;
	ResultSet rs = null;
	try {
		//1.获取数据库的连接
		conn = JDBCUtils.getConnection();
		//2.预编译sql语句,返回PreparedStatement的实例
		ps = conn.prepareStatement(sql);
		//3.填充占位符
		for(int i = 0;i < args.length;i++){
			//占位符位置从1开始,小心参数声明错误!!
			ps.setObject(i + 1, args[i]);
		}
		//4.获取结果集
		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(conn, ps, rs);
	}
	return null;
}
4、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的操作
	 */
	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 关闭资源操作
	 */
	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();
		}
	}
}
5、PreparedStatement与Statement的异同

① 指出二者的关系? 接口与子接口的关系

② 开发中,PreparedStatement替换Statement

③ An object that represents a precompiled SQL statement(预编译SQL语句的对象)

6、总结

两种思想:

  1. 面向接口编程的思想
  2. ORM编程思想(object relational mapping)
  • 一个数据表对应一个java类
  • 表中的一条记录对应java类的一个对象
  • 表中的一个字段对应java类的一个属性

两种技术:

  1. 使用结果集的元数据:ResultSetMetaData
    1. getColumnCount():获取列数
    2. getColumnLabel():获取列的别名
    3. 说明:如果sql中没给字段其别名,getColumnLabel()获取的就是列名
  1. 反射的使用
    1. 创建对应的运行时类的对象
    2. 在运行时,动态的调用指定的运行时类的属性、方法

查询的图示:

五、操作Blob类型的变量

PreparedStatement可以操作Blob类型的变量。

写入操作的方法:

setBlob(InputStream is);

读取操作的方法:

Blob blob = getBlob(int index);
InputStream is = blob.getBinaryStream();

具体的insert:

//向数据表customers中插入Blob类型的字段
public static void main1(String[] args) {
	Connection conn = null;
	PreparedStatement ps = null;
	try {
		conn = JDBCUtils.getConnection();
		String sql = "insert into customers(name,email,birth,photo)values(?,?,?,?)";
		ps = conn.prepareStatement(sql);

		ps.setObject(1,"袁浩");
		ps.setObject(2, "yuan@qq.com");
		ps.setObject(3,"1992-09-08");
		//使用IO流传输
		FileInputStream is = new FileInputStream(new File("haijing.jpg"));
		ps.setBlob(4, is);

		ps.execute();
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		JDBCUtils.closeResource(conn, ps);
	}
}

具体的query:

//查询数据表customers中Blob类型的字段
//查询数据表customers中Blob类型的字段
public static void main(String[] args) {
	Connection conn = null;
	PreparedStatement ps = null;
	ResultSet rs = null;
	InputStream in = null;
	FileOutputStream out = null;
	try {
		conn = JDBCUtils.getConnection();
		String sql = "select id,name,email,birth,photo from customers where id = ?";
		ps = conn.prepareStatement(sql);
		ps.setInt(1, 16);
		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");
			in = photo.getBinaryStream();
			out = new FileOutputStream("zuyin.jpg");
			byte[] buffer = new byte[1024];
			int len;
			while((len = in.read(buffer)) != -1){
				out.write(buffer, 0, len);
			}
		}
	} catch (Exception e) {
		e.printStackTrace();
	}finally{
		try {
			if(in != null)
				in.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			if(out != null)
				out.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		JDBCUtils.closeResource(conn, ps, rs);
	}
}

六、高效的批量插入

使用PreparedStatement实现高效的批量插入数据的操作

题目:向goods表中插入5000条数据

CREATE TABLE goods(
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(25)
 );
1、方式一:使用PreparedStatement
public static void main(String[] args) {
	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 <= 500;i++){
			ps.setObject(1, "name_" + i);
			ps.execute();
		}
		long end = System.currentTimeMillis();
		System.out.println("花费的时间为:" + (end - start));//500:30885
	} catch (Exception e) {
		e.printStackTrace();
	}finally{
		JDBCUtils.closeResource(conn, ps);
	}
}
2、方式二:

1.addBatch()、executeBatch()、clearBatch()

2.mysql服务器默认关闭批处理,需要通过一个参数,让mysql开启批处理的支持。

&&rewriteBatchedStatements=true 写在配置文件的url后面

public static void main(String[] args) {
	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 <= 5000;i++){
			ps.setObject(1, "name_" + i);
			//1."攒"sql
			ps.addBatch();
			if(i % 100 == 0){
				//2.执行batch
				ps.executeBatch();
				//3.清空batch
				ps.clearBatch();
			}
		}
		long end = System.currentTimeMillis();
		System.out.println("花费的时间为:" + (end - start));//5000:4824
	} catch (Exception e) {
		e.printStackTrace();
	}finally{
		JDBCUtils.closeResource(conn, ps);
	}
}
3、方式三:设置连接不允许自动提交数据
public static void main(String[] args) {
	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 <= 5000;i++){
			ps.setObject(1, "name_" + i);
			//1."攒"sql
			ps.addBatch();
			if(i % 100 == 0){
				//2.执行batch
				ps.executeBatch();
				//3.清空batch
				ps.clearBatch();
			}
		}
		//提交数据
		conn.commit();
		long end = System.currentTimeMillis();
		System.out.println("花费的时间为:" + (end - start));//5000:2554
	} catch (Exception e) {
		e.printStackTrace();
	}finally{
		JDBCUtils.closeResource(conn, ps);
	}
}

七、数据库的事务

1、事务

一组逻辑操作单元,使数据从一种状态变换到另一种状态。

一组逻辑操作单元:一个或多个DML操作。

2、事务处理的原则

保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。

当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。

3、说明
  1. 数据一旦提交,就不可回滚
  2. 哪些操作会导致数据的自动提交?
  • DDL操作一旦执行,都会自动提交。
    • set autocommit = false 对DDL操作失效
  • DML默认情况下,一旦执行,就会自动提交。
    • 我们可以通过set autocommit = false的方式取消DML操作的自动提交。
  • 默认在关闭连接时,会自动的提交数据
4、代码的体现
/*
 * ****************未考虑数据库事务情况下的转账操作****************
 * 针对于数据表user_table来说:AA用户给BB用户转账100
 */
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("转账成功");
}
//******************考虑数据库事务后的转账操作*******************
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();
		try {
			//3.回滚数据
			conn.rollback();
		} catch (SQLException e1) {
			e1.printStackTrace();
		}
	}finally{
		//使用数据库连接池时,应修改其为自动提交数据,以免影响下次使用
		try {
			conn.setAutoCommit(true);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		JDBCUtils.closeResource(conn, null);
	}
}
5、增删改操作(version 2.0 事务)
// 通用的增删改操作---version 2.0 (考虑上事务)
public int update(Connection conn,String sql, Object... args) {
	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;
}
6、通用的查询(version 2.0 事务)
//通用的查询操作,用于返回数据表中的一条记录(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;
}
7、事务的ACID属性
  • 原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  • 一致性(Consistency):事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
  • 隔离性(Isolation):事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
  • 持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
8、数据库的并发问题

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

  • 脏读:对于两个事务T1,T2,T1读取了已经被T2更新但还没有被提交的字段。之后, 若 T2 回滚,T1读取的内容就是临时且无效的
  • 不可重复读:对于两个事务T1,T2,T1读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字 段, 值就不同了
  • 幻读:对于两个事务T1,T2,T1从一个表中读取了一个字段, 然后T2在该表中插入了一些新的行。之后,如果T1再次读取同一个表,就会多出几行
  • 丢失更新:两个事务同时读取同一条记录,A先修改记录,B也修改记录(B不知道A修改过),B提交数据后B的修改结果覆盖了A的修改结果。
9、数据库事务的隔离性
  • 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。
  • 一个事务与其他事务隔离的程度称为隔离级别。
  • 数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度,
  • 隔离级别越高, 数据一致性就越好, 但并发性越弱。
10、数据库的四种隔离级别

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

Oracle 默认的事务隔离级别为:READ COMMITED

Mysql 支持4种事务隔离级别

Mysql默认的事务隔离级别为: REPEATABLE READ

11、MySql中查看并设置隔离级别

八、DAO

DAO(或BaseDAO):Data Access Object访问数据信息的类和接口,包括了对数据的CRUD(Create、Retrival、Update、Delete),而不包含任何业务相关的信息。

作用:为了实现功能的模块化,更有利于代码的维护和升级。

/*
 * DAO: data(base) access object
 * 封装了针对于数据表的通用的操作
 */
public abstract class BaseDAO {
	// 通用的增删改操作---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> 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;
	}
	// 通用的查询操作,用于返回数据表中的多条记录构成的集合(version 2.0:考虑上事务)
	public <T> List<T> getForList(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();
			// 创建集合对象
			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 <T> T 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 (T) rs.getObject(1);//只有一列数据
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			JDBCUtils.closeResource(null, ps, rs);
		}
		return null;
	}	
}

九、数据库连接池

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

在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:

  1. 在主程序(如servlet、beans)中建立数据库连接
  2. 进行sql操作
  3. 断开数据库连接

这种模式开发,存在的问题:

  • 普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
  • 对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。(回忆:何为Java的内存泄漏?)
  • 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
2、数据库连接池技术
  • 为解决传统开发的数据库连接问题,可以采用数据库连接池技术。
  • 数据库连接池的基本思想:为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
  • 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
  • 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
3、使用数据库连接池的好处:
  1. 资源重用

由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。

  1. 更快的系统反应速度

数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间

  1. 新的资源分配手段

对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源

  1. 统一的连接管理,避免数据库连接泄漏

在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露

自己组织语言:

  1. 提高程序的响应速度(减少了创建连接相应的时间)
  2. 降低资源的消耗(可以重复使用已经提供好的连接)
  3. 便于连接的管理
4.、多种开源的数据库连接池
  • JDBC 的数据库连接池使用 javax.sql.DataSource 接口来表示,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:
  • DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持。
  • C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。hibernate官方推荐使用
  • Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
  • BoneCP 是一个开源组织提供的数据库连接池,速度快
  • Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有BoneCP快
  • DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池
  • DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。

特别注意:

  • 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
  • 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。
(1)C3P0
  1. 导入jar包
  2. 测试连接的代码:

方式一:

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 );
}

方式二:使用配置文件

public void testGetConnection1() throws SQLException{
	ComboPooledDataSource cpds = new ComboPooledDataSource("hellc3p0");
	Connection conn = cpds.getConnection();
	System.out.println(conn);
}
<?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>
(2)DBCP
  1. 导入jar包
  2. 测试连接的代码
/**
 * @Description 使用DBCP数据库连接池技术获取数据库连接
 */
private static DataSource source;
static{
	try {
		Properties pros = new Properties();
		FileInputStream is = new FileInputStream(new File("dbcp.properties"));
		pros.load(is);
		source = BasicDataSourceFactory.createDataSource(pros);
	} catch (Exception e) {
		e.printStackTrace();
	}
}
public static Connection getConnection2() throws Exception{
	Connection conn = source.getConnection();
	return conn;
}
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///test
username=root
password=abc123
initialSize=10
(3)druid
  1. 导入jar包
  2. 测试连接的代码
/**
*@Description  使用Druid数据库连接池技术
*/
private static DataSource source1;
static{
    try {
        Properties pros = new Properties();
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
        pros.load(is);
        source1 = DruidDataSourceFactory.createDataSource(pros);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
public static Connection getConnection3() throws SQLException{
    Connection conn = source1.getConnection();
	return conn;
}
url=jdbc:mysql:///test
username=root
password=abc123
driverClassName=com.mysql.jdbc.Driver

initialSize=10
maxActive=10

十、DBUtils提供的jar包实现CRUD操作

1、导入jar包

2、使用现成的jar中的QueryRunner测试增、删、改的操作:

//测试插入
@Test
public void testInsert() {
	Connection conn = null;
	try {
		QueryRunner runner = new QueryRunner();
		conn = JDBCUtils.getConnection3();
		String sql = "insert into customers(name,email,birth)values(?,?,?)";
		int insertCount = runner.update(conn, sql, "蔡徐坤","caixukun@126.com","1997-09-08");
		System.out.println("添加了" + insertCount + "条记录");
	} catch (SQLException e) {
		e.printStackTrace();
	}finally{
		JDBCUtils.closeResource(conn, null);
	}
}
/*
 * BeanHander:是ResultSetHandler接口的实现类,用于封装表中的一条记录。
 */
@Test
public void testQuery1(){
	Connection conn = null;
	try {
		QueryRunner runner = new QueryRunner();
		conn = JDBCUtils.getConnection3();
		String sql = "select id,name,email,birth from customers where id = ?";
		BeanHandler<Customer> handler = new BeanHandler<>(Customer.class);
		Customer customer = runner.query(conn, sql, handler, 23);
		System.out.println(customer);
	} catch (SQLException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}finally{
		JDBCUtils.closeResource(conn, null);
	}
}
/*
 * BeanListHandler:是ResultSetHandler接口的实现类,用于封装表中的多条记录构成的集合。
 */
@Test
public void testQuery2() {
	Connection conn = null;
	try {
		QueryRunner runner = new QueryRunner();
		conn = JDBCUtils.getConnection3();
		String sql = "select id,name,email,birth from customers where id < ?";

		BeanListHandler<Customer>  handler = new BeanListHandler<>(Customer.class);

		List<Customer> list = runner.query(conn, sql, handler, 23);
		list.forEach(System.out::println);
	} catch (SQLException e) {
		e.printStackTrace();
	}finally{
		JDBCUtils.closeResource(conn, null);
	}
}
/*
 * ScalarHandler:用于查询特殊值
 */
@Test
public void testQuery3(){
	Connection conn = null;
	try {
		QueryRunner runner = new QueryRunner();
		conn = JDBCUtils.getConnection3();

		String sql = "select count(*) from customers";

		ScalarHandler handler = new ScalarHandler();

		Long count = (Long) runner.query(conn, sql, handler);
		System.out.println(count);
	} catch (SQLException e) {
		e.printStackTrace();
	}finally{
		JDBCUtils.closeResource(conn, null);
	}
}
/*
 * MapHander:是ResultSetHandler接口的实现类,对应表中的一条记录。
 * 将字段及相应字段的值作为map中的key和value
 */
@Test
public void testQuery4(){
	Connection conn = null;
	try {
		QueryRunner runner = new QueryRunner();
		conn = JDBCUtils.getConnection3();
		String sql = "select id,name,email,birth from customers where id = ?";
		MapHandler handler = new MapHandler();
		Map<String, Object> map = runner.query(conn, sql, handler, 23);
		System.out.println(map);
	} catch (SQLException e) {
		e.printStackTrace();
	}finally{
		JDBCUtils.closeResource(conn, null);
	}
}
/*
 * MapListHander:是ResultSetHandler接口的实现类,对应表中的多条记录。
 * 将字段及相应字段的值作为map中的key和value。将这些map添加到List中.
 */
@Test
public void testQuery5(){
	Connection conn = null;
	try {
		QueryRunner runner = new QueryRunner();
		conn = JDBCUtils.getConnection3();
		String sql = "select id,name,email,birth from customers where id < ?";

		MapListHandler handler = new MapListHandler();
		List<Map<String, Object>> list = runner.query(conn, sql, handler, 23);
		list.forEach(System.out::println);
	} catch (SQLException e) {
		e.printStackTrace();
	}finally{
		JDBCUtils.closeResource(conn, null);
	}
}
/*
 * 自定义ResultSetHandler的实现类
 */
@Test
public void testQuery6(){
	Connection conn = null;
	try {
		QueryRunner runner = new QueryRunner();
		conn = JDBCUtils.getConnection3();

		String sql = "select id,name,email,birth from customers where id = ?";
		ResultSetHandler<Customer> handler = new ResultSetHandler<Customer>(){

			@Override
			public Customer handle(ResultSet rs) throws SQLException {
				if(rs.next()){
					int id = rs.getInt("id");
					String name = rs.getString("name");
					String email = rs.getString("email");
					Date birth = rs.getDate("birth");
					Customer customer = new Customer(id, name, email, birth);
					return customer;
				}
				return null;
			}
		};
		Customer customer = runner.query(conn, sql, handler,23);
		System.out.println(customer);
	} catch (SQLException e) {
		e.printStackTrace();
	}finally{
		JDBCUtils.closeResource(conn, null);
	}
}

使用dbutils.jar包中的DbUtils工具类实现连接等资源的关闭:

/**
 * @Description 使用dbutils.jar中提供的DbUtils工具类,实现资源的关闭
 */
public static void closeResource1(Connection conn,Statement ps,ResultSet rs){
	DbUtils.closeQuietly(conn);
	DbUtils.closeQuietly(ps);
	DbUtils.closeQuietly(rs);
}
  • 12
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值