十、JDBC--核心API(三)

			第一部XML(XML相关API)
				XML技术 (2天)
					--》 写XML
					--》 读XML
					
			第二部分:JavaWeb开发
				Servlet / JSP (Servlet相关API)
				相关接口:
					HttpServletRequest/response/ServletContext/HttpSession…….
					
			第三部分:数据库
				MySQL数据库
			
			第四部分:JDBC(JDBC相关API)
				JDBC技术: java数据库连接技术!
				接口:
					Connection:  连接对象
					Statement:   执行命令对象: 把SQL语句发送到数据库执行
					ResultSet:    (在线式)结果集接口, 必须要保持与数据库的连接!
		
		我们在学某一个技术点时,大都是学习该技术点的相关API。所以,对于java来
		说,与其说是面向对象开发,不如说面向接口开发。
		
		
		+++ JDBC核心的API:	
				   驱动类
				   驱动管理器类
				   连接对象类
				   		连接对象类可以创建不同的对象,来执行不同类型的sql。
				   			statement对象:执行静态sql
				   			preparedStatemnt对象:执行动态sql
				   			callableStatent对象:执行存储过程。
		   
		
		+++
			java.sql.*   和  javax.sql.*

			|- Driver接口: 表示java驱动程序接口。所有的具体的数据库厂商要来实现此接口。
				|- connect(url, properties):  连接数据库的方法。
							
							url: 连接数据库的URL 
								URL语法:jdbc:mysql://localhost:3306/day11 
										jdbc协议:数据库子协议://主机:端口/数据库
								user: 数据库的用户名
								password: 数据库用户密码
							
		
			|- DriverManager类: 驱动管理器类,用于管理所有注册的驱动程序
				|-registerDriver(driver)  : 注册驱动类对象
				|-Connection getConnection(url,user,password);  获取连接对象


		
	  	
	  		|- Connection接口: 表示java程序和数据库的连接对象。(面向对象思想)
					|- Statement createStatement() : 创建Statement对象
					|- PreparedStatement prepareStatement(String sql)  创建PreparedStatement对象
					|- CallableStatement prepareCall(String sql) 创建CallableStatement对象
					
					|-	setAutoCommit(boolean autoCommit) ;  设置事务是否自动提交
										          			如果设置为false,表示手动提交事务。
					|-  void commit() ;						      手动提交事务
					|-  rollback() ;						  回滚(出现异常时候,所有已经执行成功的代码需要回退到事务开始前的状态。)
					|-	setSavepoint(String name)       设置回滚点
	

			

			
			|- Statement接口: 用于执行静态的sql语句
					|- int executeUpdate(String sql)  : 执行静态的更新sq l语句(DDL,DML)
					|- ResultSet executeQuery(String sql)  :执行的静态的查询sql语句(DQL)
					
					|--批处理相关方法
							void addBatch(String sql)    添加批处理
							void clearBatch()            清空批处理
							int[] executeBatch()         执行批处理


				|-PreparedStatement接口:用于执行预编译sql语句
						|- int executeUpdate() : 执行预编译的更新sql语句(DDL,DML)
						|-ResultSet executeQuery()  : 执行预编译的查询sql语句(DQL)

					|-CallableStatement接口:用于执行存储过程的sql语句(call xxx)
							|-ResultSet executeQuery()  : 调用存储过程的方法
			
			CallableStatement继承PreparedStatement接口。
			PreparedStatement接口继承Statement接口。

		


			|- ResultSet接口:用于封装查询出来的数据
					|- boolean next() : 将光标移动到下一行
					|-getXX() : 获取列的值

一、JDBC接口核心的API

1.1 Driver 类

		   
		   每个驱动程序类必须实现的接口。 
		   这个接口是JDBC规范中的一个核心接口。数据库厂商提供的数据库驱动必须
		   要实现这个接口。

1.2 DriverManager 类

 +++ DriverManager类:
   			    
   			    1.驱动管理器类, 用于管理多个JDBC驱动程序的基本服务。
				
				2.这个API的常用方法:
								DriverManager.registerDriver(new Driver())
								DriverManager.getConnection(url, user, password),

				3.注意:在实际开发中并不推荐采用registerDriver方法注册驱动。原因有二:
				
					一、查看Driver的源代码可以看到,如果采用此种方式,会导致驱动程序注册两次,也就是在内存中会有两个Driver对象。
					二、程序依赖mysql的api,脱离mysql的jar包,程序将无法编译,将来程序切换底层数据库将会非常麻烦。

				推荐方式:Class.forName(“com.mysql.jdbc.Driver”);
				采用此种方式不会导致驱动对象在内存中重复出现,并且采用此种方式,程序仅仅只需要一个字符串,不需要依赖具体的驱动,使程序的灵活性更高。
				
				同样,在开发中也不建议采用具体的驱动类型指向getConnection方法返回的connection对象。

