文章目录
1. JDBC 的概念
JDBC(Java DataBase Connectivity),Java 数据库连接。就是用 Java 语言来操作数据库。
2. JDBC 的本质
市面上有很多不同厂商的关系型数据库,对于程序员来说学习成本太高,大家期望使用统一的一套 Java 代码可以操作所有的关系型数据库,于是 sun 公司定义的一套操作所有关系型数据库的规则(接口),这就是 JDBC。各个数据库厂商去实现这套接口,提供数据库驱动 jar 包。我们可以使用这套接口来编程,真正执行的代码是驱动 jar 包中的实现类。
3. JDBC 快速入门
-
JDBC 使用步骤
- 导入驱动 jar 包
- 复制 mysql-connector-java-5.1.37-bin.jar 到项目的 libs 目录下
- 右键 --> Add As Library
- 注册驱动
- 获取数据库连接对象 Connection
- 定义 sql
- 获取执行 sql 语句的对象 Statement
- 执行 sql,接受返回结果
- 处理结果
- 释放资源
- 导入驱动 jar 包
-
编码实现
package com.zt.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class JdbcDemo1 { public static final String URL = "jdbc:mysql://localhost:3306/test1"; public static final String USER = "root"; public static final String PASSWORD = "123456"; public static void main(String[] args) throws Exception { // 1.导入驱动 jar 包 // 2.注册驱动 Class.forName("com.mysql.jdbc.Driver"); // 3.获取数据库连接对象 Connection Connection connection = DriverManager.getConnection(URL, USER, PASSWORD); // 4.定义 sql 语句 String sql = "select * from stu"; // 5.获取执行 sql 的对象 Statement Statement statement = connection.createStatement(); // 6.执行 sql,接受返回结果 ResultSet resultSet = statement.executeQuery(sql); // 7.处理结果 while (resultSet.next()){ System.out.println(resultSet.getString(1)+" "+resultSet.getString(2)); } // 8.释放资源 statement.close(); connection.close(); } }
4. JDBC 各类详解
4.1 DriverManager:驱动管理对象
DriverManager 的功能:
-
注册驱动:告诉程序该使用哪一个数据库驱动 jar 包
方法:
// 注册与给定的驱动程序 DriverManager static void registerDriver(Driver driver)
我们在上面注册驱动使用的代码为:
Class.forName("com.mysql.jdbc.Driver");
于是查看 com.mysql.jdbc.Driver 的源码发现存在静态代码块使用了 registerDriver(Driver driver) 方法
static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } }
注意:mysql5之后的驱动 jar 包可以省略注册驱动的步骤。
-
获取数据库连接
方法:
// 试图建立到给定数据库 URL 的连接 static Connection getConnection(String url, String user, String password)
参数:
-
url:指定连接的路径
语法:jdbc:mysql://ip地址(域名):端口号/数据库名称 例子:jdbc:mysql://localhost:3306/test1 细节:如果连接的是本机 mysql 服务器,并且 mysql 服务默认端口是 3306,则 url 可以简写为: jdbc:mysql:///数据库名称
-
user:用户名
-
password:密码
-
4.2 Connection:数据库连接对象
Connection 的功能:
-
获取执行 sql 的对象
方法:
// 创建一个 Statement 对象来将 SQL 语句发送到数据库 Statement createStatement() // 创建一个 PreparedStatement 对象来将参数化的 SQL 语句发送到数据库 PreparedStatement prepareStatement(String sql)
-
管理事务
-
开启事务
方法:
// 调用该方法设置参数为 false,即开启事务 void setAutoCommit(boolean autoCommit)
-
提交事务
方法:
void commit()
-
回滚事务
方法:
void rollback()
-
4.3 Statement:执行 sql 的对象
Statement 的功能:
-
执行 sql
方法:
// 可以执行任意的 sql(不常用) boolean execute(String sql) // 执行 DML(insert、update、delete)语句、DDL(create,alter、drop)语句 // 返回值 int:影响的行数,可以通过这个影响的行数判断 DML 语句是否执行成功 返回值 >0 的则执行成功,反之,则失败。 int executeUpdate(String sql) // 执行 DQL(select)语句 ResultSet executeQuery(String sql)
Statement 练习
-
需求
stu 表增加一条记录
-
编码实现
package com.zt.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; /** * stu 表增加一条记录 */ public class JDBCDemo2 { public static final String URL = "jdbc:mysql://localhost:3306/test1"; public static final String USER = "root"; public static final String PASSWORD = "123456"; public static void main(String[] args) { Connection connection = null; Statement statement = null; try { // 1. 注册驱动 Class.forName("com.mysql.jdbc.Driver"); // 2.获取数据库连接对象 connection = DriverManager.getConnection(URL,USER,PASSWORD); // 3.定义 sql 语句 String sql = "insert into stu values(null,'王五',20,1)"; // 4.获取执行 sql 的对象 Statement statement = connection.createStatement(); // 5.执行sql int i = statement.executeUpdate(sql); // 6.处理结果 if(i > 0){ System.out.println("添加成功"); }else { System.out.println("添加失败"); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } finally { // 释放资源 if (connection != null){ // 避免空指针异常 try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } if (statement != null){ try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }
4.4 ResultSet:结果集对象,封装查询结果
ResultSet 的功能:
-
移动游标
方法:
// 游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据),如果是,则返回 false,如果不是则返回 true boolean next()
-
获取数据
方法:
// Xxx:代表数据类型 如: int getInt() , String getString() Xxx getXxx(参数)
参数:
- int:代表列的编号,从1开始 如:getString(1)
- String:代表列名称。 如:getInt(“id”)
ResultSet 的练习:
-
需求
定义一个方法,查询 stu 表的数据将其封装为对象,然后装载集合,返回集合。
-
编码实现
-
定义一个 Student 类
package com.zt.domain; /** * 封装 Stu 表数据的 JavaBean */ public class Student { private int id; private String name; private int age; private int class_id; public Student(int id, String name, int age, int class_id) { this.id = id; this.name = name; this.age = age; this.class_id = class_id; } 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 int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getClass_id() { return class_id; } public void setClass_id(int class_id) { this.class_id = class_id; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", class_id=" + class_id + '}'; } }
-
定义一个方法,查询 stu 表的数据将其封装为对象,然后装载集合,返回集合。
package com.zt.jdbc; import com.zt.domain.Student; import java.sql.*; import java.util.ArrayList; import java.util.List; public class JDBCDemo3 { /** * 定义一个方法,查询 stu 表的数据将其封装为对象,然后装载集合,返回集合。 */ public List<Student> getAllStudent(){ List<Student> students = new ArrayList<Student>(); Connection connection = null; Statement statement = null; ResultSet resultSet = null; try { // 1.注册驱动 Class.forName("com.mysql.jdbc.Driver"); // 2.获取连接 connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test1", "root", "123456"); // 3.定义 sql 语句 String sql = "select * from stu"; // 4.获取执行 sql 的对象 statement = connection.createStatement(); // 5.执行 sql resultSet = statement.executeQuery(sql); // 6.处理结果集 // 遍历结果集,封装对象,装载集合 Student student = null; while (resultSet.next()){ int id = resultSet.getInt("id"); String name = resultSet.getString("name"); int age = resultSet.getInt("age"); int class_id = resultSet.getInt("class_id"); student = new Student(id,name,age,class_id); students.add(student); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } finally { if(connection != null){ try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } if(statement != null){ try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } if(resultSet != null){ try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } } return students; } }
-
单元测试
package com.zt.test; import com.zt.domain.Student; import com.zt.jdbc.JDBCDemo3; import org.junit.Test; import java.util.List; public class JDBCDemo3Test { @Test public void testGetAllStudent(){ JDBCDemo3 test = new JDBCDemo3(); List<Student> allStudent = test.getAllStudent(); for (Student student : allStudent) { System.out.println(student); } } }
-
-
执行结果
Student{id=1, name='张三', age=18, class_id=1} Student{id=2, name='李四', age=19, class_id=2} Student{id=3, name='王五', age=20, class_id=1} Process finished with exit code 0
4.5 PreparedStatement:执行 sql 的对象
-
SQL 注入问题
在用户登录方法中,如果采用 statement 来作为执行 sql 的对象,那么要定义的 sql 语句如下:
String sql = "select * from user where username = '"+username+"'and password ='"+password+"'";
此时用户输入:
用户名: fhdsjkf 密码: a' or 'a' = 'a
结果却登录成功了!
这是因为在拼接 sql 时,有一些 sql 的特殊关键字参与字符串的拼接,这就是SQL 注入。
我们分析一下传入参数后的 sql 语句:
select * from user where username = 'fhdsjkf' and password = 'a' or 'a' = 'a' ; -- 等效于,相当于没过滤条件 select * from user where 'a' = 'a' ;
-
解决 SQL 注入问题
使用 PreparedStatement 对象可以解决这个问题。
PreparedStatement 是怎样预防 SQL 注入的呢?
数据库在处理 SQL 语句时都有一个预编译的过程,而 PreparedStatement 就是把一些格式固定的 SQL 编译后,存放在内存池中即数据库缓冲池,当我们再次执行相同的 SQL 语句时就不需要预编译的过程了,所以即使 SQL 注入特殊的语句,也会只当做参数传进去,不会当做指令执行。
-
PreparedStatement 的使用步骤
-
导入驱动 jar 包
-
注册驱动
-
获取连接对象 Connection
-
定义 sql 语句(sql 的参数使用
?
作为占位符。)如:String sql = "select * from user where username = ? and password =?";
-
获取执行 sql 的对象 PreparedStatement
方法:
PreparedStatement prepareStatement(String sql)
-
给占位符赋值
方法:
setXxx(参数1,参数2)
参数:
- 参数1:
?
的位置编号从 1 开始 - 参数2:
?
的值
- 参数1:
-
执行 sql
-
处理结果
-
释放资源
-
-
PreparedStatement 的好处
- 可以防止 SQL 注入
- 效率更高
5. JDBC 工具类:JDBCUtils
-
JDBCUtils 的作用
提供连接对象,释放资源等方法,简化 JDBC 的使用
-
JDBCUtils 的功能
-
获取连接对象方法 getConnection()
需求:不想传递参数,还得保证工具类的通用性。
解决方式:把参数写在配置文件中,再读取配置文件
-
释放资源方法
-
-
编码实现
jdbc.properties:
url=jdbc:mysql://localhost:3306/test1 user=root password=123456 driver=com.mysql.jdbc.Driver
JDBCUtils:
package com.zt.util; import java.io.IOException; import java.io.InputStream; import java.sql.*; import java.util.Properties; public class JDBCUtils { private static String url; private static String user; private static String password; private static String driver; /** * 从 jdbc.properties 文件的读取这些值,只需要读取一次即可拿到,所以使用静态代码块。 * 注意:静态代码块不能抛出异常,只能捕获 */ static { try { // 1.创建 Properties 集合类 Properties properties = new Properties(); // 2.通过类加载器获取 class 目录下 jdbc.properties 文件的路径 ClassLoader classLoader = JDBCUtils.class.getClassLoader(); InputStream resourceAsStream = classLoader.getResourceAsStream("jdbc.properties"); // 3.加载 jdbc.properties 文件到 properties 集合中 properties.load(resourceAsStream); // 4.读取 jdbc.properties 文件内容,给变量赋值 url = properties.getProperty("url"); user = properties.getProperty("user"); password = properties.getProperty("password"); driver = properties.getProperty("driver"); // 5.注册驱动 Class.forName(driver); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * 获取连接对象 * @return 连接对象 */ public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url,user,password); } /** * 释放资源(注意:释放资源是按从小到大的顺序) * @param statement * @param connection */ public static void close(Statement statement,Connection connection){ if(statement != null){ try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } if(connection != null){ try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } /** * 释放资源 * @param resultSet * @param statement * @param connection */ public static void close(ResultSet resultSet,Statement statement,Connection connection){ if(resultSet != null){ try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } if(statement != null){ try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } if(connection != null){ try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
6. 用户登录练习
-
需求分析
- 用户可以通过键盘输入用户名和密码来登陆
- 判断用户是否登录成功,并返回提示信息
-
编码实现
-
创建 user 表
CREATE TABLE USER( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32), password VARCHAR(32) ); INSERT INTO USER VALUES(NULL,'张三','123'); INSERT INTO USER VALUES(NULL,'李四','321');
-
编写登陆类
package com.zt.jdbc; import com.zt.util.JDBCUtils; import java.sql.*; import java.util.Scanner; public class JDBCDemo6 { public static void main(String[] args) { // 用户输入用户名和密码 Scanner scanner = new Scanner(System.in); System.out.println("用户名:"); String username = scanner.nextLine(); System.out.println("密码:"); String password = scanner.nextLine(); // 调用登陆方法 boolean login = new JDBCDemo6().login(username, password); // 判断登陆是否成功 if(login){ System.out.println("登陆成功"); }else { System.out.println("登陆失败"); } } /** * 登录方法 * @param username * @param password * @return 成功返回 true,失败返回 false */ public boolean login(String username,String password){ // 如果用户名密码为空,返回 false if(username == null || password == null){ return false; } Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { // 1.获取连接对象 connection = JDBCUtils.getConnection(); // 2.定义 sql 语句 String sql = "select * from user where username = ? and password =?"; // 3.获取执行 sql 的对象 preparedStatement = connection.prepareStatement(sql); // 4.给占位符赋值 preparedStatement.setString(1,username); preparedStatement.setString(2,password); // 5.执行 sql resultSet = preparedStatement.executeQuery(); // 6.如果有下一行,则返回 true,没有返回 false return resultSet.next(); } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtils.close(resultSet,preparedStatement,connection); } return false; } }
-
7. JDBC 控制事务
-
事务的概念
一个包含多个步骤的业务操作,如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。
-
事务的操作(使用 Connection 对象来管理事务)
-
开启事务
// 调用该方法设置参数为 false,即开启事务 void setAutoCommit(boolean autoCommit)
-
提交事务
// 在执行sql之前开启事务 void commit()
-
回滚事务
// 在 catch 中回滚事务 void rollback()
-
-
事务的练习
-
需求
用事务管理实现张三向李四转账 500 元
-
编码实现
package com.zt.jdbc; import com.zt.util.JDBCUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class JDBCDemo7 { public static void main(String[] args) { Connection connection = null; PreparedStatement preparedStatement1 = null; PreparedStatement preparedStatement2 = null; try { // 1.获取连接 connection = JDBCUtils.getConnection(); // 2.开启事务 connection.setAutoCommit(false); // 3.定义 sql String sql1 = "update account set balance = balance - ? where id = ?"; String sql2 = "update account set balance = balance + ? where id = ?"; // 4.获取执行 sql 对象 preparedStatement1 = connection.prepareStatement(sql1); preparedStatement2 = connection.prepareStatement(sql2); // 5.给占位符赋值 preparedStatement1.setInt(1,500); preparedStatement1.setInt(2,1); preparedStatement2.setInt(1,500); preparedStatement2.setInt(2,2); // 6.执行 sql preparedStatement1.executeUpdate(); preparedStatement2.executeUpdate(); // 7.提交事务 connection.commit(); } catch (Exception e) { // 此处异常捕获一个大的 // 8.出现异常,事务回滚 try { // 保证连接不为空才回滚 if(connection != null){ connection.rollback(); } } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); } finally { // 9.释放资源 JDBCUtils.close(preparedStatement1,connection); JDBCUtils.close(preparedStatement2,null); } } }
-