文章目录
JDBCfont_1">1.JDBC基本概念
JDBC(Java DataBase Connectivity),即Java数据库连接,也就是用Java语言操作数据库。
JDBC本质上是官方(Sun公司)定义的一套操作所有关系型数据库的规则,即接口(而非实现)。各个数据库厂商去实现这套接口,提供数据库驱动jar包。程序员就可以使用这些接口编程,真正执行的代码是驱动jar包中的实现类。
综上,JDBC与数据库驱动之间为 接口和实现的关系。
JDBCfont_5">2.JDBC编程步骤
JDBCfont_6">2.1 JDBC编程步骤总述
(1)导入驱动jar包;
(2)注册驱动;
(3)获取JDBC和数据库之间的Connection对象;
(4)定义sql(尽量使用 sql参数用?作为占位符);
(5)获取执行sql语句的对象PrepareStatement;
(6)给sql中的占位符?赋值;
(7)执行sql,接收返回结果(或结果集ResultSet);
(8)处理结果;
(9)释放资源。
JDBCfont_16">2.2 JDBC编程详述
(1)导入驱动jar包
① 复制相应驱动jar包到项目的libs目录下;(libs目录是自己在工程中新建的目录,为了方便管理 jar包,当然也可以直接复制进项目目录中。)
② libs目录右键 --> 选择 Add As Library 选项。(将jar包导入到项目的工作空间中)
(2)注册驱动
- 注册驱动本质:告诉当前程序应该使用哪一个数据库驱动jar包。
- 注册驱动方法:通过初始化驱动类com.mysql.cj.jdbc.Driver。(8.0以下jar包版本驱动类为 com.mysql.jdbc.Driver)
/**
* jar包版本为8.0及以上
* Class.forName需要捕获异常ClassNotFoundException
*/
try{
Class.forName("com.mysql.cj.jdbc.Driver");
}catch (ClassNotFoundException e){
e.printStackTrace();
}
/**
* jar包版本为8.0以下
* Class.forName需要捕获异常ClassNotFoundException
*/
try{
Class.forName("com.mysql.jdbc.Driver");
}catch (ClassNotFoundException e){
e.printStackTrace();
}
- 分析
① 问题:为什么可以通过类加载的方式实现注册驱动?
② 分析:由com.mysql.cj.jdbc.Driver类源码可知,在Driver类中定义了静态代码块,在代码块中通过registerDriver函数实现了驱动注册。而Java中静态代码块在类加载时就运行了,因此可以通过Class.forName将Driver类加载到JVM的方式,完成驱动注册。
com.mysql.cj.jdbc.Driver类
package com.mysql.cj.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());//驱动注册
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
(3)获取JDBC和数据库之间的Connection对象
- 方法
/**
url基本格式:jdbc:mysql://IP地址(域名):端口号/数据库名称
user:用户名
password:密码
*/
static Connection getConnection(String url, String user, String password);
- 示例
try{
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/ecargdb3?serverTimezone=UTC&useSSL=false","username","password");
}catch (SQLException e) {
e.printStackTrace();
}
- 注意:
url除了要指定数据库的IP地址、端口号、要操作的表名外,还要设置时区serverTimeZone=UTC和useSSL=false,否则可能出现连接报错。
(4)获取执行SQL语句的对象Statement / PreparedStatement
- Statement接口(较少使用)
Statement中使用的sql字符串主要是拼接方式,此方式容易产生sql注入问题。在实际应用中,sql字符串主要是用?占位符的方式定义,因此主要是使用PreparedStatement接口。
//定义sql语句
String sql = "insert into account values('wangwu',3000);
//获取Statement对象
Statement stmt = conn.createStatement();
//执行sql
stmt.executeUpdate(sql);
- PreparedStatement接口(常用)
使用PreparedStatement接口方式执行sql与Statement基本一致,区别主要在于定义的sql的方式上。
//定义sql语句 (username和
String sql = "insert into account values(?,?);
//获取PreparedStatement对象
PreparedStatement pstmt = conn.prepareStatement(sql);
//给sql中的?占位符赋值
pstmt.setString(1,"wangwu");
pstmt.setDouble(2,3000);
//执行sql
pstmt.executeUpdate();
(5)给sql中?占位符赋值
- 获取到PreparedStatement对象后,需要定义的sql语句中的占位符赋值。主要使用PreparedStatement下的方法:setXxx(参数1,参数2)。
参数1:?占位符的位置编号,从1开始。
参数2:?占位符的值。 - 例如,以下占位符赋值方法:
void setDouble(int parameterIndex, double x);
void setInt(int parameterIndex, int x);
void setString(int paramterIndex, String x);
void setDate(int paramterIndex, Date x);
(6)执行sql,返回执行结果
- 通过 Statement 接口执行sql语句方法
① boolean execute(String sql):可以执行任意sql
② int executeUpdate(String sql):可以执行DML语句(insert、update、delete),DDL语句。返回值为执行sql后的影响行数。
③ ResultSet executeQuery(String sql):可以执行DQL语句(select)。返回值为结果集对象。 - 通过 PreparedStatement 接口执行sql语句方法(与Statement基本一致,只是不用再传递sql语句参数)
① int executeUpdate();
② ResultSet executeQuery();
(7)ResultSet接口(处理结果)
- boolean next()
游标从当前位置下移动一行。且可以用来判断当前行是否是最后一行末尾,如果是,则返回false。如果不是,则返回true。(游标初始化在第一行之前。) - xxx getXxx(参数):获取数据
① Xxx代表数据类型。例如getInt()、getString()。
② 参数
A.如果参数为int类型,则代表编号,从1开始。如getString(1):获取某行第1列的值。
B.如果参数为String类型,则代表列的名称。如getDouble(“salary”):获取某行列名称为"balance"那列的值。
(8)释放资源
在连接数据库到执行sql的过程中,创建的Connection、Statement/PreparedStatement、ResultSet等资源,在使用完毕后可以调用close()关闭。关闭操作要遵从从里到外的原则。(具体应用见下面实例)
JDBCfont_141">3.JDBC实例
JDBCfont_142">3.1 实例一:JDBC基本使用
使用JDBC技术,通过java代码实现查询数据库数据并显示在java控制台上。
(1)数据库表–员工表emp
(2)封装Emp表数据的JavaBean
package cn.ecarg.domain;
import java.util.Date;
/**
* 封装Emp表数据的JavaBean
*/
public class Emp {
private int id;
private String name;
private String gender;
private double salary;
private Date join_date;
private int dept_id;
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 String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public Date getJoin_date() {
return join_date;
}
public void setJoin_date(Date join_date) {
this.join_date = join_date;
}
public int getDept_id() {
return dept_id;
}
public void setDept_id(int dept_id) {
this.dept_id = dept_id;
}
@Override
public String toString() {
return "Emp{" +
"id=" + id +
", name='" + name + '\'' +
", gender='" + gender + '\'' +
", salary=" + salary +
", join_date=" + join_date +
", dept_id=" + dept_id +
'}';
}
}
(3)定义方法,查询emp表数据将其封装为Emp对象,并装在List集合中,返回。
package cn.ecarg.jdbc;
import cn.ecarg.domain.Emp;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* 定义一个方法,查询emp表的数据将其封装为Emp对象,然后装在List集合,并返回。
*/
public class JDBCDemo1 {
public static void main(String[] args) {
List<Emp>list = new JDBCDemo1().findAll();
System.out.println(list);
}
/**
* 查询所有emp对象
* @return List
*/
public List<Emp> findAll(){
//0.导入驱动jar包
Connection conn = null;
Statement stmt = null;
ResultSet res = null;
List<Emp> list = null;
try {
//1.驱动注册
Class.forName("com.mysql.cj.jdbc.Driver");
//2.sql语句
String sql = "select * from emp";
//3.获取connection对象
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/ecargdb2?serverTimezone=UTC&useSSL=false","root","109815");
//4.获取执行sql对象
stmt = conn.createStatement();
//5.执行sql
res = stmt.executeQuery(sql);
//6.遍历结果集,封装对象,装载集合
Emp emp = null;
list = new ArrayList<Emp>();
while(res.next()){
//获取数据
int id = res.getInt("id");
String name = res.getString("name");
String gender = res.getString("gender");
double salary = res.getDouble("salary");
Date join_date = res.getDate("join_date");
int dept_id = res.getInt("dept_id");
//创建Emp对象,并赋值
emp = new Emp();
emp.setId(id);
emp.setName(name);
emp.setGender(gender);
emp.setSalary(salary);
emp.setJoin_date(join_date);
emp.setDept_id(dept_id);
//转载集合
list.add(emp);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(res != null){
try {
res.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return list;
}
}
(4)执行结果
3.2 对实例一的分析和改进
(1)分析
实例一代码中存在冗余代码。
① 每次都需要重复写同样的 获取JDBC和数据库之间的Connection对象 语句。
② 每次都需要重复写同样的 关闭资源 语句。
(2)针对于以上问题,将这些重复操作封装为一个JDBC工具类,方便调用。
① 为了方便连接不同数据库用户,使用配置文件,将连接数据库所用信息写入配置文件中。这样,每次连接数据库时,只需要修改配置文件信息即可。
② 获取 连接数据库所用信息 代码可以写在静态代码块中。这样,在类加载时就可以完成数据库信息获取,不用再写额外代码来获取。
实例文件
JDBCUtils.java
package cn.ecarg.util;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.sql.*;
import java.util.Properties;
/**
* JDBC工具类
*/
public class JDBCUtils {
private static String url;
private static String user;
private static String password;
private static String driver;
/**
* 工具类中的方法一般为静态的,方便调用。
* 文件的读取,只需要读取一次即可拿到这些值,可以使用静态代码块
*/
static {
//读取资源文件,获取值
try {
//1.创建Properties集合类
Properties pro = new Properties();
//获取src路径下的文件的方式 ClassLoader类加载器
ClassLoader classLoader = JDBCUtils.class.getClassLoader();
URL res = classLoader.getResource("jdbc.properties");
String path = res.getPath();
//2.加载文件
pro.load(new FileReader(path));
//3.获取数据,赋值
url = pro.getProperty("url");
user = pro.getProperty("user");
password = pro.getProperty("password");
driver = pro.getProperty("driver");
//4.注册驱动
Class.forName(driver);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取连接
* @return 连接对象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url,user,password);
}
/**
* 释放资源
* @param conn
* @param stmt
*/
public static void close(Statement stmt,Connection conn){
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 释放资源
* @param res
* @param conn
* @param stmt
*/
public static void close(ResultSet res, Statement stmt,Connection conn){
if(res != null){
try {
res.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
jdbc.properties
url = jdbc:mysql://localhost:3306/ecargdb3?serverTimezone=UTC&useSSL=false
user = root
password = 109815
driver = com.mysql.cj.jdbc.Driver
JDBCDemo2.java
package cn.ecarg.jdbc;
import cn.ecarg.domain.Emp;
import cn.ecarg.util.JDBCUtils;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* 定义一个方法,查询emp表的数据将其封装为对象,然后装在集合,返回。
*/
public class JDBCDemo2 {
public static void main(String[] args) {
List<Emp>list = new JDBCDemo2().findAll();
System.out.println(list);
}
/**
* 查询所有emp对象
* 同时,测试JDBC工具类
* @return
*/
public List<Emp> findAll(){
//0.导入驱动jar包
Connection conn = null;
Statement stmt = null;
ResultSet res = null;
List<Emp> list = null;
try {
//1.驱动注册
//2.获取connection对象
conn = JDBCUtils.getConnection();
//3.sql语句
String sql = "select * from emp";
//4.获取执行sql对象
stmt = conn.createStatement();
//5.执行sql
res = stmt.executeQuery(sql);
//6.遍历结果集,封装对象,装载集合
Emp emp = null;
list = new ArrayList<Emp>();
while(res.next()){
//获取数据
int id = res.getInt("id");
String name = res.getString("name");
String gender = res.getString("gender");
double salary = res.getDouble("salary");
Date join_date = res.getDate("join_date");
int dept_id = res.getInt("dept_id");
//创建Emp对象,并赋值
emp = new Emp();
emp.setId(id);
emp.setName(name);
emp.setGender(gender);
emp.setSalary(salary);
emp.setJoin_date(join_date);
emp.setDept_id(dept_id);
//转载集合
list.add(emp);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.close(res,stmt,conn);
}
return list;
}
}
3.3 实例二:Statement 和 PreparedStatement区别
(1)实例情景
通过键盘录入用户名和密码,判断用户是否登陆成功。
(2)创建数据库:user表
CREATE TABLE USER(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32),
PASSWORD VARCHAR(32)
);
INSERT INTO USER VALUES(NULL,'zhangsan','123');
INSERT INTO USER VALUES(NULL,'lisi','234');
(3)Statement接口实现
/**
* 登录方法,使用Statement实现
* @param username
* @param password
* @return 是否登录成功
*/
public boolean login(String username,String password) {
if (username == null || password == null) {
return false;
}
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
//连接数据库判断是否登录成功
try {
//1.获取连接
conn = JDBCUtils.getConnection();
//2.定义sql
String sql = "select * from user where username = '" + username + "' and password = '" + password + "'";
//3.获取执行sql的对象
stmt = conn.createStatement();
//4.执行查询
rs = stmt.executeQuery(sql);
//5.判断
return rs.next();
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.close(rs, stmt, conn);
}
return false;
}
(4)PreparedStatement接口实现
/**
* 登录方法,使用PreparedStatement实现
* @param username
* @param password
* @return 是否登录成功
*/
public boolean login2(String username,String password) {
if (username == null || password == null) {
return false;
}
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
//连接数据库判断是否登录成功
try {
//1.获取连接
conn = JDBCUtils.getConnection();
//2.定义sql
String sql = "select * from user where username = ? and password = ?";
//3.获取执行sql的对象
pstmt = conn.prepareStatement(sql);
//4.给sql?赋值
pstmt.setString(1,username);
pstmt.setString(2,password);
//5.执行查询时,不需要传参
rs = pstmt.executeQuery();
//6.判断
return rs.next();
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.close(rs, pstmt, conn);
}
return false;
}
(5)分析
- 从定义的sql语句上看,当使用PreparedStatement接口时,定义sql使用?作为占位符,更加简洁清晰,不易出错。
- 当使用Statement接口时,若定义sql语句为
sql = select * from user where username = '" + username + "' and password = '" + password + "'
。当用户输入账户为XXXX;密码为 xxxxx’ or ‘a’ = 'a 时,则执行的sql语句变为:sql = select * from user where username = 'XXX' and password = ' xxxxx' or 'a' = 'a'
。这样这个sql语句就变成一个恒等式,用户随便输入username和password都可以直接登录成功,这就是SQL注入问题。 - 通过以上分析:在实际编程过程中,我们使用PreparedStatement来获取执行SQL的对象。
JDBCfont_630">4.JDBC控制事务
(1)使用Connection对象来管理事务
- 开启事务:setAutoCommit(boolean autoCommit):调用该方法设置参数为false,即开启事务;
在执行sql之前开启事务。 - 提交事务:commit()
当所有sql都执行完提交事务。 - 回滚事务:rollback()
在catch中回滚事务。
(2)示例
- 所用数据库account
- JDBCDemo3.java
package cn.ecarg.jdbc;
import cn.ecarg.util.JDBCUtils;//本文3.2节定义的JDBC工具类
import java.sql.*;
/**
* JDBC事务示例
*/
public class JDBCDemo3 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt1 = null;
PreparedStatement pstmt2 = null;
ResultSet rs = null;
try {
//1.获取连接(JDBCUtiles类为本文3.2节定义的JDBC工具类。)
conn = JDBCUtils.getConnection();
//2.开启事务
conn.setAutoCommit(false);
//3.定义sql
String sql1 = "update account set balance = balance - ? where id = ?";
String sql2 = "update account set balance = balance + ? where id = ?";
//4.获取执行sql的对象
pstmt1 = conn.prepareStatement(sql1);
pstmt2 = conn.prepareStatement(sql2);
//5.给sql?赋值
pstmt1.setDouble(1,500);
pstmt1.setInt(2,1);
pstmt2.setDouble(1,500);
pstmt2.setInt(2,2);
//6.执行sql
pstmt1.executeUpdate();
//手动制造异常
int m = 1/0;
pstmt2.executeUpdate();
//7.提交事务
conn.commit();
} catch (SQLException e) {
if(conn != null){
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
} finally {
JDBCUtils.close(rs, pstmt1, conn);
JDBCUtils.close(rs,pstmt2,null);
}
}
}
- 分析
以上示例代码中,当第一条sql语句执行成功后,因为手动制造的异常代码,第二条sql语句无法被执行。如果没有进行主动事务管理的话,会出现数据库表中一条数据被修改,另一条没有正常被修改,但仍然自动提交事务(mysql默认自动commit事务)。因此,当主动进行事务管理时,在执行完第一条sql语句后出现异常后,在异常处理中会进行回滚,恢复到sql执行之前,保证数据安全。