1.3 Connection 类

		Jdbc程序中的Connection,它用于代表数据库的链接。
		Collection是数据库编程中最重要的一个对象,客户端与数据库所有交互都是通
		过connection对象完成的。

		这个对象的常用方法:
					createStatement():创建向数据库发送sql的statement对象。
					prepareStatement(sql) :创建向数据库发送预编译sql的PrepareSatement对象。
					prepareCall(sql):创建执行存储过程的CallableStatement对象。 
					setAutoCommit(boolean autoCommit):设置事务是否自动提交。 
					commit() :在链接上提交事务。
					rollback() :在此链接上回滚事务。

1.4 Statement 类

		Statement对象用于向数据库发送SQL语句。

		Statement对象常用方法:
					executeQuery(String sql) :用于向数据发送查询语句。
					executeUpdate(String sql):用于向数据库发送insert、update或delete语句
					execute(String sql):用于向数据库发送任意sql语句
					addBatch(String sql) :把多条sql语句放到一个批处理中。
					executeBatch():向数据库发送一批sql语句执行。 

1.5 ResultSet 类

			ResultSet:表示查询数据的结果集。
					  该对象内部维护了一个光标,指向结果集的第一行之前
	   			
	   			+++ next()方法,判断下一行是否有数据,
	   						 如果有数据,返回true,并将光标指向下一行
	   						 如果没有,返回false。
	   						 通过该方法,可以遍历结果集。
								
											ResultSet rs = st.executeQuery(sql);
											while(rs.next()) {
												
												//根据索引值获取行数据
												int id = rs.getInt(1);
												String name = rs.getString(2);
											}

	   			
	   			+++获取行数据
		   			...			 
		   			getShort(..)  
		   			getInt(..)   
		   			getString(.., 获取行数据。
		   							 注意:列的索引从1开始。

二、使用Statement执行sql语句

	
			|- Statement接口: 用于执行静态的sql语句
					|- int executeUpdate(String sql)  : 执行静态的更新sq l语句(DDL,DML)
					|- ResultSet executeQuery(String sql)  :执行的静态的查询sql语句(DQL)
					

2.1 执行DDL语句

    /**
	 * 执行DDL语句(创建表)
	 */
	@Test
	public void test1(){
		Statement stmt = null;
		Connection conn = null;
		try {
			//1.驱动注册程序
			Class.forName("com.mysql.jdbc.Driver");
			
			//2.获取连接对象
			conn = DriverManager.getConnection(url, user, password);
			
			//3.创建Statement
			stmt = conn.createStatement();
			
			//4.准备sql
			String sql = "CREATE TABLE student(id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(20),gender VARCHAR(2))";
			
			//5.发送sql语句,执行sql语句,得到返回结果
			int count = stmt.executeUpdate(sql);
			
			//6.输出
			System.out.println("影响了"+count+"行!");
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		} finally{
			//7.关闭连接(顺序:后打开的先关闭)
			if(stmt!=null)
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
					throw new RuntimeException(e);
				}
			if(conn!=null)
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
					throw new RuntimeException(e);
				}
		}
	}

2.2 执行DML语句

/**
 * 使用Statement执行DML语句
 * @author APPle
 *
 */
public class Demo2 {
	private String url = "jdbc:mysql://localhost:3306/day17";
	private String user = "root";
	private String password = "root";

	/**
	 * 增加
	 */
	@Test
	public void testInsert(){
		Connection conn = null;
		Statement stmt = null;
		try {
			//通过工具类获取连接对象
			conn = JdbcUtil.getConnection();
			
			//3.创建Statement对象
			stmt = conn.createStatement();
			
			//4.sql语句
			String sql = "INSERT INTO student(NAME,gender) VALUES('李四','女')";
			
			//5.执行sql
			int count = stmt.executeUpdate(sql);
			
			System.out.println("影响了"+count+"行");
			
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		} finally{
			//关闭资源
			/*if(stmt!=null)
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
					throw new RuntimeException(e);
				}
			if(conn!=null)
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
					throw new RuntimeException(e);
				}*/
			JdbcUtil.close(conn, stmt);
		}
	}
	
