JDBC入门+反射+JUnit单元


JDBC概述

在这里插入图片描述

  • 全称:Java数据库连接
  • 理解为用于执行SQL语句的JavaAPI
  • JDBC定义接口规范,为关系型数据库提供统一访问方式
  • 以前(左图):了解各个驱动程序的方法(具体写法)
  • 现在(右图):各个驱动遵守一套标准:JDBC的接口规范(每个驱动实现此套规范)
  • JDBC定义接口规范,具体的驱动提供具体的实现——》不用了解每个数据库驱动程序,只需了解JDBC接口规范

搭建开发环境

在这里插入图片描述

  1. 创建表,插入数据(dos窗口下,参照输入背景的指令)
  2. 项目中引入驱动包(新建项目,新建lib文件夹,然后lib文件夹下引入包(复制粘贴),然后add to build path)

步骤

  1. 加载数据库驱动
  2. 建立连接
  3. 创建执行SQL语句的Statement对象
  4. 从结果集取出数据
  5. 释放连接资源

JDBC的API

DriverManager

  • 驱动管理类
DriverManager.registerDriver(new Driver());
  • 使用上述方法注册驱动两次原因:当加载driver类(创建driver类),默认注册驱动,见下图

在这里插入图片描述

  • 加载类——》静态代码块执行——》注册驱动

在这里插入图片描述

  • 主机名+端口号+数据库名称
  • 连接本机的MySQL时URL简写见上图

Connection

保证多个操作在同一个事务中
在这里插入图片描述


Statement

在这里插入图片描述

  • 常用方法有两个(query,update)
  • update后缀的方法返回值为影响的行数
  • 批处理:多条SQL语句发送到数据库中执行

ResultSet

  • 想象返回表格格式的数据
  • 查询结果封装到结果集中

在这里插入图片描述

next方法

在这里插入图片描述

  • 用于遍历
  • 类比集合的iterator的使用,初始在第一行之前

交互资源释放

  • 释放与数据库交互的对象(结果集,statement,connection)
  • connection对象要早释放,晚创建
  • 释放资源写在finally块中,最后赋值为null(要求外部声明,类比I/O)
  • 要手动置为null,释放内存空间,不然没有垃圾回收

CRUD

未使用工具类

public class JDBCDemo1 {
	
