JDBC及数据库连接池

JDBC

01.JDBC概述

1.数据的持久化:

  • 把数据保存到可掉电式存储设备中以供之后使用。

2.JDBC的理解:

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

简单理解:JDBC,是SUN提供的一套API,使用这套API开源实现对具体数据的操作(获取连接、关闭连接、DML、DDL、DCL)

3.图示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HSDTsx0g-1658975922834)(F:\尚硅谷\jdbc\1-课件\课件-md\尚硅谷_宋红康_JDBC.assets\1555575941569.png)]

好处:

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

》从开发程序员的角度:不需要关注具体的数据库的细节

》数据库厂商:只需要提供标准的具体实现

4.数据库的驱动:

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

类似:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UWd3rRIc-1658975922835)(D:\code\截图\jdbc学习.png)]

5.面向接口编程的思想

JDBC是sun公司提供一套用于数据库操作的接口,java程序员只需要面向这套接口编程即可。不同的数据库厂商,需要针对这套接口,提供不同实现。不同的实现的集合,即为不同数据库的驱动。 ————面向接口编程

02.数据库的连接

前四种了解即可

//方式一:
	@Test
	public void testConnection() throws SQLException{
		//1.获取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?characterEncoding=utf8";
		Properties info = new Properties();
		info.setProperty("user", "root");
		info.setProperty("password", "123456");
		
		Connection conn = driver.connect(url, info);
		System.out.println(conn);
	}
	//方式二:对方式一的迭代:在如下的程序中不出现第三方的api,使得程序可移植性更高
	@Test
	public void test2() 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?characterEncoding=utf8";
		
		//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 test3() 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?characterEncoding=utf-8";
		String user = "root";
		String password ="123456";
		
		//注册驱动
		DriverManager.registerDriver(driver);
		
		//获取连接
		Connection conn = DriverManager.getConnection(url,user,password);
		System.out.println(conn);
	}

	//方式4:可以只是加载驱动,不用显示的注册驱动了
	@Test
	public void test4() throws Exception{
		//1、供另外三个连接的基本信息:
		String url = "jdbc:mysql://localhost:3306/test?characterEncoding=utf-8";
		String user = "root";
		String password ="123456";
		
		//2.加载驱动Driver
		Class clazz = Class.forName("com.mysql.jdbc.Driver");
//		Driver driver =(Driver)clazz.newInstance();
//		//注册驱动
//		DriverManager.registerDriver(driver);
		//为什么可以省略上述操作呢?
		/*
		 static {
		try {
			java.sql.DriverManager.registerDriver(new Driver());
		} catch (SQLException E) {
			throw new RuntimeException("Can't register driver!");
		 }
	   }	 
		 */
		
		//3.获取连接
		Connection onn = DriverManager.getConnection(url,user,password);
		System.out.println(conn);
	}

最终版掌握

//方式五(final版):将数据库连接需要的4个基本信息声明在配置文件中,通过配置文件的方式,获取;连接
	/*
	 * 此方法的好处:
	 * 1.实现了数据与代码的分离。实现了解耦
	 * 2.如果需要修改配置文件信息,可以避免程序重新打包
	 */
	
	@Test
	public void getConnection5() throws Exception{
		//1.读取配置文件中四个基本信息
		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);
		
	}

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

user=root
password=123456
url=jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC
driverClass=com.mysql.cj.jdbc.Driver

JDBCUtils.java

**
 * 操作数据库的工具类
 */
public class JDBCUtils {
	/*
	 * 获取数据库的连接
	 */
	public static Connection getConnection() throws Exception{
		//1.读取配置文件中的四个基本信息
		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;
	}
	