	/**
	 * 修改
	 */
	@Test
	public void testUpdate(){
		Connection conn = null;
		Statement stmt = null;
		//模拟用户输入
		String name = "陈六";
		int id = 3;
		try {
			/*//1.注册驱动
			Class.forName("com.mysql.jdbc.Driver");
			
			//2.获取连接对象
			conn = DriverManager.getConnection(url, user, password);*/
			//通过工具类获取连接对象
			conn = JdbcUtil.getConnection();
			
			//3.创建Statement对象
			stmt = conn.createStatement();
			
			//4.sql语句
			String sql = "UPDATE student SET NAME='"+name+"' WHERE id="+id+"";
			
			System.out.println(sql);
			
			//5.执行sql
			int count = stmt.executeUpdate(sql);
			
			System.out.println("影响了"+count+"行");
			
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		} finally{
			//关闭资源
			/*if(stmt!=null)
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
					throw new RuntimeException(e);
				}
			if(conn!=null)
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
					throw new RuntimeException(e);
				}*/
			JdbcUtil.close(conn, stmt);
		}
	}
	
	/**
	 * 删除
	 */
	@Test
	public void testDelete(){
		Connection conn = null;
		Statement stmt = null;
		//模拟用户输入
		int id = 3;
		try {
		
			//通过工具类获取连接对象
			conn = JdbcUtil.getConnection();
			
			//3.创建Statement对象
			stmt = conn.createStatement();
			
			//4.sql语句
			String sql = "DELETE FROM student WHERE id="+id+"";
			
			System.out.println(sql);
			
			//5.执行sql
			int count = stmt.executeUpdate(sql);
			
			System.out.println("影响了"+count+"行");
			
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		} finally{
			JdbcUtil.close(conn, stmt);
		}
	}
}

2.3 执行DQL语句

