Java Data Base Connectivity:Java数据库连接,JDBC是Java操作数据库的规范
通过JDBC可以让Java操作数据库
JDBC规范定义接口,具体的实现由各大数据库厂商来实现
JDBC是Java访问数据库的标准规范。真正怎么操作数据库还需要具体的实现类,也就是数据库驱动。每个数据库厂商根据自家数据库的通信格式编写好自己数据库的驱动。所以我们只需要会调用JDBC接口中的方法即可。数据库驱动由数据库厂商提供。
说出JDBC的概念?
JAVA数据库连接,JDBC是Java操作数据库的规范(接口)
说出JDBC的作用?
通过JDBC可以让Java操作数据库
学习JDBC四个核心对象
这几个类都是在java.sql包中
JDBC四个核心对象?
DriverManager:注册驱动
Connection:Java程序和数据库之间的连接(桥梁)
Statement:执行SQL语句的对象(小货车)
ResultSet:结果集,保存查询到的数据
我们Java程序需要通过数据库驱动才能连接到数据库,因此需要注册驱动。
学习导入mysql驱动Jar包
学习JDBC注册数据库驱动
在注册驱动前需要先导入驱动的Jar包
我们Java程序需要通过数据库驱动才能连接到数据库,因此需要注册驱动。
MySQL的驱动的入口类是:com.mysql.jdbc.Driver
java.sql.DriverManager
类用于注册驱动。提供如下方法注册驱动
static void registerDriver(Driver driver)
向 DriverManager 注册给定驱动程序。
1.DriverManager.registerDriver(驱动对象); 传入对应参数即可
public class Demo01 {
public static void main(String[] args) throws Exception {
// 注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
}
}
通过查询com.mysql.jdbc.Driver源码,我们发现Driver类“主动”将自己进行注册
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
// 自己自动注册
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
public Driver() throws SQLException {
}
}
注意:使用
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
,存在两方面不足
- 硬编码,后期不易于程序扩展和维护
- 驱动被注册两次
使用Class.forName("com.mysql.jdbc.Driver");
加载驱动,这样驱动只会注册一次
public class Demo01 {
public static void main(String[] args) throws Exception {
Class.forName("com.mysql.jdbc.Driver"); // 后期可以将"com.mysql.jdbc.Driver"字符串写在文件中.
}
}
演示:Class.forName("包名.类名");
会走这个类的静态代码块
通常开发我们使用Class.forName() 加载驱动。Class.forName("com.mysql.jdbc.Driver");
会走Driver类的静态代码块。在静态代码块中注册一次驱动。
总结:注册MySQL驱动使用
Class.forName("com.mysql.jdbc.Driver");
导入mysql驱动Jar包
通过JDBC注册数据库驱动?
Class.forName("com.mysql.jdbc.Driver");
Connection
表示Java程序与数据库之间的连接,只有拿到Connection才能操作数据库。
学习JDBC获取数据库连接
学习获取Statement对象
java.sql.DriverManager
类中有如下方法获取数据库连接
static Connection getConnection(String url, String user, String password)
连接到给定数据库 URL ,并返回连接。
String url
:连接数据库的URL,用于说明连接数据库的位置String user
:数据库的账号String password
:数据库的密码连接数据库的URL地址格式:协议名:子协议://服务器名或IP地址:端口号/数据库名
MySQL写法:jdbc:mysql://localhost:3306/day16
如果是本地服务器,端口号是默认的3306,则可以简写:jdbc:mysql:///day16
如果数据出现乱码需要加上参数: ?characterEncoding=utf8,表示让数据库以UTF-8编码来处理数据。
如: jdbc:mysql://localhost:3306/day16?characterEncoding=utf8
public class Demo01 {
public static void main(String[] args) throws Exception {
Class.forName("com.mysql.jdbc.Driver");
// 连接到MySQL
// url: 连接数据库的URL
// user: 数据库的账号
// password: 数据库的密码
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day24", "root", "root");
System.out.println(conn);
}
}
JDBC获取数据库连接使用哪个API?
DriverManager类中的
Connection getConnection(数据库URL, 数据库账号, 数据库密码);
通过JDBC连接mysql的URL写法?
jdbc:mysql://主机名:端口号/数据库名
学习JDBC实现对单表数据增、删、改
-- 创建分类表
CREATE TABLE category (
cid INT PRIMARY KEY AUTO_INCREMENT,
cname VARCHAR(100)
);
-- 初始化数据
INSERT INTO category (cname) VALUES('家电');
INSERT INTO category (cname) VALUES('服饰');
INSERT INTO category (cname) VALUES('化妆品');
我们要对数据库进行增、删、改、查,需要使用Statement
对象来执行SQL语句。
获取Statement对象
在java.sql.Connection
接口中有如下方法获取到Statement
对象
Statement createStatement()
创建一个 Statement 对象来将 SQL 语句发送到数据库
Statement的API介绍
boolean execute(String sql)
用执行任何SQL语句,如果是查询返回true,如果不是查询语句返回false; 通常不用
int executeUpdate(String sql)
用于执行增删改等语句; 返回影响的行数
ResultSet executeQuery(String sql)
用于执行查询语句; 返回查询到的结果集
executeQuery:用于执行查询SQL
executeUpdate:用于执行除查询外的SQL
public class Demo03 {
public static void main(String[] args) throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///day24", "root", "root");
System.out.println(conn);
// 从连接中拿到一个Statement对象
Statement stmt = conn.createStatement();
// 1.插入记录
String sql = "INSERT INTO category (cname) VALUES ('手机');";
int i = stmt.executeUpdate(sql);
System.out.println("影响的行数:" + i);
// 2.修改记录
sql = "UPDATE category SET cname='汽车' WHERE cid=4;";
i = stmt.executeUpdate(sql);
System.out.println("影响的行数:" + i);
// 3.删除记录
sql = "DELETE FROM category WHERE cid=1;";
i = stmt.executeUpdate(sql);
System.out.println("影响的行数:" + i);
// 释放资源
stmt.close();
conn.close();
}
}
JDBC实现增删改的步骤
Statement对象的用于执行除查询外的SQL语句的方法
int executeUpdate(String sql);
JDBC实现对单表数据查询
ResultSet
用于保存执行查询SQL语句的结果。
我们不能一次性取出所有的数据,需要一行一行的取出。
其实ResultSet获取数据的API是有规律的get后面加数据类型。我们统称getXXX()
public class Demo04 {
public static void main(String[] args) throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///day24", "root", "root");
Statement stmt = conn.createStatement();
String sql = "SELECT * FROM category;";
ResultSet rs = stmt.executeQuery(sql);
// 内部有一个指针,只能取指针指向的那条记录
while (rs.next()) { // 指针移动一行,有数据才返回true
// 取出数据
int cid = rs.getInt("cid");
String cname = rs.getString("cname");
System.out.println(cid + " == " + cname);
}
// 关闭资源
rs.close();
stmt.close();
conn.close();
}
}
从ResultSet中能一次取出所有数据吗?
不能,一次只能处理一行
如何通过ResultSet取数据
while (rs.next()) {
获取这行数据
}
学习JDBC操作事务
之前我们是使用MySQL的命令来操作事务。接下来我们使用JDBC来操作银行转账的事务。
CREATE TABLE account (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(10),
balance DOUBLE
);
-- 添加数据
INSERT INTO account (NAME, balance) VALUES ('张三', 1000), ('李四', 1000);
Connection
接口中与事务有关的方法
void setAutoCommit(boolean autoCommit) throws SQLException;
false:开启事务, ture:关闭事务
void commit() throws SQLException;
提交事务
void rollback() throws SQLException;
回滚事务
public class Demo05 {
public static void main(String[] args) {
Connection conn = null;
try {
// 拿到连接
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql:///day24", "root", "root");
// 开启事务
conn.setAutoCommit(false);
Statement pstmt = conn.createStatement();
// 张三减500
String sql = "UPDATE account SET balance = balance - 500 WHERE id=1;";
pstmt.executeUpdate(sql);
// 模拟异常
// int i = 10 / 0;
// 李四加500
sql = "UPDATE account SET balance = balance + 500 WHERE id=2;";
pstmt.executeUpdate(sql);
pstmt.close();
// 成功,提交事务
System.out.println("成功,提交事务");
conn.commit();
} catch (Exception e) {
// 失败,回滚事务
try {
System.out.println("出了异常,回滚事务");
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
JDBC中与事务相关的API?
Connection接口中的方法。
编写JDBC获取连接与关闭连接工具类
通过上面案例需求我们会发现每次去执行SQL语句都需要注册驱动,获取连接,得到Statement,以及释放资源。发现很多重复的劳动,我们可以将重复的代码定义到某个类的方法中。直接调用方法,可以简化代码。
那么我们接下来定义一个JDBCUtils
类。把注册驱动,获取连接,得到Statement,以及释放资源的代码放到这个类的方法中。以后直接调用方法即可。
static Connection getConneciton();
close(Connection conn, Statement stmt, ResultSet rs)
close(Connection conn, Statement stmt)
JDBCUtils.java
public class JDBCUtils {
// 1.将固定字符串定义为常量
private static final String DRIVER_CLASS = "com.mysql.jdbc.Driver";
private static final String URL = "jdbc:mysql:///day24";
private static final String USER = "root";
private static final String PASSWORD = "root";
// 2.在静态代码块中注册驱动(只注册一次)
// 当这个类加载到内存的时候就走这个静态代码块,再去触发Driver类中的静态代码块,主动注册
static {
try {
Class.forName(DRIVER_CLASS);
} catch (ClassNotFoundException e) {}
}
// 3.提供一个获取连接的方法static Connection getConneciton();
// 我们面向JDBC编程
public static Connection getConnection() throws SQLException {
InputStream is = JDBCUtils.class.getResourceAsStream("/jdbc.properties");
Properties pp = new Properties();
pp.load(is);
Connection conn = DriverManager.getConnection(URL, pp);
return conn;
}
// 5.重载关闭方法close(Connection conn, Statement stmt)
public static void close(Connection conn, Statement stmt) {
close(conn, stmt, null);
}
// 4.定义关闭资源的方法close(Connection conn, Statement stmt, ResultSet rs)
public static void close(Connection conn, Statement stmt, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {}
}
}
}
编写JDBC工具类的目的是什么?
我们发现JDBC操作数据库,代码都是固定的,都是重复的,封装成工具类,让JDBC使用更加简单。
模拟用户输入账号和密码登录网站
创建一个用户表保存用户的账号和密码,并添加一些数据,SQL语句如下:
CREATE TABLE USER (
id INT AUTO_INCREMENT PRIMARY KEY,
NAME VARCHAR(50),
PASSWORD VARCHAR(50)
);
INSERT INTO USER (NAME, PASSWORD) VALUES('admin', '123'), ('test', '123'), ('gm', '123');
编写代码让用户输入账号和密码
public class Demo07 {
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();
}
使用SQL根据用户的账号和密码去数据库查询数据
public class Demo07 {
public static void main(String[] args) throws Exception {
// 让用户输入账号和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入账号: ");
String name = sc.nextLine();
System.out.println("请输入密码: ");
String password = sc.nextLine();
// 使用SQL根据用户的账号和密码去数据库查询数据
Connection conn = JDBCUtils.getConnection();
Statement stmt = conn.createStatement();
String sql = "SELECT * FROM user WHERE name='" + name + "' AND password='" + password + "';";
}
}
如果查询到数据,说明登录成功,如果查询不到数据,说明登录失败
public class Demo07 {
public static void main(String[] args) throws Exception {
// 让用户输入账号和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入账号: ");
String name = sc.nextLine();
System.out.println("请输入密码: ");
String password = sc.nextLine();
// 使用SQL根据用户的账号和密码去数据库查询数据
Connection conn = JDBCUtils.getConnection();
Statement stmt = conn.createStatement();
String sql = "SELECT * FROM user WHERE name='" + name + "' AND password='" + password + "';";
// 如果查询到数据,说明登录成功,如果查询不到数据,说明登录失败
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()) {
//能进来查询到了数据.
String name2 = rs.getString("name");
System.out.println("欢迎您," + name2);
} else {
//查询不到数据,说明登录失败
System.out.println("账号或密码错误...");
}
JDBCUtils.close(conn, stmt, rs);
}
}
}
登录案例步骤
创建用户表,保存一些用户的数据
让用户输入账号和密码
将输入的账号和密码,拼接一个查询语句,并执行
如果查询到数据,登录成功
如果查询不到数据,登录失败
学习SQL注入的概念
在我们前面JDBC实现登录案例中,当我们输入以下密码,我们发现我们账号和密码都不对竟然登录成功了
请输入用户名:
hehe
请输入密码:
a' or '1'='1
问题分析:
// 代码中的SQL语句
"SELECT * FROM user WHERE name='" + name + "' AND password='" + password + "';";
// 将用户输入的账号密码拼接后
"SELECT * FROM user WHERE name='hehe' AND password='a' or '1'='1';"
我们让用户输入的密码和SQL语句进行字符串拼接。用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义,以上问题称为SQL注入。
要解决SQL注入就不能让用户输入的密码和我们的SQL语句进行简单的字符串拼接。需要使用PreparedSatement类解决SQL注入。
什么是SQL注入?
用户输入的数据拼接成SQL语句,改变了SQL语句的含义。
了解PreparedSatement的执行原理
继承结构:
[外链图片转存失败(img-1bFr4w7d-1564843431162)(/pstmt01.png)]
我们写的SQL语句让数据库执行,数据库不是直接执行SQL语句字符串。和Java一样,数据库需要执行编译后的SQL语句(类似Java编译后的字节码文件)。
Satement
对象每执行一条SQL语句都会先将这条SQL语句发送给数据库编译,数据库再执行。Statement stmt = conn.createStatement();
stmt.executeUpdate("INSERT INTO users VALUES (1, '张三', '123456');");
stmt.executeUpdate("INSERT INTO users VALUES (2, '李四', '666666');");
上面2条SQL语句我们可以看到大部分内容是相同的,只是数据略有不一样。数据库每次执行都编译一次。如果有1万条类似的SQL语句,数据库需要编译1万次,执行1万次,显然效率就低了。
prepareStatement()
会先将SQL语句发送给数据库预编译。PreparedStatement
会引用着预编译后的结果。可以多次传入不同的参数给PreparedStatement
对象并执行。相当于调用方法多次传入不同的参数。String sql = "INSERT INTO users VALUES (?, ?, ?);";
// 会先将SQL语句发送给数据库预编译。PreparedStatement会引用着预编译后的结果。
PreparedStatement pstmt = conn.prepareStatement(sql);
// 设置参数
pstmt.setString(1, 1);
pstmt.setInt(2, "张三");
pstmt.setString(3, "123456");
pstmt.executeUpdate();
// 再次设置参数
pstmt.setString(1, 2);
pstmt.setInt(2, "李四");
pstmt.setString(3, "66666");
pstmt.executeUpdate();
上面预编译好一条SQL,2次传入了不同的参数并执行。如果有1万条类似的插入数据的语句。数据库只需要预编译一次,传入1万次不同的参数并执行。减少了SQL语句的编译次数,提高了执行效率。
prepareStatement()
会先将SQL语句发送给数据库预编译。PreparedStatement
会引用着预编译后的结果。可以多次传入不同的参数给PreparedStatement
对象并执行。减少SQL编译次数,提高效率。PreparedSatement的好处?
学习PreparedSatement相应的API
Statement
↑ 继承
PreparedStatement
在java.sql.Connection
有获取PreparedSatement
对象的方法
PreparedStatement prepareStatement(String sql)
会先将SQL语句发送给数据库预编译。PreparedStatement对象会引用着预编译后的结果。
"SELECT * FROM user WHERE name=? AND password=?;"; 参数化的SQL
在java.sql.PreparedStatement
中有设置SQL语句参数,和执行参数化的SQL语句的方法
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 值。
ResultSet executeQuery()
在此 PreparedStatement 对象中执行 SQL 查询,并返回该查询生成的ResultSet对象。
int executeUpdate()
在此 PreparedStatement 对象中执行 SQL 语句,该语句必须是一个 SQL 数据操作语言DML语句,比如 INSERT、UPDATE 或 DELETE 语句;或者是无返回内容的 SQL 语句,比如 DDL 语句。
"SELECT * FROM user WHERE name=? AND password=?;";
参数化的SQLPreparedSatement如何设置参数
void setXxx(第几个问号, 问号的具体值);
PreparedSatement如何执行SQL
ResultSet executeQuery()
int executeUpdate()
学习PreparedSatement实现增删改
CREATE TABLE employee (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
age INT,
address VARCHAR(50)
);
// 添加数据: 向Employee表添加3条记录
public static void addEmployee() throws Exception {
Connection conn = JDBCUtils.getConnection();
String sql = "INSERT INTO employee VALUES (NULL, ?, ?, ?);";
// prepareStatement()会先将SQL语句发送给数据库预编译。
PreparedStatement pstmt = conn.prepareStatement(sql);
// 设置参数
pstmt.setString(1, "刘德华");
pstmt.setInt(2, 57);
pstmt.setString(3, "香港");
int i = pstmt.executeUpdate();
System.out.println("影响的行数:" + i);
// 再次设置参数
pstmt.setString(1, "张学友");
pstmt.setInt(2, 55);
pstmt.setString(3, "澳门");
i = pstmt.executeUpdate();
System.out.println("影响的行数:" + i);
// 再次设置参数
pstmt.setString(1, "黎明");
pstmt.setInt(2, 52);
pstmt.setString(3, "香港");
i = pstmt.executeUpdate();
System.out.println("影响的行数:" + i);
JDBCUtils.close(conn, pstmt);
}
将id为2的学生地址改成台湾
// 修改数据: 将id为2的学生地址改成台湾
public static void updateEmployee() throws Exception {
Connection conn = JDBCUtils.getConnection();
String sql = "UPDATE employee SET address=? WHERE id=?;";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "台湾");
pstmt.setInt(2, 2);
int i = pstmt.executeUpdate();
System.out.println("影响的行数:" + i);
JDBCUtils.close(conn, pstmt);
}
删除id为2的员工
// 删除数据: 删除id为2的员工
public static void deleteEmployee() throws Exception {
Connection conn = JDBCUtils.getConnection();
String sql = "DELETE FROM employee WHERE id=?;";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 2);
int i = pstmt.executeUpdate();
System.out.println("影响的行数:" + i);
JDBCUtils.close(conn, pstmt);
}
PreparedSatement使用步骤?
PreparedSatement实现增删改使用哪个方法?
int executeUpdate();
不是任何地方都可以写?,字段值不确定的时候才可以?
使用PreparedSatement改写登录案例
public class Demo02 {
public static void main(String[] args) throws Exception {
// 让用户输入账号和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入账号: ");
String name = sc.nextLine();
System.out.println("请输入密码: ");
String password = sc.nextLine();
// 获取连接
Connection conn = JDBCUtils.getConnection();
// 编写SQL语句,未知内容使用?占位
String sql = "SELECT * FROM user WHERE name=? AND password=?;";
// prepareStatement()会先将SQL语句发送给数据库预编译。
PreparedStatement pstmt = conn.prepareStatement(sql);
// 指定?的值
// parameterIndex: 第几个?,从1开始算
// x: 具体的值
pstmt.setString(1, name);
pstmt.setString(2, password); // 正确的密码
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
String name = rs.getString("name");
System.out.println("name:" + name);
} else {
System.out.println("没有找到数据...");
}
JDBCUtils.close(conn, pstmt, rs);
}
}
使用PreparedStatement可以解决SQL注入问题
不要拼接SQL语句,有未知内容使用?先占位,然后使用PreparedStatement设置参数,并执行
使用PreparedSatement实现查询数据
查询id小于8的员工信息,并保存到员工类中
查询id小于8的员工信息,并保存到员工类中
public class Employee {
private int id;
private String name;
private int age;
private String address;
public Employee() {
}
public Employee(int id, String name, int age, String address) {
this.id = id;
this.name = name;
this.age = age;
this.address = address;
}
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 String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Employee2 [id=" + id + ", name=" + name + ", age=" + age + ", address=" + address + "]";
}
}
// 查询数据: 查询id小于8的员工信息,并保存到员工类中
public static void queryEmployee() throws Exception {
Connection conn = JDBCUtils.getConnection();
String sql = "SELECT * FROM employee WHERE id;";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 26);
ResultSet rs = pstmt.executeQuery();
// 创建集合存放多个Employee2对象
ArrayList<Employee> list = new ArrayList<>();
while (rs.next()) {
// 移动到下一行有数据,取出这行数据
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
String address = rs.getString("address");
// 创建Employee2对象
Employee e = new Employee(id, name, age, address);
// 将创建好的员工添加到集合中
list.add(e);
}
// 输出对象
for (Employee e : list) {
System.out.println(e);
}
JDBCUtils.close(conn, pstmt, rs);
}
表对应类
一条记录对应一个对象
字段值对应对象的成员变量的值
PreparedSatement实现查询使用哪个方法?
ResultSet executeQuery();
学习元数据的概念
学习ParameterMetaData元数据使用
ParameterMetaData
可用于获取有关PreparedStatement
对象中每个参数标记的类型和属性。
select * from user where name=? and password=?
// ParameterMetaData可以用来获取?的个数和类型
通过PreparedStatement
的getParameterMetaData()
方法来获取到ParameterMetaData
对象
int getParameterCount() 获取PreparedStatement的SQL语句参数?的个数
int getParameterType(int param) 获取指定参数的SQL类型。
ParameterMetaData
对象不是所有的数据库驱动都能后去到参数类型(MySQL会出异常)
public class Demo01 {
public static void main(String[] args) throws Exception {
Connection conn = DataSourceUtils.getConnection();
String sql = "INSERT INTO student (name, age, score) VALUES (?, ?, ?)";
PreparedStatement stmt = conn.prepareStatement(sql);
ParameterMetaData md = stmt.getParameterMetaData();
System.out.println("参数个数: " + md.getParameterCount());
// Parameter metadata not available for the given statement
// MySQL不支持获取参数类型
// System.out.println("参数类型: " + md.getParameterType(1));
}
}
表示参数的元数据,可以得到SQL中?的个数和类型(mysql得不到)
学习ResultSetMetaData元数据
ResultSetMetaData
可用于获取有关ResultSet
对象中列的类型和属性的信息。
通过ResultSet
的getMetaData()
方法来获取到ResultSetMetaData
对象
int getColumnCount() 返回此 ResultSet对象中的列数
String getColumnName(int column) 获取指定列的名称
String getColumnTypeName(int column) 获取指定列的数据库特定类型名称
ResultSetMetaData
对象// ResultSetMetaData
public static void test02() throws SQLException {
Connection conn = DataSourceUtils.getConnection();
String sql = "SELECT * FROM student WHERE id=1";
PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery();
// 获取结果集元数据
ResultSetMetaData md = rs.getMetaData();
int num = md.getColumnCount();
System.out.println("列数:" + num);
for (int i = 0; i < num; i++) {
System.out.println("列名:" + md.getColumnName(i + 1)); // 获取列名
System.out.println("列类型:" + md.getColumnTypeName(i + 1)); // 获取类的类型
System.out.println("-----------");
}
}
ResultSetMetaData的作用
表示结果集元数据,可以得到结果集的字段数量,字段名称,字段类型
分层的作用:
学习连接池的原理和好处
每次创建数据库连接的问题
我们现实生活中每日三餐。我们并不会吃一餐饭就将碗丢掉,而是吃完饭后将碗放到碗柜中,下一餐接着使用。目的是重复利用碗,我们的数据库连接也可以重复使用,可以减少数据库连接的创建次数。提高数据库连接对象的使用率。
连接池的概念: 连接池是创建和管理数据库连接的缓冲池技术。连接池就是一个容器,连接池中保存了一些数据库连接,这些连接是可以重复使用的。
连接池中保存了一些数据库连接,这些连接是可以重复使用的。节省数据库的资源消耗。
javax.sql.DataSource
表示数据库连接池,是JDK中提供的一个接口,没有具体的实现,它的实现由连接池的厂商去实现。我们只需要学习这个工具如何使用即可。
public interface DataSource {
Connection getConnection();
...
}
常用的连接池实现组件有以下这些
连接池的好处?
连接池中会保存一些连接,这些连接可以重复使用,降低数据库的资源消耗
连接池的原理?
1.启动连接池时,连接池会初始化一些连接
2.当有人要用连接时,从连接池中取出一个连接
3.当连接使用完毕后,还回连接池中
学习C3P0连接池
C3P0地址:https://sourceforge.net/projects/c3p0/?source=navbar
C3P0是一个开源的连接池。Hibernate框架,默认推荐使用C3P0作为连接池实现。
C3P0的jar包:c3p0-0.9.1.2.jar
参数 | 说明 |
---|---|
initialPoolSize | 连接池刚启动时,连接池内包含的连接数量 |
maxPoolSize | 连接池中最多可以放多少个连接 |
checkoutTimeout | 连接池中没有连接时最长等待时间 |
maxIdleTime | 连接池中的空闲连接多久没有使用就会回收。默认是0,0表示不回收 |
我们看到要使用C3P0连接池,需要设置一些参数。那么这些参数怎么设置最为方便呢?使用配置文件方式。
配置文件的要求:
配置文件c3p0-config.xml
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driverproperty>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day16property>
<property name="user">rootproperty>
<property name="password">rootproperty>
<property name="initialPoolSize">5property>
<property name="maxPoolSize">10property>
<property name="checkoutTimeout">2000property>
<property name="maxIdleTime">1000property>
default-config>
<named-config name="itheimac3p0">
<property name="driverClass">com.mysql.jdbc.Driverproperty>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day25property>
<property name="user">rootproperty>
<property name="password">rootproperty>
<property name="initialPoolSize">5property>
<property name="maxPoolSize">15property>
<property name="checkoutTimeout">2000property>
<property name="maxIdleTime">1000property>
named-config>
c3p0-config>
com.mchange.v2.c3p0.ComboPooledDataSource
类表示C3P0的连接池对象,常用2种创建连接池的方式:
1.无参构造,使用默认配置
2.有参构造,使用命名配置
public ComboPooledDataSource()
无参构造使用默认配置(使用xml中default-config标签中对应的参数)
public ComboPooledDataSource(String configName)
有参构造使用命名配置(configName:xml中配置的名称,使用xml中named-config标签中对应的参数)
public Connection getConnection() throws SQLException
从连接池中取出一个连接
c3p0-0.9.1.2.jar
c3p0-config.xml
配置文件,配置对应参数ComboPooledDataSource
,使用默认配置或命名配置C3P0配置文件名称必须为c3p0-config.xml
C3P0命名配置可以有多个
准备数据
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
age INT,
score DOUBLE DEFAULT 0.0
);
配置文件
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driverproperty>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day25property>
<property name="user">rootproperty>
<property name="password">rootproperty>
<property name="initialPoolSize">5property>
<property name="maxPoolSize">10property>
<property name="checkoutTimeout">2000property>
<property name="maxIdleTime">1000property>
default-config>
<named-config name="itheimac3p0">
<property name="driverClass">com.mysql.jdbc.Driverproperty>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day25property>
<property name="user">rootproperty>
<property name="password">rootproperty>
<property name="initialPoolSize">5property>
<property name="maxPoolSize">15property>
<property name="checkoutTimeout">2000property>
<property name="maxIdleTime">1000property>
named-config>
c3p0-config>
java代码
public class Demo01 {
public static void main(String[] args) throws Exception {
// 方式一: 使用默认配置(default-config)
// new ComboPooledDataSource();
// ComboPooledDataSource ds = new ComboPooledDataSource();
// 方式二: 使用命名配置(named-config:配置名)
// new ComboPooledDataSource("配置名");
ComboPooledDataSource ds = new ComboPooledDataSource("otherc3p0");
// for (int i = 0; i < 10; i++) {
// Connection conn = ds.getConnection();
// System.out.println(conn);
// }
// 从连接池中取出连接
Connection conn = ds.getConnection();
// 执行SQL语句
String sql = "INSERT INTO student VALUES (NULL, ?, ?, ?);";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "张三");
pstmt.setInt(2, 25);
pstmt.setDouble(3, 99.5);
int i = pstmt.executeUpdate();
System.out.println("影响的行数: " + i);
pstmt.close();
conn.close(); // 将连接还回连接池中
}
}
注意:配置文件名称必须为:
c3p0-config.xml
,将配置文件放在src目录下
只需要单独修改配置文件,不用修改代码
多个配置的好处:
CP30使用步骤?
c3p0-0.9.5.2.jar
,mchange-commons-java-0.2.12.jar
C3P0常用参数?
initialPoolSize | 启动连接池时,初始化的连接数量 |
---|---|
maxPoolSize | 连接池最大的连接数量 |
checkoutTimeout | 连接池没有连接时,最大的等待时间,单位是毫秒 |
maxIdleTime | 连接池中的连接空闲多久会被销毁,默认是0,0表示不销毁 |
学习Druid连接池的使用
Druid是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池,同时加入了日志监控,可以很好的监控数据库连接池和SQL的执行情况。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。Druid地址:https://github.com/alibaba/druid
DRUID连接池使用的jar包:druid-1.0.9.jar
参数 | 说明 |
---|---|
initialSize | 刚启动连接池时,连接池中包含连接的数量 |
maxActive | 连接池中最多可以放多少个连接 |
maxWait | 获取连接时最大等待时间,单位毫秒 |
API介绍
com.alibaba.druid.pool.DruidDataSourceFactory
类有创建连接池的方法
public static DataSource createDataSource(Properties properties)
创建一个连接池,连接池的参数使用properties中的数据
我们可以看到Druid连接池在创建的时候需要一个Properties对象来设置参数,所以我们使用properties文件来保存对应的参数。
Druid连接池的配置文件名称随便,放到src目录下面方便加载
druid.properties
文件内容:
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/day17
username=root
password=root
initialSize=5
maxActive=10
maxWait=3000
导入druid的jar包
在src目录下创建一个properties文件,并设置对应参数
加载properties文件的内容到Properties对象中
创建Druid连接池,使用配置文件中的参数
从Druid连接池中取出连接
执行SQL语句
关闭资源
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/day25
username=root
password=root
initialSize=5
maxActive=10
maxWait=3000
java代码
public class Demo04 {
public static void main(String[] args) throws Exception {
// 加载配置文件中的配置参数
InputStream is = Demo04.class.getResourceAsStream("/druid.properties");
Properties pp = new Properties();
pp.load(is);
// 创建连接池,使用配置文件中的参数
DataSource ds = DruidDataSourceFactory.createDataSource(pp);
// for (int i = 0; i < 10; i++) {
// Connection conn = ds.getConnection();
// System.out.println(conn);
// }
// 从连接池中取出连接
Connection conn = ds.getConnection();
// 执行SQL语句
String sql = "INSERT INTO student VALUES (NULL, ?, ?, ?);";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "王五");
pstmt.setInt(2, 35);
pstmt.setDouble(3, 88.5);
int i = pstmt.executeUpdate();
System.out.println("影响的行数: " + i);
// 执行查询
sql = "SELECT * FROM student;";
ResultSet rs = pstmt.executeQuery(sql);
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
double score = rs.getDouble("score");
System.out.println("id: " + id + " ,name: " + name + " ,age = " + age + " ,score = " + score);
}
pstmt.close();
conn.close(); // 将连接还回连接池中
}
}
Druid使用步骤?
Druid常用的配置参数
参数 | 说明 |
---|---|
initialSize | 刚启动连接池时,初始化连接的数量 |
maxActive | 连接池中最大的连接数量 |
maxWait | 没有连接时,最大的等待时间 |
Druid连接池基本使用不管是C3P0连接池,配置大致都可以分为2种:1.连接数据库的参数
,2.连接池的参数
,这2种配置大致参数作用都相同,只是参数名称可能不一样。
我们每次操作数据库都需要创建连接池,获取连接,关闭资源,都是重复的代码。我们可以将创建连接池和获取连接池的代码放到一个工具类中,简化代码。
编写一个连接池的工具类,简化连接池的使用
DataSourceUtils.java
public class DataSourceUtils {
// 1.声明静态数据源成员变量
private static DataSource ds;
// 2.创建连接池对象
static {
// 加载配置文件中的数据
InputStream is = DataSourceUtils.class.getResourceAsStream("/druid.properties");
Properties pp = new Properties();
try {
pp.load(is);
// 创建连接池,使用配置文件中的参数
ds = DruidDataSourceFactory.createDataSource(pp);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
// 3. 定义得到数据源的方法
public static DataSource getDataSource() {
return ds;
}
// 4. 定义得到连接对象的方法
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
// 5.定义关闭资源的方法
public static void close(Connection conn, Statement stmt, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {}
}
}
// 6.重载关闭方法
public static void close(Connection conn, Statement stmt) {
close(conn, stmt, null);
}
}
测试类代码
public class Demo03 {
public static void main(String[] args) throws Exception {
// 拿到连接
Connection conn = DataSourceUtils.getConnection();
// 执行sql语句
String sql = "INSERT INTO student VALUES (NULL, ?, ?, ?);";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "李四");
pstmt.setInt(2, 30);
pstmt.setDouble(3, 50);
int i = pstmt.executeUpdate();
System.out.println("影响的函数: " + i);
// 关闭资源
DataSourceUtils.close(conn, pstmt);
}
}
使用连接池工具类后可以简化代码,我们重点是写SQL去执行。
编写连接池工具类步骤
能够理解JDBC的概念
Java数据库连接,是Java操作数据库的规范
能够使用Connection接⼝
Connection conn = DriverManager.getConnection("jdbc:mysql:///day16", "root", "root");
Statement stmt = conn.createStatement();
能够使用Statement接⼝
执行SQL语句的对象,相当于小货车
Statement stmt = conn.createStatement();
int i = stmt.executeUpdate(sql);
ResultSet rs = stmt.executeQuery(sql);
能够使用ResultSet接⼝
ResultSet接⼝:
boolean next(); 将游标向下移动一行,如果有数据返回true,没有数据返回false
Xxx getXxx(); 得到字段的值
ResultSet rs = stmt.executeQuery("SELECT * FROM category;");
// 5.ResultSet处理结果
while (rs.next()) {
int cid = rs.getInt(1);
String cname = rs.getString(2);
System.out.println(cid + "::" + cname);
}
能够使用JDBC实现对单表数据增、删、改、查
// 1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day16", "root", "root");
// 3.获取小货车
Statement stmt = conn.createStatement();
// 4.执行SQL语句
// 添加数据
// String sql = "INSERT INTO category (cname) VALUES ('情趣用品');";
// 修改数据
// String sql = "UPDATE category SET cname='计生用品' WHERE cid=2;";
// 删除数据
String sql = "DELETE FROM category WHERE cid=3;";
// int executeUpdate(String sql)
int i = stmt.executeUpdate(sql);
System.out.println("影响的行数: " + i);
// 5.关闭资源
stmt.close();
conn.close();
能够使用JDBC操作事务
操作事务的方法在Connection中
void setAutoCommit(false): 开启事务
void commit(); 提交事务
void rollback(); 回滚事务
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
// 1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.获取连接
conn = DriverManager.getConnection("jdbc:mysql:///day16", "root", "root");
// 3.开启事务
// false: 表示关闭自动提交,开始事务
conn.setAutoCommit(false);
// 4.获取到Statement
stmt = conn.createStatement();
// 5.使用Statement执行SQL
// 张三-500,李四+500
stmt.executeUpdate("UPDATE account SET balance = balance - 500 WHERE id=1;");
// 模拟出问题啦
int a = 10 / 0;
stmt.executeUpdate("UPDATE account SET balance = balance + 500 WHERE id=2;");
// 没有出问题就提交事务
System.out.println("提交事务");
conn.commit();
} catch (Exception e) {
// 6.提交或回滚事务
System.out.println("出问题啦,回滚事务");
try {
conn.rollback();
} catch (SQLException ex) {
System.out.println("回滚失败");
}
} finally {
// 7.关闭资源
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
能够完成JDBC实现登录案例
核心去数据中查询是否有用户输入账号和密码
public static void main(String[] args) throws SQLException {
// 1.使用数据库保存用户的账号和密码
// 2.让用户输入账号和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入账号:");
String name = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
// 3.使用SQL根据用户的账号和密码去数据库查询数据
String sql = "SELECT * FROM user WHERE name='" + name+ "' AND password='" + password + "';";
// SELECT * FROM user WHERE name='nba' AND password='a' or '1'='1';
System.out.println(sql);
Connection conn = JDBCUtils.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()) {
// 查到用户
// 4.如果查询到数据,说明登录成功
System.out.println("恭喜您, " + name + "登录成功!");
} else {
// 没有查到用户
// 5.如果查询不到数据,说明登录失败
System.out.println("用户名或密码错误");
}
}