1.JDBC
1.1 客户端操作MySQL数据库的方式
(1)使用第三方客户端来访问MySQL:SQLyog、Navicat、SQLWave、MyDB studio、EMS SQL Manager for MySQL 等
(2)使用MySQL自带的命令行方式
(3)通过java来访问MySQL数据库。(今天所学习的)
JDBC的概念:
JDBC规范定义接口,具体的实现由各大数据厂商来实现。
JDBC是java访问数据库的标准规范,具体如何操作数据库还需要具体的实现类,也就是数据库驱动。每个数据库厂商会根据自己的数据库的通信格式编写好自己的数据库驱动,因此我们只需要调用JBDC接口中的方法即可,数据库驱动由数据路厂商提供。
使用JDBC的好处:
(1)程序员如果要开发访问数据库中的程序,只需要调佣JDBC接口中的方法即可,不用关注类是如何实现的。
(2)使用同一套java代码,进行少量的修改就可以访问其他JDBC支持的数据库。
使用JDBC开发使用到的包:
会使用到的包 | 说明 |
java.sql | 所有与JDBC访问数据库相关的接口和类 |
javax.sql | 数据库扩展包,提供数据库额外的功能。如连接池 |
数据库的驱动 | 由各大数据库厂商提供,需要额外的下载,对JDBC接口实现的类 |
1.2 JDBC的核心API
接口或类 | 作用 |
DriverManager类 | 1)管理和注册数据库驱动 2)得到数据库连接对象 |
Connection接口 | 一个连接对象,可用于创建Statement和PreparedStatement对象 |
Statement接口 | 一个SQL语句对象,用于将SQL语句发送给数据库服务器。 |
PreparedStatemen接口 | 一个SQL语句对象,是Statement的子接口 |
ResultSet接口 | 用于封装数据库查询的结果集,返回给客户端Java程序 |
1.3 导入驱动jar包
1.4 加载和注册驱动
加载和注册驱动的方法 | 描述 |
Class.forName(数据库驱动实现类) | 加载和注册数据库驱动,数据库驱动由mysql厂商“com.mysql.jdbc.Driver” |
为什么这样就可以注册驱动?
public class Demo1 {
public static void main(String[] args) throws ClassNotFoundException {
//抛出类找不到的异常,注册数据库驱动
Class.forName("com.mysql.jdbc.Driver");
}
}
com.mysql.jdbc.Driver源代码:
//Driver接口,所有数据库厂商必须实现接口,表示这是一个驱动类。
public class Driver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver()); //注册数据库驱动
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
注:从JDBC3开始,目前已经普遍使用的版本,可以不用注册驱动而直接使用。Class.forName 这句话可以省略。
2. DriveManager类
2.1 DriveManager类的作用:
(1)管理和注册驱动
(2)创建数据库的连接
2.2 类中的方法
DriverManager类中的静态方法 | 描述 |
Connection getConnection(String url,String user,String password) | 通过连接字符串,用户名,密码得到数据库的连接对象。 |
Connection getConnection(String url,Properties info) | 通过连接字符串,属性对象来得到连接对象。 |
2.3 使用JDBC连接数据库的四个参数
JDBC连接数据库的四个参数 | 说明 |
用户名 | 登录的用户名 |
密码 | 登录的密码 |
连接字符串URL | 不同的数据库URL是不同的,mysql的写法jdbc:mysql://locahost:3306/数据库[?参数名=参数值] |
驱动类的字符串名 | com.mysql.jdbc.Driver |
2.4 连接数据库的URL地址格式
协议名:子协议://服务器名或IP地址:端口号/数据库名?参数=参数值
(1) MySQL写法:
连接字符串URL用于标识数据库的位置,程序员通过URL地址告诉JDBC程序连接那个数据库,URL的写法为:
(2)MySQL中可以简写:
前提:必须是本地服务器,端口号是3306
jdbc:mysql://数据库名
(3)乱码的问题
如果数据库出现乱码,可以指定参数:?characterEncoding=utf8,标识数据库以UTF-8编码来处理数据。
2.5 案例:得到MySQL的数据库连接对象
(1)使用用户名、密码、URL得到连接对象
import com.mysql.jdbc.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Demo01 {
public static void main(String[] args) throws SQLException {
String url="jdbc:mysql://localhost:3306/db1";
//1) 使用用户名、密码、URL得到连接对象
Connection connection= (Connection) DriverManager.getConnection(url,"root","root");
//2)com.mysql.jdbc.JDBC4Connection@256216b3
System.out.println(connection);
}
}
(2)使用属性文件和URL得到连接对象
import com.mysql.jdbc.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class Demo02 {
public static void main(String[] args) throws SQLException {
//URL连接字符串
String url="jdbc:mysql://localhost:3306/db1";
//属性对象
Properties info=new Properties();
//把用户名和密码放到info对象中
info.setProperty("user","root");
info.setProperty("password","root");
Connection connection= (Connection) DriverManager.getConnection(url,info);
//com.mysql.jdbc.JDBC4Connection@256216b3
System.out.println(connection);
}
}
3. Connection接口:
3.1 Connection接口的作用:
Connection接口,具体的实现类由数据库的厂商实现,代表一个连接对象。
3.2 Connection 方法
Connection接口中的方法 | 描述 |
Statement createStatement() | 创建一条SQL语句对象 |
4. Statement接口
4.1 JDBC访问数据库的步骤
1.注册和加载驱动(可以省略)
2 .获取连接
3.Connection获取Statement对象
4.使用Statement对象执行SQL语句
5.返回结果
6.释放资源
4.2 Statement作用
代表一条语句对象,用于发送SQL语句给服务器,用于执行静态SQL语句并返回它所生成结果的对象。
4.3 Statement中的方法
Statement接口中的方法 | 描述 |
int executeUpdate(String sql) | 用于发送DML语句,增删改的操作,insert、update、delete 参数:SQL语句 返回值:返回对数据库影响的行数
|
ResultSet executeQuery | 用于发送DQL语句,执行查询的操作。select 参数:SQL语句 返回值:查询的结果集 |
4.4 释放资源
1.需要释放的资源对象:ResultSet结果集、Statement语句、Connection连接
2.释放原则:先开的后关,后开的先关。ResultSet—>Statement --->Connection
3.放在哪个代码块中:finally块
4.5 执行DDL操作
需求:使用JDBC在MySQL的数据库中创建一张学生表
具体实现:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
/*
创建一张学生表
*/
public class Demo03DDL {
public static void main(String[] args) {
//1.创建连接
Connection connection=null;
Statement statement=null;
try {
connection = DriverManager.getConnection("jdbc:mysql:///dbq", "root", "root");
//2.通过连接对象得到语句对象
statement=connection.createStatement();
//3 通过语句对象发送SQL语句给服务器
//4 执行SQL
statement.executeUpdate("create table student (id int primary key auto_increment," +
"name varchar(20) not null ,gender boolean, birthday date )");
//5.返回影响行数(DDL没有返回值)
System.out.println("建表成功");
} catch (SQLException e) {
e.printStackTrace();
}
//6.释放资源
finally {
//关闭之前要先判断
if(statement!=null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
4.6 执行DML操作
需求:想学生表中添加四条记录,主键是自动增长
步骤:
1.创建连接对象
2.创建Statement语句对象
3.执行SQL语句:executeUpdate(sql)
4.返回影响行数
5. 释放资源
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
/*
向学生表中添加4条记录,主键是自建
*/
public class Demo04DQL {
public static void main(String[] args) throws SQLException {
// 1. 创建连接对象
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db1",
"root", "root");
// 2.创建Statement语句对象
Statement statement = connection.createStatement();
//3.执行SQL语句:executeUpdate(sql)
int count=0;
//4. 返回影响的行数
count +=statement.executeUpdate("insert into student1 values (null,'孙悟空',1, '1993-03-24')");
count +=statement.executeUpdate("insert into student1 values (null,'白骨精', 0, '1995-03-24')");
count +=statement.executeUpdate("insert into student1 values (null,'猪八戒', 1, '1903-03-24')");
count +=statement.executeUpdate("insert into student1 values (null,'嫦娥', 0, '1993-03-11')");
System.out.println("插入了"+count+"记录");
//5 释放资源
statement.close();
connection.close();
}
}
4.7 执行DQL操作
(1)ResultSet接口
作用: 封装数据库查询的结果集,对结果集进行遍历,取出每一条记录。
接口中的方法:
ResultSet接口中的方法 | 描述 |
boolean next() | 1.游标向下移动1行 2.返回boolean类型,如果还有下一条记录,返回true,否则返回false |
数据类型 getXxx() | 1.通过字段名,参数是String类型。返回不同的类型 2.通过列号,参数是整数,从1开始。返回不同的类型 |
(2)常用的数据类型转换表
SQL类型 | Jdbc对应方法 | 返回类型 |
BIT(1) bit(n) | getBoolean() | boolean |
TINYINT | getByte() | byte |
SMALLINT | getShort() | short |
INT | getInt() | int |
BIGINT | getLong() | long |
CHAR,VARCHAR | getString() | String |
Text(Clob) Blob | getClob getBlob() | Clob Blob |
DATE | getDate() | java.sql.Date只代表日期 |
TIME | getTime() | java.sql.Time 只表示时间 |
TIMESTAMP | getTimestamp() | java.sql.Timestamp 同时有日期和时间 |
java.sql.Date、Time、Timestamp(时间戳),三个共同父类是:java.util.Date
(3)需求::确保数据库中有3条以上的记录,查询所有的学员信息
步骤:
1.得到连接对象
2.得到语句对象
3.执行SQL语句得到结果集Resultset对象
4.循环遍历取出每一条记录
5.输出的控制台上
6.释放资源
结果:
import java.sql.*;
public class Demo05DQL {
public static void main(String[] args) throws SQLException {
// 1.得到连接对象
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db1",
"root", "root");
//2 得到语句对象
Statement statement = connection.createStatement();
//3.执行SQL语句得到结果集ResultSet对象
ResultSet resultSet = statement.executeQuery("select * from student1");
//4.循环遍历取出每一条记录
while (resultSet.next()){
int id=resultSet.getInt("id");
String name=resultSet.getNString("name");
Boolean gender=resultSet.getBoolean("gender");
Date birthday=resultSet.getDate("birthday");
// 5.输出的控制台
System.out.println("编号"+id+",姓名"+name+", 性别:" + gender + ", 生日:" +birthday);
}
// 6 释放资源
resultSet.close();
statement.close();
connection.close();
}
}
(4) 关于ResultSet接口中的注意事项:
1.如果光标在第一行之前,使用rs.getXX()获取列值,报错:Before start of result set
2.如果光标在最后一行之后,使用rs.getXX()获取列值,报错:After end of result set
3.使用完毕以后要关闭结果集ResultSet,再关闭Statement,再关闭Connection
5.数据库工具类JdbcUtils
如果一个功能经常要用到,我们建议把这个功能做成一个工具类,可以在不同的地方重用。
5.1 需求
上面的代码中有很多重复的代码,可以把这些公共的代码抽取出来。
5.2 创建类JdbcUtil包含3个方法:
1.可以把几个字符串定义成常量:用户名,密码,URL,驱动类
2.得到数据库的连接:getConnection()
3.关闭所有打开的资源:
close(Connection conn, Statement stmt),close(Connection conn, Statement stmt, ResultSet rs)
import java.sql.*;
public class JdbcUtils {
//可以把几个字符串定义成常量:用户名,密码,URL,驱动类
private static final String USER = "root";
private static final String PWD = "root";
private static final String URL = "jdbc:mysql://localhost:3306/db1";
private static final String DRIVER= "com.mysql.jdbc.Driver";
/*
注册驱动
*/
static {
try {
Class.forName(DRIVER);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/*
得到数据库的连接
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL,USER,PWD);
}
/*
关闭所有打开的资源
*/
public static void close(Connection conn, Statement stmt){
if (stmt!=null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn!=null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/*
关闭所有打开的资源
*/
public static void close(Connection conn, Statement stmt, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
close(conn, stmt);
}
}
5.3 案例:用户登录
(1)需求:
1.有一张用户表
2.添加几条用户记录
create table user (
id int primary key auto_increment,
name varchar(20),
password varchar(20)
)
insert into user values (null,'jack','123'),(null,'rose','456');
-- 登录, SQL中大小写不敏感
select * from user where name='JACK' and password='123';
-- 登录失败
select * from user where name='JACK' and password='333';
3.使用Statement字符串拼接的方式实现用户的登录, 用户在控制台上输入用户名和密码。
(2)步骤:
1.得到用户从控制台上输入的用户名和密码来查询数据库
2.写一个登录的方法。
a.通过工具类得到连接
b.创建语句对象,使用拼接字符串的方式生成SQL语句
c.查询数据库,如果有记录则表示登录成功,否则登录失败
d.释放资源.
public class Demo7Login {
//从控制台上输入的用户名和密码
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String name = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
login(name, password);
}
/**
* 登录的方法
*/
public static void login(String name, String password) {
//a) 通过工具类得到连接
Connection connection = null;
Statement statement = null;
ResultSet rs = null;
try {
connection = JdbcUtils.getConnection();
//b) 创建语句对象,使用拼接字符串的方式生成SQL语句
statement = connection.createStatement();
//c) 查询数据库,如果有记录则表示登录成功,否则登录失败
String sql = "select * from user where name='" + name + "' and password='" + password + "'";
System.out.println(sql);
rs = statement.executeQuery(sql);
if (rs.next()) {
System.out.println("登录成功,欢迎您:" + name);
} else {
System.out.println("登录失败");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//d) 释放资源
JdbcUtils.close(connection, statement, rs);
}
}
}
(3)SQL注入问题
当我们输入以下密码,我们发现我们账号和密码都不对竟然登录成功了
请输入用户名:
newboy
请输入密码:
a' or '1'='1
select * from user where name='newboy' and password='a' or '1'='1'
登录成功,欢迎您:newboy
问题分析:
select * from user where name='newboy' and password='a' or '1'='1'
name='newboy' and password='a' 为假
'1'='1' 真
相当于
select * from user where true; 查询了所有记录
我们让用户输入的密码和SQL语句进行字符串拼接。用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义,以上问题称为SQL注入。要解决SQL注入就不能让用户输入的密码和我们的SQL语句进行简单的字符串拼接。
6.PreparedStatement接口
6.1 继承结构与作用:
PreparedStatement是Statement接口的子接口,继承于父接口中所有的方法。它是一个预编译的SQL语句
6.2 PreparedSatement的执行原理
1.因为有预先编译的功能,提高SQL的执行效率
2.可以有效的防止SQL注入的问题,安全性更高。
6.3 Connection创建PreparedStatement对象
Connection接口中的方法 | 描述 |
PreparedStatement prepareStatement(String sql) | 指定预编译的SQL语句,SQL语句中使用占位符?创建一个语句对象 |
6.4 PreparedStatement接口中的方法:
PreparedStatement接口中的方法 | 描述 |
int executeUpdate() | 执行DML,增删改的操作,返回影响的行数。 |
ResultSet executeQuery() | 执行DQL,查询的操作,返回结果集 |
6.5 PreparedSatement的好处
1、prepareStatement()会先将SQL语句发送给数据库预编译。PreparedStatement会引用着预编译后的结果。可以多次传入不同的参数给PreparedStatement对象并执行。减少SQL编译次数,提高效率。
2、安全性更高,没有SQL注入的隐患。
3、提高了程序的可读性
6.6 使用PreparedStatement的步骤:
1、编写SQL语句,未知内容使用?占位:"SELECT * FROM user WHERE name=? AND password=?";
2、获得PreparedStatement对象
3、设置实际参数:setXxx(占位符的位置, 真实的值)
4、执行参数化SQL语句
5、关闭资源
PreparedStatement中设置参数的方法 | 描述 |
void setDouble(int parameterIndex, double x) | 将指定参数设置为给定 Java double 值。 |
void setFloat(int parameterIndex, float x) | 将指定参数设置为给定 Java REAL 值。 |
void setInt(int parameterIndex, int x) | 将指定参数设置为给定 Java int 值。 |
void setLong(int parameterIndex, long x) | 将指定参数设置为给定 Java long 值。 |
void setObject(int parameterIndex, Object x) | 使用给定对象设置指定参数的值。 |
void setString(int parameterIndex, String x) | 将指定参数设置为给定 Java String 值。 |
使用PreparedStatement改写上面的登录程序,看有没有SQL注入的情况。
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;
/*
* 使用PreparedStatement
*/
public class Demo06Login {
//从控制台上输入的用户名和密码
public static void main(String[] args) throws SQLException {
Scanner sc =new Scanner(System.in);
System.out.println("请输入用户名:");
String name=sc.nextLine();
System.out.println("请输入密码:");
String password=sc.nextLine();
login(name,password);
}
/*
* 登录的方法
* @param name
* @param password
*/
private static void login(String name, String password) throws SQLException {
Connection connection =JdbcUtils.getConnection();
//写成登录SQL语句,没有单引号
String sql = "select * from student1 where name=? and password=?";
//得到语句对象
PreparedStatement ps = connection.prepareStatement(sql);
//设置参数
ps.setString(1, name);
ps.setString(2,password);
ResultSet resultSet = ps.executeQuery();
if(resultSet.next()){
System.out.println("登录成功:" + name);
}
else {
System.out.println("登录失败");
}
//释放资源,子接口直接给父接口
JdbcUtils.close(connection,ps,resultSet);
}
}
6.7 表与表的关系
(1)案例:使用PreparedStatement查询一条数据,封装成一个学生Student对象
public class Demo9Student {
public static void main(String[] args) throws SQLException {
//创建学生对象
Student student = new Student();
Connection connection = JdbcUtils.getConnection();
PreparedStatement ps = connection.prepareStatement("select * from student where id=?");
//设置参数
ps.setInt(1,2);
ResultSet resultSet = ps.executeQuery();
if (resultSet.next()) {
//封装成一个学生对象
student.setId(resultSet.getInt("id"));
student.setName(resultSet.getString("name"));
student.setGender(resultSet.getBoolean("gender"));
student.setBirthday(resultSet.getDate("birthday"));
}
//释放资源
JdbcUtils.close(connection,ps,resultSet);
//释放数据
System.out.println(student);
}
}
(2)案例:将多条记录封装成集合List<Student>,集合中每个元素是一个JavaBean实体类
需求: 查询所有的学生类,封装成List<Student>返回
代码:
public class Demo10List {
public static void main(String[] args) throws SQLException {
//创建一个集合
List<Student> students = new ArrayList<>();
Connection connection = JdbcUtils.getConnection();
PreparedStatement ps = connection.prepareStatement("select * from student");
//没有参数替换
ResultSet resultSet = ps.executeQuery();
while(resultSet.next()){
//每次循环是一个学生对象
Student student = new Student();
//封装成一个学生对象
student.setId(resultSet.getInt("id"));
student.setName(resultSet.getString("name"));
student.setGender(resultSet.getBoolean("gender"));
student.setBirthday(resultSet.getDate("birthday"));
//把数据放到集合中
students.add(student);
}
//关闭连接
JdbcUtils.close(connection,ps,resultSet);
//使用数据
for (Student stu: students) {
System.out.println(stu);
}
}
}
6.8 PreparedStatement执行DML操作
public class Demo11DML {
public static void main(String[] args) throws SQLException {
//insert();
//update();
delete();
}
//插入记录
private static void insert() throws SQLException {
Connection connection = JdbcUtils.getConnection();
PreparedStatement ps = connection.prepareStatement("insert into student
values(null,?,?,?)");
ps.setString(1,"小白龙");
ps.setBoolean(2, true);
ps.setDate(3,java.sql.Date.valueOf("1999-11-11"));
int row = ps.executeUpdate();
System.out.println("插入了" + row + "条记录");
JdbcUtils.close(connection,ps);
}
//更新记录: 换名字和生日
private static void update() throws SQLException {
Connection connection = JdbcUtils.getConnection();
PreparedStatement ps = connection.prepareStatement("update student set name=?,
birthday=?where id=?");
ps.setString(1,"黑熊怪");
ps.setDate(2,java.sql.Date.valueOf("1999-03-23"));
ps.setInt(3,5);
int row = ps.executeUpdate();
System.out.println("更新" + row + "条记录");
JdbcUtils.close(connection,ps);
}
//删除记录: 删除第5条记录
private static void delete() throws SQLException {
Connection connection = JdbcUtils.getConnection();
PreparedStatement ps = connection.prepareStatement("delete from student where id=?");
ps.setInt(1,5);
int row = ps.executeUpdate();
System.out.println("删除了" + row + "条记录");
JdbcUtils.close(connection,ps);
}
}
7. JDBC事务的处理
7.1 准备数据
CREATE TABLE account (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(10), balance DOUBLE
);
-- 添加数据
INSERT INTO account (NAME, balance) VALUES ('Jack', 1000), ('Rose', 1000);
7.2 API介绍
Connection接口中与事务有关的方法 | 说明 |
void setAutoCommit(boolean autoCommit) | 参数是true或false 如果设置为false,表示关闭自动提交,相当于开启事务 |
void commit() | 提交事务 |
void rollback() | 回滚事务 |
7.3 开发步骤
1.获取连接
2.开启事务
3.获取到PreparedStatement
4.使用PreparedStatement执行两次更新操作
5.正常情况下提交事务
6.出现异常回滚事务
7.最后关闭资源
public class Demo12Transaction {
//没有异常,提交事务,出现异常回滚事务
public static void main(String[] args) {
//1) 注册驱动
Connection connection = null;
PreparedStatement ps = null;
try{
//2) 获取连接
connection = JdbcUtils.getConnection();
//3) 开启事务
connection.setAutoCommit(false);
//4) 获取到PreparedStatement
//从jack扣钱
ps = connection.prepareStatement("update account set balance = balance - ? where
name=?");
ps.setInt(1, 500);
ps.setString(2,"Jack");
ps.executeUpdate();
//出现异常
System.out.println(100 / 0);
//给rose加钱
ps = connection.prepareStatement("update account set balance = balance + ? where
name=?");
ps.setInt(1, 500);
ps.setString(2,"Rose");
ps.executeUpdate();
//提交事务
connection.commit();
System.out.println("转账成功");
} catch (Exception e) {
e.printStackTrace();
try {
//事务的回滚
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
System.out.println("转账失败");
}
finally {
//7) 关闭资源
JdbcUtils.close(connection,ps);
}
}
}