	@Test
	/**
	 * JDBC资源的释放
	 */
	public void demo2(){
		Connection conn = null;
		Statement stmt = null;
		ResultSet rs = null;
		try {
			// 1.加载驱动
//			DriverManager.registerDriver(new Driver());// 会导致驱动注册两次。
			Class.forName("com.mysql.jdbc.Driver"); 
			// 2.获得连接
			conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest", "root", "abc");
			// 3.创建执行SQL语句的对象,并且执行SQL
			// 3.1创建执行sql的对象
			String sql = "select * from user";
			stmt = conn.createStatement();
			// 3.2执行sql
			rs = stmt.executeQuery(sql);
			while(rs.next()){
				int uid = rs.getInt("uid");
				String username = rs.getString("username");
				String password = rs.getString("password");
				String name = rs.getString("name");
				
				System.out.println(uid+"   "+username+"   "+password+"   "+name);
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			// 4.释放资源
		    if (rs != null) {
		        try {
		            rs.close();
		        } catch (SQLException sqlEx) { // ignore 
		        	
		        }

		        rs = null;
		    }
		    
		    if(stmt != null){
		    	try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
		    	stmt = null;
		    }
		    
		    if(conn != null){
		    	try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
		    	conn = null;// 垃圾回收机制更早回收对象。
		    }
		}
	}

	@Test
	/**
	 * JDBC的入门程序
	 */
	public void demo1(){
		
		try {
			// 1.加载驱动
//			DriverManager.registerDriver(new Driver());// 会导致驱动注册两次。
			Class.forName("com.mysql.jdbc.Driver"); 
			// 2.获得连接
			Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest", "root", "abc");
			// 3.创建执行SQL语句的对象,并且执行SQL
			// 3.1创建执行sql的对象
			String sql = "select * from user";
			Statement stmt = conn.createStatement();
			// 3.2执行sql
			ResultSet resultSet = stmt.executeQuery(sql);
			while(resultSet.next()){
				int uid = resultSet.getInt("uid");
				String username = resultSet.getString("username");
				String password = resultSet.getString("password");
				String name = resultSet.getString("name");
				
				System.out.println(uid+"   "+username+"   "+password+"   "+name);
			}
			// 4.释放资源
			resultSet.close();
			stmt.close();
			conn.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

使用工具类

public class JDBCDemo3 {

	@Test
	// 保存记录
	public void demo1(){
		Connection conn = null;
		Statement stmt  = null;
		try{
			// 获得连接:
			conn = JDBCUtils.getConnection();
			// 创建执行SQL语句的对象
			stmt = conn.createStatement();
			// 编写SQL:
			String sql = "insert into user values (null,'ggg','123','小六')";
			// 执行SQL:
			int num = stmt.executeUpdate(sql);
			if(num > 0){
				System.out.println("保存成功!");
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			// 释放资源:
			JDBCUtils.release(stmt, conn);
		}
	}
}

一般步骤

  1. 注册驱动,获得连接
  2. 创建statement对象
  3. 编写SQL语句,执行SQL(executeUpdate)
  4. 关闭连接资源

增删改

  • 与查找区别仅在于SQL语句
  • 不需要结果集对象
  • 增删改都使用 int executeUpdate(String)

  • delete from +表 +where
  • update 表名 set ----- where
  • insert into 表名 values()

查询

  • 使用结果集对象
  • 与增删改相比,外加释放结果集资源
  • if与while切换(查询一条记录与查询多条记录)

复用代码

封装如下三种方法:

  1. 注册驱动(参数为驱动类的全路径)
  2. 获取statement对象
  3. 重载的release方法

优化

原本各参数定死——》可变部分设置为静态常量,然后要给静态常量赋值——》加入静态代码块

再优化

  • 将参数提取到配置文件中,然后程序中解析并读取相应参数
  • 配置文件采用属性文件进行表示(键值对)
  • src下新建属性文件

load方法的参数要为代表属性文件的输入流

  • 方法1:使用inputstream,但存在问题:Java项目OK,但web项目发布到服务器中,没有src这一路径

  • 方法2:使用类加载器加载并解析属性文件

SQL注入漏洞

  • 登录成功与否实质是查询操作(能否查到)
  • ‘’是SQL语句的一部分,将过滤条件的常量替换为String变量时,实质是替换单引号里面内容,"++"表示一个字符串的连接
  • JDBCDemo4.login2(“aaa’ or '1=1”, “1fsdsdfsdf”);//由于and先运算,假设为false,但or后因为用户名正确,导致出现漏洞
    在这里插入图片描述
  • 由于–为注释

在这里插入图片描述

  • 漏洞原因:输入了关键字

/**
 * 演示JDBC的注入的漏洞
 * @author jt
 *
 */
public class JDBCDemo4 {
	@Test
	/**
	 * 测试SQL注入漏洞的方法
	 */
	public void demo1(){
		boolean flag = JDBCDemo4.login2("aaa' or '1=1", "1fsdsdfsdf");
		if(flag == true){
			System.out.println("登录成功!");
		}else{
			System.out.println("登录失败!");
		}
	}
	
	/**
	 * 避免SQL注入漏洞的方法
	 */
	public static boolean login2(String username,String password){
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		boolean flag = false;
		try{
			// 获得连接:
			conn = JDBCUtils.getConnection();
			// 编写SQL:
			String sql = "select * from user where username = ? and password = ?";
			// 预处理SQL:
			pstmt = conn.prepareStatement(sql);
			// 设置参数:
			pstmt.setString(1, username);
			pstmt.setString(2, password);
			// 执行SQL:
			rs = pstmt.executeQuery();
			// 判断结果街
			if(rs.next()){
				flag = true;
			}else{
				flag = false;
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			JDBCUtils.release(rs, pstmt, conn);
		}
		return flag;
	}
	
	/**
	 * 产生SQL注入漏洞的方法
	 * @param username
	 * @param password
	 * @return
	 */
	public static boolean login(String username,String password){
		Connection conn = null;
		Statement stmt  = null;
		ResultSet rs = null;
		boolean flag = false;
		try{
			conn = JDBCUtils.getConnection();
			// 创建执行SQL语句的对象:
			stmt = conn.createStatement();
			// 编写SQL:
			String sql = "select * from user where username = '"+username+"' and password = '"+password+"'";
			// 执行SQL:
			rs = stmt.executeQuery(sql);
			// 判断结果集中是否有数据。
			if(rs.next()){
				flag = true;
			}else{
				flag = false;
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			JDBCUtils.release(rs, stmt, conn);
		}
		return flag;
	}
}

解决

  • 输入框进行GS校验(但hacker可以从地址栏输入,绕过校验)

  • 分析:原本方式的SQL语句是用字符串来进行拼接(变量,例如username都是字符串的拼接)

  • 正确的解决方法:使用占位符给SQL语句的变量处占位(则SQL语句格式固定)

  • 保证把关键字当成普通字符串处理

  • 只编译一次(因为用占位符进行占位了)

  • 变量使用?占位

  • SQL语句已经预处理,不需要传入SQL语句了(execute方法)

PreparedStatement好处:

  1. 解决漏洞
  2. 相同,只编译一次

PreparedStatement

在这里插入图片描述

总结:

  • 解决漏洞
  • 传统方式,一条语句编译一次,而这种方式相同语句只编译一次(占位符占位了)
/**
 * PreparedStatement的使用
 * @author jt
 *
 */
public class JDBCDemo5 {
	@Test
	/**
	 * 查询一条记录
	 */
	public void demo5(){
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try{
			// 获得连接:
			conn = JDBCUtils.getConnection();
			// 编写SQL:
			String sql = "select * from user where uid = ?";
			// 预编译SQL:
			pstmt = conn.prepareStatement(sql);
			// 设置参数:
			pstmt.setObject(1, 3);
			// 执行SQL:
			rs = pstmt.executeQuery();
			// 判断结果集:
			if(rs.next()){
				System.out.println(rs.getInt("uid")+"  "+rs.getString("username")+"  "+rs.getString("password")+"  "+rs.getString("name"));
				//实际操作中可将一条记录存储在一个对象中,然后返回对象
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			JDBCUtils.release(rs, pstmt, conn);
		}
	}
	
	@Test
	/**
	 * 查询所有数据
	 */
	public void demo4(){
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try{
			// 获得连接:
			conn = JDBCUtils.getConnection();
			// 编写SQL:
			String sql = "select * from user";
			// 预编译SQL:
			pstmt = conn.prepareStatement(sql);
			// 设置参数
			// 执行SQL:
			rs = pstmt.executeQuery();
			while(rs.next()){
				System.out.println(rs.getInt("uid")+"  "+rs.getString("username")+"  "+rs.getString("password")+"  "+rs.getString("name"));
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			JDBCUtils.release(rs, pstmt, conn);
		}
	}
	
	@Test
	/**
	 * 删除数据
	 */
	public void demo3(){
		Connection conn = null;
		PreparedStatement pstmt = null;
		try{
			// 获得连接:
			conn = JDBCUtils.getConnection();
			// 编写SQL:
			String sql = "delete from user where uid = ?";
			// 预编译SQL:
			pstmt = conn.prepareStatement(sql);
			// 设置参数:
			pstmt.setInt(1, 6);
			// 执行SQL:
			int num = pstmt.executeUpdate();
			if(num > 0){
				System.out.println("删除成功!");
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			JDBCUtils.release(pstmt, conn);
		}
	}
	
	@Test
	/**
	 * 修改数据
	 */
	public void demo2(){
		Connection conn = null;
		PreparedStatement pstmt = null;
		try{
			// 获得连接:
			conn = JDBCUtils.getConnection();
			// 编写SQL:
			String sql = "update user set username = ?,password = ?,name = ? where uid = ?";//变量用?替代
           //有where过滤条件,不然所有记录修改
			// 预编译SQL:
			pstmt = conn.prepareStatement(sql);
			// 设置参数:
			pstmt.setString(1, "www");
			pstmt.setString(2, "123456");
			pstmt.setString(3, "张六");
			pstmt.setInt(4, 6);
			// 执行SQL:
			int num = pstmt.executeUpdate();
			if(num > 0){
				System.out.println("修改成功!");
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			JDBCUtils.release(pstmt, conn);
		}
	}

	@Test
	/**
	 * 保存数据
	 */
	public void demo1(){
		Connection conn = null;
		PreparedStatement pstmt = null;
		try{
			// 获得连接:
			conn = JDBCUtils.getConnection();
			// 编写SQL:
			String sql = "insert into user values (null,?,?,?)";//null表示根据默认设置来,此处表的第一列为自增的主键列(已知条件)
			// 预处理SQL:
			pstmt = conn.prepareStatement(sql);
			// 设置参数的值:
			pstmt.setString(1, "qqq");
			pstmt.setString(2, "123");
			pstmt.setString(3, "张武");
			// 执行SQL:
			int num = pstmt.executeUpdate();
			if(num > 0){
				System.out.println("保存成功!");
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			// 释放资源
			JDBCUtils.release(pstmt, conn);
		}
	}
}

关键

  • 设置参数时,setXXX为独有的方法,而setObject方法通吃(各种重载)
  • 注意在前方SQL已预编译(预处理),所以executeUpdate方法无参数
  • 变量用?替代

连接池

概述

在这里插入图片描述

  • 创建和管理一个连接的缓冲池的技术
  • 创建和销毁开销较大
  • 连接池理解为一个装有许多连接的容器或者一块内存空间,里面存在许多连接
  • 需要连接时,则获取连接/用完连接后归还给内存

在这里插入图片描述

  • 上图:用户通过服务器端程序(servlet),最终调用到DAO(数据访问对象),然后创建连接和数据库交互
  • 有了线程池,则初始时已创建好一个具有许多连接的容器(内存的一个地址)

C3P0连接池

  • 许多开源连接池,仅以此为例
  • 引入连接池的jar包(先添加到lib文件夹,然后右键,add to build path)

手动方式

  • 没有配置文件,手动设置参数

    • 创建连接池(主要实现类 ComboPooledDataSource),设置连接池参数(setDriverClass,setJdbcUrl,setUser,setPassword,setMaxPoolSize)
    • 获得连接
    • 编写与预编译SQL语句,设置参数
    • 执行SQL语句
    • 释放连接资源
  • 调用的是:connection.close()方法,连接池内部已增强了该方法,销毁方法增强为归还方法

  • 连接池的连接数最小默认为3,最大默认为15

@Test
	/**
	 * 手动设置连接池
	 */
	public void demo1(){

		// 获得连接:
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;   //例行操作
		try{
			// 创建连接池:
			ComboPooledDataSource dataSource = new ComboPooledDataSource();
			// 设置连接池的参数:
			dataSource.setDriverClass("com.mysql.jdbc.Driver");
			dataSource.setJdbcUrl("jdbc:mysql:///jdbctest");
			dataSource.setUser("root");
			dataSource.setPassword("abc");
			dataSource.setMaxPoolSize(20);
			dataSource.setInitialPoolSize(3);
			
			// 获得连接:
			conn = dataSource.getConnection();
			// 编写Sql:
			String sql = "select * from user";
			// 预编译SQL:
			pstmt = conn.prepareStatement(sql);
			// 设置参数
			// 执行SQL:
			rs = pstmt.executeQuery();
			while(rs.next()){
				System.out.println(rs.getInt("uid")+"   "+rs.getString("username")+"   "+rs.getString("password")+"   "+rs.getString("name"));
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			JDBCUtils.release(rs, pstmt, conn);  //封装的常用代码
		}
	}

配置文件方式

在这里插入图片描述

  • 可使用properties文件或XML文件(XML配置是默认的,属性文件配置仍然被支持)
  • 默认在类路径下查找为c3p0-config的XML文件——》创建c3p0-config.xml文件并放在src下
@Test
	/**
	 * 使用配置文件的方式
	 */
	public void demo2(){
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try{
			/*// 获得连接:
			ComboPooledDataSource dataSource = new ComboPooledDataSource();*/
			// 获得连接:
			// conn = dataSource.getConnection();
			conn = JDBCUtils2.getConnection();
			// 编写Sql:
			String sql = "select * from user";
			// 预编译SQL:
			pstmt = conn.prepareStatement(sql);
			// 设置参数
			// 执行SQL:
			rs = pstmt.executeQuery();
			while(rs.next()){
				System.out.println(rs.getInt("uid")+"   "+rs.getString("username")+"   "+rs.getString("password")+"   "+rs.getString("name"));
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			JDBCUtils2.release(rs, pstmt, conn);
		}
	}

JDBC工具类

/**
 * JDBC的工具类
 * @author jt
 *
 */
public class JDBCUtils {
	private static final String driverClass;
	private static final String url;
	private static final String username;
	private static final String password;
	
	static{
		// 加载属性文件并解析:
		Properties props = new Properties();
		// 如何获得属性文件的输入流?
		// 通常情况下使用类的加载器的方式进行获取:
		InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
		try {
			props.load(is);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		driverClass = props.getProperty("driverClass");
		url = props.getProperty("url");
		username = props.getProperty("username");
		password = props.getProperty("password");
	}

	/**
	 * 注册驱动的方法
	 * @throws ClassNotFoundException 
	 */
	public static void loadDriver() throws ClassNotFoundException{
		Class.forName(driverClass);
	}
	
	/**
	 * 获得连接的方法:
	 * @throws SQLException 
	 */
	public static Connection getConnection() throws Exception{
		loadDriver();
		Connection conn = DriverManager.getConnection(url, username, password);
		return conn;
	}
	
	/**
	 * 资源释放
	 */
	public static void release(Statement stmt,Connection conn){
		if(stmt != null){
			try {
				stmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			stmt = null;
		}
		if(conn != null){
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			conn = null;
		}
	}
	
	public static void release(ResultSet rs,Statement stmt,Connection conn){
		if(rs!= null){
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			rs = null;
		}
		if(stmt != null){
			try {
				stmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			stmt = null;
		}
		if(conn != null){
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			conn = null;
		}
	}
}

  • 定义静态常量,保证连接池只被初始化一次,不然以前写法的增删改查要创建四次连接池
/**
 * JDBC的工具类
 * @author jt
 *
 */
public class JDBCUtils2 {
	private static final ComboPooledDataSource dataSource = new ComboPooledDataSource();
	
	/**
	 * 获得连接的方法:
	 * @throws SQLException 
	 */
	public static Connection getConnection() throws Exception{
		Connection conn = dataSource.getConnection();
		return conn;
	}
	
	/**
	 * 资源释放
	 */
	public static void release(Statement stmt,Connection conn){
		if(stmt != null){
			try {
				stmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			stmt = null;
		}
		if(conn != null){
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			conn = null;
		}
	}
	
	public static void release(ResultSet rs,Statement stmt,Connection conn){
		if(rs!= null){
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			rs = null;
		}
		if(stmt != null){
			try {
				stmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			stmt = null;
		}
		if(conn != null){
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			conn = null;
		}
	}
}

JDBC总结


反射机制

基本作用/常见方法
Class类
Constructor类
Field类
Method类

反射概述

机制

在运行期,动态调用类的对象的属性和方法

JVM负责class文件执行
JVM里的classloader把字节码文件加载到JVM中,然后JVM负责解释运行
JVM运行class文件称为运行期
可动态获取属性,方法,通过class对象动态生成类的实例

字节码文件加载到内存中产生Class对象——Class对象
构造——Constructor对象

反射常用对象

Person.class;//类
person.getClass;//对象
forName全类名:要进行异常处理

Constructor对象

getConstructor(可变参数)
参数类型为Class类型
0——》无参构造
newInstance参数对应构造器,要强转

Field对象

  • 获得本类所有属性
  • 获取类的public属性(包括父类)
    关键反射私有属性(没有set/get方法)

Method对象


JUnit单元测试

  • Junit是单元测试框架,本身也是单元测试的工具

在这里插入图片描述

  • 主流开发模型:瀑布式,少量基于敏捷式开发
  • 软件测试的单元测试由开发完成

新建Test Case类型的对象过程(该文件自动帮我们创建方法【空,要补充测试逻辑】)如图

在这里插入图片描述


  • @Test注解表示Junit框架会调用方法内代码(类比main方法)
  • 断言来显示结果
  • 如果两个参数相等,通过

在这里插入图片描述


  • @Ignore:跳过此方法

  • 测试方法抛出异常

在这里插入图片描述


  • 提供限制条件,timeout=1000(考虑程序效率时)

在这里插入图片描述


@before
@after

  • 使用场景:创建连接(before)与关闭连接(after)定义为公共的资源(方法)
  • 任何一个测试方法在执行时必须调用它(before为执行自己方法体之前,after为执行自己方法体之后)

@FixMethodOrder
在这里插入图片描述

  • 多个test方法的执行顺序默认依赖于JVM的顺序,可手动指定测试方法的执行顺序,如上图

在这里插入图片描述- 打包一起测试的实例

  • 创建一个基于Test Case的测试类——》然后光标处list一起测试的场景,运行上述类则一起测试所有被list的类
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值