【JDBC】
JDBC的基本概念
概念:Java Database Connectivity,Java 数据库连接,Java语言操作数据库。
JDBC本质:希望使用统一的一套Java代码可以操作所有的关系型数据库**,JDBC定义了一套操作所有关系型数据库的规则(即接口),每一个数据库厂商都去实现这套接口,提供数据库驱动jar包,我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。**
快速入门
步骤:
- 导入驱动jar包
mysql-connector-java-8.0.11.jar
- 复制jar包到项目的libs目录下
- 右键 --> Add as Library
- 注册驱动
- 获取数据库的连接对象 Connection
- 定义sql
- 获取执行sql语句的对象 Statement
- 执行sql,接收返回的结果
- 处理结果
- 释放资源
/**
* JDBC快读入门
*/
public class JdbcDemo01 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1. 导入驱动jar包 `mysql-connector-java-8.0.11.jar`
// - 复制jar包到项目的libs目录下
// - 右键 --> Add as Library
//2. 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//3. 获取数据库的连接对象 Connection
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/practise?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT", "root", "root");
//4. 定义sql
String sql = "UPDATE account SET balance = 1000 WHERE id = 1";
//5. 获取执行sql语句的对象 Statement
Statement statement = connection.createStatement();
//6. 执行sql,接收返回的结果
int count = statement.executeUpdate(sql);
//7. 处理结果
System.out.println(count);
//8. 释放资源
statement.close();
connection.close();
}
}
详解各个类/接口
- DriverManager:驱动管理对象。
- Connection:数据库连接对象。
- Statement:执行sql的对象。
- ResultSet:结果集对象。
- PreparedStatement:执行sql的对象,功能比父接口Statement更强大。
DriverManager
功能:
- 注册驱动;
- 获取数据库连接。
注册驱动:(告诉程序该使用哪一个数据库驱动jar包)
static void registerDriver(Driver driver)
:注册与给定的驱动DriverManager。
写代码时使用:Class.forName("com.mysql.cj.jdbc.Driver");
通过查看源码发现:在com.mysql.cj.jdbc.Driver
类中存在静态代码块:
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
注意:mysql5之后的驱动jar包中可以省略注册驱动的步骤,但是建议还是写上注册启动语句。
原因:在jar包 --> META-INF --> services中存在一个
java.sql.Driver
文件,里面有com.mysql.cj.jdbc.Driver
语句,可以自动注册驱动。
获取数据库连接:
方法:
static Connection getConnection(String url, String user, String password){};
参数:
-
url:指定连接的路径
-
mysql的语法:
"jdbc:mysql://ip地址名(域名):端口号/数据库名称"
-
细节:如果连接的是本机的mysql服务器,并且mysql服务默认端口号是3306,则url可以简写为:
"jdbc:mysql:///数据库名称"
-
-
user:用户名
-
password:密码
Connection
功能:
- 获取执行sql的对象:
方法 | 含义 |
---|---|
Statement createStatement() | 创建一个 Statement 对象,用于将SQL语句发送到数据库。 |
PreparedStatement prepareStatement(String sql) | 创建一个 PreparedStatement 对象,用于将参数化SQL语句发送到数据库。 |
-
管理事务:
- 开启事务:
void setAutoCommit(boolean autoCommit)//调用该方法设置参数为false,即开启事务,关闭自动提交
- 回滚事务:
void rollback()
- 提交事务:
void commit()
Statement【重点】
功能:
- 执行sql:
方法 | 含义 |
---|---|
boolean execute(String sql) | 执行给定的SQL语句,该语句可能返回多个结果。 【了解】 |
int executeUpdate(String sql) | 执行给定的DML语句,这可能是 INSERT , UPDATE ,或 DELETE 语句,或者不返回任何内容,如SQL DDL语句的SQL语句。 |
ResultSet executeQuery(String sql) | 执行给定的DQL(SELECT )语句,该语句返回单个 ResultSet 对象。 |
注意:executeUpdate(String sql)
可以通过返回的行数判断DML语句是否执行成功。
练习
- account表中添加一条记录
- account表中修改记录
- account表中删除一条记录
- 创建一个表student,里面有id,name,age三列
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcDemo03 {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取数据库的连接对象
connection = DriverManager.getConnection("jdbc:mysql:///practise?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT", "root", "root");
//3.定义sql语句
//3.1 account表中添加2条记录
String sql1 = "INSERT INTO account VALUES(NULL,\"赵六\",1000),(NULL,\"田七\",1000)";
//3.2 account表中修改记录:
String sql2 = "UPDATE account SET NAME = \"赵敏\",balance = 3000 where NAME = \"赵六\"";
//3.3 account表中删除2条记录
String sql3 = "DELETE FROM account where id = 2 OR id = 3";
//3.4 创建一个表student,里面有id,name,age三列
String sql4 = "CREATE TABLE student(id int Primary Key Auto_increment,name varchar(32),age int)";
//4.获取.获取执行sql语句的对象 Statement
statement = connection.createStatement();
//5.执行sql语句
int count1 = statement.executeUpdate(sql1);
int count2 = statement.executeUpdate(sql2);
int count3 = statement.executeUpdate(sql3);
statement.executeUpdate(sql4);
System.out.println("数据添加成功!"+count1+"行数据受到影响!");
System.out.println("数据修改成功!"+count2+"行数据受到影响!");
System.out.println("数据删除成功!"+count3+"行数据受到影响!");
} catch (SQLException throwables) {
throwables.printStackTrace();
}catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
//7.释放资源
//先判断是否为null,避免空指针异常
if (statement != null) {
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
}
ResultSet
ResultSet对象:结果集对象,封装查询结果。
方法 | 含义 |
---|---|
boolean next() | 将光标从当前位置向前移动一行,判断当前行是否是最后一行末尾(是否有数据) |
Xxx getXxx(int index) | 获取Xxx类型的数据,参数index代表列数,从1开始 |
Xxx getXxx(String str) | 获取Xxx类型的数据,参数str代表列的名称 |
//定义sql语句
String sql = "SELECT * FROM account";
//5.执行sql语句
resultSet = statement.executeQuery(sql);
//游标向下移动一行
resultSet.next();
//获得并打印第一行的数据
int id = resultSet.getInt(1);
String name = resultSet.getString("name");
double balance = resultSet.getDouble(3);
System.out.println(id+"--"+name+"--"+balance);
注意:ResultSet对象创建后,要.close
释放资源。
Result遍历结果集
while(resultSet.next()) {
//获得并打印第一行的数据
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
double balance = resultSet.getDouble(3);
System.out.println(id + "--" + name + "--" + balance);
}
练习
需求:以上一节多表查询练习中的emp表为例,查询emp表中的数据,并将其封装为对象,将对象存储在集合中,然后遍历集合打印。
分析:
emp表中的每一列就相当于类的每一个属性,emp表中的每一行数据,就相当于是该类的每一个对象。所以可以新建一个Emp类,将emp表的每一行数据都封装为一个对象。
在domain包中,定义一个Emp类:
package cn.kaikeba.domain;
import java.util.Date;
public class Emp {
private int id;
private String name;
private int job_id;
private int mgr;
private Date joindate;
private double salary;
private double bonus;
private int dept_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 getJob_id() {
return job_id;
}
public void setJob_id(int job_id) {
this.job_id = job_id;
}
public int getMgr() {
return mgr;
}
public void setMgr(int mgr) {
this.mgr = mgr;
}
public Date getJoindate() {
return joindate;
}
public void setJoindate(Date joindate) {
this.joindate = joindate;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
public int getDept_id() {
return dept_id;
}
public void setDept_id(int dept_id) {
this.dept_id = dept_id;
}
@Override
public String toString() {
return "Emp{" +
"id=" + id +
", name='" + name + '\'' +
", job_id=" + job_id +
", mgr=" + mgr +
", joindate=" + joindate +
", salary=" + salary +
", bonus=" + bonus +
", dept_id=" + dept_id +
'}';
}
}
使用Jdbc,读取表中数据,将每一行封装为一个对象,存储到集合中,遍历集合打印对象:
package cn.kaikeba.jdbc;
import cn.kaikeba.domain.Emp;
import java.sql.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class JdbcDemo04 {
public static void main(String[] args) {
//8.调用方法获取集合
List<Emp> list = findAll();
//9.遍历集合,获取并打印对象
//增强for循环遍历:
for (Emp emp : list) {
System.out.println(emp);
}
System.out.println("------------------------------------------------------------------");
//迭代器遍历:
Iterator<Emp> iterator = list.iterator();
while (iterator.hasNext()){
Emp emp = iterator.next();
System.out.println(emp);
}
}
/**
* 查询所有emp对象,存储到集合中并返回
* @return
*/
public static List<Emp> findAll(){
List<Emp> list = new ArrayList<>();
Connection connection = null;
Statement statement = null;
ResultSet rs = null;
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取Connection对象
connection = DriverManager.getConnection("jdbc:mysql:///practise?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT", "root", "root");
//3.获取执行sql语句的Statement对象
statement = connection.createStatement();
//4.定义sql语句
String sql = "SELECT * FROM emp";
//5.执行sql语句,获取结果集对象rs
rs = statement.executeQuery(sql);
//6.遍历结果集,将获得的数据存储到emp对象中,并将emp对象存储到集合中
while(rs.next()){
Emp emp = new Emp();
emp.setId(rs.getInt("id"));
emp.setBonus(rs.getDouble("bonus"));
emp.setDept_id(rs.getInt("dept_id"));
emp.setJob_id(rs.getInt("job_id"));
emp.setJoindate(rs.getDate("joindate"));
emp.setMgr(rs.getInt("mgr"));
emp.setName(rs.getString("ename"));
emp.setSalary(rs.getDouble("salary"));
list.add(emp);
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
//7.释放资源
//先判断是否为null,避免空指针异常
if (statement != null) {
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (rs != null) {
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
return list;
}
}
JDBC工具类
抽取JDBC工具类:JDBCUtils。
-
目的:简化书写。
-
分析:
-
抽取注册驱动的方法
-
抽取获取连接对象的方法
-
需求:不想传递参数(麻烦),还得保证工具类的通用性。
-
解决:配置文件
jdbc.properties
url = …
user = …
password = …
-
-
抽取释放资源的方法
-
将相关配置信息写进配置文件jdbc.properties
中:
url=jdbc:mysql:///practise?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT
user= root
password = root
driver = com.mysql.cj.jdbc.Driver
编写JDBCUtil
类:
mport java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.sql.*;
import java.util.Properties;
/**
* JDBC工具类
*/
public class JDBCUtil {
private static String url;
private static String user;
private static String password;
/**
* 文件的读取,只需要读取一次即可拿到这些值,使用静态代码块
*/
static{
//读取资源文件,获取值
try {
//1.创建Properties集合类对象
Properties pro = new Properties();
//获取src路径下的文件的方式-->Classload 类加载器
ClassLoader classLoader = JDBCUtil.class.getClassLoader();
URL resource = classLoader.getResource("jdbc.properties");
String path = resource.getPath();
//2.加载文件
pro.load(new FileReader(path));
//3.获取属性,赋值
url = pro.getProperty("url");
user = pro.getProperty("user");
password = pro.getProperty("password");
Class.forName(pro.getProperty("driver"));
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取连接
* @return 连接对象
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url,user,password);
}
/**
* 释放资源1
* @param statement
* @param connection
*/
public static void close(Statement statement,Connection connection){
if (statement != null){
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection != null){
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
/**
* 释放资源2
* @param statement
* @param connection
* @param resultSet
*/
public static void close(Statement statement, Connection connection, ResultSet resultSet){
if (statement != null){
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection != null){
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (resultSet != null){
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
使用JDBCUtil工具类对快速入门的代码进行修改:
public class JdbcDemo01 {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
try {
//1.注册驱动
//2.创建数据库连接对象,数据库信息在配置文件中修改
connection = JDBCUtil.getConnection();
//4. 定义sql
String sql = "INSERT INTO account VALUES(null,\"王五\",1000)";
//5. 获取执行sql语句的对象 Statement
statement = connection.createStatement();
//6. 执行sql,接收返回的结果
int count = statement.executeUpdate(sql);
//7. 处理结果
System.out.println(count);
} catch (SQLException e) {
e.printStackTrace();
} finally {
//8. 释放资源
JDBCUtil.close(statement, connection);
}
}
}
练习
需求:通过键盘录入用户名和密码,判断用户是否登录成功,如登录成功则提示登陆成功,失败则体是登录失败。
步骤:
1.创建一个数据库表,里面存储用户的用户名和密码;
2.创建一个类,里面有一个判断接收的账号密码在数据库表中是否存在的方法。
.创建一个数据库表:
CREATE TABLE USER(
id int PRIMARY KEY auto_increment,
username VARCHAR(20),
PASSWORD varchar(20)
);
INSERT INTO USER VALUES(null,"zhangsan","123");
INSERT INTO USER VALUES(null,"lisi","234");
创建一个类,里面有一个判断接收的账号密码在数据库表中是否存在的方法:
import cn.kaikeba.util.JDBCUtil;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
/**
* 需求:通过键盘录入用户名和密码,判断用户是否登录成功,如登录成功则提示登陆成功,失败则体是登录失败。
*/
public class JdbcDemo05 {
public static void main(String[] args) {
//接收用户输入
Scanner input = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = input.next();
System.out.println("请输入密码:");
String password = input.next();
JdbcDemo05 jdbcDemo05 = new JdbcDemo05();
boolean login = jdbcDemo05.login(username, password);
if (login){
System.out.println("登陆成功!");
}else{
System.out.println("用户名或密码错误!");
}
}
/**
* 登录方法
*/
public boolean login(String username,String password){
if (username == null || password == null){
return false;
}
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
//1.注册驱动
//2.创建数据库连接对象,数据库信息在配置文件中修改
connection = JDBCUtil.getConnection();
//4. 定义sql
String sql = "Select * From user WHERE username = '"+username+"' and password = '"+password+"'";
//5. 获取执行sql语句的对象 Statement
statement = connection.createStatement();
//6. 执行sql,接收返回的结果
resultSet = statement.executeQuery(sql);
//7. 判断结果集中是否有参数中的帐号密码
return resultSet.next();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//8. 释放资源
JDBCUtil.close(statement, connection,resultSet);
}
return false;
}
}
SQL注入问题
SQL注入问题:在拼接sql语句时,有一些sql的特殊关键字参与字符串的拼接。会造成安全性问题。
如,在上面用户登录的练习中:
用户名随便输入,密码输入:
a' or 'a' = 'a
这时无论如何都可以登陆成功。
sql语句为:
Select * From user WHERE username = username and password = 'a' or 'a' = 'a'
解决SQL注入问题:使用PreparedStatement对象来解决。
PreparedStatement中的SQL语句是预编译SQL。
预编译SQL:参数使用?作为占位符。
注意:后期都会使用PreparedStatement来完成增删改查的所有操作
- 可以防止sql注入
- 效率更高
步骤:
- 导入驱动jar包
mysql-connector-java-8.0.11.jar
- 注册驱动
- 获取数据库的连接对象 Connection
- 定义sql
- 注意:sql的参数使用?作为占位符,如:
Select * From user WHERE username =?and password = ?;
- 获取执行sql语句的对象 PreparedStatement,调用传入预编译sql语句
- 给?赋值:
- 方法,setXxx(参数1,参数2)
- 参数1:?的位置编号 从1开始
- 参数2:?的值
- 执行sql,接收返回的结果,不需要传递sql语句
- 处理结果
- 释放资源
上面的用户登录案例可以进行如下改进:
import cn.kaikeba.util.JDBCUtil;
import java.sql.*;
import java.util.Scanner;
/**
* 需求:通过键盘录入用户名和密码,判断用户是否登录成功,如登录成功则提示登陆成功,失败则体是登录失败。
*/
public class JdbcDemo05 {
public static void main(String[] args) {
//接收用户输入
Scanner input = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = input.next();
System.out.println("请输入密码:");
String password = input.next();
JdbcDemo05 jdbcDemo05 = new JdbcDemo05();
boolean login = jdbcDemo05.login(username, password);
if (login){
System.out.println("登陆成功!");
}else{
System.out.println("用户名或密码错误!");
}
}
/**
* 登录方法
*/
public boolean login(String username,String password){
if (username == null || password == null){
return false;
}
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//1.注册驱动
//2.创建数据库连接对象,数据库信息在配置文件中修改
connection = JDBCUtil.getConnection();
//4. 定义sql
String sql = "Select * From user WHERE username = ? and password = ? ";
//5. 获取执行sql语句的对象 Statement,不需要传递sql语句
preparedStatement = connection.prepareStatement(sql);
//给? 赋值
preparedStatement.setString(1,username);
preparedStatement.setString(2,password);
//执行sql,接收返回的结果
resultSet = preparedStatement.executeQuery();
//7. 判断结果集中是否有参数中的帐号密码
return resultSet.next();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//8. 释放资源
JDBCUtil.close(preparedStatement, connection,resultSet);
}
return false;
}
}
JDBC控制事务
事务:一个包含多个步骤的业务操作。如果这个业务被事务管理,则这多个步骤要么同时成功,要么同时失败。
操作:
- 开启事务
- 回滚事务
- 提交事务
使用Connection对象来管理事务:
- 开启事务:在执行sql之前开启事务
void setAutoCommit(boolean autoCommit)//调用该方法设置参数为false,即开启事务,关闭自动提交
- 回滚事务:在catch中写回滚事务的代码,当有异常发生时执行代码,回滚事务
void rollback()
- 提交事务:当所有sql都执行完就提交事务
void commit()
转账案例
import cn.kaikeba.util.JDBCUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* 事务操作
*/
public class JdbcDemo06 {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement1 = null;
PreparedStatement preparedStatement2 = null;
try {
//1.注册驱动,获取连接
connection = JDBCUtil.getConnection();
//开启事务,将自动提交设为关:
connection.setAutoCommit(false);
//2.定义sql
//?转出?元
String sql1 = "UPDATE account set balance = balance - ? where name = ?";
//?接收?元
String sql2 = "UPDATE account set balance = balance + ? where name = ?";
//3.获取执行sql对象
preparedStatement1 = connection.prepareStatement(sql1);
preparedStatement2 = connection.prepareStatement(sql2);
//4.设置参数
//转出500元
preparedStatement1.setDouble(1,500);
//接收500元
preparedStatement2.setDouble(1,500);
//张三转出
preparedStatement1.setString(2,"张三");
//李四收钱
preparedStatement2.setString(2,"李四");
//5.执行sql语句
int count1 = preparedStatement1.executeUpdate();
int i = 3/0;
int count2 = preparedStatement2.executeUpdate();
//提交事务
connection.commit();
} catch (Exception throwables) {
//如果出现异常,事务回滚
if (connection != null){
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
throwables.printStackTrace();
}finally{
JDBCUtil.close(preparedStatement1,connection);
JDBCUtil.close(preparedStatement2,null);
}
}
}