学完了Java SE部分的内容后,继续学习Java EE。学习JavaWeb以及一些主流的开发框架如SSM,SpringBoot,SpringClound等。首先来学习JDBC,一个与数据库连接相关的API。
一、JDBC 1、概述 1)概念 使用Java语言操作关系型数据库的一套API
JDBC(Java DataBase Connectivity),即==Java数据库连接==
2)本质
是一套操作所有关系型数据库的规则,即接口
由各数据库厂商去实现这套接口,提供数据库驱动jar包 (驱动即实现类)
面向接口(JDBC)编程,真正执行代码的是驱动jar包中的实现类
3)好处
各数据库厂商使用相同的接口,Java代码不需要针对不同数据库分别开发
可随时替换底层数据库,访问数据库的Java代码基本不变
2、基本原理 2.1 使用步骤 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 1. 创建工程,导入驱动jar包- 注意要Add as Library将jar包添加到 java项目中,才会生效 2. 注册驱动- class.forName("com.mysql.jdbc.Driver" ); (用到反射) 3. 获取连接- Connection conn=DriverManager.getConnecction(url,username,password); 4. 定义SQL语句- String sql="update..." ; 5. 获取执行SQL对象- Statement stmt=conn.createStatement(); 6. 执行SQL- stmt.executeUpdate(sql); 7. 处理返回结果8. 释放资源流程小结: 导入jar包---->注册驱动---->获取连接---->定义SQL---->获取对象---->执行SQL---->返回结果---->释放资源
2.2 代码示例 1)数据库准备 在数据库管理工具Navicat下创建一个数据库db1和表account,表中定义了id,name,money三个字段,查询表中的数据如下 2)使用Java操作数据库(JDBC数据库连接) IDEA右键src,new一个Java Class,命名为com.itweb.jdbc.JDBCDemo,这样使层级结构更加清晰,方便管理不同功能的类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package com.jdbc;import java.sql.Connection;import java.sql.DriverManager;import java.sql.Statement;public class jdbcDemo { public static void main (String[] args) throws Exception { Class.forName("com.mysql.jdbc.Driver" ); String url="jdbc:mysql://127.0.0.1:3306/db1" ; String username="root" ; String password="123456" ; Connection conn = DriverManager.getConnection(url, username, password); String sql="UPDATE account SET money=500 WHERE id=1" ; Statement stmt = conn.createStatement(); int count = stmt.executeUpdate(sql); System.out.println(count); stmt.close(); conn.close(); } }
3)查看运行结果 运行结果如下图 此时再返回Navicat查询结果
注:可能有的同学在java那边操作完成后,回来直接通过点击表的方式来查看,发现没变化,这是因为没有刷新。右键选择刷新即可。
3、相关API 3.1 DriverManager(驱动管理类) 主要有两个作用
1 2 3 4 5 6 7 8 9 10 1. 注册驱动- Class.forName("com.mysql.jdbc.Driver" ); 感兴趣的可ctrl+B查看Driver类源码 2. 获取数据库连接DriverManager.getConnection(url, username, password); 相关说明 1 )url:连接路径(jdbc:mysql:如:"jdbc:mysql://127.0.0.1:3306/db1" ,如果连接的是本机mysql服务器(ip为127.0 .0 .1 ,也可写为localhost), 且mysql服务默认端口为3306 ,则以上可以简写为 "jdbc:mysql:///db1" ; 2 )配置useSSL=false 参数,禁用安全连接方式,解决警告问题。
3.2 Connection(数据库连接对象) 主要作用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 1 )获取执行SQL的对象- 普通执行SQL对象:Statement createStatement () - 预编译SQL的执行SQL对象,防止SQL注入:PreparedStatement prepareStatement (sql) - 执行存储过程的对象:CallableStatement prepareCall (sql) 2 )管理事务(重点)- mysql事务管理 - 开启事务:begin;/start transaction; - 提交事务:commit - 回滚事务:rollback - jdbc事务管理:Connection接口中定义了3 个对应的方法(使用try -catch 异常处理机制) - 开启事务:setAutoCommit(Boolean autoCommit):true 为自动提交事务;false 为手动提交事务,即为开启事务 - 提交事务:commit() - 回滚事务:rollback() 由上,只要单独记一下两者的开启事务,提交和回滚相同
3.3 Statement(执行SQL语句) 主要作用
1 2 3 4 5 6 7 1 )int excuteUpdate (sql) :执行DML、DDL语句- 返回值 - DML语句影响的行数 - DDL语句执行后,执行成功也可能返回0 2 )ResultSet excuteQuery (sql) :执行DQL语句- 返回值 - ResultSet结果集对象
拓展(DDL和DML):
DML(Data Manipulation Language)数据操作语言-数据库的基本操作,SQL中处理数据等操作统称为数据操纵语言,简而言之就是实现了基本的“增删改查”操作。常用关键字:select、update、delete、insert、merge
DDL(Data Definition Language)数据定义语言-用于定义和管理 SQL 数据库中的所有对象的语言,对数据库中的某些对象(例如,database,table)进行管理。关键字:create、alter、drop、truncate、comment、grant、revoke
区别:① DML操作是可以手动控制事务的开启、提交和回滚的。② DDL操作是隐性提交的,不能rollback!
3.4 ResultSet(结果集对象) API文档解释
ResultSet对象维护指向其当前数据行的游标 。 最初,光标位于第一行之前 。 next方法将光标移动到下一行,并且因为当ResultSet对象中没有更多行时它返回false ,它可以在while循环中用于迭代结果集。
主要作用
1 2 3 4 5 6 7 8 9 10 11 12 13 1 )封装了DQL查询语句的结果:ResultSet stmt.executeQuery(sql) - 执行DQL语句,返回ResultSet对象 2 )获取查询结果boolean next () - 将光标从当前位置向前移动一行 - 判断行当前是否为有效行(是否有数据) [数据类型] get[数据类型](参数):获取数据,如 int getInt (参数) ; String getString (参数) 其中int 为列编号(从1 开始),String为列名称
3.5 PreparedStatement(预编译SQL对象) API文档解释
是一个接口,继承自Statement,表示预编译SQL语句的对象
主要作用:预编译SQL语句并执行,预防SQL注入问题
SQL注入:通过操作输入来修改事先定义好的SQL语句,来达到执行代码对服务器进行攻击的方法
防止SQL注入步骤(重点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1 )获取PreparedStatement对象- SQL语句中的参数值用占位符?替代 String sql="SELECT * FROM tb_user WHERE username=? AND password=?" ; - 通过Connection对象的获取,并传入对应的sql语句 PreparedStatement pstmt=conn.prepareStatement(sql) 2 )设置参数值PreparedStatement对象:setXxx(参数1 ,参数2 ):给?赋值 Xxx:数据类型;如setInt(参数1 ,参数2 ) 参数: - 参数1 :?的位置编号,从1 开始 - 参数2 :?的值 3 )执行SQLexecuteUpdate () ; / executeQuery(); 不需要传递sql
防止SQL注入的好处(了解)
1 2 1. 预编译SQL,性能更高2. 防止SQL注入,将敏感字符进行转义
3.6 代码示例 1)演示Connection
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package com.itweb.jdbc;import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;import java.sql.Statement;public class JDBCDemo { public static void main (String[] args) throws Exception { Class.forName("com.mysql.cj.jdbc.Driver" ); String url="jdbc:mysql://127.0.0.1:3306/db1?useSSL=false" ; String username="root" ; String password="123456" ; Connection conn = DriverManager.getConnection(url, username, password); String sql1="UPDATE account SET money=1000 WHERE id=1" ; String sql2="UPDATE account set money=2000 WHERE id=3" ; Statement stmt = conn.createStatement(); try { conn.setAutoCommit(false ); int count1 = stmt.executeUpdate(sql1); System.out.println(count1); int i=3 /0 ; int count2 = stmt.executeUpdate(sql2); System.out.println(count2); conn.commit(); } catch (SQLException e) { conn.rollback(); e.printStackTrace(); } stmt.close(); conn.close(); } }
运行结果
1 2 3 1 Exception in thread "main" java.lang.ArithmeticException: / by zero at com.itweb.jdbc.JDBCDemo.main(JDBCDemo.java:31 )
回去刷新数据库表,发现原来的数据没有被更改,这样保证了在任意一个出现异常时都不会提交事务而是回滚。很好理解,比如自己可以思考一下生活中银行转账的例子。 2)演示Statement
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package com.itweb.jdbc;import org.junit.Test;import java.sql.Connection;import java.sql.DriverManager;import java.sql.Statement;public class JDBCDemo { @Test public void testDML () throws Exception { String url="jdbc:mysql://127.0.0.1:3306/db1?useSSL=false" ; String username="root" ; String password="123456" ; Connection conn = DriverManager.getConnection(url, username, password); String sql="UPDATE account SET money=3000 WHERE id=1" ; Statement stmt = conn.createStatement(); int count=stmt.executeUpdate(sql); if (count>0 ){ System.out.println("修改成功!" ); } else { System.out.println("修改失败!" ); } stmt.close(); conn.close(); } @Test public void testDDL () throws Exception{ String url="jdbc:mysql:///db1?useSSL=false" ; String username="root" ; String password="123456" ; Connection conn = DriverManager.getConnection(url, username, password); String sql="DROP DATABASE db2" ; Statement stmt = conn.createStatement(); int count=stmt.executeUpdate(sql); System.out.println(count); stmt.close(); conn.close(); } }
运行结果说明 运行testDML()方法前,数据库表的数据 运行testDML()方法,如下,再回去刷新数据库,发现id为1的用户money修改成功。
运行testDDL()方法,如下,再回去刷新数据库,发现db2已经成功被删除。
3)演示ResultSet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package com.itweb.jdbc;import org.junit.Test;import java.sql.Connection;import java.sql.DriverManager;import java.sql.ResultSet;import java.sql.Statement;public class JDBCDemo { @Test public void testResultSet () throws Exception{ String url="jdbc:mysql:///db1?useSSL=false" ; String username="root" ; String password="123456" ; Connection conn = DriverManager.getConnection(url, username, password); String sql="SELECT * FROM `account`" ; Statement stmt = conn.createStatement(); ResultSet rs=stmt.executeQuery(sql); while (rs.next()){ int id = rs.getInt("id" ); String name = rs.getString("name" ); double money = rs.getDouble("money" ); System.out.println(id); System.out.println(name); System.out.println(money); System.out.println("-----------" ); } rs.close(); stmt.close(); conn.close(); } }
运行结果 4)ResultSet综合案例 需求:查询account账户表数据,封装为Account对象,并储存到ArrayList集合中。 首先需要创建一个Account类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package com.itweb.pojo;public class Account { private int id; private String name; private double money; public int getId () { return id; } public void setId (int id) { this .id = id; } public String getName () { return name; } public void setName (String name) { this .name = name; } public double getMoney () { return money; } public void setMoney (double money) { this .money = money; } @Override public String toString () { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}' ; } }
然后再编写测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 package com.itweb.jdbc;import com.itweb.pojo.Account;import org.junit.Test;import java.sql.Connection;import java.sql.DriverManager;import java.sql.ResultSet;import java.sql.Statement;import java.util.ArrayList;import java.util.List;public class JDBCDemo { @Test public void testResultSet () throws Exception{ String url="jdbc:mysql:///db1?useSSL=false" ; String username="root" ; String password="123456" ; Connection conn = DriverManager.getConnection(url, username, password); String sql="SELECT * FROM `account`" ; Statement stmt = conn.createStatement(); ResultSet rs=stmt.executeQuery(sql); List<Account> list=new ArrayList <>(); while (rs.next()){ Account account=new Account (); int id = rs.getInt("id" ); String name = rs.getString("name" ); double money = rs.getDouble("money" ); account.setId(id); account.setName(name); account.setMoney(money); list.add(account); } System.out.println(list); rs.close(); stmt.close(); conn.close(); } }
运行结果 5)用户登录案例+演示SQL注入(使用单元测试Junit) 首先在数据库db1下创建一个user表,包含username和password两个字段,查询如下 编写测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 package com.itweb.jdbc;import org.junit.Test;import java.sql.Connection;import java.sql.DriverManager;import java.sql.ResultSet;import java.sql.Statement;public class JDBCDemo { @Test public void testLogin () throws Exception{ String url="jdbc:mysql:///db1?useSSL=false" ; String username="root" ; String password="123456" ; Connection conn = DriverManager.getConnection(url, username, password); String name="张三" ; String pwd="123" ; String sql="SELECT * FROM `user` where username='" +name+"' and password='" +pwd+"'" ; Statement stmt = conn.createStatement(); ResultSet rs=stmt.executeQuery(sql); if (rs.next()){ System.out.println("登录成功~" ); } else { System.out.println("登录失败~" ); } rs.close(); stmt.close(); conn.close(); } @Test public void testLogin_Inject () throws Exception{ String url="jdbc:mysql:///db1?useSSL=false" ; String username="root" ; String password="123456" ; Connection conn = DriverManager.getConnection(url, username, password); String name="aaa" ; String pwd="' or '1'='1" ; String sql="SELECT * FROM `user` where username='" +name+"' and password='" +pwd+"'" ; System.out.println(sql); Statement stmt = conn.createStatement(); ResultSet rs=stmt.executeQuery(sql); if (rs.next()){ System.out.println("登录成功~" ); } else { System.out.println("登录失败~" ); } rs.close(); stmt.close(); conn.close(); } }
运行testLogin(),结果如下 运行testLogin_Inject(),结果如下 ==原因分析(重点)==
使用(’ or ‘1’=’1)等特殊字符作为登录密码,破解了上述定义sql查询语句的字符串拼接漏洞(即SQL注入的本质:通过操作输入来修改事先定义好的SQL语句,来达到执行代码对服务器进行攻击的方法)。通过打印sql,得到SELECT * FROM user
where username=’aaa’ and password=’’ or ‘1’=’1’,可以发现,where后面的条件判断为 false and false or true ,由于and的优先级比or高,所以整体结果为true ,即无论输入的是什么用户名,通过该串密码都能查询成功。当然,现在的程序都不会让SQL注入发生,因为这是一个低级且严重的错误。
==6)演示防止SQL注入(重点)==
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package com.itweb.jdbc;import org.junit.Test;import java.sql.*;public class JDBCDemo { @Test public void testPreparedStatement () throws Exception{ String url="jdbc:mysql:///db1?useSSL=false" ; String username="root" ; String password="123456" ; Connection conn = DriverManager.getConnection(url, username, password); String name="aaa" ; String pwd="' or '1'='1" ; String sql="SELECT * FROM user WHERE username=? AND password=?" ; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1 ,name); pstmt.setString(2 ,pwd); ResultSet rs=pstmt.executeQuery(); if (rs.next()){ System.out.println("登录成功~" ); } else { System.out.println("登录失败~" ); } rs.close(); pstmt.close(); conn.close(); } }
运行结果
实质:pstmt对象在传入参数时会对其值进行转义操作 ,从而避免了由于拼接产生的语义问题 。
4、数据库连接池(了解,会用就行) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1 )概念管理数据库连接的容器,负责分配、管理数据库连接 2 )特点- 允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个 - 释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏 3 )好处- 资源重用 - 提示系统响应速度 - 避免数据库连接遗漏 4 )Driud(德鲁伊)数据库连接池(阿里巴巴)使用步骤 - 导入jar包 - 定义配置文件 - 加载配置文件 - 获取数据库连接池对象 - 获取连接