最全的jdbc笔记

JDBC概述
  1. 数据的持久化

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

  2. JDBC的理解

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

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

  3. JDBC的好处

    • 面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)
    • 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序使用
    • 从开发程序员的角度:不需要关注具体的数据库的细节
    • 从数据库厂商的角度:只需要提供标准的具体实现
  4. 数据库的驱动

    • 数据库厂商针对于JDBC这套接口,提供的具体实现类的集合
  5. 面向接口编程的思想

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

获取Connection对象的五种方式
  1. 方式一:

     public void testConnection1() throws SQLException{
    		//获取Driver的实现类对象
    		Driver driver = new com.mysql.jdbc.Driver();
    		
    		/*
    		 * 协议:jdbc
    		 * 子协议:mysql
    		 * 子名称:localhost:3306/test
    		 */
    		String url = "jdbc:mysql://localhost:3306/test";
    		
    		//将用户名和密码封装在Properties中
    		Properties info = new Properties();
    		info.setProperty("user", "root");
    		info.setProperty("password", "root");
    		
    		//获取连接对象
    		Connection conn = driver.connect(url, info);
    		System.out.println(conn);
    	}
    
  2. 方式二:对方式一的迭代:在如下程序中不出现第三方的api,使得程序具有更好的可移植性

    	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", "root");
    		
    		//4.获取连接
    		Connection conn = driver.connect(url, info);
    		System.out.println(conn);
    	}
    
  3. 方式三:使用DriverManager替换Driver

    	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 = "root";
    		
    		//注册驱动
    		DriverManager.registerDriver(driver);
    		
    		//获取连接
    		Connection conn = DriverManager.getConnection(url, user, password);
    		System.out.println(conn);
    	}
    
  4. 方式四:可以只是加载驱动,不用显式的注册驱动

    	public void testConnection4() throws Exception{
    		//1.提供三个连接的基本信息
    		String url = "jdbc:mysql://localhost:3306/test";  
    		String user = "root";
    		String password = "root";
    		
    		//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);
    	}
    
  5. 方式五(final,建议使用):将数据库连接需要的四个基本信息声明在配置文件中,通过读取配置文件的方式,获取连接

    	/*
    	 * 此种方式的好处:
    	 * 1.实现了数据与代码的分离,实现了解耦
    	 * 2.如果需要修改配置文件信息,可以避免程序重新打包
    	 */
    	public void testConnection5() 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 driver = pros.getProperty("driver");
    		
    		//2.加载驱动
    		Class.forName(driver);
    		
    		//3.获取连接
    		Connection conn = DriverManager.getConnection(url, user, password);
    		System.out.println(conn);
    	}
    
    jdbc.properties
    user=root
    password=root
    url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
    driver=com.mysql.jdbc.Driver

    JDBCUtils工具类
    public class JDBCUtils {
    	/**
    	 * @Description 获取数据库的连接
    	 * @return
    	 * @throws Exception
    	 */
    	public static Connection getConnection() throws Exception{
    		//读取配置文件(就是上文中的jdbc.properties)中的四个基本信息
    		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 driver = pros.getProperty("driver");
    		
    		//加载驱动
    		Class.forName(driver);
    		
    		//获取连接
    		Connection conn = DriverManager.getConnection(url, user, password);
    		return conn;
    	}
    	/**
    	 * @Description 关闭连接和Statement的操作
    	 * @param stmt
    	 * @param conn
    	 */
    	public static void closeResource(Statement stmt,Connection conn){
    		try{
    			if(stmt != null) stmt.close();
    			if(conn != null) conn.close();
    		} catch(SQLException e){
    			e.printStackTrace();
    		}
    	}
    	
    	/**
    	 * @Description 关闭连接、Statement和ResultSet的操作
    	 * @param ps
    	 * @param conn
    	 * @param resultSet 结果集
    	 */
    	public static void closeResource(PreparedStatement ps,Connection conn,ResultSet resultSet){
    		try{
    			if(ps != null) ps.close();
    			if(conn != null) conn.close();
    			if(resultSet != null) resultSet.close();
    		} catch(SQLException e){
    			e.printStackTrace();
    		}
    	}
    }
    

    Statement接口实现CRUD操作
    	/*
    	 * 实现对数据表中数据的增删改操作
    	 */
    	public void update(String sql){
    		Connection conn = null;
    		Statement st = null;
    		try {
    			//1.获取数据库的连接
    			conn = JDBCUtils.getConnection();
    			
    			//2.创建一个Statement的实例
    			st = conn.createStatement();
    			
    			//3.根据提供的sql语句,执行
    			st.execute(sql);
    			
    		} catch (Exception e) {
    			e.printStackTrace();
    		}finally{
    			//4.资源的关闭
    			JDBCUtils.close(conn, st);
    		}
    	}
    
        /*
         * 实现对数据表的查询操作。需要使用结果集:ResultSet
         * 
         */
    	public <T> T get(String sql, Class<T> clazz) {// (sql, Customer.class)
    		T t = null;
    		
    		Connection conn = null;
    		Statement st = null;
    		ResultSet rs = null;
    		try {
    			conn = JDBCUtils.getConnection();
    
    			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. 将数据表中得到的数据,封装进对象
    					// 注意:反射根据Java中类的属性获取Field对象
    					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的弊端
    1. 需要拼接sql语句,繁琐

      String sql = "select user,password from user_table where user = '"+ user +"' and password = '"+password+"'";
      
    2. 存在SQL注入的问题

      • SQL注入是利用某些系统没有对用户输入进行充分的检查,而在用户输入数据中注入非法的SQL语句段或者命令(如:select user,password from user_table where user=‘a’ or 1=’ and password=’ or ‘1’ = ‘1’),从而利用系统的SQL引擎完成恶意行为的做法。
    3. 其他问题

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

    PreparedStatement的理解:
    1. PreparedStatement 是Statement的子接口
    2. An object that represents a precompiled SQL statement,可以预编译SQL语句
    3. 可以解决Statement的sql注入问题,拼串问题

    PreparedStatement替换Statement实现CRUD操作
    1. 两种思想
      1. ORM编程思想(object relational mapping)
        1. 一个数据表对应一个java类
        2. 表中的一条记录对应java类的一个对象
        3. 表中的一个字段对应java类的一个属性
      2. 面向接口编程的思想
    2. 两种技术
      1. 使用结果集的元数据:ResultSetMetaData
        • getColumnCount():获取列数
        • getColumnLabel():获取列的别名
        • 说明:如果sql中没给字段其别名,getColumnLabel()获取的就是列名
      2. 反射的使用
        1. 创建对应的运行时类的对象
        2. 在运行时,动态的调用指定的运行时类的属性、方法)
    3. 通用的增删改操作(针对于不同表)
    	//通用的增删改操作
    	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 (Exception e) {
    			e.printStackTrace();
    		} finally {
    			//5.资源的关闭
    			JDBCUtils.closeResource(ps, conn);
    		}
    }
    
    1. 通用的查询操作(针对于不同表)
    • 针对于表的字段名跟类的属性名不一致时:

      • 1.必须在声明sql时,用类的属性名来命名字段的别名

      • 2.在使用ResultSetMetaData时,需要使用getColumnLabel()来替换 getColumnName(),获取列的别名

      • 说明:如果sql中没有给字段起别名,getColumnLabel()获取的就是列名

    • 获取一个对象

      	/*
      	 * 使用PreparedStatement获取一个对象
      	 */
      	public <T> T getInstance(Class<T> clazz,String sql,Object... args) {
      		Connection conn = null;
      		PreparedStatement ps = null;
      		ResultSet resultSet = 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.执行,获取结果集
      			resultSet = ps.executeQuery();
      			//5.获取结果集的元数据ResultSetMetaData
      			ResultSetMetaData metaData = resultSet.getMetaData();
      			//通过元数据获取结果集的列数
      			int columnCount = metaData.getColumnCount();
      			if(resultSet.next()){
      				T t = clazz.newInstance();
      				//处理结果集一行数据中的每一个列:给t对象指定的属性赋值
      				for(int i = 0;i < columnCount;i++){
      					//获取列值
      					Object columnValue = resultSet.getObject(i + 1);
      					//获取每个列的列名或者列的别名
      					String columnLabel = metaData.getColumnLabel(i + 1);
      					//根据列名找到类中对应的属性
      					Field field = clazz.getDeclaredField(columnLabel);
      					//将此对象的 accessible标志设置为true。
      					field.setAccessible(true);
      					//将列值赋值给对应的对象的对应属性
      					field.set(t, columnValue);
      				}
      				return t;
      			}
      		} catch (Exception e) {
      			e.printStackTrace();
      		} finally{
      			JDBCUtils.closeResource(ps, conn, resultSet);
      		}
      		return null;
         }
      
    • 获取多个对象

      	public <T> List<T> getForList(Class<T> clazz,String sql,Object... args){
      		Connection conn = null;
      		PreparedStatement ps = null;
      		ResultSet resultSet = 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.执行,获取结果集
      			resultSet = ps.executeQuery();
      			//5.获取结果集的元数据ResultSetMetaData
      			ResultSetMetaData metaData = resultSet.getMetaData();
      			//通过元数据获取结果集的列数
      			int columnCount = metaData.getColumnCount();
      			//创建集合对象
      			ArrayList<T> list = new ArrayList<T>();
      			while(resultSet.next()){
      				T t = clazz.newInstance();
      				//处理结果集一行数据中的每一个列:给t对象指定的属性赋值
      				for(int i = 0;i < columnCount;i++){
      					//获取列值
      					Object columnValue = resultSet.getObject(i + 1);
      					//获取每个列的列名或者列的别名
      					String columnLabel = metaData.getColumnLabel(i + 1);
      					//根据列名找到类中对应的属性
      					Field field = clazz.getDeclaredField(columnLabel);
      					//将此对象的 accessible标志设置为true。
      					field.setAccessible(true);
      					//将列值赋值给对应的对象的对应属性
      					field.set(t, columnValue);
      				}
      				list.add(t);
      			}
      			return list;
      		} catch (Exception e) {
      			e.printStackTrace();
      		} finally{
      			JDBCUtils.closeResource(ps, conn, resultSet);
      		}
      		return null;
         }
      

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

      • 写入操作的方法:setBlob(InputStream is);

      • 读取操作的方法:

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

      • 具体的insert

        	//向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, "mama");
        		ps.setObject(2, "mama@qq.com");
        		ps.setObject(3, "2200-12-31");
        		InputStream is = new FileInputStream(new File("a.jpg"));
        		ps.setBlob(4, is);
        		ps.execute();
        		JDBCUtils.closeResource(ps, conn);
        	}
        
      • 具体的query

        	//查询customers表中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.setObject(1, 22);
        			
        			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("2.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();
        				if(fos != null) fos.close();
        			} catch (IOException e) {
        				e.printStackTrace();
        			}
        			JDBCUtils.closeResource(ps, conn, rs);
        		}
        	}
        
      • 注意:如果在制定了相关的Blob类型以后,还报错:xxx too large,那么在mysql的安装目录下,找到my.ini文件,加上如下配置参数:

        max_allowed_packet=16MB
        

        同时注意:修改了my.ini文件之后,需要重新启动mysql服务。

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

        • TinyBlob 255字节
        • Blob 65K字节
        • MediumBlob 16M字节
        • LongBlob 4G字节