1.3.1 ResultSet接口
	ResultSet:表示查询数据的结果集。该对象内部维护了一个光标,指向结果集的第一行之前
	   			
	   			+++ next()方法,判断下一行是否有数据,
	   						 如果有数据,返回true,并将光标指向下一行
	   						 如果没有,返回false。
	   						 通过该方法,可以遍历结果集。
								
											ResultSet rs = st.executeQuery(sql);
											while(rs.next()) {
												
												//根据索引值获取行数据
												int id = rs.getInt(1);
												String name = rs.getString(2);
											}

	   			
	   			+++获取行数据
		   	
						方式一:根据索引值获取行数据(索引值从1开始)
									getString(1)
									getInt(2)
									getShort(3)  
	
						方式二:根据列名获取行数据
									getString("name")
									getInt("age")
									getShort("sex"
1.3.2 获取行数据有两种方式

在这里插入图片描述

方式一:根据索引值获取行数据(索引值从1开始)

	根据索引值获取行数据,该方式虽然推荐使用,效率高。
	但是在实际应用中,还是使用列名获取行数据。
	原因:如果表的字段特别多,使用索引值取值,容易错乱。
/**
	 * 查询数据
	 * @throws SQLException
	 */
	@Test
	public void select() throws SQLException {
		
		Connection 	conn = JdbcUtil.getConn();
		Statement st=null;
		
		try {
			st = conn.createStatement();
			
			String sql="select * from stu";
			ResultSet rs = st.executeQuery(sql);
		
			while(rs.next()) {
				
				//根据索引值获取行数据
				int id = rs.getInt(1);
				String name = rs.getString(2);
				int age = rs.getInt(3);
				System.out.println(id+" - "+name+" - "+age);
			}
			
		} catch (Exception e) {
			throw new RuntimeException(e);
			
		}finally {
			JdbcUtil.close(conn,st,rs);
		}
		
	}

方式二:根据列名称获取行数据

/**
	 * 查询数据
	 * @throws SQLException
	 */
	@Test
	public void select() throws SQLException {
		
		Connection 	conn = JdbcUtil.getConn();
		Statement st=null;
		
		try {
			st = conn.createStatement();
			
			String sql="select * from stu";
			ResultSet rs = st.executeQuery(sql);
		
			while(rs.next()) {
			
				//根据字段名称获取行数据
				int id1 = rs.getInt("id");
				String name1 = rs.getString("name");
				int age1 = rs.getInt("age");
				System.out.println(id1+" - "+name1+" - "+age1);
			}
			
		} catch (Exception e) {
			throw new RuntimeException(e);
			
		}finally {
			JdbcUtil.close(conn,st,rs);
		}
		
	}

1.3.3 执行DQL语句
/**
 * 使用Statement执行DQL语句(查询操作)
 * @author APPle
 */
public class Demo3 {

	@Test
	public void test1(){
		Connection conn = null;
		Statement stmt = null;
		try{
			//获取连接
		    conn = JdbcUtil.getConn();
			//创建Statement
			stmt = conn.createStatement();
			//准备sql
			String sql = "SELECT * FROM student";
			//执行sql
			ResultSet rs = stmt.executeQuery(sql);
			
			//移动光标
			/*boolean flag = rs.next();
			
			flag = rs.next();
			flag = rs.next();
			if(flag){
				//取出列值
				//索引
				int id = rs.getInt(1);
				String name = rs.getString(2);
				String gender = rs.getString(3);
				System.out.println(id+","+name+","+gender);
				
				//列名称
				int id = rs.getInt("id");
				String name = rs.getString("name");
				String gender = rs.getString("gender");
				System.out.println(id+","+name+","+gender);
			}*/
			
			//遍历结果
			while(rs.next()){
				int id = rs.getInt("id");
				String name = rs.getString("name");
				String gender = rs.getString("gender");
				System.out.println(id+","+name+","+gender);
			}
			
		}catch(Exception e){
			e.printStackTrace();
			throw new RuntimeException(e);
		}finally{
			JdbcUtil.close(conn, stmt);
		}
	}
}

三、使用PreparedStatement执行sql语句

3.1 执行预编译sql

public class Demo1 {

	/**
	 * 增加
	 */
	@Test
	public void testInsert() {
		Connection conn = null;
		PreparedStatement stmt = null;
		try {
			//1.获取连接
			conn = JdbcUtil.getConnection();
			
			//2.准备预编译的sql
			String sql = "INSERT INTO student(NAME,gender) VALUES(?,?)"; //?表示一个参数的占位符
			
			//3.执行预编译sql语句(检查语法)
			stmt = conn.prepareStatement(sql);
			
			//4.设置参数值
			/**
			 * 参数一: 参数位置  从1开始
			 */
			stmt.setString(1, "李四");
			stmt.setString(2, "男");
			
			//5.发送参数,执行sql
			int count = stmt.executeUpdate();
			
			System.out.println("影响了"+count+"行");
			
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		} finally {
			JdbcUtil.close(conn, stmt);
		}
	}
	
	/**
	 * 修改
	 */
	@Test
	public void testUpdate() {
		Connection conn = null;
		PreparedStatement stmt = null;
		try {
			//1.获取连接
			conn = JdbcUtil.getConnection();
			
			//2.准备预编译的sql
			String sql = "UPDATE student SET NAME=? WHERE id=?"; //?表示一个参数的占位符
			
			//3.执行预编译sql语句(检查语法)
			stmt = conn.prepareStatement(sql);
			
			//4.设置参数值
			/**
			 * 参数一: 参数位置  从1开始
			 */
			stmt.setString(1, "王五");
			stmt.setInt(2, 9);
			
			//5.发送参数,执行sql
			int count = stmt.executeUpdate();
			
			System.out.println("影响了"+count+"行");
			
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		} finally {
			JdbcUtil.close(conn, stmt);
		}
	}
	
	/**
	 * 删除
	 */
	@Test
	public void testDelete() {
		Connection conn = null;
		PreparedStatement stmt = null;
		try {
			//1.获取连接
			conn = JdbcUtil.getConnection();
			
			//2.准备预编译的sql
			String sql = "DELETE FROM student WHERE id=?"; //?表示一个参数的占位符
			
			//3.执行预编译sql语句(检查语法)
			stmt = conn.prepareStatement(sql);
			
			//4.设置参数值
			/**
			 * 参数一: 参数位置  从1开始
			 */
			stmt.setInt(1, 9);
			
			//5.发送参数,执行sql
			int count = stmt.executeUpdate();
			
			System.out.println("影响了"+count+"行");
			
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		} finally {
			JdbcUtil.close(conn, stmt);
		}
	}
	
	/**
	 * 查询
	 */
	@Test
	public void testQuery() {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		try {
			//1.获取连接
			conn = JdbcUtil.getConnection();
			
			//2.准备预编译的sql
			String sql = "SELECT * FROM student"; 
			
			//3.预编译
			stmt = conn.prepareStatement(sql);
			
			//4.执行sql
			rs = stmt.executeQuery();
			
			//5.遍历rs
			while(rs.next()){
				int id = rs.getInt("id");
				String name = rs.getString("name");
				String gender = rs.getString("gender");
				System.out.println(id+","+name+","+gender);
			}
			
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		} finally {
			//关闭资源
			JdbcUtil.close(conn,stmt,rs);
		}
	}
}

3.2 获取自增长值

创建数据库
设置id为主键,并且为自增长


CREATE TABLE `mem` (
  `id` INT(11) PRIMARY KEY AUTO_INCREMENT,
  `name` VARCHAR(20) DEFAULT NULL,
  `pwd` VARCHAR(20) DEFAULT NULL
) ENGINE=INNODB DEFAULT CHARSET=utf8

代码:

	@Test
	public  void  test() {
		Connection conn = JdbcUtil.getConn();
		String sql="insert into mem(name,pwd) values(?,?)";
		PreparedStatement pst=null;
		
		try {
			
			 pst = conn.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
			 pst.setString(1, "小白");
			 pst.setString(2, "1234");
			 
			 //执行sql
			 pst.executeUpdate();
			 
			 //获取自增长列的值
			 ResultSet rs = pst.getGeneratedKeys();
			 while(rs.next()) {
				 int id = rs.getInt(1);
				 System.out.println("自增长id为:"+id);
			 }
			 
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	

3.3 批处理

	假设我们插入100个学生数据,如果使用pst执行sql,一个一个的插入学生的数据,
	肯定是消耗数据资源的。所以这时候我们可以采用批量处理。


	|-- Statement
	
			批处理相关方法
			void addBatch(String sql)     添加批处理
			void clearBatch()            清空批处理
			int[] executeBatch()         执行批处理
	

批量新增100个学生

		//批量新增100个学生
		@Test
		public  void demo11()  {
			
			String sql="insert into student(name,chinese) values(?,?)";
			
			Connection conn = JdbcUtil.getConn();
			
			PreparedStatement pst=null;
			
			try {
				pst = conn.prepareStatement(sql);
				
				//设置参数
				for(int i=0;i<100;i++) {
					pst.setString(1, "xiao"+i);
					pst.setInt(2, i);
					
					//新增批量处理
					pst.addBatch(); //不传入sql
				}
				
				//执行批量处理
				pst.executeBatch();
				
				//清空批量处理
				pst.clearBatch();
				
			} catch (SQLException e) {
				e.printStackTrace();
			}finally {
				try {
					pst.close();
					conn.close();
				} catch (SQLException e) {
						throw new RuntimeException(e);
				}
			}
			
			
			
		}
		

3.3 Jdbc大文本类型的数据处理

	
	+++ 什么是大文本数据?
	
		在实际开发中,程序需要把长文本(txt文件,存储大文本)或二进制数据
		(存储二进制数据,例如图像、声音、二进制文)保存到数据库。
		长文本数据:指的就是文章、小说、txt文件内容等大篇字符。
		二进制数据:指的就是图像、声音、二进制文等。
	

 	+++ 大文本数据类型
 	
		Oracle中大文本数据类型,
			Clob    长文本类型   (MySQL中不支持,使用的是text)
			Blob    二进制类型

		MySQL数据库大文本数据类型,
			Text    长文本类型 : TINYTEXT、TEXT、MEDIUMTEXT和LONGTEXT
			Blob    二进制类型 : TINYBLOB、BLOB、MEDIUMBLOB和LONGBLOB
			

创建数据库

		CREATE  TABLE test(
			id INT PRIMARY KEY AUTO_INCREMENT,
			content LONGTEXT  ,  -- 大文本数据类型
			img LONGBLOB			-- 二进制数据类型
		)

3.3.1 保存长文本数据类型

	//保存大文本数据类型
	@Test
	public  void  test3() {
		
		Connection conn=null;
		PreparedStatement pst=null;
		try {
			String sql="insert into test(content) values(?)";
			 conn = JdbcUtil.getConn();
			 pst = conn.prepareStatement(sql);
			 
			 //设置参数值
			 FileReader reader=new FileReader("C:\\Test\\a.txt");
			 pst.setCharacterStream(1, reader);
			 
			 //执行
			 pst.executeUpdate();
			 
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
	
	
	//获取大文本数据类型
	@Test
	public  void  test4() {
		
		Connection conn=null;
		PreparedStatement pst=null;
		try {
			String sql="select * from test where id =?";
			 conn = JdbcUtil.getConn();
			 pst = conn.prepareStatement(sql);
			 
			 //设置参数值
			 pst.setInt(1, 1);
			 
			 
			 
			 //执行
			 ResultSet rs = pst.executeQuery();
			 while(rs.next()) {
				 //方式一:
				 Reader reader = rs.getCharacterStream("content");
				 
				 //方式二:
				 String string = rs.getString("content");
				 System.out.println(string);
			 }
			 
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
3.2.2 保存二进制数据
	
	//保存二进制数据类型
	@Test
	public  void  test33() {
		
		Connection conn=null;
		PreparedStatement pst=null;
		try {
			String sql="insert into test(img) values(?)";
			 conn = JdbcUtil.getConn();
			 pst = conn.prepareStatement(sql);
			 
			 //设置参数值
			 FileInputStream in=new FileInputStream("C:\\Users\\BGS\\Desktop\\m.jpg");
			 pst.setBinaryStream(1, in);
			 
			 //执行
			 pst.executeUpdate();
			 
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
	
	
	//获取二进制数据类型
	@Test
	public  void  test44() {
		
		Connection conn=null;
		PreparedStatement pst=null;
		try {
			String sql="select * from test where id =?";
			 conn = JdbcUtil.getConn();
			 pst = conn.prepareStatement(sql);
			 
			 //设置参数值
			 pst.setInt(1, 2);
			 
			 
			 
			 //执行
			 ResultSet rs = pst.executeQuery();
			 while(rs.next()) {
				 //方式一:
				InputStream in = rs.getBinaryStream("img");
				 
				 //方式二:
				 String string = rs.getString("img");
				 System.out.println(string);
			 }
			 
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
}

3.4 模拟SQL注入(会员登录)

public class Demo {
	

	private String url="jdbc:mysql://localhost:3306/day11";
	private String user="root";
	private String password="root";
	
	/**
	 * Statement 执行静态sql
	 * 
	 * @throws ClassNotFoundException
	 * @throws SQLException
	 */
	@Test
	public void test_login() throws ClassNotFoundException, SQLException {
			
		
		Class.forName("com.mysql.jdbc.Driver");
		
		Connection conn = DriverManager.getConnection(url, user, password);
		
		
		//模拟用户登录,输入账号密码
		String user="hlp";
		String pwd="1234";
		
		//模拟用户登录,sql入侵
		String user1="hlp";
		String pwd1=" ' or 1=1 -- ";
		
		String sql=String.format("select * from mem where name='%s' and pwd='%s'",user1,pwd1);
		Statement st=null;
		ResultSet rs=null;
		
		try {
			
			 st = conn.createStatement();
			 rs = st.executeQuery(sql);
			 while(rs.next()) {
				 System.out.println("登录ID:"+rs.getInt("id"));
			 }
			 
		} catch (Exception e) {
			throw new RuntimeException(e);
			
		}finally {
			rs.close();
			st.close();
			conn.close();
		}
		
		
	}
	
	
	/**
	 * PreparedStatement:执行动态sql
	 * 
	 * @throws ClassNotFoundException
	 * @throws SQLException
	 */
	@Test
	public void test_login2() throws ClassNotFoundException, SQLException {
			
		
		Class.forName("com.mysql.jdbc.Driver");
		
		Connection conn = DriverManager.getConnection(url, user, password);
		
		
		//模拟用户登录,输入账号密码
		String user="hlp";
		String pwd="1234";
		
		//模拟用户登录,sql入侵
		String user1="hlp";
		String pwd1=" ' or 1=1 -- ";
		
		String sql="select * from mem where name=? and pwd=?";
		PreparedStatement pst=null;
		ResultSet rs=null;
		
		try {
			
			pst = conn.prepareStatement(sql);
			
			pst.setString(1, user1);
			pst.setString(2, pwd1);
			
			rs = pst.executeQuery();
			while(rs.next()) {
				 System.out.println("登录ID:"+rs.getInt("id"));
			}
			 
		} catch (Exception e) {
			throw new RuntimeException(e);
			
		}finally {
			rs.close();
			pst.close();
			conn.close();
		}
		
		
	}
	
	
}

	sql侵入的原理:
			使用statement执行的sql,只能是静态sql。
			静态sql说白了就是用户输入用户名和密码,然后拼接的sql。
			所以用户可以手动拼接一些条件。
	
	阻值sql侵入的原理:
			使用预编译sql。PreparedStatement。
			PreparedStatement有一个方法,setString(..),其作用就是设置入参。
			设置入参时,还会参数中的 ' 进行了转义。所以对于输入的用户名、密码,
			用户无法注释掉。		

四、 PreparedStatement vs Statement

	
	1)语法不同:PreparedStatement可以使用占位符,执行预编译的sql,而Statment只能使用静态的sql。
		动态sql:可以预编译
		静态sql:sql只能拼接。
		
	2)效率不同: PreparedStatement可以使用sql缓存区,效率比Statment高(在mysql中,效率是一样的。)
	3)安全性不同: PreparedStatement可以有效防止sql注入,而Statment不能防止sql注入。

	推荐使用PreparedStatement

4.1 关于sql缓冲区的支持

在这里插入图片描述

执行过程

		Statement 在执行sql语句时,
						1)首先会把sql语句发送到服务器端,
										select * from stu where id=1 
						2)服务器端检查语法以及权限,
						3)服务器把sql加入到sql缓冲区
								sql缓冲区相当于一个map
										Map<Sring,执行任务>
										select * from stu where id=1 , 执行任务1
					  4)然后从sql缓冲区中取出sql执行。
					  5)再次执行sql语句时,由于在缓冲区map中获取不到sql,所以会重新加入。
					  					select * from stu where id=2。 
	
	
	   PreparedStatement 在执行sql语句时,
	   				 	1)首先会把sql语句发送到服务器端。
										select * from stu where id=?
						2)服务器端检查语法以及权限,
						3)服务器把sql加入到sql缓冲区
								sql缓冲区相当于一个map
										Map<Sring,执行任务>
										select * from stu where id=1 , 执行任务1
					  4)然后客户端传参,从sql缓冲区中取出sql执行。
					  5)再次执行sql语句时,由于在缓冲区map中可以获取到sql,所以直接传参即可执行。
										select * from stu where id=?

		综上,如果数据库支持sql缓冲区,
			  那么使用PreparedStatement要比statement效率高。因为PreparedStatement支持sql缓冲区。
			  
			  如果数据库不支持sql缓冲区,
			  那么PreparedStatement和statement效率是一样的。
		

