JDBC
JDBC 概述
概念
JDBC 是规范定义的接口,实现是由各个数据库厂商来实现。
JDBC 是Java 访问数据库的标准规范,真正怎么操作数据库还需要具体的实现类,实现类也就是数据库驱动,每个数据库厂商根据自己的数据库的通信格式编写自己的数据库驱动,我们只需调用jdbc 接口中的方法即可。数据库是由数据库厂商提供,
使用jdbc 的好处:
- 开发人员如果要开发访问数据库的程序,只需要会调用jdba 的接口定义的方法即可,而不用去关心数据库厂商是如何实现的。
- 使用的是同一套Java 接口,进行少量的修改,就可以访问所有支持jdba 规范的数据库。
使用jdba 开发使用的包
- java.sql:所有与jdbc访问数据库相关的接口和类
- javax.sql :数据库扩展包,提供数据库额外的访问功能:如:连接池技术
- 数据库驱动:由数据库厂商提供,需要去单独下载,是对jdbc的接口的实现
JDBA的核心API
- DriverManager 类:
- 管理和注册数据库驱动
- 管理数据库的链接对象
- Connection 接口:
- 一个连接对象,可用于创建Statement 和 PreparedStatement
- Statement 接口:
- 一个SQL语句对象,用于将SQL语句发送给数据库服务器
- PreparedStatemen 接口:
- 一个SQL语句对象,是Statement 的子接口
- ResultSet 接口:
- 用于封装数据库查询的结果,返回给客户端Java程序
Driver 接口实现类 DriverManager 类
Driver 接口介绍
-
public interface Driver
每个驱动程序类必须实现的接口。这个接口是供数据库厂商来使用的。在开发中,我们不直接访问Driver接口,而是使用他的实现类DriverManager实现类,实现对于驱动的管理 -
不同数据库的驱动类(记忆):
- Oracle: oracle.jdbc.driver.OracleDriver
- Mysql:com.mysql.jdbc.Driver
驱动包的获取:
- Oracle(虚拟机中的路径)
C:\app\BORN\virtual\product\12.2.0\dbhome_1\jdbc\lib
- Mysql:
DriverManager
是Driver接口的实现类,用于管理一组JDBC驱动程序的基本服务
作用:
- 管理和注册驱动
- 常见数据库链接
连接方法:
static Connection | getConnection(String url) 尝试建立与给定数据库URL的连接。 |
---|---|
static Connection | getConnection(String url, Properties info) 尝试建立与给定数据库URL的连接。 |
static Connection | getConnection(String url, String user, String password) 尝试建立与给定数据库URL的连接。 |
数据库连接的4个参数:
- url:数据库链接字符串 不同数据库的写法不同:
- oracle:jdbc:oracle:thin:@数据库服务器的ip:端口:数据库实例名(sid)
- mysql: jdbc:mysql:// 数据库服务器的ip:端口/数据库名称[?参数列表]
- user:链接数据库的用户名
- password:链接数据库的密码
- driverClass 数据库的驱动类:
- Oracle: oracle.jdbc.driver.OracleDriver
- Mysql:com.mysql.jdbc.Driver
数据库链接的URL地址的格式
Jdbc urL 用于标识一个被注册的驱动程序,驱动管理器通过url选择正确的驱动程序,从而建立数据库的链接。
URL标准由三部分组成:
- oracle:jdbc:oracle:thin:@数据库服务器的ip:端口:数据库实例名(sid)
- mysql: jdbc:mysql:// 数据库服务器的ip:端口/数据库名称[?参数列表]
- 格式: jdbc:链接数据库的协议: 子协议
子名称:标识数据库的方法,子名称可以根据不同的子协议变化,目的就是为了定位到需要链接的数据库,为链接提供足够的信息。包括主机名、端口号、数据库标识
获取数据库的链接
- 导入需要链接的数据库驱动包
junit5以及oracle 所用的包
程序:
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class JDBCDemo {
@Test
// 获取数据库的链接
public void getConnection() throws ClassNotFoundException, SQLException {
// 使用反射完成加载和注册驱动(可省略)
Class.forName("oracle.jdbc.driver.OracleDriver");
// 定义链接属性
String url = "jdbc:oracle:thin:@DESKTOP-BUVSNG0:1521:orcl";
String user = "scott";
String password = "tiger";
// 获取链接,可将字符串写到外面
//Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@DESKTOP-BUVSNG0:1521:orcl","scott","tiger");
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
}
}
从jdbc3规范开始,很对数据库都已经实现了可以根据url来自动注册和加载驱动了,意味着:Class.forName(“oracle.jdbc.driver.OracleDriver”); 可以省略
上述的代码采用了硬编码的方式,将url 、user、password都写在代码中 ,修改不方便,为解决该问题,使用属性文件来配置链接属性
使用属性文件来配置链接属性
-
配置文件
配置文件所在位置:
配置文件:
#oracle
oracle.url=jdbc:oracle:thin:@DESKTOP-BUVSNG0:1521:orcl
oracle.user=scott
oracle.password=tiger
oracle.driverClassName=oralce.jdbc.driver.OracleDriver
#mysql
mysql.url = jdbc:mysql://localhost:3306/test
mysql.user = root
mysql.password=root
mysql.driverClassName=com.mysql.jdbc.Driver
@Test
public void getConnectionByProp() throws IOException, SQLException {
//加载配置文件
Properties prop = new Properties();
// 加载文件
InputStream in= JDBCDemo.class.getClassLoader().getResourceAsStream("resources/jdbc.properties");
prop.load(in);
//读取配置文件
String url = prop.getProperty("oracle.url");
String user = prop.getProperty("oracle.user");
String password = prop.getProperty("oracle.password");
// 省略加载和注册驱动
// 获取链接
Connection con = DriverManager.getConnection(url,user,password);
System.out.println(con);
}
注意:
上图两个文件地址要放在同一个src下
好处:
- 实现了代码和数据的分离,如果需要修改配置信息,直接修改配置文件即可
- 如果修改了配置信息,此时我们将不再需要重新编译代码
Connection 接口
Collection 接口是由数据库厂商实现,代表了一个链接对象
与特定数据库的连接(会话)。 执行SQL语句并在连接的上下文中返回结果。
Statement | createStatement() 创建一个 Statement对象,用于将SQL语句发送到数据库。 |
---|
Statement 接口
用于执行静态SQL语句并返回其生成的结果的对象
jdba 访问数据库的步骤
- 注册和加载驱动
- 获取连接(DirverManager)
- Connection 获取 Statement 对象
- 使用 Statement 对象执行SQL语句
- 返回 ResultSet 结果集
- 释放资源
Statement 方法
作用:代表一条SQL语句对象,用于发送sql语句到服务器,执行静态sql并返回其执行后的结果对象
ResultSet | executeQuery(String sql) 执行给定的SQL语句,该语句返回单个 ResultSet对象。 |
---|---|
int | executeUpdate(String sql) 执行给定的SQL语句,这可能是 INSERT , UPDATE ,或 DELETE语句,或者不返回任何内容,如SQL DDL语句的SQL语句。 |
执行 DDL 操作
需求:使用 jdbc 在 Oracle 中创建一张学生表
字段名 | 类型 | 是否为null |
---|---|---|
Id | Number(10) | × |
Name | Varchar2(20) | √ |
Gender | Number(1) | √ |
Birthday | Date | √ |
@Test
public void createTableStudent() throws SQLException {
//省略利用反射完成加载和注册驱动
// 1.创建连接
String url = "jdbc:oracle:thin:@DESKTOP-BUVSNG0:1521:orcl";
String user = "scott";
String password = "tiger";
Connection con = DriverManager.getConnection(url,user,password);
// 2.通过Connection 获取 Statement
Statement st = con.createStatement();
// 3.执行 SQL
String sql = "create table student(id number(10),name varchar2(20),gender number(1),birthday date,constraint stu_id_pk PRIMARY KEY(ID))";
int i = st.executeUpdate(sql);
System.out.println(i);
// 4.处理结果
System.out.println("创建表成功");
// 5.释放资源
st.close();
con.close();
}
jdba 操作中的异常的处理:
@Test
public void createTableStudent1(){
//省略利用反射完成加载和注册驱动
// 1.创建连接
String url = "jdbc:oracle:thin:@DESKTOP-BUVSNG0:1521:orcl";
String user = "scott";
String password = "tiger";
Connection con = null;
// 将st定义在外面
Statement st = null;
try {
con = DriverManager.getConnection(url,user,password);
// 2.通过Connection 获取 Statement
st = con.createStatement();
// 3.执行 SQL
String sql = "create table student(id number(10),name varchar2(20),gender number(1),birthday date,constraint stu_id_pk PRIMARY KEY(ID))";
int i = st.executeUpdate(sql);
System.out.println(i);
// 4.处理结果
System.out.println("创建表成功");
} catch (SQLException e) {
e.printStackTrace();
}finally {
// 5.释放资源
try {
st.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
con.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
执行 DML 操作
需求:给学生表添加5条记录
// 插入数据
@Test
public void insertTest() throws SQLException {
//1.注册和加载驱动(省略),创建连接对象
String url = "jdbc:oracle:thin:@DESKTOP-BUVSNG0:1521:orcl";
String user = "scott";
String password = "tiger";
Connection con = DriverManager.getConnection(url,user,password);
//2.获取Statement对象
Statement st = con.createStatement();
//3.执行sql
int count = 0;
count += st.executeUpdate("insert into student values (1,'张三',to_date('1999-12-12','yyyy-mm-dd'))");
count += st.executeUpdate("insert into student values (2,'李四',to_date('1998-04-02','yyyy-mm-dd'))");
count += st.executeUpdate("insert into student values (3,'赵六',to_date('1998-06-06','yyyy-mm-dd'))");
count += st.executeUpdate("insert into student values (4,'王五',to_date('1996-03-01','yyyy-mm-dd'))");
count += st.executeUpdate("insert into student values (5,'孙琪',to_date('1992-02-23','yyyy-mm-dd'))");
//4.处理执行结果,收影响的行数
System.out.println("插入了" + count + "条记录");
//5.释放资源
st.close();
con.close();
}
查看表中数据:
执行 DQL 操作
ResultSet executeQuery(String sql)throws SQLException
执行给定的SQL语句,返回单个ResultSet对象。
ResultSet 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成。
next() 将光标从当前位置向前移动一行
long | getLong(int columnIndex) 这个检索的当前行中指定列的值 ResultSet对象为 long的Java编程语言。 |
---|---|
long | getLong(String columnLabel) 这个检索的当前行中指定列的值 ResultSet对象为 long的Java编程语言。 |
参数可以为列的索引(从1开始)或列的名称
常用数据类型转化表:
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 同时有日期和时间 |
需求:查询所有学生的记录
步骤:
- 获取连接
- 得到语句对象
- 执行sql得到ResultSet对象
- 遍历结果集,获取记录,一条记录对应Java中的一个对象
- 释放资源
//查询所有学生的信息
@Test
public void selectDate() throws SQLException {
//1.注册和加载驱动(省略),创建连接对象
String url = "jdbc:oracle:thin:@DESKTOP-BUVSNG0:1521:orcl";
String user = "scott";
String password = "tiger";
Connection con = DriverManager.getConnection(url,user,password);
//2.获取Statement对象
Statement st = con.createStatement();
//3.执行sql
String sql = "select * from student";
ResultSet rs = st.executeQuery(sql);
//4.处理结果集
while (rs.next()){
int id = rs.getInt("id");
String name = rs.getString(2);
Date birthday = rs.getDate("birthday");
//输出结果
System.out.println(id + "-" + name + "-" + birthday);
}
//释放资源
rs.close();
st.close();
con.close();
}
运行结果:
实现代码的重构
将前面代码中重复的代码提炼出来,封装成一个工具类。
工具类中:
- 将连接时所使用的字符串定义为常量
- 获取连接 getCommection
- 释放资源
import java.sql.*;
public class JDBCUtils {
//定于连接数据库的属性变量
private static final String USER = "scott";
private static final String PASSWORD = "tiger";
private static final String URL = "jdbc:oracle:thin:@DESKTOP-BUVSNG0:1521:orcl";
private static final String DRIVER = "oracle.jdba.driver.OracleDriver";
//完成驱动的注册和加载(静态变量使用静态代码块)
static {
try {
Class.forName(DRIVER);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//获取连接
public static Connection getConnection(){
Connection conn = null;
try {
conn = DriverManager.getConnection(URL,USER,PASSWORD);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return conn;
}
//释放资源
public static void close(ResultSet resultSet, Statement statement,Connection connection){
if (resultSet != null){
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (statement != null){
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection != null){
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
//释放资源
public static void close(Statement statement,Connection connection){
if (statement != null){
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection != null){
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
案例:用户登陆
准备工作:
- 一张用户表
- 向其中插入几条记录
create table users(
id number(10),
name varchar2(20),
password varchar2(50),
constraint user_id_pk PRIMARY KEY (id)
)
insert into users values(1,'jack','123');
insert into users values(2,'admin','888888');
--登录成功
select * from users where name='admin' and password='888888';
--登录失败
select * from users where name='admin' and password='88888';
实现步骤:
- 通过用户从控制台输入用户名和密码,来查询数据库
- 实现登陆查询
- 通过工具集获取连接
- 创建语句对象,执行sql
- 处理结果集,通过判断是否与结果相同来判定登陆成功与否
- 释放资源
代码实现:
在sql中执行以下代码:
create table users(
id number(10),
name varchar2(20),
password varchar2(50),
constraint user_id_pk PRIMARY KEY (id)
)
insert into users values(1,'jack','123');
insert into users values(2,'admin','888888');
--登录成功
select * from users where name='admin' and password='888888';
--登录失败
select * from users where name='admin' and password='88888';
在IDEA中运行:
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
public class LoginDemo {
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();
//连接已在JDBCUtils中完成
//调用登陆方法,完成用户名和密码的校验
login(name,password);
}
//实现登陆校验
public static void login(String name,String password) throws SQLException {
//获取连接
Connection conn = JDBCUtils.getConnection();
//得到执行对象
Statement st = conn.createStatement();
//执行sql
String sql = "select * from users where name='"+name +"' and password='"+ password+"'";
System.out.println(sql);
//ResultSet 用于封装数据库查询的结果,返回给客户端Java程序
ResultSet rs = st.executeQuery(sql);
//处理结果集
if (rs.next()){
System.out.println("登陆成功");
}else {
System.out.println("登陆失败");
}
//释放资源
JDBCUtils.close(rs,st,conn);
}
}
运行结果:
SQL 注入
此时我们发现用户名与密码都不相同,但是可以登陆成功。
select * from users where name=‘tom’ and password=‘a’ or ‘1’=‘1’
相当于SELECT * FROM users WHERE 1=1
原因在于sql语句使用了字符串拼接的方式。
解决方案:将输入的用户名和密码,不要使用简单的字符串拼接
PreparedStatement 接口
是 statement 对象的子接口,作用与 statement 相同,都是将sql 语句发送给数据库服务器执行,并接收执行的结果。
与statement 的区别:
- statement 所执行的 sql 是静态 sql ,这种 sql 是使用字符串拼接方式拼接出来的
- PreparedStatement 在发送之前,会将 sql 语句进行预编译,将 sql 语句所需要的参数绑定给 PreparedStatement 的对象。不用拼接 sql ,此时就可减少sql注入的风险,同时,由于sql语句被预编译过,后边在执行的时候,只是为PreparedStatement对象绑定参数值,提高效率。
使用PreparedStatement对象的步骤
- 获取连接
- 获取PreparedStatement对象
- 编写sql
静态sql:Select * from users where name=’admin’ and password=’1230’;
PreparedStatement:SELECT * FROM users where name=? and password=?;
(?表示占位符) - 绑定参数
- 执行 sql
- 释放资源
PreparedStatement | prepareStatement(String sql) 创建一个 PreparedStatement对象,用于将参数化的SQL语句发送到数据库 |
---|
绑定参数的方法:
void | setBlob(int parameterIndex, InputStream inputStream) 将指定的参数设置为 InputStream对象。 |
---|---|
void | setBlob(int parameterIndex, InputStream inputStream, long length) 将指定的参数设置为 InputStream对象。 |
void | setBoolean(int parameterIndex, boolean x) 将指定的参数设置为给定的Java boolean值。 |
void | setByte(int parameterIndex, byte x) 将指定的参数设置为给定的Java byte值。 |
void | setBytes(int parameterIndex, byte[] x) 将指定的参数设置为给定的Java字节数组。 |
void | setCharacterStream(int parameterIndex, Reader reader) 将指定的参数设置为给定的 Reader对象。 |
void | setCharacterStream(int parameterIndex, Reader reader, int length) 将指定的参数设置为给定的 Reader对象,这是给定的长度的字符数。 |
void | setCharacterStream(int parameterIndex, Reader reader, long length) 将指定的参数设置为给定的 Reader对象,这是给定的长度的字符数。 |
void | setClob(int parameterIndex, Clob x) 将指定的参数设置为给定的 java.sql.Clob对象。 |
void | setClob(int parameterIndex, Reader reader) 将指定的参数设置为 Reader对象。 |
void | setClob(int parameterIndex, Reader reader, long length) 将指定的参数设置为 Reader对象。 |
void | setDate(int parameterIndex, Date x) 使用运行应用程序的虚拟机的默认时区将指定的 java.sql.Date设置为给定的java.sql.Date值。 |
void | setDate(int parameterIndex, Date x, Calendar cal) 使用给定的 Calendar对象将指定的 Calendar设置为给定的 java.sql.Date值。 |
void | setDouble(int parameterIndex, double x) 将指定的参数设置为给定的Java double值。 |
使用PreparedStatement改造登录案例:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;
//使用PreparedStatement改造登录案例
public class LoginDemoByPreparedStatement {
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);
}
//使用PreparedStatement实现登陆校验
public static void login(String name,String password) throws SQLException {
//获取连接
Connection conn = JDBCUtils.getConnection();
//执行sql
String sql = "select * from users where name=? and password=?";
//得到执行对象PreparedStatement,预编译 sql
PreparedStatement ps = conn.prepareStatement(sql);
System.out.println(ps);
//绑定参数
ps.setString(1,name);
ps.setString(2,password);
//执行sql
ResultSet rs = ps.executeQuery();
//处理结果集
if (rs.next()){
System.out.println("登陆成功");
}else {
System.out.println("登陆失败");
}
//释放资源
JDBCUtils.close(rs,ps,conn);
}
}
表和类的关系
一张表的结构类似于类,表中的一条记录就类似于一个对象。
案例:使用PreparedStatement查询一条student记录 ,并将结果封装成一个student对象
import org.junit.jupiter.api.Test;
import java.sql.*;
class PreparedStatementDemo {
@Test
public void getStudentObj() throws SQLException {
//获取连接
Connection conn = JDBCUtils.getConnection();
String sql = "select * from student where id=?";
//获取语句对象
PreparedStatement ps = conn.prepareStatement(sql);
//绑定参数
ps.setInt(1,5);
//执行sql
ResultSet rs = ps.executeQuery();
//处理结果
while (rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
Date birthday = rs.getDate("birthday");
//将一条记录封装成一个对象
Student stu = new Student(id,name,birthday);
System.out.println(stu);
}
JDBCUtils.close(rs,ps,conn);
}
}
运行结果:
需求:查询所有的学生对象,将其封装成一个集合
import org.junit.jupiter.api.Test;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
class PreparedStatementDemo {
@Test
public void getStudentObj1() throws SQLException {
//获取连接
Connection conn = JDBCUtils.getConnection();
String sql = "select * from student";
//获取语句对象
PreparedStatement ps = conn.prepareStatement(sql);
//执行sql
ResultSet rs = ps.executeQuery();
//声明集合
List<Student> stuList = new ArrayList<>();
//处理结果
while (rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
Date birthday = rs.getDate("birthday");
//将一条记录封装成一个对象
Student stu = new Student(id,name,birthday);
stuList.add(stu);
}
//遍历集合
for (Student s : stuList){
System.out.println(s);
}
JDBCUtils.close(rs,ps,conn);
}
}
运行结果:
使用PreparedStatement完成DML操作
需求:插入数据实现主键自增:
新建序列
@Test
public void insertTest() throws SQLException {
Connection conn = JDBCUtils.getConnection();
//查询序列,获取自定递增的id的下一个值
PreparedStatement ps1 = conn.prepareStatement("select stu_seq.nextval from dual");
ResultSet rs1 = ps1.executeQuery();
int id = 0;
while (rs1.next()){
id = rs1.getInt(1);
}
// 创建数据插入
PreparedStatement ps2 = conn.prepareStatement("insert into student values (?,?,?)");
ps2.setInt(1,6);
ps2.setString(2,"白龙马");
ps2.setDate(3,java.sql.Date.valueOf("1999-11-11"));
//执行sql
int row = ps2.executeUpdate();
System.out.println("插入了"+ row +"行记录");
JDBCUtils.close(rs1,ps2,conn);
}
在sql 中可以看到已插入数据
JDBC 对于事务的处理
- 数据准备:
CREATE TABLE account(
id number(10),
name varchar2(20),
balance number(10,2)
)
-- 插入数据
insert into account values(1,'Mike',1000.00);
insert into account values(2,'Sanar',1000.00);
-- 查询
select * from account;
- 与Connection 相关的方法:
返回值类型 | 方法 |
---|---|
void | commit() 使自上次提交/回滚以来所做的所有更改都将永久性,并释放此 Connection对象当前持有的任何数据库 |
void | rollback() 撤消在当前事务中所做的所有更改,并释放此 Connection对象当前持有的任何数据库锁。 |
void | setAutoCommit(boolean autoCommit) 将此连接的自动提交模式设置为给定状态。 |
-
操作步骤
- 获取连接
- 开启事务
- 获取PreparedStatement 对象
- 执行 sql
- 提交事务(运行正常)
- 回滚事务(出现异常)
- 释放资源
-
程序
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Transfer {
@Test
public void testTx() {
//1 获取连接
Connection conn = JDBCUtils.getConnection();
PreparedStatement ps = null;
try {
//2 开启事务,将事务的提交设置为手动模式(不设置会出错)
conn.setAutoCommit(false);
//3 获取PreparedStatement对象
String sql1 = "update account set balance = balance - ? where id=?";
ps = conn.prepareStatement(sql1);
//4 绑定参数
ps.setDouble(1, 200);
ps.setInt(2, 1);
ps.executeUpdate();
//System.out.println(1/0);
String sql2 = "update account set balance = balance + ? where id=?";
ps = conn.prepareStatement(sql2);
ps.setDouble(1, 200);
ps.setInt(2, 2);
ps.executeUpdate();
//5 正常情况下 提交事务
conn.commit();
System.out.println("转账成功");
} catch (Exception e) {
e.printStackTrace();
try {
//6 出现异常 回滚事务
conn.rollback();
} catch (SQLException se) {
se.printStackTrace();
}
System.out.println("转账失败");
} finally {
//7 释放资源
JDBCUtils.close(ps, conn);
}
}
}
运行前:
第一次运行:
第二次运行:
JDBC 高级
数据库连接池技术
解决传统开发中数据库频繁连接释放的问题。
- 数据库连接池的思想
就是为数据库建立一个“缓冲池”,预先在缓冲池中放入一定数量的连接,当需要建立数据库连接的时候,从缓冲池汇总去取出一个来使用,当使用完毕之后,再将连接归还到缓冲池。
数据库连接池的作用:
- 负责数据库连接的分配、管理、释放等工作。它允许程序重复使用一个现有的连接,而不是重新建立一个。
数据库连接池在初始化的时候就创建一定数量的连接放到连接池中,这些连接是由连接池的最小连接数来决定,无论连接是否被使用,连接都将保证在连接池中存在最小数量的连接。当连接数不满足需求的时候,它会再创建一定数量的连接,创建的连接上限将由最大连接数来决定。当程序请求的连接数超过了连接池所能提供的最大连接数的时候,此时这些请求将被加入到排队队列中等待空闲连接。
- 数据库连接池的优点:
- 资源的重用
- 更快的系统响应
- 新的资源分配手段
- 统一的连接管理,避免了数据库连接的暴露
- 常见的数据库连接池
JDBC连接池使用的是javax.sql Interface DataSource 。
接口的常见实现:- DBCP 是Apache提供的数据库连接池。
- C3P0 是一个开源组织提供的 一个数据库连接池。速度相对DBCP要慢一些。Hibernate的默认使用技术。
- Druid 是由阿里提供的一个数据库连接池,集DBCP和C3P0的优点与一身。
DataSource通常被称为数据源。他包含了数据库连接池的创建和管理两部分工作。
使用中,我们将使用DataSource来取代DriverManager获取数据库连接Connection 从而提供数据库的访问速度。
当使用数据库连接池获取到的连接,在调用Connection的close方法的时候 ,不再是直接将连接关闭,而是将连接归还到连接池。
连接池的使用
C3P0 的使用步骤:
-
导包
-
配置连接池
-
创建数据源对象
-
获取连接
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.jupiter.api.Test;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;
public class c3p0Demo {
@Test
public void getConnectionByC3p0() throws PropertyVetoException, SQLException {
// 获取数据源
ComboPooledDataSource ds = new ComboPooledDataSource();
// 设置连接属性
ds.setDriverClass("oracle.jdbc.driver.OracleDriver");
ds.setJdbcUrl("jdbc:oracle:thin:@DESKTOP-BUVSNG0:1521:orcl");
ds.setUser("scott");
ds.setPassword("tiger");
// 获取连接
for(int i = 0 ; i < 5;i++){
Connection conn = ds.getConnection();
System.out.println(conn);
}
}
}
- 使用C3P0的配置文件来获取连接
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.jupiter.api.Test;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;
public class c3p0Demo {
//使用C3P0的配置文件来获取连接
@Test
public void getConnection2() throws SQLException {
ComboPooledDataSource ds = new ComboPooledDataSource();
for(int i = 0 ; i < 5;i++){
Connection conn = ds.getConnection();
System.out.println(conn);
}
}
}
配置文件:默认的文件名c3p0-config.xml(放在当前路径下的src下)
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<!-- 超时时间 毫秒-->
<property name="checkoutTimeout">30000</property>
<!-- 初始化连接数-->
<property name="initialPoolSize">10</property>
<!-- 最大空闲数-->
<property name="maxIdleTime">30</property>
<!-- 最大连接数-->
<property name="maxPoolSize">100</property>
<!-- 最小连接数-->
<property name="minPoolSize">10</property>
<!-- 每个连接随支持 最大的statement对象-->
<property name="maxStatements">200</property>
<!-- 当链接数不足时,每次向数据库申请的连接数-->
<property name="acquireIncrement">5</property>
<!--链接属性的配置 链接属性的name值是固定的 必须参考官方文档来写-->
<property name="driverClass">oracle.jdbc.driver.OracleDriver</property>
<property name="jdbcUrl">jdbc:oracle:thin:@DESKTOP-BUVSNG0:1521:orcl</property>
<property name="user">scott</property>
<property name="password">tiger</property>
</default-config>
</c3p0-config>
DBCP
导包:
import org.apache.commons.dbcp2.BasicDataSource;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.SQLException;
public class DbcpDemo {
@Test
public void getConnectionByDBCP() throws SQLException {
//创建数据源
BasicDataSource ds = new BasicDataSource();
//设置连接属性
ds.setDriverClassName("oracle.jdbc.driver.OracleDriver");
ds.setUrl("jdbc:oracle:thin:@DESKTOP-BUVSNG0:1521:orcl");
ds.setUsername("scott");
ds.setPassword("tiger");
//配置连接池
ds.setInitialSize(5);
for(int i = 0 ; i < 5;i++){
Connection conn = ds.getConnection();
System.out.println(conn);
}
}
}
使用配置文件:
配置文件:dbcp.properties
driverClassName= oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@DESKTOP-BUVSNG0:1521:orcl
username=scott
password=tiger
initialSize=10
maxTotal=20
maxIdle=10
minIdle=5
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.junit.jupiter.api.Test;
import javax.xml.crypto.Data;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class DataSourceDemo {
@Test
public void getConnectionByDBCP2() throws Exception {
Properties ps = new Properties();
ps.load(DataSourceDemo.class.getClassLoader().getResourceAsStream("dbcp.properties"));
BasicDataSource bds = BasicDataSourceFactory.createDataSource(ps);
for(int i = 0 ; i < 5;i++){
Connection conn = bds.getConnection();
System.out.println(conn);
}
}
}
Druid
国内最受欢迎的数据库连接池。
- 导包
配置文件:druid.properties
url=jdbc:oracle:thin:@DESKTOP-BUVSNG0:1521:orcl
driverClassName=oracle.jdbc.driver.OracleDriver
username=scott
password=tiger
initialSize=10
maxActive=20
maxWait=1000
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.jupiter.api.Test;
import javax.sql.DataSource;
import java.sql.Connection;
import java.util.Properties;
public class DruidDemo {
@Test
public void getConnectionByDruid() throws Exception {
Properties ps = new Properties();
ps.load(DataSourceDemo.class.getClassLoader().getResourceAsStream("druid.properties"));
DataSource ds = DruidDataSourceFactory.createDataSource(ps);
for(int i = 0 ; i < 5;i++){
Connection conn = ds.getConnection();
System.out.println(conn);
}
}
}
JDBC 工具类的重构
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.jupiter.api.Test;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
//重构jdbc的工具类,使用数据库连接池技术
public class JDBCUtils2 {
//数据源
private static DataSource ds;
//初始化数据源,使用druid 数据源
static {
Properties ps = new Properties();
try {
ps.load(JDBCUtils2.class.getClassLoader().getResourceAsStream("druid.properties"));
ds = DruidDataSourceFactory.createDataSource(ps);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接
public static Connection getConnection(){
Connection conn = null;
try {
conn = ds.getConnection();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return conn;
}
//释放资源
public static void close(ResultSet resultSet, Statement statement,Connection conn){
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();
}
}
}
//释放资源
public static void close(Statement statement,Connection conn){
if (statement != null){
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (conn != null){
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
//获取数据源的方法
public static DataSource getDataSource(){
return ds;
}
}
测试:
@Test
public void jdbcUtilsTest(){
for(int i = 0 ; i < 5;i++){
Connection conn = JDBCUtils2.getConnection();
System.out.println(conn);
}
}
JDBC 工具类库–Apache Common DBUtils
特点:
- Small - you should be able to understand the whole package in a short amount of time.
小-你应该能够在短时间内理解整个程序包。 - Transparent - DbUtils doesn’t do any magic behind the scenes. You give it a query, it executes it and cleans up for you.
透明-DbUtils不会在幕后发挥任何魔力。你给它一个查询,它执行它并为你清理。 - Fast - You don’t need to create a million temporary objects to work with DbUtils.
快速-使用DbUtils不需要创建非常多的临时对象。
DBUtils是一个开源JDBC工具类库。对JDBC的操作进行了封装。简化了JDBC编码。
常见API:
- QueryRunner 负责执行查询,或者更新
- ResultSetHandler 负责结果集的处理
- DbUtils 操作的工具类
DbUtils
提供了关闭数据库连接,装载驱动等工作。
QueryRunner
封装了SQL查询、更新等相关的数据库操作,简化了操作的步骤 提供ResultSetHanddler 来处理结果集。
构造方法:
ResultSetHandler
实现类:
DBUtils 的使用
导包:
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.SQLException;
public class DBUtilsDemo {
//测试插入数据
@Test
public void insertTest() throws SQLException {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
String sql = "insert into student(id,name,birthday) values(?,?,?)";
Connection conn = JDBCUtils2.getConnection();
int row = qr.update(conn,sql,8,"张三",java.sql.Date.valueOf("2021-01-01"));
System.out.println("插入了"+row+"条记录");
DbUtils.close(conn);
}
// 测试更新
@Test
public void updateTest() throws SQLException {
//创建QuertRunner对象
QueryRunner qr = new QueryRunner();
String sql = "update student set name=? where id=?";
Connection conn = JDBCUtils2.getConnection();
int row = qr.update(conn,sql,"李四",8);
System.out.println("更新了"+row+"条记录");
DbUtils.close(conn);
}
//测试删除
@Test
public void deleteTest() throws SQLException {
//创建QuertRunner对象
QueryRunner qr = new QueryRunner();
String sql = "delete from student where id=?";
Connection conn = JDBCUtils2.getConnection();
int row = qr.update(conn,sql,8);
System.out.println("删除了"+row+"条记录");
DbUtils.close(conn);
}
}
DBUtils 执行查询
import cn.lanqiao.JDBCUtils2;
import cn.lanqiao.Student;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.junit.jupiter.api.Test;
import javax.sql.DataSource;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class DBUtilsDemo2 {
//查询一个对象
@Test
public void selectTest() throws SQLException {
//获取数据源
DataSource ds = JDBCUtils2.getDataSource();
//利用数据源构建QueryRunner对象
QueryRunner qr = new QueryRunner(ds);
String sql = "select * from student where id=?";
//BeanHandler来处理结果集,可直接得到想要的对象,要求使用时列名要与javabean的属性名一致
Student stu = qr.query(sql,new BeanHandler<>(Student.class),2);
System.out.println(stu);
}
//查询一个对象,列名与属性名不一致,可以使用查询时的字段的别名来映射javabeadde 的属性
@Test
public void selectObjTest2() throws SQLException {
//获取数据源
DataSource ds = JDBCUtils2.getDataSource();
//利用数据源构建QueryRunner对象
QueryRunner qr = new QueryRunner(ds);
String sql = "select id,name,birthday from student where id=?";
//BeanHandler来处理结果集,可直接得到想要的对象,要求使用时列名要与javabean的属性名一致
Student stu = qr.query(sql,new BeanHandler<>(Student.class),2);
System.out.println(stu);
}
//查询多条记录,将查询到的结果封装成一个集合
@Test
public void selectListTest() throws SQLException {
//获取数据源
DataSource ds = JDBCUtils2.getDataSource();
//利用数据源构建QueryRunner对象
QueryRunner qr = new QueryRunner(ds);
String sql = "select id,name stuName,birthday from student";
//BeanHandler来处理结果集,可直接得到想要的对象,要求使用时列名要与javabean的属性名一致
List<Student> stuList = qr.query(sql,new BeanListHandler<>(Student.class));
System.out.println(stuList);
}
//自定义结果集处理器
@Test
public void selectListTest2() throws SQLException {
//获取数据源
DataSource ds = JDBCUtils2.getDataSource();
//利用数据源构建QueryRunner对象
QueryRunner qr = new QueryRunner(ds);
String sql = "select id,name,birthday from student";
List<Student> stuList = qr.query(sql, new ResultSetHandler<List<Student>>() {
@Override
public List<Student> handle(ResultSet resultSet) throws SQLException {
//创建集合
List<Student> studentList = new ArrayList<>();
while (resultSet.next()){
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
Date birthday = resultSet.getDate("birthday");
Student student = new Student(id,name,birthday);
//将student类型的数据存储到集合中
studentList.add(student);
}
return studentList;
}
});
System.out.println(stuList);
}
//查询单个值,记录数,最大值,最小值,平均值,和
@Test
public void selectValueTest() throws SQLException {
//获取数据源
DataSource ds = JDBCUtils2.getDataSource();
//利用数据源构建QueryRunner对象
QueryRunner qr = new QueryRunner(ds);
String countSql = "select count(id) from student";
//BigDecimal API 提供的用来精确计算的类
BigDecimal count = qr.query(countSql,new ScalarHandler<BigDecimal>());
System.out.println(count.intValue());
//获取年龄最小的学生
String ageSql = "select max(birthday) from student";
Date birthday = qr.query(ageSql,new ScalarHandler<Date>());
System.out.println(birthday);
//获取id最小值
String idMin = "select min(id) from student";
BigDecimal id = qr.query(idMin,new ScalarHandler<BigDecimal>());
System.out.println(id);
}
}
- 事务的处理:
//事务处理
@Test
public void txTest(){
//建立连接
Connection conn = JDBCUtils2.getConnection();
QueryRunner qr = new QueryRunner();
try {
conn.setAutoCommit(false);
String sql1 = "update account set balance-? where id=?";
qr.update(conn,sql1,100,1);
System.out.println(1/0);
String sql2 = "update account set balance+? where id=?";
qr.update(conn,sql2,100,2);
//提交事务
conn.commit();
System.out.println("转账成功");
} catch (Exception throwables) {
throwables.printStackTrace();
try {
//回滚事务
DbUtils.rollbackAndClose(conn);
} catch (SQLException e) {
e.printStackTrace();
}
System.out.println("转账失败");
}finally {
//释放资源
try {
DbUtils.close(conn);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
项目中数据访问层的处理
一般都将数据访问层定义为 DAO
DAO :Date Access Object 访问数据信息的类和接口,包括了对数据的 CRUD(Create、Retrivel、Update、Delete),而不包含任何业务相关的信息。
作用:为了实现功能的模块化,更有利于代码的维护和升级。
Dao 接口:
import cn.lanqiao.Student;
import java.util.List;
//Dao接口
public interface IStudentDao {
void insert(Student student);
void update(Student student);
void delete(int id);
Student select(int id);
List<Student> selectAll();
List<Student> pageList(int currentPage,int pages);
}
BaseDao:
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class BaseDao {
//增删改的封装
public int update(String sql, Object ... objs){
//建立连接
DataSource ds = JDBCUtils2.getDataSource();
QueryRunner qr = new QueryRunner(ds);
int row = 0;
try {
row = qr.update(sql, objs);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return row;
}
//查询一个对象
public <T> T getObject(String sql,Class<? extends T> clazz,Object ... objs){
DataSource ds = JDBCUtils2.getDataSource();
QueryRunner qr = new QueryRunner(ds);
T t = null;
try {
t = qr.query(sql,new BeanHandler<>(clazz),objs);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return t;
}
// 查询结果为一个集合 支持分页查询
public <T> List<T> getObjectList(String sql , Class<? extends T> clazz, Object... objs){
DataSource ds = JDBCUtils2.getDataSource();
QueryRunner qr = new QueryRunner(ds);
List<T> list = new ArrayList<>();
try {
list = qr.query(sql,new BeanListHandler<T>(clazz),objs);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return list;
}
}
Dao实现:
import cn.lanqiao.Student;
import java.util.List;
//Dao实现
public class StudentDaoImpl extends BaseDao implements IStudentDao{
@Override
public void insert(Student student) {
String sql = "insert into";
update(sql,student);
}
@Override
public void update(Student student) {
String sql = "";
update(sql,student);
}
@Override
public void delete(int id) {
String sql = "";
update(sql,id);
}
@Override
public Student select(int id) {
String sql = "";
return getObject(sql,Student.class,id);
}
@Override
public List<Student> selectAll() {
String sql ="";
return getObjectList(sql,Student.class) ;
}
@Override
public List<Student> pageList(int currentPage, int pages) {
String sql = "select * from (select rownum r, student.* from student ) temp where temp.r >? and temp.r <= ?";
List<Student> stuList = getObjectList(sql,Student.class,(currentPage -1 ) * pages,currentPage * pages);
return stuList;
}
}
Dao测试:
import cn.lanqiao.Student;
import org.junit.jupiter.api.Test;
import java.util.List;
//测试
public class DaoTest {
@Test
public void pageListTest(){
IStudentDao dao = new StudentDaoImpl();
List<Student> list = dao.pageList(1,2);
System.out.println(list);
}
}