	/*
	 * 关闭连接和Statement的操作
	 */
	public static void closeResource(Connection conn,Statement ps){
		
		try {
			if(ps !=null)
				ps.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		try {
			if(conn!=null)
				conn.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	/*
	 * 关闭资源操作
	 */
	public static void closeResource(Connection conn,Statement ps,ResultSet rs){
		try {
			if(ps !=null)
				ps.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		try {
			if(conn!=null)
				conn.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		try {
			if(rs!=null)
				rs.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

}

03.Statement接口实现CRUD操作(了解)

public class StatementTest {

	// 使用Statement的弊端:需要拼写sql语句,并且存在SQL注入的问题
	@Test
	public void testLogin() {
		Scanner scan = new Scanner(System.in);

		System.out.print("用户名:");
		String userName = scan.nextLine();
		System.out.print("密   码:");
		String password = scan.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 = '" + userName + "' AND PASSWORD = '" + password
				+ "'";
		User user = get(sql, User.class);
		if (user != 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;
	}
}

Statement使用的弊端:

  • 问题一:存在拼串操作,繁琐

  • 问题二:存在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 就可以了。

其它问题:

  • Statement没办法操作Blob类型变量
  • Statement实现批量插入时,效率低

04.使用PreparedStatement实现CRUD操作

1.PreparedStatement的理解:

①Statement的子接口

②预编译 (好处均源于此)

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

2.使用PreparedStatement实现增、删、改的方法:version 1.0

//通用的增删改查
	public void 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.执行
			ps.execute();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			//5.资源的关闭
			JDBCUtils.closeResource(conn, ps);
		}	
	}

3.使用PreparedStatement实现通用的查询操作:Version 1.0

/*
	 * 针对于不同表的通用查询操作,返回表中的一条记录
	 * 
	 */
	public <T> T getInstance(Class<T> clazz,String sql,Object ...args){
		
		Connection conn = null;
		PreparedStatement ps =null;
		ResultSet rs =null;
		try {
			conn = JDBCUtils.getConnection();
			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();
			int columnCount = rsmd.getColumnCount();
			
			if(rs.next()){
				T t = clazz.newInstance();
				//处理结果集一行数据中的每一个列
				for(int i=0;i<columnCount;i++){
					//获取列值
					Object columnValue = rs.getObject(i+1);
					
					//获取每个列的列名
					String columnName = rsmd.getColumnLabel(i+1);
					
					//给对象指定的columnName属性,赋值为columnValue,通过反射
					Field field = clazz.getDeclaredField(columnName);
					field.setAccessible(true);
					field.set(t, columnValue);
				}
				return t;
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			JDBCUtils.closeResource(conn, ps, rs);
		}
		return null;
}

返回多个对象构成的集合

public <T> List<T> getForList(Class<T> clazz,String sql,Object ...args){
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			conn = JDBCUtils.getConnection();
			
			ps = conn.prepareStatement(sql);
			
			//填充占位符
			for(int i=0;i<args.length;i++){
				ps.setObject(i+1, args[i]);
			}	
			rs = ps.executeQuery();
			//获取结果集的元数据
			ResultSetMetaData rsmd = rs.getMetaData();
			int columnCount = rsmd.getColumnCount();
			
			//创建集合对象
			ArrayList<T> list = new ArrayList<>();
			while(rs.next()){
				T t = clazz.newInstance();
				//处理结果集一行数据中的每一个列:给t对象指定的属性赋值
				for(int i=0;i<columnCount;i++){
					//获取列值
					Object columnValue = rs.getObject(i+1);
					
					//获取每个列的列名
					String columnName = rsmd.getColumnLabel(i+1);
					
					//给t对象指定columanName属性,赋值为columanValue
					Field field = clazz.getDeclaredField(columnName);
					field.setAccessible(true);
					field.set(t, columnValue);
				}
				list.add(t);
			}
			return list;
		
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			JDBCUtils.closeResource(conn, ps, rs);
		}
		return null;		
}

总结:

两种思想:

面向接口编程的思想

ORM编程思想:(object relational mapping)

  • 一个数据表对应一个java类
  • 表中的一条记录对应java的一个对象
  • 表中的一个字段对应表中的一个属性

两种技术:

  • 使用结果集的元数据:ResultSetMetaData
    • getColumnCount():获取列名
    • getColumnLabel():获取列的别名
  • 反射的使用
    • 创建运行时类的对象
    • 在运行时,动态的调用指定的运行时类指定的属性、方法

查询流程图:

**[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wKZfvNWM-1658975922836)(F:\尚硅谷\jdbc\1-课件\课件-md\尚硅谷_宋红康_JDBC.assets\1555579816884.png)]**

05.PreparedStatement操作Blob类型的变量

MySQL BLOB类型

  • MySQL中,BLOB是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据。

  • 插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据无法使用字符串拼接写的。

  • MySQL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是等同的)

1555581069798.png

  • 实际使用中根据需要存入的数据大小定义不同的BLOB类型。
  • 需要注意的是:如果存储的文件过大,数据库的性能会下降。
  • 如果在指定了相关的Blob类型以后,还报错:xxx too large,那么在mysql的安装目录下,找my.ini文件加上如下的配置参数: max_allowed_packet=16M。同时注意:修改了my.ini文件之后,需要重新启动mysql服务。

**写入的方法:**setBlob(InputStream is);

读取操作的方法:

Blob blob = getBlob(int index);

InputStream is = blob.getBinaryStream();

写入代码:

//向customers中插入Blob类型的字段
	@Test
	public void testInsert(){
		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, "xiao@qq.com");
			ps.setObject(3, "1999-12-29");
			FileInputStream is = new FileInputStream(new File("P(clothes).jpg"));
			ps.setBlob(4, is);
			
			ps.execute();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			JDBCUtils.closeResource(conn, ps);
			
		}
	}
	

查询代码:

//查询数据表customer中Blob类型的字段
	@Test
	public void testQuery() {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		InputStream is=null;
		FileOutputStream fos =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("xiaohan.jpg");
				byte[] buffer = new byte[1024];
				int len;
				while((len = is.read(buffer))!=-1){
					fos.write(buffer,0,len);
					
				}
			}
		
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			JDBCUtils.closeResource(conn, ps,rs);
			
			try {
				if(is!=null)
					is.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			try {
				if(fos!=null)fos.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}	
		}	
	}

06.PreparedStatement实现高效的批量插入

1.测试使用PreparedStatement

层次一:使用Statement实现

代码略

层次二:使用PreparedStatement替换Statement

//批量插入的方式二:使用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));
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			JDBCUtils.closeResource(conn, ps);	
		}
	}

