JDBC及衍生知识(上)
前言
补了补MySQL,终于又回到了Java的怀抱。
今天来开始学习JDBC等知识。
JDBC
概念
JDBC,Java DataBase Connectivity,即Java数据库连接。
就是通过Java去连接数据库。
JDBC的本质:官方定义的一套操作所有关系型数据库的规则(接口)。各个数据库厂商去实现这套接口提供,提供数据库驱动jar包,我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
Java代码可能操作不同的数据库,为了让不同数据库的操作能够统一,sun公司的前辈开发了一套操作所有关系型数据库的接口,即JDBC,Java中的JDBC只是做到了定义接口,而接口具体的实现是由各个数据库厂商(例如MySQL、Oracle、DB2)自己继承接口来实现。这样,不同的厂商提供不同的实现类,不同的实现类就可以操作不同的数据库,这些实现类就是“数据库驱动”。
底层原理:接口类型 接口声明=new 接口实现类(); 接口声明.方法();
,利用多态去调用具体实现类中对接口的实现。
步骤
- 导入对应的驱动jar包进入工程。
- 注册驱动(让程序知道使用的是哪一个jar包)
- 获取数据库连接对象 Connection
- 定义sql语句
- 获取执行sql语句的对象 Statement
- 执行sql,接受返回结果
- 处理结果
- 释放资源(避免内存泄露)
示例
数据库db3中有表account:
id name balance
------ ------ ---------
1 Tom 1000
2 Jack 1000
下面是一套简单的JDBC操作代码,其中对url的写法格式及对版本的影响什么的请了解一下这篇文章
public class JdbcDemo1 {
//定义JDBC的url地址(MySQL8+版本)
static final String DB_URL =
"jdbc:mysql://localhost:3306/db3?useSSL=false&serverTimezone=UTC";
// 数据库的用户名与密码,需要根据自己的设置
static final String USER = "root";
static final String PASS = "964939451";
public static void main(String[] args) throws Exception {
//1.导入驱动jar包(此处以mysql的为例)
//2.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//3.获取数据库连接对象
Connection conn=DriverManager.getConnection(DB_URL,USER,PASS);
//4.定义SQL语句
String sql="UPDATE account SET balance = 500 WHERE id = 1";
//5.获取执行sql的对象 Statement
Statement stmt=conn.createStatement();
//6.执行sql
int count=stmt.executeUpdate(sql);
//7.处理结果
System.out.println(count);
//8.释放资源
conn.close();
stmt.close();
}
}
执行之后,控制台打印了一个1,而数据库中检索一下account表:
id name balance
------ ------ ---------
1 Tom 500
2 Jack 1000
执行成功。
讲解各个对象
详解各个重要类/接口的对象:
- DriverManager类对象:驱动管理对象
- Connection接口对象:数据库连接对象
- Statement接口对象:执行sql的对象
- ResultSet接口对象:结果集对象
- PreparedStatement接口对象:执行sql的对象(继承自Statement接口)
DriverManager类对象:驱动管理对象
功能:
- 注册驱动:告诉程序该使用哪一个jar包(某些驱动jar包在目录META-INF->services->java.sqlDriver中写了配置,使得你可以不必在主程序中手写注册驱动)。
上例中我们注册驱动使用的是Class.forName(com.mysql.cj.jdbc.Driver)可以理解为将指定字节码文件加载进内存,我们追踪这个类文件(即com.mysql.cj.jdbc.Driver),会发现Driver.java文件中存在一个静态代码块,也就是说我们手动将这个类文件加载进内存,然后该类文件就调用了静态代码块,就是这个静态代码块调用了DriverManager的静态方法registerDriver,从而注册了驱动。
static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } }
- 获取数据库连接
static Connection getConnection(String url, String user, String password)
参数中,user、password是登录的用户及密码,而url是一个指令连接的路径。
如上例:
定义连接url:jdbc:mysql://localhost:3306/数据库名?useSSL=false&serverTimezone=UTC
MySQL 8.0 以上版本不需要建立 SSL 连接的,需要显式关闭,还有就是要设置时区。
另外说一个简写:
jdbc:mysql://localhost:3306/数据库名
如果数据库地址是本机且端口为默认的3306,则可以直接写成:
jdbc:mysql:///数据库名
Connection接口对象:数据库连接对象
功能:
- 获取执行sql的对象
Statement createStatement();
PreparedStatement prepareStatement(String sql)
- 管理事务
- 开启事务:void setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
- 提交事务:void commit()
- 回滚事务:void rollback()
Statement接口对象:执行sql的对象
执行静态SQL语句
- boolean execute(String sql)
- int executeUpdate(String sql):执行DML语句(增删改数据表中的数据)、DDL语句(CRUD库/表) 。返回值是操作影响的行数,可以通过这个影响的行数来判断DML语句是否执行成功,即返回值大于0则执行成功。如果是DDL,不管成功失败都是返回0
- ResultSet executeQuery(String sql):执行DQL语句,返回值是一个结果集对象
练习:
为了更好地学习后面的内容,这里我们巩固一下前面的内容,来做几个练习:
account表 添加一条记录
public class JdbcDemo2 { //定义JDBC的url地址(MySQL8+版本) static final String DB_URL = "jdbc:mysql:///db3?useSSL=false&serverTimezone=UTC"; // 数据库的用户名与密码,需要根据自己的设置 static final String USER = "root"; static final String PASS = "964939451"; public static void main(String[] args) { Statement stmt=null; Connection conn=null; try { //1.注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //2.定义sql String sql="INSERT INTO account VALUES(null,'Lisa',3000)"; //3.获取Connection对象 conn=DriverManager.getConnection(DB_URL,USER,PASS); //4.获取执行SQL的对象 stmt=conn.createStatement(); //5.执行SQL int count=stmt.executeUpdate(sql); //影响的行数 //6.处理结果 System.out.println(count); if(count>0){ System.out.println("添加成功!"); }else{ System.out.println("添加失败!"); } }catch(ClassNotFoundException | SQLException e) { e.printStackTrace(); }finally { //7.释放资源 //后建立的(stmt)先释放 //避免空指针异常(stmt被赋值前就进入catch) if(stmt!=null){ try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } //避免空指针异常(conn被赋值前就进入catch) if(conn!=null){ try { //后建立的先释放 conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }
account表 修改记录
public class JdbcDemo3 { //定义JDBC的url地址(MySQL8+版本) static final String DB_URL = "jdbc:mysql:///db3?useSSL=false&serverTimezone=UTC"; // 数据库的用户名与密码,需要根据自己的设置 static final String USER = "root"; static final String PASS = "964939451"; public static void main(String[] args) { Statement stmt=null; Connection conn=null; try { //1.注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //2.定义sql String sql="UPDATE account SET balance=1500 WHERE id=3"; //3.获取Connection对象 conn=DriverManager.getConnection(DB_URL,USER,PASS); //4.获取执行SQL的对象 stmt=conn.createStatement(); //5.执行SQL int count=stmt.executeUpdate(sql); //影响的行数 //6.处理结果 System.out.println(count); if(count>0){ System.out.println("修改成功!"); }else{ System.out.println("修改失败!"); } }catch(ClassNotFoundException | SQLException e) { e.printStackTrace(); }finally { //7.释放资源 //后建立的(stmt)先释放 //避免空指针异常(stmt被赋值前就进入catch) if(stmt!=null){ try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } //避免空指针异常(conn被赋值前就进入catch) if(conn!=null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }
account表 删除一条记录
上例改一下SQL语句即可(String sql=”DELETE FROM account WHERE id=3″;),我就不重新复制这些代码了。
PreparedStatement接口对象:执行sql的对象(继承自Statement接口)
执行预编译的SQL。
不同于Statement接口执行静态的SQL语句,直接将完整的SQL语句执行,预编译的SQL会先用“?”来作为占位符替代参数,可以有效预防SQL注入的问题。
我们在定义sql语句的时候,sql的参数使用?作为占位符,然后我们在获取sql执行对象的时候(PrepareStatement),传入预编译的SQL,然后给占位符“?”赋值,然后再执行即可。
给占位符赋值方法:setXXX(参数1,参数2)
- 参数1:?的位置编号 (从1开始)
- 参数2:?的值
后面我们执行execute的时候就不需要传入sql语句了(这个executeUpdate/executeQuery被PreparedStatement重写了无参方法)
//2.定义sql
String sql="SELECT * FROM user WHERE username=? and password=?";
//3.获取执行sql的对象
pstmt=conn.prepareStatement(sql);
//4.设置参数
pstmt.setString(1,username);
pstmt.setString(2,password);
//5.执行查询
rs=pstmt.executeQuery();
后期我们会经常使用这个PreparedStatement对象来替代Statement对象,因为可以有效防止SQL注入,效率更高。
ResultSet接口对象:结果集对象
上面我们说了Statement的对象的executeQuery方法执行DQL语句,返回的是一个ResultSet对象,即结果集对象。
我们的ResultSet对象是对查询结果的一个封装。
一个ResultSet对象维护一个游标指向其当前行的数据,我们主要就是通过这个游标来访问数据。最初游标位于第一行之前的位置。通过next方法移动游标到下一行,如果移动后它返回false即说明ResultSet对象没有更多的行,即游标已经到头了。
常用方法:
- boolean next():移动游标到下一行,判断当前行是否是最后一行末尾,如果是则返回true,否则返回false
- getXXX(参数):获取数据
- 参数情况;
- Int:代表列的编号,这里是从1开始,例如getString(1),得到第一列的值,返回String
- String:代表列的名称。如:getDouble(“balance”),得到字段balance的值,返回Double
- 参数情况;
使用示例:
public class JdbcDemo5 {
//定义JDBC的url地址(MySQL8+版本)
static final String DB_URL =
"jdbc:mysql:///db3?useSSL=false&serverTimezone=UTC";
// 数据库的用户名与密码,需要根据自己的设置
static final String USER = "root";
static final String PASS = "964939451";
public static void main(String[] args) {
Statement stmt=null;
Connection conn=null;
ResultSet rs=null;
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.定义sql
String sql="SELECT * FROM account";
//3.获取Connection对象
conn=DriverManager.getConnection(DB_URL,USER,PASS);
//4.获取执行SQL的对象
stmt=conn.createStatement();
//5.执行SQL
rs=stmt.executeQuery(sql); //影响的行数
//6.处理结果
//6.1 游标向下移动一行
rs.next();
int id=rs.getInt(1);
String name=rs.getString("name");
double balance=rs.getDouble(3);
System.out.println(id+"---"+name+"---"+balance);
}catch(ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
//7.释放资源
//后建立的(rs)先释放
//避免空指针异常(rs被赋值前就进入catch)
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//避免空指针异常(stmt被赋值前就进入catch)
if(stmt!=null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//避免空指针异常(conn被赋值前就进入catch)
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
这个示例只是一个基础使用的案例,其实在实际开发中我们不会这样使用的。
我们也可以用个while循环,来遍历所有结果
while(rs.next()){
int id=rs.getInt(1);
String name=rs.getString("name");
double balance=rs.getDouble(3);
System.out.println(id+"---"+name+"---"+balance);
}
检索示例
基本效果
为了更好地学习后面的内容,这里我们巩固一下前面的内容,来做一个练习:
数据库db3中有表,内容如下:
id ename job_id mgr joindate salary bonus dept_id
------ --------- ------ ------ ---------- -------- -------- ---------
1001 孙悟空 4 1004 2000-12-17 8000.00 (NULL) 20
1002 卢俊义 3 1006 2001-02-20 16000.00 3000.00 30
1003 林冲 3 1006 2001-02-22 12500.00 5000.00 30
1004 唐僧 2 1009 2001-04-02 29750.00 (NULL) 20
1005 李逵 4 1006 2001-09-28 12500.00 14000.00 30
1006 宋江 2 1009 2001-05-01 28500.00 (NULL) 30
1007 刘备 2 1009 2001-09-01 24500.00 (NULL) 10
1008 猪八戒 4 1004 2007-04-19 30000.00 (NULL) 20
1009 罗贯中 1 (NULL) 2001-11-17 50000.00 (NULL) 10
1010 吴用 3 1006 2001-09-08 15000.00 0.00 30
1011 沙僧 4 1004 2007-05-23 11000.00 (NULL) 20
1012 李逵 4 1006 2001-12-03 9500.00 (NULL) 30
1013 小白龙 4 1004 2001-12-03 30000.00 (NULL) 20
1014 关羽 4 1007 2002-01-23 13000.00 (NULL) 10
定义一个方法,查询emp表的数据将其封装为对象,然后装载集合,返回。
- 定义Emp类
- 定义方法 public List<Emp> findAll(){}
- 实现方法 SELECT * FROM emp;
然后我们在pers.luoluo.domain中定义一个Emp类
public class Emp {
private int id;
private String ename;
private int job_id;
private int mgr;
private Date joindate;
private double salary;
private double bonus;
private int dept_id;
@Override
public String toString() {
return "Emp{" +
"id=" + id +
", ename='" + ename + '\'' +
", job_id=" + job_id +
", mgr=" + mgr +
", joindate=" + joindate +
", salary=" + salary +
", bonus=" + bonus +
", dept_id=" + dept_id +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public int getJob_id() {
return job_id;
}
public void setJob_id(int job_id) {
this.job_id = job_id;
}
public int getMgr() {
return mgr;
}
public void setMgr(int mgr) {
this.mgr = mgr;
}
public Date getJoindate() {
return joindate;
}
public void setJoindate(Date joindate) {
this.joindate = joindate;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
public int getDept_id() {
return dept_id;
}
public void setDept_id(int dept_id) {
this.dept_id = dept_id;
}
}
然后在pers.luoluo.jdbc中创建一个新类:
public class JdbcDemo6 {
public List<Emp> findAll(){
//定义JDBC的url地址(MySQL8+版本)
final String DB_URL =
"jdbc:mysql:///db3?useSSL=false&serverTimezone=UTC";
// 数据库的用户名与密码,需要根据自己的设置
final String USER = "root";
final String PASS = "964939451";
Statement stmt=null;
Connection conn=null;
ResultSet rs=null;
//创建emp对象
Emp emp=null;
List<Emp> empList=new ArrayList<Emp>();
try{
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
conn=DriverManager.getConnection(DB_URL,USER,PASS);
//3.定义SQL
String sql="SELECT * FROM emp";
//4.获取执行SQL的对象
stmt=conn.createStatement();
//5.执行SQL语句
rs=stmt.executeQuery(sql);
//6.遍历结果集、封装对象、装载集合
while(rs.next()){
emp=new Emp();
//获取数据(rs的getXXX的参数传入名必须与数据库端字段名相同)
emp.setId(rs.getInt("id"));
emp.setEname(rs.getString("ename"));
emp.setJob_id(rs.getInt("job_id"));
emp.setMgr(rs.getInt("mgr"));
emp.setJoindate(rs.getDate("joindate"));
emp.setSalary(rs.getDouble("salary"));
emp.setBonus(rs.getDouble("bonus"));
emp.setDept_id(rs.getInt("dept_id"));
//将员工加入
empList.add(emp);
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt!=null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return empList;
}
public static void main(String[] args) {
List<Emp> empList=new JdbcDemo6().findAll();
for (Emp emp:empList) {
System.out.println(emp);
}
}
}
控制台打印效果为数据库数据效果(即成功了),这里就不复制了。
制作JDBC工具类
审视上述代码,我们在每一次的fineAll中都需要连接数据库再释放资源,如果我们要实现类似的方法,那么又需要重复写这些繁琐的代码,所以我们可以整合成一个JDBC的工具类,这样可以为我们简化书写:
src下创建一个注解文件jdbc.properties:
url=jdbc:mysql:///db3?useSSL=false&serverTimezone=UTC
user=root
password=964939451
driver=com.mysql.cj.jdbc.Driver
然后在pers.luoluo.util下的JDBCUtils.java中:
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();
//2.加载文件
/*利用ClassLoader获取src路径下的文件的方式
ClassLoader是一个“类加载器”,可以加载字节码文件进入内容,
并且可以获得字节码文件的路径
*/
ClassLoader classLoader = JDBCUtils.class.getClassLoader();
URL res = classLoader.getResource("jdbc.properties");
String path = res.getPath();
path=java.net.URLDecoder.decode(path,"utf-8"); //url编码转utf-8
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 | ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* @Description 获取连接的工具方法(集合配置文件)
**/
public static Connection getConnection( ) throws SQLException {
return DriverManager.getConnection(url,user,password);
}
/**
* @Description 针对DDL、DML语言的释放资源方法
**/
public static void close(Statement stmt,Connection conn){
if(stmt!=null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* @Description 针对DQL语言的释放资源方法(重载close方法)
**/
public static void close(ResultSet rs, Statement stmt, Connection conn){
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt!=null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
然后回到我们的JdbcDemo6.java
public class JdbcDemo6 {
public List<Emp> findAll(){
Statement stmt=null;
Connection conn=null;
ResultSet rs=null;
//创建emp对象
Emp emp=null;
List<Emp> empList=new ArrayList<Emp>();
try{
conn= JDBCUtils.getConnection();
//3.定义SQL
String sql="SELECT * FROM emp";
//4.获取执行SQL的对象
stmt=conn.createStatement();
//5.执行SQL语句
rs=stmt.executeQuery(sql);
//6.遍历结果集、封装对象、装载集合
while(rs.next()){
emp=new Emp();
//获取数据(rs的getXXX的参数传入名必须与数据库端字段名相同)
emp.setId(rs.getInt("id"));
emp.setEname(rs.getString("ename"));
emp.setJob_id(rs.getInt("job_id"));
emp.setMgr(rs.getInt("mgr"));
emp.setJoindate(rs.getDate("joindate"));
emp.setSalary(rs.getDouble("salary"));
emp.setBonus(rs.getDouble("bonus"));
emp.setDept_id(rs.getInt("dept_id"));
//将员工加入
empList.add(emp);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.close(rs,stmt,conn);
}
return empList;
}
public static void main(String[] args) {
List<Emp> empList=new JdbcDemo6().findAll();
for (Emp emp:empList) {
System.out.println(emp);
}
}
}
然后就又成功打印了数据库中的表内容。
代码精妙之处我已写在各个注释的地方,请详读。
模拟登陆
我们再来重新做个需求:
- 通过键盘录入用户名和密码
- 判断用户是否登录成功
我们来创建一个数据库db4,再创建一个user表:
CREATE TABLE user(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32),
password VARCHAR(32)
);
INSERT INTO user VALUES(NULL,'Tom','123');
INSERT INTO user VALUES(NULL,'Jack','123');
SELECT * FROM user;
id username password
------ -------- ----------
1 Tom 123
2 Jack 123
数据表就创建完成,接下来我们的任务是实现代码访问,利用JDBC工具类模拟登陆:
工具类还是不变,我们只要修改src/jdbc.properties下的url后面数据库指定好db4即可(体会到我们的代码的可扩展性了吗?这也体现了设计模式中 开放-封闭原则 ),然后,在JdbcDemo7.java中,内容如下:
public class JdbcDemo7 {
/**
* @Description 登录方法
**/
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;
}
public static void main(String[] args) {
//1.键盘录入,接收用户名和密码
Scanner sc=new Scanner(System.in);
System.out.println("请输入用户名:");
String username=sc.nextLine();
System.out.println("请输入密码:");
String password=sc.nextLine();
//2.调用方法
boolean flag=new JdbcDemo7().login(username,password);
//3.判断结果
if(flag)
System.out.println("登录成功!");
else
System.out.println("用户名或密码错误!");
}
}
请输入用户名:
Jack
请输入密码:
123
登录成功!
升级安全性
了解网络安全的朋友肯定很敏感地发现了,这样的登录机制根本挡不住sql注入的攻击,例如,用户名你随便输入,密码输:a’ or ‘a’ = ‘a,最后sql语句就变成了:SELECT * FROM user WHERE username=’xxx’ and password=’a’ or ‘a’=’a’;,利用后面的恒等式会将所在用户都检索出来,自然结果肯定有next(大于0),故就会登录成功。
SQL注入问题:在拼接SQL时,有一些SQL的特殊关键字参与字符串的拼接,会造成安全性的问题。
这就需要我们之前聊过的PreparedStatement对象来解决SQL注入问题,利于预编译的SQL语句来代替静态的SQL语句,从而预防SQL注入问题。
我们修改login方法即可:
public boolean login(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.设置参数
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;
}
测试:
请输入用户名:
waefrwe
请输入密码:
a' or 'a' = 'a
用户名或密码错误!
非常成功!
JDBC管理事务
概述
事务:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败(原子性)。
对事务的操作
- 开启事务
- 提交事务
- 回滚事务
在JDBC中,我们会使用Connection对象来管理事务:
- 开启事务:void setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
- 提交事务:void commit()
- 回滚事务:void rollback()
操作示例
首先,在我们的数据库db3中有这样的一个account表:
mysql> SELECT* FROM account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | Tom | 2000 |
| 2 | Jack | 2000 |
+----+------+---------+
2 rows in set (0.00 sec)
好了,然后在jdbc.properties配置文件中修改url访问db3数据库,下面是Java代码:
public class JdbcDemo8 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement pstmt1=null;
PreparedStatement pstmt2=null;
try {
//1.获取连接
conn=JDBCUtils.getConnection();
//2.定义SQL
//2.1 Tom-500
String sql1="UPDATE account SET balance=balance-? WHERE name=?";
//2.2 Jack+500
String sql2="UPDATE account SET balance=balance+? WHERE name=?";
//3.获取执行SQL对象
pstmt1=conn.prepareStatement(sql1);
pstmt2=conn.prepareStatement(sql2);
//4.设置参数
pstmt1.setDouble(1,500);
pstmt1.setString(2,"Tom");
pstmt2.setDouble(1,500);
pstmt2.setString(2,"Jack");
//5.执行语句
pstmt1.executeUpdate();
pstmt2.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.close(pstmt1,conn);
JDBCUtils.close(pstmt2,null);
}
}
}
执行不出意外会成功,最后Tom的账户为1500,Jack的账户为2500,那么,如果我们在pstmt1.executeUpdate();后面添加一个异常(例如3/0),那么只有Tom会扣钱,Jack不会增加钱,这不是我们想要的结果。
所以我们要使用到事务。
public class JdbcDemo8 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement pstmt1=null;
PreparedStatement pstmt2=null;
try {
//1.获取连接
conn=JDBCUtils.getConnection();
//开启事务
conn.setAutoCommit(false);
//2.定义SQL
//2.1 Tom-500
String sql1="UPDATE account SET balance=balance-? WHERE name=?";
//2.2 Jack+500
String sql2="UPDATE account SET balance=balance+? WHERE name=?";
//3.获取执行SQL对象
pstmt1=conn.prepareStatement(sql1);
pstmt2=conn.prepareStatement(sql2);
//4.设置参数
pstmt1.setDouble(1,500);
pstmt1.setString(2,"Tom");
pstmt2.setDouble(1,500);
pstmt2.setString(2,"Jack");
//5.执行语句
pstmt1.executeUpdate();
pstmt2.executeUpdate();
//提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
//回滚事务
try {
if(conn!=null) {
conn.rollback();
}
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
JDBCUtils.close(pstmt1,conn);
JDBCUtils.close(pstmt2,null);
}
}
}
好了,这样就成功了!
这一篇就先写到这里了,我们把连接池等内容放到下一篇来介绍。
商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