JDBC概述
JDBC是 Java 访问数据库的标准规范(接口)
JDBC就是一套操作所有关系型数据库的规则(接口),而数据库厂商需要实现这套接口,提供数据库驱动jar包, 我们可以使用这套接口编程,真正执行的代码是对应驱动包中的实现类。
开发准备
数据准备
-- 创建 jdbc_user表
CREATE TABLE jdbc_user (
id INT PRIMARY KEY AUTO_INCREMENT ,
username VARCHAR(50),
PASSWORD VARCHAR(50),
birthday DATE
);
-- 添加数据
INSERT INTO jdbc_user (username, PASSWORD,birthday)
VALUES
('admin1', '123','1991/12/24'),
('admin2','123','1995/12/24'),
('test1', '123','1998/12/24'),
('test2', '123','2000/12/24');
IDEA中建立仓库
首先在自己的D盘下创建一个名为MyJar的文件专门放包
这里把JDBC中的MySQL驱动包也放进去了
点击这里的+,直接关联Myjar文件,那么这个MyJar文件就可以用来当作是我们放包的仓库了
API使用: 1.注册驱动
JDBC规范定义的驱动接口: java.sql.Driver
MySql驱动包提供了实现类: com.mysql.jdbc.Driver
public class JDBCdemo01 {
public static void main(String[] args) throws Exception{
//1.注册驱动
// forName 方法执行将类进行初始化
Class.forName("com.mysql.jdbc.Driver");
}
}
其中Class.forName(数据库驱动实现类)
加载和注册数据库驱动,数据库驱动由数据库厂商MySql提供"com.mysql.jdbc.Driver"
这里Class类的forName方法 ,将类初始化了
Driver类的源码如下:
// Driver类是MySql提供的数据库驱动类, 实现了JDBC的Driver接口 java.sql.Driver
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
// 空参构造
public Driver() throws SQLException {
}
//静态代码块,Class类的 forName()方法将Driver类 加载到内存, static代码块会自动执行
//静态代码块是随着类的加载而加载
static {
try {
/*
DriverManager 驱动管理类
registerDriver(new Driver) 注册驱动的方法 registerDriver就是用来注册驱动的
注册数据库驱动
*/
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
从 JDBC3 开始,目前已经普遍使用的版本。可以不用注册驱动而直接用。 Class.forName 这句话可以省略。
API使用: 2.获得连接
- Connection 接口,代表一个连接对象
- 使用 DriverManager类的静态方法,getConnection可以获取数据库的连接
其中: - user:登录用户名
- password:登录密码
- url:访问mysql数据库的地址(要按格式)
mysql url标准格式:
jdbc:mysql://localhost:3306/db4
- localhost:IP地址
- 3306:端口号
- db4:数据库名称
- jdbc:mysql:这是协议,不变的
打印连接对象看看
public class JDBCdemo01 {
public static void main(String[] args) throws Exception{
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接 connection连接对象
String url = "jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8";
Connection con = DriverManager.getConnection(url,"root","hmyhmy");
//打印连接对象
System.out.println(con);
}
}
API 使用: 3.获取语句执行平台
通过Connection 的 createStatement方法 获取sql语句执行对象
Statement : 代表一条语句对象,用于发送 SQL 语句给服务器,用于执行静态 SQL 语句并返回它所生成结果的对象。
executeUpdate的使用
增删改用executeUpdate
public class JDBCdemo01 {
public static void main(String[] args) throws Exception{
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接 connection连接对象
String url = "jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8";
Connection con = DriverManager.getConnection(url,"root","hmyhmy");
//打印连接对象
//System.out.println(con);
//3.获取语句执行平台 statement
Statement statement = con.createStatement();
//3.1通过statement对象的executeUpdate方法 创建一张表
String sql = "create table test(id int,name varchar(20),age int);";
int i = statement.executeUpdate(sql);//返回值是int类型,表示受影响的行数
System.out.println(i);
//关闭流
statement.close();
con.close();
}
}
结果:
test表已创建成功
API 使用: 4.处理结果集
excuteQuery操作执行查询操作
ResultSet接口
ResultSet接口的作用是封装数据库查询的结果集,对结果进行遍历,取出每一条记录
方法:
- next(): 判断是否有下一条数据,返回 boolean 类型,如果还有下一条记录,返回 true,否则返回 false
- xxx getXxx(String or int):
- int getInt()表示获取的这列数据是int类型
int getInt("列名")
或
int getInt(1)
表示第一列 - String getString()表示获取的这列数据是String类型
String getString("列名")
或
String getString(1)
表示第一列
public class JDBCdemo02 {
public static void main(String[] args) throws Exception{
//1.获取连接
String url = "jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8";
Connection con = DriverManager.getConnection(url,"root","hmyhmy");
//2.获取语句执行平台对象
Statement statement = con.createStatement();
//3.执行查询操作 使用executeQuery
String sql = "select * from jdbc_user;";
//ResultSet 是结果集对象
ResultSet resultset = statement.executeQuery(sql);
//处理结果集对象 ResultSet
boolean next=resultset.next();//判断是否有下一条数据
System.out.println(next);
//获取id
int id = resultset.getInt("id");//通过列名方式
System.out.println("通过列名方式获取第一列id:"+id);
int id2 = resultset.getInt(1);
System.out.println("通过列号方式获取第一列id:"+id2);
//通过while循环 遍历获取resultset中数据
while(resultset.next()){
//id
int id3 = resultset.getInt("id");
System.out.print("当前id是:"+id3+" ");
//username
String name = resultset.getString("username");
System.out.print("当前username是"+name+" ");
//PASSWORD
String password = resultset.getString("PASSWORD");
System.out.print("当前PASSWORD是:"+password+" ");
//birthday
Date birthday = resultset.getDate("birthday");
System.out.println("当前birthday是:"+birthday);
}
//4.关闭流
resultset.close();
statement.close();
con.close();
}
}
这里就已经让指针向下移动了一格,所以结果从id为2开始
但是注意
必须要让next先行动,否则xxx getxxx()返回不了数据
API 使用: 5.释放资源
- 需要释放的对象:ResultSet 结果集(只有查询会使用到),Statement 语句,Connection 连接
- 释放原则:先开的后关,后开的先关。ResultSet ==> Statement ==> Connection
- 放在哪个代码块中:finally 块
public class JDBCdemo03 {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8";
Connection con =null;
Statement statement =null;
ResultSet resultset = null;
try {
//获取连接
con = DriverManager.getConnection(url,"root","hmyhmy");
//2.获取语句执行平台对象
statement = con.createStatement();
//3.执行查询操作 使用executeQuery
String sql = "select * from jdbc_user;";
resultset = statement.executeQuery(sql);
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
//finally 中代码始终会执行
try {
resultset.close();
statement.close();
con.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
步骤总结
- 获取连接
- 获取Statement对象
- 处理结果集(只在查询时处理)
- 释放资源
JDBC实现增删改查
编写JDBC工具类
- 如果一个功能经常要用到,我们建议把这个功能做成一个工具类,可以在不同的地方重用。
- “获得数据库连接”操作,将在以后的增删改查所有功能中都存在,可以封装工具类JDBCUtils。提供获取连接对象的方法,从而达到代码的重复利用。
JDBC工具类编写方式如下:
public class JDBCUtils {
//1.将连接信息定义为 字符串常量
public static final String DRIVERNAME = "com.mysql.jdbc.Driver";//对应注册驱动
public static final String URL = "jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8";//对应获取连接
public static final String USER = "root";
public static final String PASSWORD = "hmyhmy";
//2.编写静态代码块
static {
//随着类的加载而加载
try {
//注册驱动
Class.forName(DRIVERNAME);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//3.获取连接的 静态方法
//返回值也是个Connection对象
public static Connection getConnection(){
try {
//获取连接对象 并返回
Connection connection = DriverManager.getConnection(URL,USER,PASSWORD);
return connection;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
//4.关闭资源的方法
public static void close(Connection con, Statement statement){
if(con != null && statement != null){
try {
statement.close();
con.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
//重载一下,处理执行了查找操作的情况
public static void close(Connection con, Statement statement, ResultSet resultSet){
if(con != null && statement != null){
try {
statement.close();
con.close();
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
这样一个JDBCUtils工具类就写好了
DML(增删改查)操作
插入记录
//插入数据
@Test //添加junit架包 可让当前方法像main方法那样允许
public void testInsert() throws SQLException {
//1.通过JDBCUtils工具类 获取连接
Connection connection = JDBCUtils.getConnection();//这是个静态方法
//2.获取statement对象
Statement statement = connection.createStatement();
//2.1编写SQL
String sql = "insert into jdbc_user values(null,'admin3','123','2020/11/11')";//这里张百万用单引号
//2.2执行SQL
int i = statement.executeUpdate(sql);//返回受影响的行数
System.out.println(i);
//3.关闭流
JDBCUtils.close(connection,statement);
}
返回值
更新操作
//更新操作 根据id修改username
@Test
public void testUpdate() throws SQLException {
Connection con = JDBCUtils.getConnection();
Statement statement = con.createStatement();
String sql = "update jdbc_user set username='aaaaa' where id = 1";
statement.executeUpdate(sql);
JDBCUtils.close(con,statement);
}
删除操作
//删除操作 删除id为1和2的数据
@Test
public void testDelete() throws SQLException {
Connection con = JDBCUtils.getConnection();
Statement statement = con.createStatement();
String sql = "delete from jdbc_user where id in(1,2)";
statement.executeUpdate(sql);
JDBCUtils.close(con,statement);
}
DQL操作
需求:查询username = 'admin3’的一条数据
public class TestDQL {
public static void main(String[] args) throws SQLException {
//1.通过JDBCUtils工具类 获取连接
Connection connection = JDBCUtils.getConnection();//这是个静态方法
//2.获取statement对象
Statement statement = connection.createStatement();
//2.1编写SQL
String sql = "select * from jdbc_user where username = 'admin3'";
ResultSet resultSet = statement.executeQuery(sql);
//3.处理结果集
while(resultSet.next()) {
//通过列名的方式获取
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String password = resultSet.getString("PASSWORD");
Date date = resultSet.getDate("birthday");
System.out.println(id + " : " + username + " : " + password + " : " + date);
}
//4.释放资源
JDBCUtils.close(connection,statement,resultSet);
}
}
SQL注入问题
问题演示
例子数据如下
因为关键字OR
的存在,使得SQL注入问题的语句为真,不知道正确用户名和密码也能完成登录操作
sql注入案例:用户登陆
需求:用户在控制台上输入用户名和密码, 然后使用 Statement 字符串拼接的方式 实现用户的登录
public class TestLogin01 {
public static void main(String[] args) throws SQLException {
//用户登录案例
//1.通过JDBCUtils工具类 获取连接
Connection connection = JDBCUtils.getConnection();
//2.获取statement对象
Statement statement = connection.createStatement();
//3.获取用户输入的用户名和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String name = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
//4.拼接SQL语句
String sql = "select * from jdbc_user where username = '" + name + "' and PASSWORD='"+password+"'";
System.out.println(sql);
//5.执行查询 获取结果集对象
ResultSet resultSet = statement.executeQuery(sql);
//6.处理结果集
if(resultSet.next()){
System.out.println("登录成功!");
System.out.println("欢迎您:"+name);
} else {
System.out.println("登录失败");
}
//7.关闭流
JDBCUtils.close(connection,statement,resultSet);
}
}
非法操作:使用错误信息拼接也能使登录成功
用户输入的内容作为了 SQL 语句语法的一部分,改变了 原有SQL 真正的意义,以上问题称为 SQL 注入
这个问题的主要原因是使用了拼接SQL的操作
解决方法
解决 SQL 注入就不能让用户输入的密码和我们的 SQL 语句进 行简单的字符串拼接
使用预处理对象解决SQL注入
使用预处理对象 PreparedStatement 是Statement接口的子接口
好处:
- 使用预处理对象 有预编译功能,提高SQL的执行效率
- 使用预处理对象 通过占位符的方式 设置参数 可以有效防止SQL注入
PreparedStatement 使用 ?占位符的方式来设置参数
PreparedStatement继承于Statement
public class TestLogin02 {
//SQL注入中用户会改变SQL原有的意思
public static void main(String[] args) throws SQLException {
//1.通过JDBCUtils工具类 获取连接
Connection connection = JDBCUtils.getConnection();
//2.获取PreparedStatement预处理对象
//使用?占位符的方式来设置参数
String sql = "select * from jdbc_user where username = ? and PASSWORD = ?";
PreparedStatement ps = connection.prepareStatement(sql);
//3.获取用户输入的用户名和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String name = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
//4.设置参数 使用setXXX(占位符的位置(从1开始,int类型),要设置的值)的方法设置占位符参数
ps.setString(1,name);//设置第一个?值为name
ps.setString(2,password);
//5.执行查询
ResultSet resultSet = ps.executeQuery();
//6.处理结果集
if(resultSet.next()){
System.out.println("登录成功!");
System.out.println("欢迎您:"+name);
} else {
System.out.println("登录失败");
}
//7.关闭流
JDBCUtils.close(connection,ps,resultSet);
}
}
这时候再想使用SQL注入非法登录就不行了
PreparedStatement的执行原理
- Statement用于执行静态SQL语句,在执行时,必须指定一个事先准备好的SQL语句
- PrepareStatement是预编译的SQL语句对象,语句中可以包含动态参数“?”,在执行时可以为“?”动态设置参数值
- PrepareStatement可以减少编译次数提高数据库性能
JDBC控制事务
数据准备
-- 创建账户表
CREATE TABLE account(
-- 主键
id INT PRIMARY KEY AUTO_INCREMENT,
-- 姓名
NAME VARCHAR(10),
-- 转账金额
money DOUBLE
);
-- 添加两个用户
INSERT INTO account (NAME, money) VALUES ('tom', 1000), ('jack', 1000);
事务相关API
开发例子:使用JDBC操作事务
执行两次修改操作:tom-500,jack+500
public class TestJDBCConnection {
public static void main(String[] args) {
Connection con = null;
PreparedStatement ps = null;
try {
//1.获取连接
con = JDBCUtils.getConnection();
//2.开启事务
con.setAutoCommit(false);//手动提交事务
//3.获取预处理对象 执行SQL (执行两次修改操作:tom-500,jack+500)
//3.1tom账户-500
ps = con.prepareStatement("update account set money = money - ? where name = ?");
ps.setDouble(1,500.0);
ps.setString(2,"tom");
ps.executeUpdate();
//3.2jack账户+500
ps = con.prepareStatement("update account set money = money + ? where name = ?");
ps.setDouble(1,500.0);
ps.setString(2,"jack");
ps.executeUpdate();
//4.提交事务(正常情况)
con.commit();
System.out.println("转账成功");
} catch (SQLException throwables) {
throwables.printStackTrace();
//5.出现异常就回滚事务
try {
con.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
} finally {
//6.释放资源
JDBCUtils.close(con,ps);
}
}
}
若转账成功:
回滚
在3.1和3.2中加入语句
//模拟tom转账后出现异常
System.out.println(1/0);
执行了回滚操作,数据不发生变化