层次三:

  • 1.addBatch()、executeBatch()、clearBatch()
  • 2.mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql 开启批处理的支持 ?rewriteBatchedStatements=true写在配置文件的url后面
  • 3.使用更新的mysql 驱动:mysql-connector-java-8.0-bin.jar
/*
	 * 批量插入方式三:
	 * 1.addBatch()、executeBatch()、clearBatch()
	 * 2.mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持
	 *      ?rewriteBatchedStatements=true写在配置文件的url后面
	 * 3.使用更新的mysql 驱动:mysql-connector-java-8.0-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));
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			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));
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			JDBCUtils.closeResource(conn, ps);	
		}	
	}

总结:PreparedStatement与Statement的异同?(面试题)

①PreparedStatement是Statement的子接口

②开发中,PreparedStatement替换Statement

③PreparedStatement是预编译sql语句的

④PreparedStatement可以防止sql注入

  • 代码的可读性和可维护性。

  • PreparedStatement 能最大可能提高性能:

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

07.数据库事务

1.事务:

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

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

2.事务处理的原则:(笔试会考)

事务处理的原则:保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永远地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态

说明:

1.数据一旦提交,就不可回滚

4.哪些操作会导致数据库的自动提交?

  • DDL操作一旦执行,都会自动提交
    • set autocommit = false 对DDL操作失效
  • DML默认情况下,一旦执行,就会自动提交。
    • 我们可以通过set autocommit = false的方式取消DML操作的自动提交
  • 默认关闭连接时,会自动的提交数据

3.代码的体现

@Test
	public void testUpdateWithTx() {

		Connection conn = null;
		try {
			conn = JDBCUtils.getConnection();

			System.out.println(conn.getAutoCommit());
			// 1.取消数据的自动提交
			conn.setAutoCommit(false);
			System.out.println(conn.getAutoCommit());

			String sql = "update user_table set balance = balance -100 where user = ?";
			update(conn, sql, "AA");

			// 模拟网络异常
			System.out.println(10 / 0);

			String sql1 = "update user_table set balance = balance +100 where user = ?";
			update(conn, sql1, "BB");

			System.out.println("转账成功");
			conn.commit();

		} catch (Exception e) {

			e.printStackTrace();
			// 3.回滚数据
			try {
				conn.rollback();
				System.out.println(conn.getAutoCommit());

			} catch (SQLException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}

		} finally {

			// 改回去
			try {
				conn.setAutoCommit(true);
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			JDBCUtils.closeResource(conn, null);
		}
	}

考虑事务以后,实现通用的增删改操作:version2.0

public int update(Connection conn, String sql, Object... args) {

		// 1.预编译sql语句,返回PreparedStatement的实例
		PreparedStatement ps = null;
		try {
			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) {
			// TODO Auto-generated catch block
			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 {
			conn = JDBCUtils.getConnection();
			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();
			int columnCount = rsmd.getColumnCount();

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

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

					// 给对象指定的columnName属性,赋值为columnValue,通过反射
					Field field = clazz.getDeclaredField(columnName);
					field.setAccessible(true);
					field.set(t, columnValue);
				}
				return t;
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			JDBCUtils.closeResource(null, ps, rs);
		}
		return null;
	}

4.事务的ACID属性

  1. 原子性(Atomicity)
    原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

  2. 一致性(Consistency)
    事务必须使数据库从一个一致性状态变换到另外一个一致性状态。

  3. 隔离性(Isolation)
    事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

  4. 持久性(Durability)
    持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。

5.数据操作过程中可能出现的问题:(针对隔离性)

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

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

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

6.数据库的四种隔离级别:(一致性和并发性:一致性越好,并发性越差)

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OZ7S9PVb-1658975922837)(F:/尚硅谷/jdbc/1-课件/课件-md/尚硅谷_宋红康_JDBC.assets/1555586275271.png)]

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

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

如何查看和设置隔离级别:

  • 查看当前的隔离级别:

    SELECT @@tx_isolation;
    
  • 设置当前 mySQL 连接的隔离级别:

    set  transaction isolation level read committed;
    
  • 设置数据库系统的全局的隔离级别:

    set global transaction isolation level read committed;
    

08.DAO及其子类

封装了针对于数据表的通用的操作

/*
 * DAO:data(base) access object 
 * 
 * 封装了针对于数据表的通用的操作
 */