数据库sql缓冲区

	支持sql缓冲区:
				oracle 、sql server
	
	不支持sql缓冲区:
				mysql
				
	结论:并不是所有的数据库都支持sql缓冲区这一部分,
		  所以对mysql来说,PreparedStatemt对sql缓冲区的优化在mysql中是无效的。
		  即是在mysql中,PreparedStatemt和Statement的效率一样高。				

4.2 关于防止sql注入的支持

mysql数据库的注释

		SELECT * FROM men WHERE id=1 -- 注释
		SELECT * FROM men WHERE id=1 -- or 1=1

关于sql注入的代码演示

	
	//模拟用户登录
	@Test
	public void login() {
		
		Demo d=new Demo();
		
		String name="hlp";
		
		//输入正常登录的密码
		String pwd1="1234";
		//输入sql注入的密码
		String pwd2="1' or 1=1 -- ";

		//使用statement测试sql注入
		//--登录失败
		d.testPwd(name, pwd2);
		
		//使用preparedStatemnt测试sql注入
		//--登录成功
		d.testPwd2(name, pwd2);
	}
	
	
	
/**
	 * statment对象执行sql
	 * 
	 * @param name
	 * @param pwd
	 */
	public void testPwd(String name ,String pwd) {
		Connection conn = JdbcUtil.getConn();
		

		String sql="select * from mem where name='"+name+"' and pwd='"+pwd+"'";
		
		System.out.println(sql);
		
		Statement st=null;
		ResultSet rs =null;
		try {
			 st = conn.createStatement();
			 rs = st.executeQuery(sql);
			 
			boolean isExist = rs.next();
			System.out.println(isExist?"密码正确":"密码错误");
			
		} catch (SQLException e) {
			JdbcUtil.close(conn, st, rs);
		}
	}
	
	
	/**
	 * preparedStatement对象查询sql
	 * 
	 * @param name
	 * @param pwd
	 */
	public void testPwd2(String name ,String pwd) {
		Connection conn = JdbcUtil.getConn();
	

		String sql="select * from mem where name=? and pwd=?";
		
		System.out.println(sql);
		
		PreparedStatement pst=null;
		ResultSet rs =null;
		try {
			 pst = conn.prepareStatement(sql);
			 
			 //设置参数
			 pst.setString(1, name);
			 pst.setString(2, pwd);
			 
			 
			 rs = pst.executeQuery();
			 
			boolean isExist = rs.next();
			System.out.println(isExist?"密码正确":"密码错误");
			
		} catch (SQLException e) {
			e.printStackTrace();
			JdbcUtil.close(conn, pst, rs);
		}
	}

