# 数据库阶段
1. sql入门: 2天
2. jdbc(java程序访问mysql服务) : 1天
1). 之前: 用可视化工具或dos作为客户端
2). 今天: 如何将java程序作为客户端
3. javase增强: 2天
1). 反射 + 注解 + xml配置文件
2). 为框架mybatis铺垫
4. mybatis框架: 3天
1). mybatis是jdbc封装
2). jdbc是技术标准,mybatis是jdbc标准实现方案的封装
第一章 JDBC概述
java程序访问关系型数据库(mysql)的技术
1.1 什么是JDBC
Java DataBase Connectivity:Java的数据库连接技术,它是一组Java语言编写的接口API,给多种数据库提供统一的访问接口,是Java访问数据库的标准规范
(JDBC理解成接口)
JDBC访问数据库需要驱动程序,利用驱动程序和数据库进行通信。驱动程序是Java语言编写的实现了JDBC接口的类,由第三方数据库厂商提供
(驱动理解成JDBC接口的实现类)
1.2 JDBC的特点
- 它是一组接口,我们的程序是面向接口开发
- 不需要关注具体的实现细节,驱动程序(JDBC接口的实现类)由数据库厂商提供
# 面向接口编程(多态)
0. 多态公式: 父类引用指向子类对象,父类调用方法,执行的是子类重写的方法
接口类型 变量 = 此接口的实现类对象
Animal a = new Dog();
1. 接口 : 统一的规范
//接口是给一类事物下定义(制定标准,规范)
interface Animal{
void run(); // 可以跑
void eat(); // 可以吃
}
// 实现类1(实现接口: 实现一套标准,规范)
class Dog implements Animal{
void run(){ // 狗飞快的跑 };
void eat(){ // 狗吃骨头}
}
// 实现类2(实现接口: 实现一套标准,规范)
class Cat implements Animal{
void run(){ // 猫很慢的跑 };
void eat(){ // 猫吃鱼}
}
2. 面向接口编程是面向对象中多态特性的一种体现
// 接口类型 变量 = 接口实现类对象 (必然)
Animal a = Animal实现类;
a.eat(); // 动物在吃东西
a.run(); // 动物在跑
面向对象: 解决问题关注的对象本身,不关注实现的过程
面向接口: 隐藏具体实现,暴露接口, 开发者只需要关注接口和其方法含义即可
(为啥不需要关心具体实现: 具体实现太多了,关心不过来)
3. 面向接口编程的好处
0). 场景
mysql (mysql驱动: JDBC实现)
java官方(JDBC接口: java程序如何访问数据库) oracle (oracle驱动)
sqlserver
....
不同的数据库(底层不一样:解析器,引擎),具体的访问方法也不一样
java官方无法逐一实现对应的访问方法,干脆不实现,只提供标准(接口),让数据库厂商去实现
数据库厂商实现之后(实现类),提供驱动包
1). 从java设计者(sun公司) 来说
减少了工作量,只需要制定标准(接口),无需具体实现
2). 从java开发者(我们:程序员)来说
1). 只需要关注接口和方法含义, 无需关注接口的具体实现,降低学习成本
2). 程序不易耦合,易于扩展(切换数据库,代码几乎无需改变,只需要切换对应数据库的驱动)
4. java开发者的工作
1). 学习jdbc规范: 我们就知道如何让java程序访问关系型数据库
2). 操作:
比如我们想要访问mysql,在项目中导入mysql驱动就可以了
比如我们想要访问oracle,在项目中导入oracle驱动就可以了
1.3 面向接口开发的优势
- 程序不产生耦合
- 易于程序扩展
- 方便程序维护
第二章 JDBC的API
2.1 API介绍
JDBC开发使用到的包
会使用到的包 | 说明 |
---|---|
java.sql | JDBC的核心包 |
javax.sql | 数据库连接池的包(extension扩展) |
数据库的驱动 | 数据库厂商提供 |
JDBC的核心接口和类
接口或类 | 作用 |
---|---|
DriverManager类 | 注册驱动(jdbc4.0默认,给JDBC接口类型的变量赋值)和创建数据库连接 |
Connection接口 | 表示一个数据库连接对象 |
Statement接口 | 表示一个发送给数据库执行的SQL语句 |
ResultSet接口 | 数据库返回的查询结果集 |
JDBC访问数据库的步骤
- 利用DriverManager创建数据库连接
- 获取一个连接对象,客户端利用Statement对象发送SQL到数据库
- 数据库执行SQL语句
- 数据库返回查询的结果集ResultSet给客户端
入门案例
/*
JDBC入门案例
1. 作用: 先让大家体会一下 java程序如何访问mysql数据库
2. 实现思路:
1). 理解jdbc
2). 导入mysql驱动 (mysql数据库厂商提供的,mysql官网下载的)
3. 做法:
1). 找到jar包
资料/mysql-connector-java-5.1.37-bin.jar
2). 导入jar包
I. 在工程根目录下创建一个lib目录
II. 将mysql-connector-java-5.1.37-bin.jar放到lib目录下
III. 右单击jar包 -> add as library -> ok
3). 编写jdbc代码
*/
public class JdbcDemo {
public static void main(String[] args) throws SQLException {
//1. 注册驱动
// 注意包: com.mysql.jdbc.Driver
DriverManager.registerDriver(new Driver());
//2. 获取连接
// mysql -h 127.0.0.1 -P 3306 -u root -p
String url = "jdbc:mysql://localhost:3306/day02_3";
String username = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 执行sql,返回结果
String sql = "select * from user";
Statement statement = conn.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
//4. 处理结果
while(resultSet.next()){
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
int sex = resultSet.getInt("sex");
System.out.println(id + "," + name +"," + age + "," + sex);
}
//5. 释放连接
resultSet.close();
statement.close();
conn.close();
}
}
# 数据准备
create database day02_3;
use day02_3;
CREATE TABLE `user` (
`id` int(11) NOT NULL DEFAULT '0',
`username` varchar(32) DEFAULT NULL,
`password` varchar(32) DEFAULT NULL,
`sex` varchar(6) DEFAULT NULL,
`email` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`),
KEY `username` (`username`)
);
2.2 DriverManager类
作用
- 注册驱动,JDK1.5之后自动注册
- 创建数据库连接
JDBC连接数据库的四个参数
JDBC连接数据库的四个参数 | 值 |
---|---|
用户名 | root |
密码 | root |
驱动类全名 | 例如MySQL驱动: com.mysql.jdbc.Driver (MyDQL5之前) com.mysql.cj.jdbc.Driver (MySQL6开始) |
连接字符串 | jdbc:mysql://localhost:3306/itcast |
URL后面参数的作用
如果数据库出现乱码,可以指定参数 ?characterEncoding=utf8,表示让数据库以UTF-8编码来处理数据
jdbc:mysql://localhost:3306/itcast?characterEncoding=utf8
如果服务器是本地,端口号是默认的3306,可以简写为
jdbc:mysql:///itcast?characterEncoding=utf8
DriverManager类中的方法
DriverManager类中的静态方法 | 描述 |
---|---|
Connection getConnection (String url, String user, String password) | 根据连接字符串url、用户名、密码获取一个数据库连接对象 |
Connection getConnection (String url, Properties info) | 根据连接字符串url、属性集合获取一个数据库连接对象 |
应用
package com.itheima02.drivermanager;
import com.mysql.jdbc.Driver;
import java.sql.*;
/*
TODO 类: DriverManager
1. static synchronized void registerDriver(java.sql.Driver driver)
1). 作用: 注册驱动
让当前项目中的 mysql-connector-java.jar生效
2). 参数:
I. 形参: java.sql.Driver接口
II. 实参: 对应驱动的实现类 (mysql : com.mysql.jdbc.Driver)
3). 自动注册
I. 驱动本身的自动注册
a. com.mysql.jdbc.Driver驱动类的静态代码块自己注册了自己
静态代码块随着类的加载而执行
b. 所以只要让com.mysql.jdbc.Driver类加载即可
Class.forName("com.mysql.jdbc.Driver"); //明天: 反射
II. SPI机制自动注册
a.要求: jdbc4.0版本以上 (JDK5以后)
b.实现: 程序会自动注册驱动,不需要编写代码
c. SPI机制: web阶段会学习
2. static Connection getConnection (String url, String user, String password)
1). 作用: 让java程序访问mysql数据库,产生连接 (比喻: 拨打电话并接通 或者 登录过程)
2). 参数:
I. url : mysql服务的访问地址
格式-> 协议://ip:port/资源位置?参数
例子-> jdbc:mysql://localhost:3306/day02_3?characterEncoding=utf8
协议是两个程序通讯的规则: 通讯的方向必须要遵循统一协议 (jdbc:mysql -> 主协议:子协议)
ip: mysql服务所在的计算机ip
port: mysql服务运行占用的端口
资源位置: java程序要访问的数据仓库在mysql中的位置
参数: 传输数据所使用的编码(如果你的java程序读到的数据是乱码,可以加个此参数试试)
II. user
III. password
mysql账户的用户名和密码
*/
public class JdbcDemo {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
//1. 注册驱动
// 注意包: com.mysql.jdbc.Driver
// DriverManager.registerDriver(new Driver()); // 这个代码没有必要了,因为Driver自己注册了
Class.forName("com.mysql.jdbc.Driver");// 反射技术: 让这个类加载(明天学习)
//2. 获取连接
// mysql -h 127.0.0.1 -P 3306 -u root -p
String url = "jdbc:mysql://localhost:3306/day02_3";
String username = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 执行sql,返回结果
String sql = "select * from user";
Statement statement = conn.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
//4. 处理结果
while(resultSet.next()){
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
int sex = resultSet.getInt("sex");
System.out.println(id + "," + name +"," + age + "," + sex);
}
//5. 释放连接
resultSet.close();
statement.close();
conn.close();
}
}
2.3 Connection接口
作用
表示一个连接对象,获取的是它的子类对象
方法
Connection接口中的方法 | 描述 |
---|---|
Statement createStatement() | 创建一个Statement对象,用来发送SQL语句给数据库服务器执行 |
Connection可以获取预编译对象,以及事务操作
2.4 Statement接口
作用
表示一个数据库执行的SQL语句,获取的是它的子类对象
方法
Statement接口中的方法 | 描述 |
---|---|
boolean execute(String sql) | 作用:可以执行任意的SQL语句,常用来执行DDL 返回值: true:返回的第一个结果是ResultSet对象 false:如果是更新计数或没有结果 |
int executeUpdate(String sql) | 作用:执行增删改操作 返回值:返回影响的记录行数 |
ResultSet executeQuery(String sql) | 作用:用于执行查询操作 返回值:数据库查询的结果集 |
/*
# Connection接口 (连接)
1. Statement statement = conn.createStatement()
创建一个Statement对象,用来发送SQL语句给数据库服务器执行
2. 其他作用: 获取预编译对象,还可以进行事务操作
# TODO: Statement接口 (语句)
1. 作用: 发送sql到mysql服务器,并返回执行结果
比喻: 如果把connection看成是java程序客户端和mysql服务期之间的桥梁
那么可以把statement看成桥上运行的汽车
statement作用: 将java程序想要的内容(通过sql表达)运输给mysql服务器
mysql服务器执行sql之后,将结果给statement带回去
2. 需要大家掌握几个方法
1). ResultSet resultSet = statement.executeQuery(sql);
执行DQL语句(查询), 返回结果集(看成一张表)
2). int count = statement.executeUpdate(sql);
执行DML语句(增删改 insert,delete,update),返回被影响的行数
*/
public class JdbcDemo {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");// 反射技术: 让这个类加载(明天学习)
//2. 获取连接
String url = "jdbc:mysql://localhost:3306/day02_3";
String username = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 执行sql,返回结果
String sql = "select * from user";
//创建一个Statement对象,用来发送SQL语句给数据库服务器执行
Statement statement = conn.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
//4. 处理结果
while(resultSet.next()){
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
int sex = resultSet.getInt("sex");
System.out.println(id + "," + name +"," + age + "," + sex);
}
//5. 释放连接
resultSet.close();
statement.close();
conn.close();
}
}
/*
# 此demo演示增删改操作
*/
public class JdbcDemo2 {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");// 反射技术: 让这个类加载(明天学习)
//2. 获取连接
String url = "jdbc:mysql://localhost:3306/day02_3";
String username = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 执行sql,返回结果
//创建一个Statement对象,用来发送SQL语句给数据库服务器执行
Statement statement = conn.createStatement();
// String sql = "delete from user where id = 4";
// String sql = "update user set sex=0";
String sql = "inser into user values(null,'zs',10,1),(null,'ls',11,0)";
int count = statement.executeUpdate(sql);
//4. 处理结果
System.out.println("被影响的行数:" + count);
//5. 释放连接
statement.close();
conn.close();
}
}
2.5 ResultSet接口
原理图
方法
ResultSet接口中的方法 | 描述 |
---|---|
boolean next() | 将指针向下移动一行,如果当前行由数据,就返回true;否则,返回false |
数据类型 get数据类型(参数) | 1. 根据列名 2. 根据列号,从1开始编号 |
ResultSet接口中的getXXX()方法
方法签名 |
---|
boolean getBoolean(String columnLabel); |
byte getByte(String columnLabel); |
short getShort(String columnLabel); |
int getInt(String columnLabel); |
long getLong(String columnLabel); |
float getFloat(String columnLabel); |
double getDouble(String columnLabel); |
String getString(String columnLabel); |
常用的数据类型转换表
其中,下面的类都是 java.util.Date 的子类
java.sql.Date
java.sql.Time
java.sql.Timestamp
/*
# TODO: ResultSet接口 (结果集)
1. 作用: 用来获取查询语句执行结果的入口
2. 方法:
1). boolean next()
获取下一行数据,如果有返回true,如果没有返回false
2). String name = resultSet.getString("name");
获取当前行数据对应字段的值
String 列对应的值 = resultSet.getString("结果集的列名");
*/
public class JdbcDemo {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");// 反射技术: 让这个类加载(明天学习)
//2. 获取连接
String url = "jdbc:mysql://localhost:3306/day02_3";
String username = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 执行sql,返回结果
String sql = "select * from user";
//创建一个Statement对象,用来发送SQL语句给数据库服务器执行
Statement statement = conn.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
//4. 处理结果
while(resultSet.next()){
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
int sex = resultSet.getInt("sex");
System.out.println(id + "," + name +"," + age + "," + sex);
}
//5. 释放连接
resultSet.close();
statement.close();
conn.close();
}
}
释放资源
每次访问数据库结束,都需要释放资源
- 需要释放的对象:Connection、Statement、ResultSet
- 释放顺序:和创建的顺序相反,
创建是时候:connnection->statement->resultSet
释放的时候:resultSet->statement->connection - 写在哪里:finally的代码块
是否可以放在try()代码块中:需要实现AutoCloseable接口,均可以放
/* TODO: 释放资源 1). java程序访问数据库底层本质是IO流, 访问结束释放资源 java中有涉及两个程序的数据传输,本质就是IO 2). 流的关闭: 先开后关 3). 标准写法: try...catch...finally(在finally里释放资源) */ public class JdbcDemo { public static void main(String[] args) { Connection conn = null; Statement statement = null; ResultSet resultSet = null; try { //1. 注册驱动 //可以不写: jdk5(jdbc4.0)会自动注册 Class.forName("com.mysql.jdbc.Driver"); //2. 获取连接 String url = "jdbc:mysql://localhost:3306/day02_3?characterEncoding=utf8"; String user = "root"; String password = "root"; conn = DriverManager.getConnection(url, user, password); //3. 执行sql,获取结果 statement = conn.createStatement(); String sql = "select * from user"; resultSet = statement.executeQuery(sql); //4. 处理结果 while(resultSet.next()){ int id = resultSet.getInt("id"); String name = resultSet.getString("name"); int age = resultSet.getInt("age"); int sex = resultSet.getInt("sex"); System.out.println(id + "," + name + "," + age + "," + sex); } } catch (Exception throwables) { throwables.printStackTrace(); } finally { //5. 释放资源 if(resultSet != null){ try { resultSet.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if(statement != null){ try { statement.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if(conn != null){ try { conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } } }
第三章 JDBC的应用
# sql分类
0. 我们可以怎么操作数据库
1. DDL : data definition language(数据定义语言) 很少用!!!
create/drop/alter/truncate + database/table ...
操作仓库和表
2. DML : manipulation(数据操作语言) 常用!!!
insert/delete/update ...
增删改数据
3. DQL : query(数据查询语言) 常用!!!
select ...
查询数据
4. DCL : control(数据控制语言) 用的少,重要!!
事务,权限控制
3.1 执行DDL操作
需求
使用JDBC在MySQL的数据库中创建一张学生表
- id是主键,整数类型,自增长
- name是varchar(20),非空
- 性别是boolean类型
- 生日是date类型
-- 创建student学生表
create table student(
id int primary key auto_increment,
name varchar(20) not null,
sex boolean, -- 自动转成微整型1 表示真 0 表示假 tinyint
birthday date
);
步骤
- 创建连接
- 通过连接对象得到语句对象
- 通过语句对象发送SQL语句给服务器,执行SQL
- 释放资源
代码
public class DDLdemo {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/itcast";
// 1. 创建数据库连接
Connection connection = DriverManager.getConnection(url, "root", "root");
// 2. 获取语句对象
Statement statement = connection.createStatement();
// 3. 执行DDL语句
statement.execute("create table student(id int primary key auto_increment,name varchar(20) not null,sex boolean,birthday date)");
// 4. 关闭资源
statement.close();
}
}
3.2 执行DML操作
需求
- 向学生表添加4条记录
- 更新其中1条记录
- 删除其中1条记录
SQL语句
-- 添加4条记录
insert into student (name,sex,birthday) values ('孙悟空',1,'1999-2-10'),('猪八戒',1,'1999-2-10'),('唐三藏',1,'1999-2-10'),('沙悟净',1,'1999-2-10');
-- 更新1条,将唐三藏,姓名改成嫦娥,性别改成女,生日换成1997-04-10
update student set name='嫦娥', sex=0, birthday='1997-04-10' where name='唐三藏';
-- 删除4号记录
delete from student where name='沙悟净';
代码
package com.itheima06.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class DMLdemo {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
// insert();
// update();
delete();
}
private static void delete() throws SQLException {
String url = "jdbc:mysql://localhost:3306/itcast?characterEncoding=utf8";
// 1.创建数据库连接
Connection connection = DriverManager.getConnection(url, "root", "root");
// 2.获取语句对象
Statement statement = connection.createStatement();
// 3.执行删除SQL
int rows = statement.executeUpdate("delete from student where name='沙悟净'");
System.out.println("删除了" + rows + "行");
// 4.关闭资源
statement.close();
connection.close();
}
private static void update() throws SQLException {
String url = "jdbc:mysql://localhost:3306/itcast?characterEncoding=utf8";
// 1.创建数据库连接
Connection connection = DriverManager.getConnection(url, "root", "root");
// 2.获取语句对象
Statement statement = connection.createStatement();
// 3.执行更新SQL
String sql = "update student set name='嫦娥', sex=0, birthday='1997-04-10' where name='唐三藏'";
int rows = statement.executeUpdate(sql);
System.out.println("更新了" + rows + "行");
// 4.关闭资源
statement.close();
connection.close();
}
private static void insert() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/itcast?characterEncoding=utf8";
// 1.创建数据库连接
Connection connection = DriverManager.getConnection(url, "root", "root");
// 2.获取语句对象
Statement statement = connection.createStatement();
// 3.执行新增SQL
int rows = statement.executeUpdate("insert into student (name,sex,birthday) values ('孙悟空',1,'1999-2-10'),('猪八戒',1,'1999-2-10'),('唐三藏',1,'1999-2-10'),('沙悟净',1,'1999-2-10');");
System.out.println("新增了" + rows + "行");
// 4.关闭资源
statement.close();
connection.close();
}
}
3.3 执行DQL操作
需求
查询所有的学员信息
查询结果
步骤
- 获取连接对象
- 得到语句对象
- 执行SQL语句后得到结果集ResultSet对象
- 循环遍历取出每一条记录
- 输出的控制台上
- 释放资源
代码
public class DQLdemo {
public static void main(String[] args) throws SQLException {
String url = "jdbc:mysql://localhost:3306/itcast?characterEncoding=utf8";
// 1.获取连接对象
Connection connection = DriverManager.getConnection(url, "root", "root");
// 2.获取语句对象
Statement statement = connection.createStatement();
// 3.执行DQL语句
ResultSet resultSet = statement.executeQuery("select * from student");
// 4.遍历ResultSet
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
boolean sex = resultSet.getBoolean("sex");
Date birthday = resultSet.getDate("birthday");
System.out.println("id: " + id + ", name: " + name + ",sex:" + (sex ? '男': '女') + ",birthday: " + birthday);
}
// 5.关闭资源
resultSet.close();
statement.close();
connection.close();
}
}
第四章 封装JDBC工具类
需求
上面的代码中出现了很多重复的代码,把这些代码抽取到一个公共类里面
步骤
创建类JdbcUtils包含2个方法:
- 得到数据库的连接:getConnection()
- 关闭所有打开的资源:
close(Connection conn, Statement stmt, ResultSet rs)
代码
src目录下 jdbc.properties
url = jdbc:mysql://localhost:3306/day02_3?characterEncoding=utf8
user = root
password = root
driver = com.mysql.jdbc.Driver
/*
TODO 工具类: 工具方法的集合
1. 工具方法: 重复出现的代码抽取成的
2. 工具方法的特点: 一般是static修饰,调用方便
TODO: 关于getConnection方法的优化
1. 注册驱动 放到静态代码块中: 类加载只需要一次
2. 参数直接写死是不合适的
1). 方案A: 设置成方法参数 (变化频率比较大,每次调用都可能不同)
2). 方案B: 搞个配置文件 (变量频率比较小,有可能变动,一般不会每次调用都变动)
I. 实参放入到properties文件中
II. 要读取properties文件(写在静态代码块中,只需要一次)
*/
public class JdbcUtil {
//注意: 作用域!
private static String url;
private static String user;
private static String password;
private static String driver;
static{
//静态代码块的特点: 随着类加载的而执行,我们程序中用一个类多次,也只加载一次
try {
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
url = bundle.getString("url");
user = bundle.getString("user");
password = bundle.getString("password");
driver = bundle.getString("driver");
//1. 注册驱动 : 只需要一次,所以放着静态代码块中即可
Class.forName(driver);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws Exception {
//2. 获取连接
// 参数直接写死了
Connection conn = DriverManager.getConnection(url, user, password);
return conn;
}
public static void release(Connection conn, Statement statement, ResultSet resultSet){
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(statement != null){
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
第五章 JDBC事务
5.1 API介绍
Connection接口中与事务有关的方法 | 说明 |
---|---|
void setAutoCommit(boolean autoCommit) | 设置事务模式,参数是false表示手动提交;参数是true表示自动提交 (相当于start transaction) |
void commit() | 提交事务 (commit) |
void rollback() | 回滚事务(rollback) |
事务操作
start transaction; — 开启事务(设置事务手动提交,禁止事务自动提交)
connection.setAutoCommit(false)
commit; — 成功提交
connection.commit()
rollback; — 失败回滚
connection.rollback();
5.2 事务控制案例
sql脚本:
create table account(
id int(11) primary key auto_increment,
name varchar(20),
money float
);
insert into account values(null,"tom",1000),(null,"rose",1000);
没有事务的转账情况
- 获取连接
- 获取到Statement
- 使用Statement执行两次更新操作
- 最后关闭资源
public class TransferDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("开始转账...");
System.out.println("请输入转出的账户:");
String outUser = sc.nextLine();
System.out.println("请输入转入的账户:");
String inUser = sc.nextLine();
System.out.println("请输入转账的金额:");
int money = Integer.parseInt(sc.nextLine());
// String outsql = "update account set money = money - ? where name = '?'"; // ?去掉,用 "+变量+" 替代
String outsql = "update account set money = money - "+money+" where name = '"+outUser+"'";
String insql = "update account set money = money + "+money+" where name = '"+inUser+"'";
try {
Connection conn = JdbcUtil.getConnection();
Statement statement = conn.createStatement();
//转账
statement.executeUpdate(outsql); // 转出
//TODO: 银行系统崩溃 (用算术异常模拟)
int i = 1/0;
statement.executeUpdate(insql); // 转入
System.out.println("转账成功");
JdbcUtil.release(conn,statement,null); //释放资源要放在finally中,这里偷个懒
} catch (Exception e) {
e.printStackTrace();
System.out.println("转账失败");
}
}
}
使用事务的情况
- 获取连接
- 开启事务
- 获取到Statement
- 使用Statement执行两次更新操作
- 正常情况下提交事务
- 出现异常回滚事务
- 最后关闭资源
/*
TODO: 有事务的代码
start transaction; -- 开启事务(设置事务手动提交,禁止事务自动提交)
connection.setAutoCommit(false)
commit; -- 成功提交
connection.commit()
rollback; -- 失败回滚
connection.rollback();
try{
//开启事务
//转账操作(事务操作)
//成功提交
}catch(e){
//失败回滚
}
*/
public class TransferDemo2 {
public static void main(String[] args) throws SQLException {
Scanner sc = new Scanner(System.in);
System.out.println("开始转账...");
System.out.println("请输入转出的账户:");
String outUser = sc.nextLine();
System.out.println("请输入转入的账户:");
String inUser = sc.nextLine();
System.out.println("请输入转账的金额:");
int money = Integer.parseInt(sc.nextLine());
String outsql = "update account set money = money - "+money+" where name = '"+outUser+"'";
String insql = "update account set money = money + "+money+" where name = '"+inUser+"'";
Connection conn = null;
Statement statement = null;
try {
conn = JdbcUtil.getConnection();
statement = conn.createStatement();
//TODO: 开启事务!!!
conn.setAutoCommit(false);
//转账
statement.executeUpdate(outsql); // 转出
//银行系统崩溃 (用算术异常模拟)
// int i = 1/0;
statement.executeUpdate(insql); // 转入
//TODO: 成功提交
conn.commit();
System.out.println("转账成功");
} catch (Exception e) {
//TODO: 失败回滚
conn.rollback();
e.printStackTrace();
System.out.println("转账失败");
}finally{
JdbcUtil.release(conn,statement,null); //释放资源要放在finally中,这里偷个懒
}
}
}
第六章 案例:用户登陆
6.1 实现用户登录
需求
通过用户名和密码能够查询到数据库中记录表示登录成功,否则登录失败
步骤和代码实现
- 创建一张用户表,添加2条用户记录
`` -- 建表 create table
user`( id int primary key auto_increment, name varchar(20), password varchar(32) );
— 插入记录
insert into user
values(null,’Jack’,’123’),(null,’Rose’,’456’);
select * from user;
— Jack登陆成功
— 1. 用户输入: Jack的用户名和密码
— 2. 执行以下的sql
— 1). 根据用户输入的用户名和密码执行之后能够查询到结果
— 说明用户名和密码正确, 表示登录成功
— 2). 根据用户输入的用户名和密码执行之后能够查不到结果
— 说明用户名不存在或密码错误,表示登录失败
select * from user where name=’Jack’ and password=’123’;
— Rose登陆成功 select * from user where name=’Rose’ and password=’456’;
2. 用户在控制台上输入用户名和密码,实现登录的功能。<br />步骤如下:
1. 得到用户从控制台上输入的用户名和密码
1. 调用下面写的登录方法来实现登录
1. 写一个登录的方法
1. 通过工具类得到连接
1. 创建语句对象,使用拼接字符串的方式生成SQL语句
1. 查询数据库,如果有记录则表示登录成功,否则登录失败
1. 释放资源
```java
/*
TODO:
我们jdbc.properties中的用户名和密码
是java程序访问mysql的账户 (开发者)
登录案例:
1). java开发者设计了一套程序: 微信
2). 这里的用户名和密码, 是普通用户在这套程序上注册的账户
微信(java编写的) mysql数据库
root/root
微信用户
jack/123
*/
public class LoginDemo {
public static void main(String[] args) throws Exception {
System.out.println("这是用户登录界面!!!");
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
// String sql = "select * from user where name='?' and password='?'";
String sql = "select * from user where name='"+username+"' and password='"+password+"'";
Connection conn = JdbcUtil.getConnection();
Statement statement = conn.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
//有结果表示登录成功,没有结果表示登录失败
if(resultSet.next()){ //如果有结果,可以向下移动一行,并返回true
System.out.println("登录成功");
}else{
System.out.println("登录失败: 用户名不存在或密码错误");
}
JdbcUtil.release(conn,statement,resultSet);
//Map<String,String> map = new HashMap<>();
//map集合没有数据, 但不为null
}
}
6.2 SQL注入问题
访问步骤
输入下面的用户名和密码,即可登陆成功
请输入用户名:
Jack
请输入密码:
abc' or '1' = '1
完整的sql : select * from user where name='Jack' and password='abc' or '1' = '1'
登录成功!
问题分析
TODO: SQL注入
1). 用户通过往sql中注入 "含有sql语法的参数" , 使用原sql的含义发现了变化, 这种攻击方式称之为sql注入
2). 举例
原sql: select * from user where name=? and password=?;
根据用户名和密码查询信息
被注入攻击之后的sql
select * from user where name=? and password=? or '1' = '1';
查询信息
3). sql注入 往往是专业人员 的恶意攻击
解决问题:不能使用Statement接口,应该使用它的子接口:PreparedStatement,以后我们都使用这个子接口进行编程
第七章 PreparedStatement接口
7.1 接口介绍
继承结构
成员方法
Connection接口中的方法
方法 | 描述 |
---|---|
PreparedStatement prepareStatement(String sql) | 创建一个预编译的SQL语句 |
PreparedStatement接口中的方法
方法 | 描述 |
---|---|
int executeUpdate() | 执行增删改 返回值是影响的行数 |
ResultSet executeQuery() | 查询 返回值是结果集 |
void set数据类型(int 参数1,参数2) | 设置占位符参数 参数1:第几个占位符,从1开始计算 参数2:占位符的值 |
PreparedStatement设置参数
7.2 执行DML操作
示例
/*
# TODO: PreparedStatement(预编译语句)
1. 介绍
是Statement的子接口,比Statement更强大
1). 防止sql注入
2). 提高批量操作的效率
2. 方法
1). 获取预编译语句对象
PreparedStatement pstm = connection.prepareStatement(sql);
//这里的sql的参数不能直接写进去,需要占位符?来替代
2). 设置参数
void setInt(int parameterIndex, int x)
void setString(int parameterIndex, String x)
I. parameterIndex : 参数索引, 从sql的左边数起,从1开始
II. x : 实参
api不需要拼接参数到sql中!!!!
*/
public class PrepareDemo {
public static void main(String[] args) throws Exception {
// update();
insert();
}
private static void insert() throws Exception{
String sql = "insert into student values(null,?,?,?)";
Connection conn = JdbcUtil.getConnection();
//预编译sql
PreparedStatement pstm = conn.prepareStatement(sql);
//sql中的第1个?的参数为0
pstm.setString(1,"白龙马");
pstm.setInt(2,1);
pstm.setString(3,"1998-01-01");
int count = pstm.executeUpdate();
System.out.println(count);//1
JdbcUtil.release(conn,pstm,null);
}
private static void update() throws Exception {
String sql = "update student set sex = ? where id = ?";
Connection conn = JdbcUtil.getConnection();
//预编译sql
PreparedStatement pstm = conn.prepareStatement(sql);
//sql中的第1个?的参数为0
pstm.setInt(1,0);
pstm.setInt(2,1);
int count = pstm.executeUpdate();
System.out.println(count);//1
JdbcUtil.release(conn,pstm,null);
}
}
7.3 改写用户登陆案例
代码
/*
TODO: PreparedStatement解决sql注入
1. 实现方案: PreparedStatement替代 Statement
2. 原理: 将sql预先发给数据库进行预编译(先让数据库知道sql含义),后续再发送参数,就不会有sql注入问题啦
3. PreparedStatement (预编译语句)
1). 防止sql注入
2). 提高效率
*/
public class LoginDemo3 {
public static void main(String[] args) throws Exception {
System.out.println("这是用户登录界面!!!");
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
String sql = "select * from user where name = ? and password= ?";
Connection conn = JdbcUtil.getConnection();
PreparedStatement pstm = conn.prepareStatement(sql);
pstm.setString(1,username);
pstm.setString(2,password);
ResultSet resultSet = pstm.executeQuery();
//有结果表示登录成功,没有结果表示登录失败
if(resultSet.next()){ //如果有结果,可以向下移动一行,并返回true
System.out.println("登录成功");
}else{
System.out.println("登录失败: 用户名不存在或密码错误");
}
JdbcUtil.release(conn,pstm,resultSet);
}
}
第八章 连接池
8.1 连接池解决现状的原理
8.1.1 JDBC访问数据库原理
每人每次访问数据库都是创建一个新的连接对象,连接对象不能共享。当用户使用完毕以后,就把连接对象关闭了
8.1.2 连接存在的问题和解决方案
连接对象的使用问题
- 创建连接对象比较耗时
- 一个连接对象只使用一次就被关闭
需要解决两个问题
- 提高获取连接对象的速度
- 提高连接对象的使用率
8.1.3 应用连接池
- 服务器启动的时候,就会预先创建一个连接池,然后再创建一批连接对象放入连接池
- 当用户需要访问数据库的时候,就直接返回一个连接对象给用户使用。当用户使用完毕的时候,就把连接对象放回到连接池,给下一个用户使用
使用连接池的目的:提高访问数据库的效率
8.2 连接池API
- 接口名 (数据源)
javax.sql.DataSource - 实现类
由第三方厂商来实现,我们只需要学习使用这个工具就可以了 - 连接池接口中的方法
connection.close(); // 本来是释放资源,在连接池中被重写为将连接还给连接池 | DataSource接口中的方法 | 描述 | | —- | —- | | Connection getConnection() | 从连接池中得到一个连接对象 |
8.3 连接池应用
8.3.1 常用连接池概述
DataSource本身只是一个接口,没有具体的实现,它的实现由连接池的数据库厂商去实现,我们只需要学习这个工具如何使用即可
常用的连接池实现组件有这些:
- 阿里巴巴-德鲁伊 Druid连接池:Druid是阿里巴巴开源平台上的一个项目
- DBCP(DataBase Connection Pool)数据库连接池,是Apache上的一个Java连接池项目,也是Tomcat使用的连接池组件
- c3p0是一个开源的JDBC连接池,目前使用它的开源项目有Hibernate,Spring等。c3p0有自动回收空闲连接功能
8.3.2 Druid连接池配置
Druid简介
Druid是阿里巴巴开发的号称为监控而生的数据库连接池,在功能、性能、扩展性方面,都超过其他数据库连接池。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。如:一年一度的双十一活动,每年春运的抢火车票
Druid的下载地址:https://github.com/alibaba/druid
Druid连接池使用的jar包如下:
常用的配置参数
参数 | 说明 |
---|---|
url | 数据库连接字符串,jdbc:mysql://localhost:3306/itcast |
username | 数据库用户名 |
password | 数据库密码 |
driverClassName | 数据库驱动全名,com.mysql.jdbc.Driver |
initialSize | 连接池初始连接数 |
maxActive | 连接池最大连接数 |
maxWait | 连接池最长等待时间,单位是毫秒 |
Druid连接池API介绍
Class类中的方法 | 说明 |
---|---|
InputStream getResourceAsStream(String path) | 读取连接池的配置文件为字节流 |
创建一个Properties对象,读取上面的输入流:load(输入流)
DruidDataSourceFactory的方法 | 方法 |
---|---|
public static DataSource createDataSource(Properties properties) | 根据properties属性值创建连接池 |
8.3.3 案例:使用Druid连接池
资料简介
需求
使用druid创建连接池,从连接池中得到连接对象,输出得到的连接对象
步骤
- 导入jar包
- 在src目录下创建一个properties文件,命名为:druid.properties,设置上面的参数
- Java代码
- 加载properties文件的内容到Properties对象中
- 使用工厂类,创建Druid连接池,使用配置文件中的参数
- 从Druid连接池中取出10个连接输出
代码
属性文件druid.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/itcast
username=root
password=root
initialSize=3
maxActive=5
maxWait=2000
Java代码
package com.itheima11.druid;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.itheima07.util.JdbcUtil;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;
/*
TODO: druid使用
1. 环境
1). lib目录导入druid-1.0.0.jar包
2). src目录下放druid.properties
2. 编码
1). static DataSource createDataSource(Properties properties)
*/
public class DruidDemo {
public static void main(String[] args) throws Exception {
//1. 创建druid连接池
//加载druid.properties配置文件的
Properties p = new Properties();
p.load(new FileInputStream("day04-jdbc/src/druid.properties"));
/*
创建一个连接池 : 预先放入一些连接
1). 创建连接:
driverClassName 驱动信息
url : 地址
username : 用户名
password : 密码
2). 初始连接数: initialSize
*/
DataSource ds = DruidDataSourceFactory.createDataSource(p);
//2. 从连接池中获取连接
Connection conn = ds.getConnection();
String sql = "select * from user";
PreparedStatement pstm = conn.prepareStatement(sql);
ResultSet resultSet = pstm.executeQuery();
while(resultSet.next()){
String name = resultSet.getString("name");
System.out.println(name);
}
//3. 用完了要将连接还给连接池
//connection.close(); // 本来是释放资源,在连接池中被重写为将连接还给连接池
JdbcUtil.release(conn,pstm,resultSet);
}
}
效果
public class DruidDemo2 {
public static void main(String[] args) throws Exception {
//1. 创建druid连接池
Properties p = new Properties();
p.load(new FileInputStream("day04-jdbc/src/druid.properties"));
/*
创建一个连接池 : 预先放入一些连接
1). 创建连接:
driverClassName 驱动信息
url : 地址
username : 用户名
password : 密码
2). 初始连接数: initialSize
3). 最大连接数: maxActive
4). 最长等待时间: maxWait
(超过最大连接数的并发,等待时间超过此限制,会抛出异常,中断等待)
*/
DataSource ds = DruidDataSourceFactory.createDataSource(p);
//2. 从连接池中获取连接 (循环模拟并发)
for (int i = 0; i < 6; i++) {
Connection conn = ds.getConnection();
System.out.println(conn);
//将第4次访问的连接还给连接池
if(i == 3){
conn.close();
}
}
//connection.close(); // 本来是释放资源,在连接池中被重写为将连接还给连接池
}
}