public abstract class BaseDAO<T> {
	
	private Class<T> clazz = null;
	
	{
		//获取当前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) {

		// 1.预编译sql语句,返回PreparedStatement的实例
		PreparedStatement ps = null;
		try {
			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) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			// 4.资源关闭
			JDBCUtils.closeResource(null, ps);

		}
		return 0;
	}

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

			// 填充占位符
			for (int i = 0; i < args.length; i++) {
				ps.setObject(i + 1, args[i]);
			}

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

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

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

					// 给t对象指定columanName属性,赋值为columanValue
					Field field = clazz.getDeclaredField(columnName);
					field.setAccessible(true);
					field.set(t, columnValue);
				}
				list.add(t);
			}
			return list;

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			JDBCUtils.closeResource(null, ps, rs);
		}

		return null;

	}

	// 通用的查询操作,用于返回数据表中的一条记录(version 2.0:考虑事务)
	public T getInstance(Connection conn, String sql, Object... args) {
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			conn = JDBCUtils.getConnection();
			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();
			int columnCount = rsmd.getColumnCount();

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

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

					// 给对象指定的columnName属性,赋值为columnValue,通过反射
					Field field = clazz.getDeclaredField(columnName);
					field.setAccessible(true);
					field.set(t, columnValue);
				}
				return t;
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			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) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			JDBCUtils.closeResource(null, ps, rs);
			
		}
		return null;
	}
}


CustomerDao接口:

/*
 * 此接口用于规范针对于customers表的常用操作
 */
public interface CustomerDao {
	/*
	 * 将cust对象添加到数据库中
	 */
	/*
	 * 针对于内存中的cust对象,去修改数据库中指定的记录
	 */
	void insert(Connection conn,Customer cust);
	
	void deleteById(Connection conn,int id);
	
	void update(Connection conn,Customer cust);
	
	/*
	 * 针对于指定的id查询得到对应的Customer对象
	 */
	Customer getCustomerById(Connection conn,int id);

	/*
	 * 查询表中的所有记录构成的集合
	 */
	List<Customer>getAll (Connection conn);
	
	/*
	 * 返回数据表中数据的条目数
	 */
	Long getCount(Connection conn);
	
	/*
	 * 返回数据表中最大的生日
	 */
	Date getMaxBirth(Connection conn);
}

CustomerDaoImpl:

public class CustomerDAOImpl extends BaseDAO<Customer> implements CustomerDao{


	
	@Override
	public void insert(Connection conn, Customer cust) {
		// TODO Auto-generated method stub
		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);
	}
}

总结:考虑到事务以后的数据库操作(重点)

1.获取数据库的连接

Connection conn = JDBCUtils.getConnection();//方式1:手动获取连接;方式二:数据库连接池

conn.setAutoCommit(false); //体现事务

2.如下的多个DML操作,作为一个事务出现:

操作1:需要使用通用的增删改查操作 //通用的增删改查如何实现?

操作2:需要使用通用的增删改查操作 //方式1:手动使用PreparedStatement

操作3:需要使用通用的增删改查操作 //方式2:使用dbutils.jar中QueryRunnrt类

conn.commit();

3.如果出现异常,则:

conn.rollback();

4.关闭资源

JDBCUtils.closeResource(…); //方式1:手动关闭; 方式2:DbUtils类的关闭方法

09.数据库连接池

1.传统连接的问题:

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

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

2.如何解决传统开发中的数据库连接问题

使用数据库连接池

3.数据库连接池的好处:

  • 提高程序的响应速度(减少了创建连接相应的时间)
  • 降低资源的消耗(可以重复使用已经提供好的连接)
  • 便于连接的管理

4.实现的方式:

  • DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持。
  • C3P0 是一个开源组织提供的一个数据库连接池,**速度相对较慢,稳定性还可以。**hibernate官方推荐使用
  • Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有BoneCP快