sql注入的原理

	
			 1.使用mysql的注释,来注释有效的代码。
			 2.客户端发送sql到数据库,由于sql本身就是拼接的,
			   所以,我们可以在输入密码的时候,在密码中我们使用mysql注释来注释有效的sql片段,并拼接一些条件。
				
				原有的sql:   select * from mem where name='hlp' and pwd='1' 

				被注入的sql: select * from mem where name='hlp' and pwd='1' or 1=1 -- '

PreparedStatement和Statement对sql注入的支持

	
	对于JDBC而言, 
	SQL注入攻击只对Statement有效,对PreparedStatement是无效的, 

	原理:
	     mysql驱动类的底层的setString()方法,有三个作用:
		        1.用值替换?
		     	2.在值的两边加上了单引号
		     	3.把值中所有的单引号替换成了斜杠单引号,进行了转义。
		     	
		 说白了,这个方法的作用就是把值当中的所有单引号给转义了!这就达到了防止sql注入的目的。
		 
		 
	 详细:
		  mysql驱动类的PreparedStatement实现类的setStrig(),方法内部做了单引
	  号的转义,而Statement不能防止sql注入,就是因为它没有把单引号做转
	  义,而是简单粗暴的直接拼接字符串,所以达不到防止sql注入的目的。
	  所以,不管是mysql数据库产商的PreparedStatement实现类还是sqlserver数据库
	  产商的PreparedStatement实现类,其实底层的原理都差不多,当然啦,我们直接
	  在java程序中把那些单引号直接转义也行,只不过比较繁琐一点罢了,既然各个数
	  据库产商的PreparedStatement实现类的setString()方法中已经做了转义