PreparedStatement实现高效的批量插入
  • 层次一:使用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);
    }
  • 层次二:使用PreparedStatement替换Statement
    上面已经提到了,此处省略。

  • 层次三:

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

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

    ?rewriteBatchedStatements=true
    

    写在配置文件的url后面

  3. 使用更新的mysql 驱动:mysql-connector-java-5.1.37-bin.jar

  • 层次四:设置连接不允许自动提交数据

  • 最终版的代码体现

    	@Test
    	public void insertTest4() {
    		Connection conn = null;
    		PreparedStatement ps = null;
    		try {
    			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();
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally{
    			JDBCUtils.closeResource(ps, conn);
    		}
    	}
    

总结:PreparedStatement与Statement的异同?
  1. 指出二者的关系? 接口 与 子接口的关系
  2. 开发中,PreparedStatement替换Statement
  3. An object that represents a precompiled SQL statement,预编译SQL语句
  • 代码的可读性和可维护性。
  • PreparedStatement 能最大可能提高性能:
    • DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
    • 在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。
    • (语法检查,语义检查,翻译成二进制命令,缓存)
  • PreparedStatement 可以防止 SQL 注入

数据库的事务
  1. 什么叫数据库事务

    • 一组逻辑操作单元,使数据从一种状态变换到另一种状态。
    • 一组逻辑操作单元:一个或多个DML操作
  2. 事务处理的原则

    • 保证所有事务都作为一个工作单元执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久的保存下来;要么数据库管理系统将放弃所做的所有修改,整个事务**回滚(rollback)**到最初的状态。
    • 说明:
      • 数据一旦提交,就不可回滚。
      • 哪些操作会导致数据库自动提交数据
        • DDL操作一旦执行,都会自动提交。(set autocommit = false对DDL操作失效)
        • DML默认情况下,一旦执行,就会自动提交。(我们可以通过set autocommit = false的方式取消DML操作的自动提交)
        • 默认在关闭连接的时候会自动提交数据
  3. 代码实现

    	@Test
    	public void testUpdateWithTransaction() {
    		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 {
    			try {
    				//修改其为自动提交数据
    				//主要针对于使用数据库连接池的情况
    				conn.setAutoCommit(true);
    			} catch (SQLException e) {
    				e.printStackTrace();
    			}
    			JDBCUtils.closeResource(null, conn);
    		}
    	}
    
  4. 考虑到事务以后,实现的通用的增删改操作:

    	// 通用的增删改操作---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(ps, null);
    		}
    		return 0;
    	}
    
  5. 考虑到事务以后,实现的通用的查询: version 2.0

    	//通用的查询操作,用于返回数据表中的一条记录(version2.0,考虑上事务)
    	public <T> T getInstance(Connection conn,Class<T> clazz,String sql,Object... args) {
    		PreparedStatement ps = null;
    		ResultSet resultSet = 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.执行,获取结果集
    			resultSet = ps.executeQuery();
    			//4.获取结果集的元数据ResultSetMetaData
    			ResultSetMetaData metaData = resultSet.getMetaData();
    			//通过元数据获取结果集的列数
    			int columnCount = metaData.getColumnCount();
    			if(resultSet.next()){
    				T t = clazz.newInstance();
    				//处理结果集一行数据中的每一个列:给t对象指定的属性赋值
    				for(int i = 0;i < columnCount;i++){
    					//获取列值
    					Object columnValue = resultSet.getObject(i + 1);
    					//获取每个列的列名或者列的别名
    					String columnLabel = metaData.getColumnLabel(i + 1);
    					//根据列名找到类中对应的属性
    					Field field = clazz.getDeclaredField(columnLabel);
    					//将此对象的 accessible标志设置为true。
    					field.setAccessible(true);
    					//将列值赋值给对应的对象的对应属性
    					field.set(t, columnValue);
    				}
    				return t;
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally{
    			JDBCUtils.closeResource(ps, null, resultSet);
    		}
    		return null;
    	}
    

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

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

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

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

  • 数据操作过程中可能出现的问题:(针对隔离性)
  • 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
    • 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
    • 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
    • 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
  • 数据库的四种隔离级别:(一致性和并发性:一致性越好,并发性越差)

    • READ UNCOMMITTED(读未提交数据):允许事务读取未被其他事务提交的变更,脏读、不可重复读和幻读的问题都会出现
    • READ COMMITTED(读已提交数据):只允许事务读取已经被其他事务提交的变更,可以避免脏读,但不可重复度和幻读问题仍然可能出现
    • REPEATABLE READ(可重复读):确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,但幻读的问题仍然存在
    • SERIALIZABLE(串行化):确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,所有并发问题都可以避免,但性能十分低下
    • 不同数据库支持的隔离级别:
      • Oracle支持的2种事务隔离级别:READ COMMITTED、SERIALIZABLE。Oracle默认的事务隔离级别为:READ COMMITTED
      • Mysql支持4种事务隔离级别。Mysql默认的事务隔离级别为:REPEATABLE READ
  • 如何查看并设置隔离级别

    • 查看当前的隔离级别:

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

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

      set global transaction isolation level read committed;
      

DAO及其子类
  • BaseDAO

    /*
     * 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(ps, null);
    		}
    		return 0;
    	}
    
    	// 通用的查询操作,用于返回数据表中的一条记录(version2.0,考虑上事务)
    	public T getInstance(Connection conn, String sql, Object... args) {
    		PreparedStatement ps = null;
    		ResultSet resultSet = 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.执行,获取结果集
    			resultSet = ps.executeQuery();
    			// 4.获取结果集的元数据ResultSetMetaData
    			ResultSetMetaData metaData = resultSet.getMetaData();
    			// 通过元数据获取结果集的列数
    			int columnCount = metaData.getColumnCount();
    			if (resultSet.next()) {
    				T t = clazz.newInstance();
    				// 处理结果集一行数据中的每一个列:给t对象指定的属性赋值
    				for (int i = 0; i < columnCount; i++) {
    					// 获取列值
    					Object columnValue = resultSet.getObject(i + 1);
    					// 获取每个列的列名或者列的别名
    					String columnLabel = metaData.getColumnLabel(i + 1);
    					// 根据列名找到类中对应的属性
    					Field field = clazz.getDeclaredField(columnLabel);
    					// 将此对象的 accessible标志设置为true。
    					field.setAccessible(true);
    					// 将列值赋值给对应的对象的对应属性
    					field.set(t, columnValue);
    				}
    				return t;
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			JDBCUtils.closeResource(ps, null, resultSet);
    		}
    		return null;
    	}
    	// 通用的查询操作,用于返回数据表中的多条记录构成的集合(version2.0,考虑上事务)
    	public List<T> getForList(Connection conn, String sql, Object... args) {
    		PreparedStatement ps = null;
    		ResultSet resultSet = 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.执行,获取结果集
    			resultSet = ps.executeQuery();
    			// 4.获取结果集的元数据ResultSetMetaData
    			ResultSetMetaData metaData = resultSet.getMetaData();
    			// 通过元数据获取结果集的列数
    			int columnCount = metaData.getColumnCount();
    			// 创建集合对象
    			ArrayList<T> list = new ArrayList<T>();
    			while (resultSet.next()) {
    				T t = clazz.newInstance();
    				// 处理结果集一行数据中的每一个列:给t对象指定的属性赋值
    				for (int i = 0; i < columnCount; i++) {
    					// 获取列值
    					Object columnValue = resultSet.getObject(i + 1);
    					// 获取每个列的列名或者列的别名
    					String columnLabel = metaData.getColumnLabel(i + 1);
    					// 根据列名找到类中对应的属性
    					Field field = clazz.getDeclaredField(columnLabel);
    					// 将此对象的 accessible标志设置为true。
    					field.setAccessible(true);
    					// 将列值赋值给对应的对象的对应属性
    					field.set(t, columnValue);
    				}
    				list.add(t);
    			}
    			return list;
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			JDBCUtils.closeResource(ps, null, resultSet);
    		}
    		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(ps, null, rs);
    		}
    		return null;
    	}
    }
    
  • CustomerDAO

    /*
     * 此接口用于规范针对于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
    	 * @return
    	 */
    	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);
    }
    
  • CustomerDAOImpl

    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);
    	}
    
    }
    
  • 总结:考虑到事务以后的数据库操作(重点)

    1. 获取数据库的连接

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

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

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

    ​ 操作1:需要使用通用的增删改查操作 //通用的增删改查操作如何实现?
    ​ //方式1:手动使用PreparedStatement实现
    ​ 操作2:需要使用通用的增删改查操作 //方式2:使用dbutils.jar中QueryRunner类

    ​ 操作3:需要使用通用的增删改查操作

    ​ conn.commit();

    1. 如果出现异常,则:
      conn.rollback();

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


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

    • 在主程序(如servlet、beans)中建立数据库连接
    • 进行sql操作
    • 断开数据库连接
  • 传统连接的问题

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

  • 使用数据库连接池的好处

    1. 资源重用

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

    2. 更快的系统反应速度

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

    3. 新的资源分配手段

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

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

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

    或者说大白话:

    1. 提高程序的响应速度(减少了创建连接需要的时间)
    2. 降低资源的消耗(可以重复使用已经提供好的连接)
    3. 便于连接的管理
  • 实现的方式

    • DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持。

    • C3P0 是一个开源组织提供的一个数据库连接池,**速度相对较慢,稳定性还可以。**hibernate官方推荐使用

    • Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点

    • BoneCP 是一个开源组织提供的数据库连接池,速度快

    • Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有BoneCP快