5.C3P0

导入jar包:c3p0-0.9.1.2.jar

测试连接的代码:·

public class C3P0Test {
	//方式一:
	@Test
	public void testGetConnection() throws Exception{
		//获取c3p0数据库连接池
		ComboPooledDataSource cpds = new ComboPooledDataSource();
		cpds.setDriverClass( "com.mysql.cj.jdbc.Driver" ); //loads the jdbc driver            
		cpds.setJdbcUrl( "jdbc:mysql://localhost:13306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC" );
		cpds.setUser("root");                                  
		cpds.setPassword("root");    
		
		//通过设置相关的参数对数据库连接池进行管理
		//设置初始时数据库连接池中的连接数
		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("helloc3p0");
		
		Connection conn = cpds.getConnection();
		System.out.println(conn);
	}
}

配置文件:在src下叫c3p0-config.xml

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


<c3p0-config>
  <!-- This app is massive! -->
  <named-config name="helloc3p0"> 
    <!-- 提供获取连接的4个基本信息 -->
    <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://localhost:13306/test?serverTimezone=UTC</property>
    <property name="user">root</property>
    <property name="password">root</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">5</property>
  </named-config>
</c3p0-config>

6.DBCP

到jar包:commons-dbcp-1.4.jar

public class DBCPTest {

	/*
	 * 测试DBCP的数据库连接池技术
	 */
	//方式一:不推荐
	@Test
	public void testGetConnection() throws SQLException{
		//创建了DBCP的数据库连接池
		BasicDataSource source = new BasicDataSource();
		
		//设置基本信息
		source.setDriverClassName("com.mysql.cj.jdbc.Driver");
		source.setUrl("jdbc:mysql://localhost:13306/test?serverTimezone=UTC");
		source.setUsername("root");
		source.setPassword("root");
		
		//还可以设置其它数据库连接池管理的相关属性
		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);
	}
}

配置文件在src下:dbcp.properties

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:13306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC
username=root
password=root

initialSize=10

7.Druid

导jar包:commons-dbutils-1.3.jar

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

配置文件:druid.properties

url=jdbc:mysql://localhost:13306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC
username=root
password=root
driverClassName=com.mysql.cj.jdbc.Driver

initialSize=10
maxActive=10

8.DBUtils提供的jar包实现CRUD操作

导入jar包:commons-dbutils-1.3.jar

使用现成的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 i = runner.update(conn,sql, "蔡徐坤","caixukun@126.com","1997-09-08");
			
			System.out.println("添加了"+i+"条记录");
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			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,22);
			System.out.println(customer);
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			JDBCUtils.closeResource(conn, null);
		}
	}
	/*
	 * BeanLsitHandler:是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,22);
			list.forEach(System.out::println);
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			JDBCUtils.closeResource(conn, null);
		}
	}
	/*
	 * MapHander:是ResultSetHandler接口的实现类,用于封装表中的一条记录
	 * 将字段及相应字段的值作为map中的key和value
	 */
	@Test
	public void testQuery3(){
		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,22);
			System.out.println(map);
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			JDBCUtils.closeResource(conn, null);
		}
	}
	
	/*
	 * MapListHander:是ResultSetHandler接口的实现类,用于封装表中的多条记录
	 * 将字段及相应字段的值作为map中的key和value,将这些map添加到list中
	 */
	@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 <?";
			
			MapListHandler handler = new MapListHandler();
			
			List<Map<String,Object>> list = runner.query(conn,sql,handler,22);
			list.forEach(System.out::println);
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			JDBCUtils.closeResource(conn, null);
		}
	}
	/*
	 * ScalarHandler:用于查询特殊值
	 */
	
	@Test
	public void testQuery5(){
		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) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			JDBCUtils.closeResource(conn, null);
		}
	}
	
	@Test
	public void testQuery6(){
		Connection conn = null;
		try {
			QueryRunner runner = new QueryRunner();
			conn = JDBCUtils.getConnection3();
			String sql ="select max(birth) from customers";
			
			ScalarHandler handler = new ScalarHandler();
			
			Date maxBirth = (Date)runner.query(conn,sql,handler);
			
			System.out.println(maxBirth);
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			JDBCUtils.closeResource(conn, null);
		}
	}
	
    /*
     * 自定义ResultSetHandler的实现类
     */
	@Test
	public void testQuery7(){
		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 {
//					System.out.println("handler");
//					return null;
					
//					return new Customer(12,"成龙","Jack@126.com",new Date(45454L));
					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) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			JDBCUtils.closeResource(conn, null);
		}
	}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值