4.3 花絮——myBatis的 # 和 $

		# 相当于对数据 加上 双引号,$ 相当于直接显示数据。
		一句话言简意赅。分条陈述:

		# 能够sql注入问题,$ 不行;
		$ 用于传入数据库对象,例如表名;
		能用 # 就不要使用 $;

五、使用CallableStatement执行sql语句


		String sql="call pro_sel(?,?,?)";
				第一个?输入参数,第二个?输入参数,第三个?输出参数。	

	   1.设置预编译sql的参数:
						设置存储过程中的输入参数: cst.setInt(1,1);
									  cst.setString(2,'小白');
						设置存储过程中的输出参数:
									  cst.registerOutParameter(3,  java.sql.Types.VARCHAR);
	   
	   2.获取存储过程中输出参数	 
	   	  												
	   	  				              cst.getString(3);
			
		
	  3.使用CallableStatement调用存储过程,执行预编译的存储过程sql时,
	    都使用 cst.executeQuery()来执行。
	


5.1 执行带有输入参数的存储过程

存储过程

	DELIMITER &
	CREATE PROCEDURE pro_sel(IN sid INT )
	BEGIN
		
		SELECT * FROM mem WHERE id =sid;
		
	END &

代码

	@Test
	public void test1()  {
		
		
		Connection conn=null;
		CallableStatement cst=null;
		ResultSet rs=null;
		try {
			
			//获取连接对象
			conn = JdbcUtil.getConn();
			
			//准备sql
			String sql="call pro_sel(?)";
			
			//预编译sql
			cst = conn.prepareCall(sql);
			
			//设置输入参数
			cst.setInt(1, 1);
			
			//发送参数,执行
			//注意:所有调用存储过程的sql语句都使用executeQuery方法来执行。
			rs= cst.executeQuery();
			
			
			//遍历结果集
			while(rs.next()) {
				
				int id = rs.getInt("id");
				String name = rs.getString("name");
				String pwd = rs.getString("pwd");
				
				System.out.println(id+"-"+name+"-"+pwd);
			}
			
			
		} catch (SQLException e) {
			e.printStackTrace();
			throw new RuntimeException();
			
		}finally {
			
			JdbcUtil.close(conn, cst,rs);
		}
		
		
	}

