目录
Connection创建PreparedStatement对象
案例:使用PreparedStatement查询id为1的一条学生数据,封装成一个学生Student对象
案例:将多条记录封装成集合List,集合中每个元素是一个JavaBean实体类
JDBC入门
什么是JDBC
Java DataBase Connectivity Java数据库连接技术,使用Java来访问数据库,实现对数据库中表的增删改操作。英文缩写:CRUD Create Retreive Update Delete
JDBC规范定义接口,具体的实现由各大数据库厂商来实现。
JDBC是Java访问数据库的标准规范,真正怎么操作数据库还需要具体的实现类,也就是数据库驱动。每个数据库厂商根据自家数据库的通信格式编写好自己数据库的驱动。所以我们只需要会调用JDBC接口中的方法即可,数据库驱动由数据库厂商提供。
使用JDBC的好处:
- 我们只需要会调用JDBC接口中的方法即可,使用简单
- 使用同一套Java代码,进行少量的修改就可以访问其他JDBC支持的数据库
JDBC开发使用到的包:
导入驱动Jar包
JDBC的核心API
JDBC访问数据库的步骤
- 由DriverManager加载和注册MySQL驱动:com.mysql.jdbc.Driver
- 由DriverManager创建连接对象Connection
- 通过Connection对象得到Statement语句对象,表示一条要执行的SQL语句
- 执行SQL语句,将执行的结果集ResultSet返回给客户端
- 释放资源,关闭连接。
注:一个服务器的连接数量是有限的,使用完以后一定要关闭连接对象释放资源。
DriverManager类
加载和注册驱动
- 疑问:为什么这样可以注册驱动?
静态代码块是什么执行
- 查看com.mysql.jdbc.Driver源代码
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
DriverManager.registerDriver(new Driver()); //注册驱动程序
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!"); //注册失败就抛出异常
}
}
}
注:JDBC3以后JDK1.5以后,注册驱动已经由JDBC自动完成了,这句话可省略。有时会保留的原因是为了兼容以前的代码。
com.mysql.jdbc.Driver类中的static块会创建本类对象,并注册到DriverManager中。这说明只要去加载com.mysql.jdbc.Driver类,那么就会执行这个static块,从而也就会把com.mysql.jdbc.Driver注册到DriverManager中,所以可以把注册驱动类的代码修改为加载驱动类。
Class.forName(“com.mysql.jdbc.Driver”);
DriverManager作用:
- 注册和加载驱动 Class.forName("com.mysql.jdbc.Driver");
- 创建连接对象Connection
类中的方法:
使用JDBC连接数据库的四个参数:
jdbc:mysql://localhost:3306/day18?useUnicode=true&characterEncoding=UTF8
useUnicode:指定这个连接数据库的过程中,使用的字节集是Unicode字节集;
characherEncoding:指定连接数据库的过程中,使用的字节集编码为UTF-8编码。防止数据库出现乱码
请注意,mysql中指定UTF-8编码是给出的是UTF8,而不是UTF-8。要小心了!(MySQL的“utf8”实际上不是真正的UTF8)
MySQL写法
注意:主机也可用IP地址替代
MySQL中可以简写:
乱码的处理
如果数据库出现乱码,可以指定参数: ?characterEncoding=utf8,表示让数据库以UTF-8编码来处理数据。
jdbc:mysql://localhost:3306/day18?characterEncoding=utf8
案例:得到MySQL的数据库连接对象
通过包名可以知道,这个类是由mysql厂商来实现的。
package com.itheima;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class Demo02Connection {
public static void main(String[] args) throws SQLException {
//使用用户名、密码、URL得到连接对象
Connection c1 = DriverManager.getConnection("jdbc:mysql://localhost:3306/day18", "root", "root");
System.out.println("得到连接对象:" + c1);
//使用属性对象和url得到连接对象
//创建Properites对象
Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password", "root");
Connection c2 = DriverManager.getConnection("jdbc:mysql:///day18", info);
System.out.println("得到连接对象:" + c2);
}
}
Connection接口:
Connection作用:
代表一个连接对象,创建一个语句对象Statement或PreparedStatement接口
Connection方法:
Statement接口
Statement作用:
代表一个要执行的SQL语句对象
Statement中的方法:
释放资源
- 需要释放的对象:结果集ResultSet,语句Statement,连接对象Connection
- 释放顺序:先开的后关,后开的先关。ResultSet --> Statement --> Connection
- 放在哪个代码块中:finally
执行DDL操作
- 需求:使用JDBC在MySQL的数据库中创建一张学生表
- id是主键,整数类型,自增长
- name是varchar(20),非空
- 性别是boolean类型
- 生日是date类型
CREATE TABLE Student (
id int PRIMARY KEY auto_increment,
name VARCHAR(20) not null,
gender boolean,
birthday date
)
- 开发步骤:
- 创建连接
- 通过连接对象得到语句对象
- 通过语句对象发送SQL语句给服务器,执行SQL
- 通过语句对象发送SQL语句给服务器
- 释放资源
- 代码:
package com.itheima;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class Demo04DDL {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
//1) 创建连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day18", "root", "root");
//2) 通过连接对象得到语句对象
stmt = conn.createStatement();
//3) 通过语句对象发送SQL语句给服务器,执行SQL
boolean result = stmt.execute("CREATE TABLE Student (id int PRIMARY KEY auto_increment, " + "name VARCHAR(20) not null, gender boolean, birthday date)");
System.out.println(result); //false 因为没有结果集
} catch (SQLException e) {
e.printStackTrace();
}
//4) 释放资源
finally {
if (stmt !=null) { //关闭语句对象
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//关闭连接对象
if (conn!=null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
执行DML操作
- 需求:向学生表中添加4条记录,主键是自动增长
- 开发步骤:
- 创建连接对象
- 创建Statement语句对象
- 执行SQL语句:executeUpdate(sql)
- 返回影响的行数
- 释放资源
- 代码:
package com.itheima;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
/**
* 向表中添加4条记录
*/
public class Demo05DML {
public static void main(String[] args) throws SQLException {
//1) 创建连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql:///day18","root","root");
//2) 创建Statement语句对象
Statement stmt = conn.createStatement();
//3) 执行SQL语句:
//4) 返回影响的行数
int rows = stmt.executeUpdate("INSERT INTO student VALUES (null,'孙悟空',1,'1993-11-11'),(null,'孙悟天',1,'1993-11-11'),(null,'孙悟饭',1,'1993-11-11'),(null,'琪琪',0,'1993-11-11');");
//5) 释放资源
stmt.close();
conn.close();
System.out.println("添加了" + rows + "行记录");
}
}
执行DQL操作
ResultSet接口:
- 作用:代表一个从服务器上返回结果集,封装好所有数据。我们就是从结果集中取出数据。
- 接口中的方法:
常用数据类型转换表
注:java.sql.Date、java.sql.Time、java.sql.Timestamp(时间戳),三个共同父类是:java.util.Date
需求:确保数据库中有3条以上的记录,查询所有的学员信息
- 查询结果:
- 开发步骤:
- 得到连接对象
- 得到语句对象
- 执行SQL语句后得到结果集ResultSet对象
- 循环遍历取出每一条记录
- 输出的控制台上
- 释放资源
- 代码
package com.itheima;
import java.sql.*;
public class Demo06DQL {
public static void main(String[] args) throws SQLException {
//1) 得到连接对象
Connection connn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day18","root","root");
//2) 得到语句对象
Statement stmt = connn.createStatement();
//3) 执行SQL语句后得到结果集ResultSet对象
ResultSet rs = stmt.executeQuery("SELECT * FROM student");
//4) 循环遍历取出每一条记录
while(rs.next()) {
//int id = rs.getInt("id"); //通过列名
//int id = rs.getInt(1); //通过列号
String id = rs.getString("id"); //使用不同的类型
String name = rs.getString("name");
//int name = rs.getInt("name"); // java.sql.SQLException: Invalid value for getInt() - '孙悟空'
boolean gender = rs.getBoolean("gender");
Date birthday = rs.getDate("birthday");
//5) 输出的控制台上
System.out.println("编号:" + id + ",姓名:" + name + ", 性别:" + (gender?"男":"女") + ", 生日:" + birthday);
}
//6) 释放资源
rs.close();
stmt.close();
connn.close();
}
}
关于ResultSet接口中的注意事项:
- 如果光标在第一行之前,使用rs.getXX()获取列值,报错:Before start of result set
- 如果光标在最后一行之后,使用rs.getXX()获取列值,报错:After end of result set
- 同一列可以通过getXxx()方法得到不同的数据类型
数据库工具类JdbcUtils
什么时候需要创建自己的工具类?
如果某个方法中的代码可以在不同的地方重复使用,而且代码没有变化,我们就可以考虑将它创建成一个工具类的方法,可以重用。
创建工具类
- 需求:上面的代码中出现了很多重复的代码,可以把这些公共代码抽取出来。
- 创建类JdbcUtils包含3个方法:
- 可以把几个字符串定义成常量:用户名,密码,URL,驱动类
- 注册驱动,为了兼容以前的程序
- 得到数据库的连接:getConnection()
- 关闭所有打开的资源:
close(Connection conn, Statement stmt)
close(Connection conn, Statement stmt, ResultSet rs)
- 代码
package com.itheima.utils;
import java.sql.*;
/**
* 数据库的工具类
*/
public class JdbcUtils {
// 几个字符串定义成常量:用户名,密码,URL,驱动类
private static final String USER = "root";
private static final String PASSWORD = "root";
private static final String URL = "jdbc:mysql://localhost:3306/day18";
private static final String DRIVER = "com.mysql.jdbc.Driver";
//注册驱动,为了兼容以前的程序
static {
try {
Class.forName(DRIVER);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 得到数据库的连接
*/
public static Connection getConnection() {
try {
return DriverManager.getConnection(URL,USER,PASSWORD);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* 关闭所有打开的资源
*/
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);
}
}
案例:用户登陆
需求:
- 有一张用户表,添加几条用户记录
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');
select * from user;
-- 什么时候登录成功,通过用户名和密码查询的结果集中有记录表示登录成功
select * from user where name='Jack' and password='123';
-- 什么时候登录失败
select * from user where name='Jack' and password='111';
select * from user where name='NewBoy' and password='222';
- 使用Statement字符串拼接的方式实现用户的登录, 用户在控制台上输入用户名和密码。
"select * from user where name='" + name + "' and password='" + password + "'"
开发步骤:
- 得到用户从控制台上输入的用户名和密码
- 调用下面写的登录方法来实现登录
- 写一个登录的方法
- 通过工具类得到连接
- 创建语句对象,使用拼接字符串的方式生成SQL语句
- 查询数据库,如果有记录则表示登录成功,否则登录失败
- 释放资源
代码:
public class Demo07Login {
public static void main(String[] args) {
// 1) 得到用户从控制台上输入的用户名和密码
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String name = scanner.nextLine();
System.out.println("请输入密码:");
String password = scanner.nextLine();
// 2) 调用下面写的登录方法来实现登录
login(name, password);
}
//登录的方法
private static void login(String name, String password) {
//a) 通过工具类得到连接
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
//b) 创建语句对象,使用拼接字符串的方式生成SQL语句
stmt = conn.createStatement();
String sql = "select * from user where name='" + name + "' and password='" + password + "'";
System.out.println(sql);
rs = stmt.executeQuery(sql);
//c) 查询数据库,如果有记录则表示登录成功,否则登录失败
if (rs.next()) {
//登录成功
System.out.println("登录成功,欢迎您:" + name);
} else {
System.out.println("登录失败,请重试");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//d) 释放资源
JdbcUtils.close(conn, stmt, rs);
}
}
}
SQL注入问题
- 当我们输入以下密码,我们发现我们账号和密码都不对竟然登录成功了
- 问题分析:
select * from user where name='NewBoy' and password='a' or '1'='1'
select * from user where false and false or true;
select * from user where true; //查询所有的记录
我们让用户输入的密码和SQL语句进行字符串拼接。用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义,以上问题称为SQL注入。要解决SQL注入就不能让用户输入的密码和我们的SQL语句进行简单的字符串拼接。
PreparedStatement接口
继承结构与作用:
PreparedSatement的执行原理
Connection创建PreparedStatement对象
PreparedStatement接口中的方法:
PreparedSatement的好处
- prepareStatement()会先将SQL语句发送给数据库预编译。PreparedStatement会引用着预编译后的结果。可以多次传入不同的参数给PreparedStatement对象并执行。减少SQL编译次数,提高效率。
- 安全性更高,没有SQL注入的隐患。
- 提高了程序的可读性
package com.itheima;
import com.itheima.utils.JdbcUtils;
import java.sql.*;
import java.util.Scanner;
public class Demo07Login {
public static void main(String[] args) {
// 1) 得到用户从控制台上输入的用户名和密码
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String name = scanner.nextLine();
System.out.println("请输入密码:");
String password = scanner.nextLine();
// 2) 调用下面写的登录方法来实现登录
login(name, password);
}
//使用子接口:PreparedStatement
private static void login(String name, String password) {
//1. 得到连接对象
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
//2. 创建语句对象,使用占位符
ps = conn.prepareStatement("select * from user where name=? and password=?");
//3. 设置占位符的真实值
ps.setString(1, name);
ps.setString(2, password);
//4. 查询
rs = ps.executeQuery();
//5. 判断是否登录成功
if (rs.next()) {
System.out.println("登录成功," + name);
} else {
System.out.println("登录失败");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//6. 关闭连接
JdbcUtils.close(conn, ps, rs);
}
}
/*
//Statement登录的方法
private static void login(String name, String password) {
//a) 通过工具类得到连接
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
//b) 创建语句对象,使用拼接字符串的方式生成SQL语句
stmt = conn.createStatement();
String sql = "select * from user where name='" + name + "' and password='" + password + "'";
System.out.println(sql);
rs = stmt.executeQuery(sql);
//c) 查询数据库,如果有记录则表示登录成功,否则登录失败
if (rs.next()) {
//登录成功
System.out.println("登录成功,欢迎您:" + name);
} else {
System.out.println("登录失败,请重试");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//d) 释放资源
JdbcUtils.close(conn, stmt, rs);
}
}
*/
}
使用PreparedStatement的步骤:
- 编写SQL语句,未知内容使用占位符
- 获得PreparedStatement对象
- 设置实际参数
- 执行SQL语句
- 关闭资源
- 使用PreparedStatement改写上面的登录程序,看有没有SQL注入的情况
public class Demo07Login {
public static void main(String[] args) {
// 1) 得到用户从控制台上输入的用户名和密码
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String name = scanner.nextLine();
System.out.println("请输入密码:");
String password = scanner.nextLine();
// 2) 调用下面写的登录方法来实现登录
login(name, password);
}
//使用子接口:PreparedStatement
private static void login(String name, String password) {
//1. 得到连接对象
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
//2. 创建语句对象,使用占位符
ps = conn.prepareStatement("select * from user where name=? and password=?");
//3. 设置占位符的真实值
ps.setString(1, name);
ps.setString(2, password);
//4. 查询
rs = ps.executeQuery();
//5. 判断是否登录成功
if (rs.next()) {
System.out.println("登录成功," + name);
} else {
System.out.println("登录失败");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//6. 关闭连接
JdbcUtils.close(conn, ps, rs);
}
}
}
表与类的关系
案例:使用PreparedStatement查询id为1的一条学生数据,封装成一个学生Student对象
- 开发步骤:
- 创建一个学生对象
- 得到连接对象
- 得到语句对象,SQL设置占位符
- 传递参数,替换占位符
- 将结果封装成一个学生对象
- 释放资源
- 使用数据,输出到控制台
- 代码:
//每个方法都可以运行, 必须是public void 方法不能有参数
@Test
public void testFindOne() throws SQLException {
//1) 创建一个学生对象
Student student = new Student();
//2) 得到连接对象
Connection conn = JdbcUtils.getConnection();
//3) 得到语句对象,SQL设置占位符
PreparedStatement ps = conn.prepareStatement("select * from student where id=?");
//4) 传递参数,替换占位符
ps.setInt(1, 1);
ResultSet rs = ps.executeQuery();
//5) 将结果封装成一个学生对象
if (rs.next()) {
student.setId(rs.getInt("id"));
student.setName(rs.getString("name"));
student.setGender(rs.getBoolean("gender"));
student.setBirthday(rs.getDate("birthday"));
}
//6) 释放资源
JdbcUtils.close(conn,ps,rs);
//7) 使用数据,输出到控制台
System.out.println(student);
}
案例:将多条记录封装成集合List<Student>,集合中每个元素是一个JavaBean实体类
- 需求: 查询所有的学生类,封装成List<Student>返回
- 执行效果:
- 开发步骤:
- 创建一个集合用于封装所有的记录
- 得到连接对象
- 得到语句对象,SQL语句没有占位符
- 每次循环封装一个学生对象
- 把数据放到集合中
- 关闭连接
- 使用数据,循环输出学生对象
- 代码:
@Test
public void testFindAll() throws SQLException {
//1) 创建一个集合用于封装所有的记录
List<Student> students = new ArrayList<>();
//2) 得到连接对象
Connection conn = JdbcUtils.getConnection();
//3) 得到语句对象,SQL语句没有占位符
PreparedStatement ps = conn.prepareStatement("select * from student");
ResultSet rs = ps.executeQuery();
//4) 每次循环封装一个学生对象
while (rs.next()) {
//5) 把数据放到集合中
Student student = new Student();
student.setId(rs.getInt("id"));
student.setName(rs.getString("name"));
student.setGender(rs.getBoolean("gender"));
student.setBirthday(rs.getDate("birthday"));
students.add(student);
}
//6) 关闭连接
JdbcUtils.close(conn,ps,rs);
//7) 使用数据,循环输出学生对象
students.forEach(System.out::println);
}
PreparedStatement执行DML操作
- 需求:
- 向学生表中添加1条记录代码
- 将id为2的用户,姓名更新为"猪八戒",性别换成男
- 将id为4的学员删除
- 代码:
// 1) 向学生表中添加1条记录代码
@Test
public void testAdd() throws SQLException {
//1.创建连接
Connection conn = JdbcUtils.getConnection();
//2.创建语句对象
PreparedStatement ps = conn.prepareStatement("INSERT into student VALUES (null,?,?,?)");
//3. 替换占位符
ps.setString(1, "短笛");
ps.setBoolean(2, true);
ps.setDate(3, Date.valueOf("1998-02-20")); //将字符串转成日期,格式必须是:yyyy-MM-dd
//4. 执行更新操作
int row = ps.executeUpdate();
//5.关闭连接
JdbcUtils.close(conn, ps);
System.out.println("影响的行数:" + row);
}
// 2) 将id为2的用户,姓名更新为"猪八戒",性别换成男
@Test
public void testUpdate() throws SQLException {
//1.创建连接
Connection conn = JdbcUtils.getConnection();
//2.创建语句对象
PreparedStatement ps = conn.prepareStatement("update student set name=?,gender=? where id=?");
//3. 替换占位符
ps.setString(1, "猪八戒");
ps.setBoolean(2, true);
ps.setInt(3, 2);
//4. 执行更新操作
int row = ps.executeUpdate();
//5.关闭连接
JdbcUtils.close(conn, ps);
System.out.println("影响的行数:" + row);
}
// 3) 将id为4的学员删除
@Test
public void testDelete() throws SQLException {
//1.创建连接
Connection conn = JdbcUtils.getConnection();
//2.创建语句对象
PreparedStatement ps = conn.prepareStatement("delete from student where id=?");
//3. 替换占位符
ps.setInt(1, 4);
//4. 执行更新操作
int row = ps.executeUpdate();
//5.关闭连接
JdbcUtils.close(conn, ps);
System.out.println("影响的行数:" + row);
}
JDBC事务的处理
之前我们是使用MySQL的命令来操作事务。接下来我们使用JDBC来操作银行转账的事务。
准备数据
CREATE TABLE account (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(10),
balance DOUBLE
);
-- 添加数据
INSERT INTO account (NAME, balance) VALUES ('Jack', 1000), ('Rose', 1000);
API介绍
开发步骤
- 先试一下没有事务的转账情况
- 使用事务的情况
- 获取连接
- 开启事务
- 获取到PreparedStatement
- 使用PreparedStatement执行两次更新操作
- 正常情况下提交事务
- 出现异常回滚事务
- 最后关闭资源
- 案例代码
package com.itheima;
import com.itheima.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Demo09Transaction {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
//1) 获取连接
conn = JdbcUtils.getConnection();
//开启事务
conn.setAutoCommit(false);
//3) 获取到PreparedStatement
ps = conn.prepareStatement("update account set balance = balance - 500 where name='jack'");
ps.executeUpdate();
System.out.println(100 / 0);
//执行第2次
ps = conn.prepareStatement("update account set balance = balance + 500 where name='rose'");
ps.executeUpdate();
//提交事务
conn.commit();
System.out.println("转账成功");
} catch (Exception e) {
System.out.println("转账失败");
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
//7) 最后关闭资源
JdbcUtils.close(conn, ps);
}
}
}