C3P0数据库连接池
  1. 导入jar包

    c3p0-0.9.1.2.jar

  2. 测试连接的代码

    /**
    	 * 
    	 * @Description 使用C3P0的数据库连接池技术
    	 * @return
    	 * @throws SQLException
    	 */
    	//数据库连接池只需提供一个即可。
    	private static ComboPooledDataSource cpds = new ComboPooledDataSource("hellc3p0");
    	public static Connection getConnection1() throws SQLException{
    		Connection conn = cpds.getConnection();
    		
    		return conn;
    	}
    
  3. 配置文件定义在src下。名为:c3p0-config.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <c3p0-config>
    	<named-config name="helloc3p0">
    		<!-- 提供获取连接的四个基本信息 -->
    		<property name="driverClass">com.mysql.jdbc.Driver</property>
    		<property name="jdbcUrl">jdbc:mysql://localhost:3306/test</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">2</property>
    
    	</named-config>
    </c3p0-config>
    

DBCP数据库连接池
  1. 导入jar包

    1. commons-dbcp-1.4.jar
    2. commons-pool-1.5.5.jar
  2. 测试连接的代码

    /**
    	 * 
    	 * @Description 使用DBCP数据库连接池技术获取数据库连接
    	 * @return
    	 * @throws Exception
    	 */
    	//创建一个DBCP数据库连接池
    	private static DataSource source;
    	static{
    		try {
    			Properties pros = new Properties();
    			FileInputStream is = new FileInputStream(new File("src/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;
    	}
    
  3. 配置文件在src下。名为:dbcp.properties

    driverClassName=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/test
    username=root
    password=root
    
    initialSize=10
    

Druid(德鲁伊)数据库连接池
  1. 导入jar包

    druid-1.1.10.jar

  2. 测试连接的代码

    	/**
    	 * 使用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;
    	}
    
  3. 配置文件在src下。名为:druid.properties

    url=jdbc:mysql://localhost:3306/test
    username=root
    password=root
    driverClassName=com.mysql.jdbc.Driver
    
    initialSize=10
    maxActive=10
    

DBUtils提供的jar包实现CRUD操作
  1. 导入jar包

    commons-dbutils-1.3.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);
    		}
    		
    	}
    
  3. 使用现成的jar中的QueryRunner测试查询的操作:

    	//测试查询
    	/*
    	 * 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);
    		}
    		
    	}
    	
    	/*
    	 * 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, 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 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, 23);
    			list.forEach(System.out::println);
    		} catch (SQLException e) {
    			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) {
    			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) {
    			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("handle");
    //					return null;
    					
    //					return new Customer(12, "成龙", "Jacky@126.com", new Date(234324234324L));
    					
    					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);
    			
    		}
    		
    	}
    
  4. 使用dbutils.jar包中的DbUtils工具类实现连接等资源的关闭:

    	/**
    	 * 
    	 * @Description 使用dbutils.jar中提供的DbUtils工具类,实现资源的关闭
    	 * @param conn
    	 * @param ps
    	 * @param rs
    	 */
    	public static void closeResource1(Connection conn,Statement ps,ResultSet rs){
    //		try {
    //			DbUtils.close(conn);
    //		} catch (SQLException e) {
    //			e.printStackTrace();
    //		}
    //		try {
    //			DbUtils.close(ps);
    //		} catch (SQLException e) {
    //			e.printStackTrace();
    //		}
    //		try {
    //			DbUtils.close(rs);
    //		} catch (SQLException e) {
    //			e.printStackTrace();
    //		}
    		
    		DbUtils.closeQuietly(conn);
    		DbUtils.closeQuietly(ps);
    		DbUtils.closeQuietly(rs);
    	}
    
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JDBCJava Database Connectivity)是Java语言操作数据库的基础API,它提供了一种标准的方法来连接和操作各种关系型数据库。 JDBC的基本使用分为以下几个步骤: 1. 加载驱动程序:在使用JDBC操作数据库之前,需要先加载相应的驱动程序。不同的数据库需要加载不同的驱动程序,一般情况下驱动程序的jar包都会提供。 ```java Class.forName("com.mysql.jdbc.Driver"); ``` 2. 建立连接:使用DriverManager类的getConnection()方法连接数据库。 ```java String url = "jdbc:mysql://localhost:3306/test?useSSL=false"; String username = "root"; String password = "123456"; Connection conn = DriverManager.getConnection(url, username, password); ``` 其中url为连接数据库的URL,username和password为连接数据库的用户名和密码。 3. 创建Statement对象:通过Connection对象的createStatement()方法创建一个Statement对象,用于执行SQL语句。 ```java Statement stmt = conn.createStatement(); ``` 4. 执行SQL语句:可以通过Statement对象的execute()或executeQuery()方法执行SQL语句,如果是更新操作则使用executeUpdate()方法。 ```java ResultSet rs = stmt.executeQuery("SELECT * FROM users"); ``` 5. 处理结果集:如果执行的SQL语句返回了结果集,则可以通过ResultSet对象进行处理。 ```java while (rs.next()) { int id = rs.getInt("id"); String name = rs.getString("name"); System.out.println("id: " + id + ", name: " + name); } ``` 6. 关闭连接:使用完数据库后需要关闭连接,释放资源。 ```java rs.close(); stmt.close(); conn.close(); ``` 以上就是JDBC的基本使用过程,通过JDBC可以实现Java程序与数据库的交互,完成各种数据库操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值