4.2 执行带有输出参数的存储过程

存储过程

DELIMITER &
CREATE PROCEDURE pro_sel(IN sid INT ,OUT sname VARCHAR(20))
BEGIN
	
	SELECT NAME INTO sname FROM mem WHERE id =sid;
	
END &

代码:

@Test
	public void test2()  {
		
		
		Connection conn=null;
		CallableStatement cst=null;
		try {
			
			//获取连接对象
			conn = JdbcUtil.getConn();
			
			//准备sql
			String sql="call pro_sel(?,?)";
			
			//预编译sql
			cst = conn.prepareCall(sql);
			
			//设置参数
			//1.设置输入参数
			cst.setInt(1, 1);
			//2.设置输出参数
			cst.registerOutParameter(2,  java.sql.Types.VARCHAR);
			
			//发送参数,执行
			//注意:所有调用存储过程的sql语句都使用executeQuery方法来执行。
			cst.executeQuery(); //结果不是返回到结果集中,而是返回到输出参数中
			
			//获取结果
			String sname = cst.getString(2);
			
			System.out.println(sname);
			
		} catch (SQLException e) {
			e.printStackTrace();
			throw new RuntimeException();
			
		}finally {
			
			JdbcUtil.close(conn, cst);
		}
		
		
	}

六、执行完SQL后,要关闭连接

	
	sql执行完毕,Connection接口需要执行关闭。
	原因:
		sql执行完毕,Connection对象就会被JVM回收。但是由于Connection对象是
	java和mysql服务端的连接对象,即使Connection对象被JVM回收,mysq服务器端的
	连接依旧存在,此时,当连接增多时,数据库服务器就会出现内存溢出的情况。
	
	
	
	关闭连接,要依次关闭。关闭Connection接口顺序:
				1.关闭result
				2.关闭statement
				3.关闭connection
				

	/**
	 * 关闭连接
	 * @param conn
	 * @param st
	 */
	public static void close(Connection conn,Statement st,ResultSet rs) {
		try {
			
			//关闭resulset
			if(rs!=null) {
				rs.close();
			}
			
			//关闭statement
			if(st!=null) {
				st.close();
			}
			
			//关闭连接对象
			if(conn!=null) {
				conn.close();
			}
			
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}
			

发布了94 篇原创文章 · 获赞 0 · 访问量 631

猜你喜欢

转载自blog.csdn.net/weixin_45602227/article/details/104217153
今日推荐