目录
1、什么是JDBC?
通过Java语言操作数据库,操作表中的数据。
SUN公司为了简化、统一对数据库的操作,定义了一套Java操作数据库的规范,称之为JDBC。
2、JDBC的本质
是官方(sun公司)定义的一套操作所有关系型数据库的规则(接口)。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,运行时的代码其实是驱动jar包中的实现类。
在Java中要想访问数据库只能通过JDBC,JDBC是Java访问数据库的基础,其他数据库访问技术都是对JDBC的封装(Hibernate,MyBatis),JDBC是为了访问不同的数据库,提供了一种统一的访问方式,JDBC本身是Java连接数据库的一个标准,是进行数据库连接的抽象层。由Java编写的一组类和接口,接口的实现由各大数据库厂商来实现。
3、JDBC入门案例
3.1、前期准备
为了能够测试JDBC,这里利用junit进行测试,首先创建一个JavaWeb项目,在项目src包下创建一个测试类UserTest,并导入mysql-connector的jar包,这就是JDBC的核心jar包,没有它是不能调用其中的类方法的,jar包放到WEB-INF下的lib包内。
这里提供了mysql5版本和mysql8版本的jar包,对应的就是mysql的版本,如果是mysql5系列版本的就用5.16的jar包,如果是mysql8系列的版本就用8.0.16版本jar包。
链接:https://pan.baidu.com/s/1loxaN41BXlfdePT_8DDBDw
提取码:388i
添加到lib包下的jar包需要手动去导入一下右键jar包,点击ADD Library
3.2、mysql数据库准备
创建一个t_user表,自行添加数据
CREATE TABLE `t_user` (
`id` int(11) NOT NULL,
`username` varchar(20) DEFAULT NULL,
`password` varchar(20) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
这里是我添加的数据
3.3、测试类代码
前期工作准备就绪后,就开始连接数据库,编写代码了,下面就演示一下CRUD操作
public class UserTest {
@Test
public void select_User() throws ClassNotFoundException, SQLException {
//加载mysql数据库JDBC驱动
Class.forName("com.mysql.jdbc.Driver");
//后面test是我t_user存放的数据库名
String url="jdbc:mysql://localhost:3306/test";
//数据库账户
String username="xxx";
//数据库密码
String password="xxx";
//获取connection对象
Connection con = DriverManager.getConnection(url, username, password);
//创建statement对象
Statement statement = con.createStatement();
//查询语句得到一个ResultSet类型的结果集
ResultSet resultSet = statement.executeQuery("select * from t_user");
//遍历结果集 输出控制台 next()方法如果有结果就返回true
while (resultSet.next()){
int id = resultSet.getInt("id");
String username1 = resultSet.getString("username");
String password1 = resultSet.getString("password");
int age = resultSet.getInt("age");
System.out.println(id+" "+username1+" "+password1+" "+age);
}
//后创建的先关闭
resultSet.close();
statement.close();
con.close();
}
@Test
public void add_User() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
String url="jdbc:mysql://localhost:3306/test";
String username="xxx";
String password="xxx";
//获取connection对象
Connection con = DriverManager.getConnection(url, username, password);
//创建statement对象
Statement statement = con.createStatement();
//返回int 类型 代表的是修改的条数 executeUpdate可以为insert、delete、update操作
int i = statement.executeUpdate("insert into t_user value(6,'王五','wda4822',26)");
System.out.println(i);
//后创建的先关闭
statement.close();
con.close();
}
@Test
public void delete_User() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
String url="jdbc:mysql://localhost:3306/test";
String username="xxx";
String password="xxx";
//获取connection对象
Connection con = DriverManager.getConnection(url, username, password);
//创建statement对象
Statement statement = con.createStatement();
int i = statement.executeUpdate("delete from t_user where id=6");
System.out.println(i);
//后创建的先关闭
statement.close();
con.close();
}
@Test
public void update_User() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
String url="jdbc:mysql://localhost:3306/test";
String username="xxx";
String password="xxx";
//获取connection对象
Connection con = DriverManager.getConnection(url, username, password);
//创建statement对象
Statement statement = con.createStatement();
int i = statement.executeUpdate("update t_user set username='宋浩',password='555sss4',age=24 where id=5 ");
System.out.println(i);
//后创建的先关闭
statement.close();
con.close();
}
}
3.4、异常处理
在之前的代码中我们都是通过throws抛出异常,但是实际开发中需要我们去手动处理异常,下面我就以查询语句为例,演示如何处理异常
public class UserTestException {
@Test
public void select_User() {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//因为try里面为局部变量 所以需要在外面定义初始化,为后面释放资源提供方便
Connection con = null;
Statement statement = null;
ResultSet resultSet = null;
try {
String url = "jdbc:mysql://localhost:3306/test";
String username = "root";
String password = "xxxx";
//获取connection对象
con = DriverManager.getConnection(url, username, password);
//创建statement对象
statement = con.createStatement();
//查询语句得到一个ResultSet类型的结果集
resultSet = statement.executeQuery("select * from t_user");
//遍历结果集 输出控制台 next()方法如果有结果就返回true
while (resultSet.next()) {
int id = resultSet.getInt("id");
String username1 = resultSet.getString("username");
String password1 = resultSet.getString("password");
int age = resultSet.getInt("age");
System.out.println(id + " " + username1 + " " + password1 + " " + age);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//后创建的先关闭
try {
if (resultSet != null) {
//先判断是否为null 不为null就释放掉
resultSet.close();
//手动把资源赋值为null,提示gc垃圾回收机制释放资源
resultSet = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (statement != null) {
statement.close();
statement = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (con != null) {
con.close();
con = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
4、抽取工具类
4.1、抽取重复代码
以上代码虽然能够实现功能,但是代码的耦合度太高,比如加载驱动和定义url路径、用户、密码,以及最后的清除资源等,这都是重复工作,为了降低耦合度,我们需要把这些重复代码抽取出来到一个工具类中。
首先创建一个utils包,里面存放的就是工具类
public class JDBCUtils {
//利用静态代码块去加载驱动,随着类的加载而加载且加载一次
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//获取连接对象
public static Connection getConnection(){
String url="jdbc:mysql://localhost:3306/test";
String user="root";
String password="xxxx";
Connection con = null;
try {
con = DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
e.printStackTrace();
}
return con;
}
//释放查询语句资源
public static void close(ResultSet rs,Statement stmt,Connection con){
try {
if (rs != null) {
rs.close();
rs = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (stmt != null) {
stmt.close();
stmt = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (con != null) {
con.close();
con = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
//释放insert、delete、update语句的资源
public static void close(Statement stmt,Connection con){
try {
if (stmt != null) {
stmt.close();
stmt = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (con != null) {
con.close();
con = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
我们再去测试功能时,代码就会得到简化,下面就是使用工具类以后
@Test
public void select_user(){
Connection con = JDBCUtils.getConnection();
Statement stmt = null;
ResultSet rs = null;
try {
stmt = con.createStatement();
rs = stmt.executeQuery("select * from t_user");
while (rs.next()) {
int id = rs.getInt("id");
String username = rs.getString("username");
String password = rs.getString("password");
int age = rs.getInt("age");
System.out.println(id + " " + username + " " + password + " " + age);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.close(rs,stmt,con);
}
}
4.2、抽取jdbc.properties配置文件
目前还是有一点尚且优化的地方,就是我们填写jdbc的信息,以后肯定是会改变这些信息的,为了方便维护和查看,我们需要把这些信息抽取出来到一个配置文件进行存放,然后通过获取这个配置文件去加载内容。
在src目录下创建一个文件名为jdbc.properties
jdbc.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.user=root
jdbc.password=xxxx
在工具类中进行优化
public class JDBCUtils2 {
public JDBCUtils2() {
}
private static String className;
private static String url;
private static String username;
private static String password;
private static Connection con = null;
static {
//初始化properties对象
Properties pro = null;
InputStream is=null;
try {
//通过反射获取配置文件io对象
is = JDBCUtils2.class.getClassLoader().getResourceAsStream("jdbc.properties");
pro = new Properties();
//调用load方法读取jdbc.properties配置文件内容
pro.load(is);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//分别调用getProperty方法获取对应的value值
className = pro.getProperty("jdbc.className");
url = pro.getProperty("jdbc.url");
username = pro.getProperty("jdbc.user");
password = pro.getProperty("jdbc.password");
//加载驱动
try {
Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//创建连接
try {
con = DriverManager.getConnection(url, username, password);
} catch (SQLException e) {
e.printStackTrace();
}
}
public static Connection getConnection() {
return con;
}
//释放查询语句资源
public static void close(ResultSet rs, Statement stmt, Connection con) {
try {
if (rs != null) {
rs.close();
rs = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (stmt != null) {
stmt.close();
stmt = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (con != null) {
con.close();
con = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
//释放insert、delete、update语句的资源
public static void close(Statement stmt, Connection con) {
try {
if (stmt != null) {
stmt.close();
stmt = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (con != null) {
con.close();
con = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
测试类代码
@Test
public void delete_user(){
Connection con = JDBCUtils2.getConnection();
Statement stmt = null;
try {
stmt = con.createStatement();
int i = stmt.executeUpdate("delete from t_user where id=5");
System.out.println(i);
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils2.close(stmt,con);
}
}
5、封装实体类
首先创建一个bean包,用来存放实体类
public class User {
private String username;
private String password;
private int age;
public User() {
}
public User(String username, String password, int age) {
this.username = username;
this.password = password;
this.age = age;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
'}';
}
}
测试类
public class UserTest3 {
@Test
public void queryUser(){
Connection conn = JDBCUtils2.getConnection();
User user=null;
Statement stmt=null;
ResultSet rs=null;
List<User> list=new ArrayList<>();
try {
stmt = conn.createStatement();
rs = stmt.executeQuery("select * from t_user");
while(rs.next()){
user=new User();
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
user.setAge(rs.getInt("age"));
list.add(user);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils2.close(rs,stmt,conn);
}
//遍历list集合
list.forEach(System.out::println);
}
}
6、 解决SQL注入问题
先来看这一问题
public class UserTest3 {
@Test
public void queryUser(){
Connection conn = JDBCUtils2.getConnection();
User user=null;
Statement stmt=null;
ResultSet rs=null;
List<User> list=new ArrayList<>();
try {
stmt = conn.createStatement();
rs = stmt.executeQuery("select * from t_user where username=''or'1'='1'");
while(rs.next()){
user=new User();
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
user.setAge(rs.getInt("age"));
list.add(user);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils2.close(rs,stmt,conn);
}
//遍历list集合
list.forEach(System.out::println);
}
}
我在查询语句后面加了一个条件为username=''or'1'='1',我们现在运行程序
发现所有信息被查询出来了,这是怎么一回事呢,这就是SQL注入问题,是一个严重的安全隐患,那我们就要开始解决这个BUG,这也是字符串拼接带来的坏处,为了解决这个BUG,就要想办法不用字符串拼接,下面就讲述一下preparedStatment方法,其实现是通过占位符来完成的。
改造代码如下
public class UserTest3 {
@Test
public void queryUser() {
Connection conn = JDBCUtils2.getConnection();
User user = null;
// Statement stmt=null;
PreparedStatement pstmt = null;
ResultSet rs = null;
List<User> list = new ArrayList<>();
try {
// stmt = conn.createStatement();
// rs = stmt.executeQuery("select * from t_user where username=''or'1'='1'");
//创建pstmt对象并赋值sql语句
pstmt = conn.prepareStatement("select * from t_user where username= ?");
//为占位符进行赋值,左边是占位符索引从1开始,右边为字符串内容
pstmt.setString(1,"''or'1'='1'");
rs = pstmt.executeQuery();
while (rs.next()) {
user = new User();
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
user.setAge(rs.getInt("age"));
list.add(user);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils2.close(rs, pstmt, conn);
}
//遍历list集合
list.forEach(System.out::println);
}
}
可见,这次再输入就查询不到了。