01 JDBC简介
快速入门
public static void main(String[] args) throws Exception {
//1、导入驱动jar包
//2、注册驱动
Class.forName("com.mysql.jdbc.Driver"); 【获取Driver类对象】
//3、获取数据库连接对象
Connection conne = DriverManager.getConnection("jdbc:mysql://主机:端口/表名", "用户名", "用户密码");
//4、定义SQL语句
String sql = "select * from db3";
//5、获取执行SQL对象语句
Statement sta = conne.createStatement();
//6、执行SQL语句
int count = sta.executeUpdate(sql);
//处理结果
System.out.println(count);
//释放资源
sta.close();
conne.close();
详解对象
1. DriverManager:驱动管理对象
1. DriverManager:驱动管理对象
* 功能:
1. 注册驱动:告诉程序该使用哪一个数据库驱动jar
static void registerDriver(Driver driver) :注册与给定的驱动程序 DriverManager 。
写代码使用: Class.forName("com.mysql.jdbc.Driver");【把Driver类加载内存中】
通过查看源码发现:在com.mysql.jdbc.Driver类中存在静态代码块
static {
try {
java.sql.DriverManager.registerDriver(new Driver());【register注册】
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
注意:mysql5之后的驱动jar包可以省略注册驱动的步骤。
2. 获取数据库连接:
* 方法:static Connection getConnection(String url, String user, String password)
* 参数:
* url:指定连接的路径
* 语法:jdbc:mysql://ip地址(域名):端口号/数据库名称
* 例子:jdbc:mysql://localhost:3306/db3
* 细节:如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称
* user:用户名
* password:密码
这里有个疑问:我们在使用【Class.forName(“com.mysql.jdbc.Driver”)】把这个字节码加载进内存当中,但是我们从头到尾都没有使用过Driver类做过什么事情,它又是怎么来实现注册驱动的呢?
原来,Class.forName(“com.mysql.jdbc.Driver”) 是把字节码文件加载进内存里,在字节码加载进内存中的时候,静态代码块会优先自动执行。故,Driver里面确实存在静态代码块来执行java.sql.DriverManager.registerDriver(new Driver());【类名.DriverManager.registerDriver(new Driver()】。所以,直接获取类对象,就执行了静态代码块这个注册驱动的操作。
疑问:DriverManager类哪里来的?数据库驱动包给的。
2.Connection:数据库连接对象
代表了当前代码连接数据库的桥梁
2. Connection:数据库连接对象
1. 功能:
1. 获取执行sql 的对象
* Statement createStatement()
* PreparedStatement prepareStatement(String sql)
2. 管理事务:
* 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
* 提交事务:commit()
* 回滚事务:rollback()
3. Statement:执行sql的对象【少用,子类就常用】
3. Statement:执行sql的对象
1. 执行sql
1. boolean execute(String sql) :可以执行任意的sql 了解
2. int executeUpdate(String sql) :执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句
* 返回值:影响的行数,可以通过这个影响的行数判断DML语句是否执行成功 返回值>0的则执行成功,反之,则失败。
3. ResultSet executeQuery(String sql) :执行DQL(select)语句
2. 练习:
1. account表 添加一条记录
2. account表 修改记录
3. account表 删除一条记录
练习1:
代码:
Statement stmt = null; //为什么要在外面赋值null?因为finally要释放资源,而不这样他们的引用变量只能在try有效
Connection conn = null;
try {
//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2. 定义sql
String sql = "insert into account values(null,'王五',3000)";
//3.获取Connection对象
conn = DriverManager.getConnection("jdbc:mysql:///db3", "root", "root");
//4.获取执行sql的对象 Statement
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 e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//stmt.close();
//7. 释放资源
//避免空指针异常 这样关闭资源不合理,因为如果输入密码错误,sttm就没有被赋值,就会出现空指针异常。所以要判断
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
为什么要使用Try··catch?
实际上thows异常相当于忽视异常,而try…catch和throw 才解决。如果中间出错了,程序终止,下面的资源释放就没有被执行那么,这些资源会留在内存没有被释放。
4-ResultSet:结果集对象,封装查询结果。
数据库中使用select语句查询表,查询后的表就是结果集对象。
那么表和ResultSet是什么样的关系呢?
ResultSet把select查询后的表封装起来。
ResultSet封装后,那应该怎么把数据拿出来?
当ResultSet封装查询结果集之后,会有一个游标指向第一行
我们获取数据肯定不是获取第一行,因为数据在下面,游标在第一行,游标在哪一行我们才能获取哪一行的数据。所以,我们需要用一个方法把右表往下移一行,即next()。
但是注意:
我们读取的时候,不是一行一行数据的读取数据,而是一行一个属性的读,所以你要确定读取哪列的哪个列名属性,即,使用getXxxx();
- getXxx(参数):获取数据
* Xxx:代表数据类型 如: int getInt() , String getString()
* 参数:
1. int:代表列的编号,从1开始 如: getString(1)
2. String:代表列名称。 如: getDouble(“balance”)
Xxx: 代表数据类型是个什么意思呢?
比如,上图中,你要读取的是id,而id是int类型的值、name是String类型得值,banlance是double类型的值(换到Java来表示的话)。即,分别使用int getInt() , String getString(),double getDouble()
参数: getXxx()有2个参数,即int和String类型。参数的作用是找到游标当前行的哪一列名属性值。int类型是根据【编号】查询,即第一个列名id为1,其他依次排;String类型是根据【列名】来查询。
5、ResultSet_遍历结果集
这里重点说说next方法:使游标往下移动一行并判断游标当前行是否有数据。有,则返回true并执行;无,则相反。
public class JDBCDemo7 {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
conn = DriverManager.getConnection("jdbc:mysql:///db3", "root", "root");
//3.定义sql
String sql = "select * from account";
//4.获取执行sql对象
stmt = conn.createStatement();
//5.执行sql
rs = stmt.executeQuery(sql);
--------------------------------------
//6.处理结果
//循环判断游标是否是最后一行末尾。
while(rs.next()){ 使游标往下移一行,并判断是否有数据;有,则执行;无,则停止
//获取数据
//6.2 获取数据
int id = rs.getInt(1);
String name = rs.getString("name");
double balance = rs.getDouble(3);
System.out.println(id + "---" + name + "---" + balance);
}
---------------------------------------------------
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//7.释放资源
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();
}
}
}
}
}
【综合】select语句练习
思路:【把表中的数据封装成emp的对象】
创建emp类看作emp表—>把表中的列名看作emp类的成员变量—>测试类中定义个方法findAll:读取ResultSet结果集对象,并把读取的数据赋值给对象的成员变量—>把对象装进集合中—>测试类遍历输出集合
emp类
封装Emp表数据的JavaBean
*/
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;
下面省略了getter/setter方法和重写的toString方法
测试类
* 定义一个方法,查询emp表的数据将其封装为对象,然后装载集合,返回。
*/
public class JDBCDemo8 {
public static void main(String[] args) {
List<Emp> list = new JDBCDemo8().findAll2();
System.out.println(list);
System.out.println(list.size());
}
/**
* 查询所有emp对象
* @return
*/
public List<Emp> findAll(){
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
List<Emp> list = null;
try {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
conn = DriverManager.getConnection("jdbc:mysql:///db3", "root", "root");
//3.定义sql
String sql = "select * from emp";
//4.获取执行sql的对象
stmt = conn.createStatement();
//5.执行sql
rs = stmt.executeQuery(sql);
//6.遍历结果集,封装对象,装载集合(从ResultSet结果集对象中读取emp数据)
Emp emp = null; 【为什么要把对象引用退=提出外面:因为语句在循环中如果不这样会栈空间会频繁开辟对象空间】
list = new ArrayList<Emp>();
while(rs.next()){
//获取数据
int id = rs.getInt("id");
String ename = rs.getString("ename");
int job_id = rs.getInt("job_id");
int mgr = rs.getInt("mgr");
Date joindate = rs.getDate("joindate"); //注意:Date使unit.Date类的子类
double salary = rs.getDouble("salary");
double bonus = rs.getDouble("bonus");
int dept_id = rs.getInt("dept_id");
// 创建emp对象,并赋值
emp = new Emp();
emp.setId(id);
emp.setEname(ename);
emp.setJob_id(job_id);
emp.setMgr(mgr);
emp.setJoindate(joindate);
emp.setSalary(salary);
emp.setBonus(bonus);
emp.setDept_id(dept_id);
//装载集合
list.add(emp);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (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 list;
}
把emp表中的列名封装并看作成emp类中的成员属性,一行中的数据为类中成员属性的值,整个emp类就是一个对象,这个对象包含了这一行的所有数据。
重写了toString方法,是想打印对象名直接显示集合中的对象数据。
JDBC工具类—简化代码
配置文件
从上图发现代码冗长且繁琐,因此把这些功能抽取处理去定义一个工具类
抽取:
- 1、注册驱动
- 2、连接对象
- 3、资源释放
JDBC工具类:【工具类都有一个特点,使用静态方法(不需要实例化对象,直接调用)】
文件的读取,只需要读取一次即可拿到这些值。使用静态代码块
*/
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();
// System.out.println(path);///D:/IdeaProjects/itcast/out/production/day04_jdbc/jdbc.properties
//2. 加载文件
// pro.load(new FileReader("D:\\IdeaProjects\\itcast\\day04_jdbc\\src\\jdbc.properties"));
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 连接对象
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, user, password);
}
释放资源
* @param stmt
* @param conn
*/
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();
}
}
}
释放资源
* @param stmt
* @param conn
*/
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();
}
}
}
}
疑问解答:
- 为什么要使用静态代码块来获取并注册驱动?
因为我要保证在读取配置文件,获取注册驱动的参数值,以及注册驱动的时候,只读取一次,并且最先完成。因为静态代码块是随着类被加载进内存就执行了,而且只执行一次。
- 为什么要写一个配置文件,并把src作为根目录?
- 写配置文件的原因是为了能狗保证获取注册驱动的参数是动态的,通用的,不想把它们给写死,好处是无论换什么,哪怕是数据库,我不需要懂代码,我改改配置文件信息就好了。
- 放在scr目录是为了方便读取,直接调用【
//获取src路径下的文件的方式—>ClassLoader 类加载器
ClassLoader classLoader = JDBCUtils.class.getClassLoader();
URL res = classLoader.getResource(“jdbc.properties”);
String path = res.getPath();
】就可以获取。
字节码文件对象.getClassLoader()能够返回ClassLoader对象,ClassLoader对象可以调用getResource()方法来获取并加载scr目录下的配置文件数据,并返回URL(代表统一资源标识符定位符,它能够定位某个文件的资源的绝对路径),使用它的getPath()能够转换为字符串类型。
- 为什么不使用下面语句来获取配置文件?
如果使用这种方式:
pro.load(new FileReader(“D:\IdeaProjects\itcast\day04_jdbc\src\jdbc.properties”));
有2个问题:
- 1、代码跟不改变一样,不如不抽取
- 2、不保证通用性和动态性。
如果没有把它放进src同目录,使用绝对定位,那么如果我改变配置文件的位置呢?或者我想改名呢?是不是还要改代码?
- 为什么要使用方法重载来进行释放资源?
因为做事情要考虑全面,资源释放有2种情况:
- 1、只进行增删改----只需要释放2个资源
- 2、使用select语句----只需要释放3个资源
所以,需要根据不同的情况通过传入不同的参数来进行调用工具类的释放方法。
- 私有静态成员属性可以在静态代码块直接赋值。
复习:
- Properties以及方法load,load的getProperty( )是什么,有什么作用?
- Properties是Map的子类,它的load方法参数传入路径的字节流/字符流,并把传入路径的文件以键对值的方法读取并存进 Properties集合中
- getProperty()参数是获取Properties数据的key,把key带入获取values。
- ClassLoader 类加载器是什么,有什么作用?
ClassLoader不仅可以把字节码文件加载进内存,而且能够获取src目录下资源文件的路径。
获取资源文件路径之前,需要获取对应的字节码文件对象,随便什么字节码文件都可以。
自写代码
* - 1、注册驱动
* - 2、连接对象
* - 3、资源释放
*/
public class JDBCUnit02 {
private static String url, user, password, driver;
//获取配置文件并注册驱动
static {
try {
//创建Properties集合
Properties p = new Properties();
//使用getClassLoader获取src目录下的配置文件
ClassLoader classLoader = JDBCUnit02.class.getClassLoader();
//getResource()传入配置文件名,返回URL统一资源定位符,可以找到文件的绝对路径
URL res = classLoader.getResource("jdbc.properties");
//把URL类型转换成String类型
String path = res.getPath();
//使用load()以键对值的方式读取配置文件数据
p.load(new FileReader(path));
//给静态属性赋集合中的values值,getProperty()传入key返回values
url= p.getProperty(url);
user= p.getProperty(user);
password= p.getProperty(password);
driver= p.getProperty(driver);
//1、注册驱动
Class.forName(driver);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* @return 2、连接对象
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, user, password);
}
/**
* 3、释放资源
*
* @param sttm
* @param conn
*/
public static void close(Statement sttm, Connection conn) {
if (sttm != null) {
try {
sttm.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(ResultSet r, Statement sttm, Connection conn) {
if (r != null) {
try {
r.close();
} catch (SQLException e) {
e.printStackTrace();
}
if (sttm != null) {
try {
sttm.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
测试类
演示JDBC工具类
* @return
*/
public List<Emp> findAll2(){
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
List<Emp> list = null;
try {
/* //1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
conn = DriverManager.getConnection("jdbc:mysql:///db3", "root", "root");*/
conn = JDBCUtils.getConnection();
//3.定义sql
String sql = "select * from emp";
//4.获取执行sql的对象
stmt = conn.createStatement();
//5.执行sql
rs = stmt.executeQuery(sql);
//6.遍历结果集,封装对象,装载集合
Emp emp = null;
list = new ArrayList<Emp>();
while(rs.next()){
//获取数据
int id = rs.getInt("id");
String ename = rs.getString("ename");
int job_id = rs.getInt("job_id");
int mgr = rs.getInt("mgr");
Date joindate = rs.getDate("joindate");
double salary = rs.getDouble("salary");
double bonus = rs.getDouble("bonus");
int dept_id = rs.getInt("dept_id");
// 创建emp对象,并赋值
emp = new Emp();
emp.setId(id);
emp.setEname(ename);
emp.setJob_id(job_id);
emp.setMgr(mgr);
emp.setJoindate(joindate);
emp.setSalary(salary);
emp.setBonus(bonus);
emp.setDept_id(dept_id);
//装载集合
list.add(emp);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//释放资源
JDBCUtils.close(rs,stmt,conn);
}
return list;
}
}
JDBC练习-登陆案列
JDBC工具类不变
测试类
练习:
* * 需求:
* 1. 通过键盘录入用户名和密码
* 2. 判断用户是否登录成功
public class Login {
public static void main(String[] args) {
//键盘录入,接受用户名和密码
Scanner scanner = new Scanner(System.in);
System.out.println("请输入账号:");
String user = scanner.nextLine();
System.out.println("请输入密码");
String password = scanner.nextLine();
if (new Login().login(user, password)) {
System.out.println("登陆成功");
} else {
System.out.println("登陆失败");
}
}
/**
* 定义登陆方法,参数传入账号密码。如果正确,返回真;错误,返回假。
*
* @return 账户密码的真假。
*/
public boolean login(String user, String password) {
Connection conn = null;
Statement stt = null;
ResultSet r = null;
try {
//判断数据是否为空,为空返回false
if (user == null || password == null) {
return false;
}
//获取数据库连接对象
conn = JDBCUtils.getConnection();
//利用数据库对象调用createStatement()返回SQL执行对象
stt = conn.createStatement();
//定义SQL执行语句
String sql = "select * from USER where username = '" + user + "' and password = '" + password + "'";
//返回查询结果集
r = stt.executeQuery(sql);
//游标往下移动一行,如果有数据就返回真。
return r.next();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//释放资源
JDBCUtils.close(r,stt,conn);
}
return false;
}
}
练习中出现的错误:
这里是表名。
6、PreparedStatement:执行sql的对象
登陆案列做好了,但是存在一个漏洞,即“SQL注入”。我使用这种方式可以登陆成功。
这种方式能够查出表中所有数据
预编译SQL:参数使用了占位符“?”来代替,不能使用拼接,要给占位符赋值。
静态编译SQL:当传进参数的时候,SQL语句就生成了。
PreparedStatement对象与Statement对象的异同:
同:
- 都是使用连接对象Connection来调用,都是SQL执行对象
异:
- Connection使用无参的createStatement()创建Statement对象
- Connection使用有参的prepareStatement(String sql) 。sql是要执行的sql语句,但是执行之前要给占位符赋值。
给占位符赋值
调用PreparedStatement对象的setXxx(参数1,参数2)方法
Xxx代表不同的数据类型,
参数1:代表?的位置编号,从一开始1
参数2:代表要赋的值
执行SQL语句的时候,不需要传【定义的SQL语句了】,为什么?
在获取PreparedStatement对象已经传过了,而且executeQuery( 参数)是继承父类的方法的,无参的executeQuery()是子类特有的。
5. PreparedStatement:执行sql的对象
1. SQL注入问题:在拼接sql时,有一些sql的特殊关键字参与字符串的拼接。会造成安全性问题
1. 输入用户随便,输入密码:a' or 'a' = 'a
2. sql:select * from user where username = 'fhdsjkf' and password = 'a' or 'a' = 'a'
2. 解决sql注入问题:使用PreparedStatement对象来解决
3. 预编译的SQL:参数使用?作为占位符
4. 步骤:
1. 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar
2. 注册驱动
3. 获取数据库连接对象 Connection
4. 定义sql
* 注意:sql的参数使用?作为占位符。 如:select * from user where username = ? and password = ?;
5. 获取执行sql语句的对象 PreparedStatement Connection.prepareStatement(String sql)
6. 给?赋值:
* 方法: setXxx(参数1,参数2)
* 参数1:?的位置编号 从1 开始
* 参数2:?的值
7. 执行sql,接受返回结果,不需要传递sql语句
8. 处理结果
9. 释放资源
5. 注意:后期都会使用PreparedStatement来完成增删改查的所有操作
1. 可以防止SQL注入
2. 效率更高
代码跟Statement对象差不多,就是获取执行SQL对象和执行SQL语句不同
练习:
* * 需求:
* 1. 通过键盘录入用户名和密码
* 2. 判断用户是否登录成功
*/
public class JDBCDemo9 {
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 JDBCDemo9().login2(username, password);
//3.判断结果,输出不同语句
if(flag){
//登录成功
System.out.println("登录成功!");
}else{
System.out.println("用户名或密码错误!");
}
}
* 登录方法,使用PreparedStatement实现
*/
public boolean login2(String username ,String password){
if(username == null || password == null){
return false;
}
//连接数据库判断是否登录成功
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
//1.获取连接
try {
conn = JDBCUtils.getConnection();
//2.定义sql
String sql = "select * from user where username = ? and password = ?";
//3.获取执行sql的对象
pstmt = conn.prepareStatement(sql);
//给?赋值
pstmt.setString(1,username);
pstmt.setString(2,password);
//4.执行查询,不需要传递sql
rs = pstmt.executeQuery();
//5.判断
return rs.next();//如果有下一行,则返回true
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.close(rs,pstmt,conn);
}
return false;
}
}
JDBC管理事务–概述
JDBC控制事务管理之转账案例
首先设置转账代码,执行发现结果和预期相同;但是,手动添加了异常后,再次运行,结果张三扣钱了,李四钱没加。
解决:控制事务管理
事务操作
*/
public class JDBCDemo10 {
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 张三 - 500
String sql1 = "update account set balance = balance - ? where id = ?";
//2.2 李四 + 500
String sql2 = "update account set balance = balance + ? where id = ?";
//3.获取执行sql对象
pstmt1 = conn.prepareStatement(sql1);
pstmt2 = conn.prepareStatement(sql2);
//4. 设置参数
pstmt1.setDouble(1,500);//张三
pstmt1.setInt(2,1);
pstmt2.setDouble(1,500);//李四
pstmt2.setInt(2,2);
//5.执行sql
pstmt1.executeUpdate();
// 手动制造异常
int i = 3/0;
pstmt2.executeUpdate();
//提交事务
conn.commit();
} catch (Exception e) { 【异常捕获最大个:无论转账过程出现什么错误,都捕获】
//事务回滚
try {
if(conn != null) { 【避免可能各种异常导致转账出错,Cnnection对象没有传进为null】
conn.rollback();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally {
JDBCUtils.close(pstmt1,conn);
JDBCUtils.close(pstmt2,null);
}
}
}