JAVA JDBC(存储过程和事务管理)
2017-08-31 14:17 晨曦曙光 阅读(8723) 评论(0) 编辑 收藏 举报1.什么是存储过程
存储过程(Stored Procedure)是在大型数据库系统中,一组为了完成特定功能的SQL 语句集,存储在数据库中,经过第一次编译后再次调用不需要再次编译,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。存储过程是数据库中的一个重要对象。
2.存储过程的优点
(1)存储过程只在创造时进行编译,以后每次执行存储过程都不需再重新编译,而一般SQL语句每执行一次就编译一次,所以使用存储过程可提高数据库执行速度。
(2)当对数据库进行复杂操作时(如对多个表进行Update,Insert,Query,Delete时),可将此复杂操作用存储过程封装起来与数据库提供的事务处理结合一起使用。
(3)存储过程可以重复使用,可减少数据库开发人员的工作量
(4)安全性高,可设定只有某此用户才具有对指定存储过程的使用权
3.存储过程的缺点
(1)如果更改范围大到需要对输入存储过程的参数进行更改,或者要更改由其返回的数据,则您仍需要更新程序集中的代码以添加参数、更新 GetValue() 调用,等等,这时候估计比较繁琐了。
(2)可移植性差
由于存储过程将应用程序绑定到 SQL Server,因此使用存储过程封装业务逻辑将限制应用程序的可移植性。
4.存储过程数据库操作(Navicat 操作)
4.1调用入参的存储过程
(1)在navicat 的Student中 创建添加数据的函数(add_date)
public static void add(Student stu) throws Exception{
//通过工具类,获取数据库链接对象
Connection conn= DBUtil.getConn();
//创建 sql 语句
String sql = "insert Student (name,age,address) value (?,?,?)";
//创建 预加载 的sql 语句执行对象
PreparedStatement ptmt=conn.prepareStatement(sql);
//给名字赋值
ptmt.setString(1, stu.getName());
//给年龄赋值
ptmt.setInt(2, stu.getAge());
ptmt.setString(3, stu.getAddress());
//执行 sql 语句
ptmt.execute();
}
4.2调用出参存储过程
(1)创建记录数据条数的函数(get_record_count)
4.3调用无参存储过程
(1)创建一个打印所有数据的函数(select_all)
(2)代码实例
public class StudentProcedureDao {
/**
* 1、查询数据
*/
public ResultSet select_all() {
ResultSet rs = null;
// 1 获取数据库连接对象
Connection conn = DBUtil.getConn();
try {
// 2 创建存储过程调用对象
CallableStatement cs = conn.prepareCall("call select_all()");
// 3 执行存储过程
cs.execute();
// 4 获取结果集
rs = cs.getResultSet();
} catch (SQLException e) {
e.printStackTrace();
}
return rs;
}
5.jdbc 存储过程实例代码如下:
//DBUtil 连接数据库 package com.j1702.db; import java.sql.*; public class DBUtil { private static final String URL="jdbc:mysql://127.0.0.1:3306/jdbc_test"; private static final String USER="root"; private static final String PASSWORD=""; private static Connection conn = null; static { try { // 1.加载mysql驱动 Class.forName("com.mysql.jdbc.Driver"); // 2.获得数据库链接对象conn setConn(DriverManager.getConnection(URL, USER, PASSWORD)); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static Connection getConn() { return conn; } public static void setConn(Connection conn) { DBUtil.conn = conn; } } //model 层 package com.j1702.model; public class Student { private Integer id; private String name; private Integer age; private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } } //dao 层 package com.j1702.dao; import java.sql.*; import java.util.*; import com.j1702.db.DBUtil; import com.j1702.model.Student; public class StudentDao { //1.增 public static void add(Student stu) throws Exception{ //通过工具类,获取数据库链接对象 Connection conn= DBUtil.getConn(); //创建 sql 语句 String sql = "insert Student (name,age,address) value (?,?,?)"; //创建 预加载 的sql 语句执行对象 PreparedStatement ptmt=conn.prepareStatement(sql); //给名字赋值 ptmt.setString(1, stu.getName()); //给年龄赋值 ptmt.setInt(2, stu.getAge()); ptmt.setString(3, stu.getAddress()); //执行 sql 语句 ptmt.execute(); } //2.删 public static void delete(Integer id) throws Exception{ //通过工具类,获取数据库链接对象 Connection conn= DBUtil.getConn(); //创建 sql 语句 String sql = " delete from Student where id =?"; //创建 预加载 的sql 语句执行对象 PreparedStatement ptmt=conn.prepareStatement(sql); //给名字赋值 ptmt.setInt(1,id); ptmt.execute(); } //3.改 public static void update(Student stu) throws Exception{ //通过工具类,获取数据库链接对象 Connection conn= DBUtil.getConn(); //创建 sql 语句 String sql = "update Student set name=?,age=?,address=? where id =?"; //创建 预加载 的sql 语句执行对象 PreparedStatement ptmt=conn.prepareStatement(sql); //给名字赋值 ptmt.setString(1, stu.getName()); //给年龄赋值 ptmt.setInt(2, stu.getAge()); ptmt.setString(3, stu.getAddress()); ptmt.setInt(4, stu.getId()); //执行 sql 语句 ptmt.execute(); } //4.查所有 public static void query(Student stu ) throws Exception{ //通过工具类,获取数据库链接对象 Connection conn= DBUtil.getConn(); //创建 sql 语句 String sql = "select * from Student"; //创建 预加载 的sql 语句执行对象 Statement stmt=conn.createStatement(); ResultSet rs=stmt.executeQuery(sql); while(rs.next()){ System.out.println("id:"+rs.getInt("id")+"\t \tname:"+rs.getString("name")+"\tage"+ rs.getInt("age")+"\taddress"+rs.getString("address")); } } //5.查某个 public static void queryWithId(Integer id) throws Exception{ Connection conn= DBUtil.getConn(); //创建 sql 语句 String sql = "select * from Student where id=?"; //创建 预加载 的sql 语句执行对象 PreparedStatement ptmt= conn.prepareStatement(sql); ptmt.setInt(1, id); ResultSet rs=ptmt.executeQuery(); while(rs.next()){ System.out.println("id:"+rs.getInt("id")+"\t \tname:" +rs.getString("name")+"\tage:"+rs.getInt("age")+"\taddress"+rs.getString("address")); } } //6.条件查询 // public static void queryWithParam(List<Map<String,Object>>param) throws Exception{ // // Connection conn= DBUtil.getConn(); // //创建 sql 语句 // StringBuffer sql = new StringBuffer(); // sql.append("select * from Student where 1=1"); // for (Map<String, Object> map : param) { // sql.append(" and "+map.get("key") // +"="+map.get("value")); //创建 预加载 的sql 语句执行对象 // PreparedStatement ptmt= conn.prepareStatement(sql.toString()); // // ResultSet rs=ptmt.executeQuery(); // // while(rs.next()){ // System.out.println("id:"+rs.getInt("id")+"\t \tname:" // +rs.getString("name")+"\tage:"+rs.getInt("age")+"\taddress:"+rs.getString("address")); // } // } public static void queryWithParam1(List<Map<String,Object>>param) throws Exception{ Connection conn= DBUtil.getConn(); //创建 sql 语句 StringBuffer sql = new StringBuffer(); sql.append("select * from Student where 1=1"); for (Map<String, Object> map : param) { sql.append(" and "+map.get("key") +" like '%"+map.get("value")+"%'"); //创建 预加载 的sql 语句执行对象 System.out.println(sql); PreparedStatement ptmt= conn.prepareStatement(sql.toString()); ResultSet rs=ptmt.executeQuery(); while(rs.next()){ System.out.println("id:"+rs.getInt("id")+"\t \tname:" +rs.getString("name")+"\tage:"+rs.getInt("age")+"\taddress:"+rs.getString("address")); System.out.println(sql); } } } public static void queryWithParam2(List<Map<String,Object>>param) throws Exception{ Connection conn= DBUtil.getConn(); //创建 sql 语句 StringBuffer sql = new StringBuffer(); sql.append("select * from Student where 1=1"); for (Map<String, Object> map : param) { sql.append(" and "+map.get("key") +"="+map.get("value")); //创建 预加载 的sql 语句执行对象 PreparedStatement ptmt= conn.prepareStatement(sql.toString()); ResultSet rs=ptmt.executeQuery(); while(rs.next()){ System.out.println("id:"+rs.getInt("id")+"\t \tname:" +rs.getString("name")+"\tage:"+rs.getInt("age")+"\taddress:"+rs.getString("address")); } } } public static void queryWithParam3(List<Map<String,Object>>param) throws Exception{ Connection conn= DBUtil.getConn(); //创建 sql 语句 StringBuffer sql = new StringBuffer(); sql.append("select * from Student where 1=1"); for (Map<String, Object> map : param) { sql.append(" and "+map.get("key") +"="+map.get("value")); //创建 预加载 的sql 语句执行对象 PreparedStatement ptmt= conn.prepareStatement(sql.toString()); ResultSet rs=ptmt.executeQuery(); while(rs.next()){ System.out.println("id:"+rs.getInt("id")+"\t \tname:" +rs.getString("name")+"\tage:"+rs.getInt("age")+"\taddress:"+rs.getString("address")); } } } } //action 行动层 package com.j1702.action; import com.j1702.dao.StudentDao; import com.j1702.model.Student; //这一层是我们的controller 层:他是view和model层的服务员 public class StudentAction { //想数据库中插入一条数据 public static void insert(Student stu) throws Exception{ StudentDao.add( stu); } //根据传入的id 删除学生 public static void delete(Integer id) throws Exception{ StudentDao.delete(id); } //更新学生信息数据 public static void update(Student stu) throws Exception{ StudentDao.update(stu); } //查找Student public static void find(Integer id) throws Exception{ StudentDao.queryWithId(id); } } //view 视图界面层 package com.j1702.view; import java.sql.Connection; import java.sql.PreparedStatement; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Scanner; import com.j1702.dao.*; import com.j1702.db.DBUtil; import com.j1702.model.Student; public class TestView { public static void main(String[] args) throws Exception{ //编程以终端为view 层,实现数据的增、删、改 /** * 请输入你要做的操作:A-添加,D-删除,U更新,F-查询,exit-退出 * * A 请输入插入的数据 name,age,address * */ login(); } public static void login(){ System.out.println("请输入你要做的操作:A-添加,D-删除,U更新,F-查询,exit-退出"); Scanner in= new Scanner (System.in); String xz=in.next(); switch(xz){ case "A" : try { add(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } break; case "D": try { delete(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } break; case "U": try { update(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } break; case "F" : find(); break; case "exit": exit(); break; default : System.out.println("没有你输入的选项,请重新选择"); login(); } } public static void add() throws Exception{ Student stu=new Student(); System.out.println("请输入插入的数据 name:"); Scanner cn=new Scanner (System.in); String name=cn.next(); System.out.println("请输入插入的数据 age:"); Scanner ca=new Scanner (System.in); int age=ca.nextInt(); System.out.println("请输入插入的数据 address :"); Scanner cd=new Scanner (System.in); String address = cd.next(); stu.setName(name); stu.setAge(age); stu.setAddress(address); StudentDao l1 =new StudentDao(); l1.add(stu); System.out.println("数据插入成功!"); System.out.println("请选择:1 继续插入数据, 2.返回上一层"); Scanner in= new Scanner (System.in); int x2=in.nextInt(); switch (x2) { case 1: add(); break; case 2: login(); break; default: System.out.println("没有你要的选项,请重新输入!"); } } public static void delete() throws Exception{ Connection conn= DBUtil.getConn(); System.out.println("请输入你要删除数据的ID"); Scanner in=new Scanner (System.in); Integer xid=in.nextInt(); StudentDao l2=new StudentDao(); l2.delete(xid); System.out.println("数据删除成功!"); System.out.println("请选择:1 继续继续数据, 2.返回上一层"); Scanner in1= new Scanner (System.in); int x2=in1.nextInt(); switch (x2) { case 1: delete(); break; case 2: login(); break; default: System.out.println("没有你要的选项,请重新输入!"); } } public static void update() throws Exception{ Student stu1=new Student(); System.out.println("请输入修改的数据 ID:"); Scanner ci=new Scanner (System.in); int id=ci.nextInt(); System.out.println("请输入修改的数据 name:"); Scanner cn1=new Scanner (System.in); String name=cn1.next(); System.out.println("请输入修改的数据 age:"); Scanner ca1=new Scanner (System.in); int age=ca1.nextInt(); System.out.println("请输入修改的数据 address :"); Scanner cd1=new Scanner (System.in); String address = cd1.next(); stu1.setId(id); stu1.setName(name); stu1.setAge(age); stu1.setAddress(address); StudentDao l3=new StudentDao(); l3.update(stu1); System.out.println("数据修改成功!"); System.out.println("请选择:1 继续修改数据, 2.返回上一层"); Scanner in1= new Scanner (System.in); int x2=in1.nextInt(); switch (x2) { case 1: update(); break; case 2: login(); break; default: System.out.println("没有你要的选项,请重新输入!"); } } public static void find(){ System.out.println("请选择:1.查询所有 2.查询某个 3.条件查询"); Scanner in= new Scanner (System.in); int xz3=in.nextInt(); switch(xz3){ case 1: Student stu=new Student(); StudentDao st=new StudentDao(); try { st.query(stu); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } cgf(); break; case 2: System.out.println("请输入要查询的ID:"); Scanner cid=new Scanner(System.in); Integer id1=cid.nextInt(); try { StudentDao st1=new StudentDao(); st1.queryWithId(id1); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } cgf(); break; case 3: tiaojian(); break; } } public static void exit(){ System.out.println("谢谢使用,再见!"); return; } public static void cgf(){ System.out.println("数据查询成功!"); System.out.println("请选择:1 继续查询数据, 2.返回上一层"); Scanner in3= new Scanner (System.in); int x3=in3.nextInt(); switch (x3) { case 1: find(); break; case 2: login(); break; default: System.out.println("没有你要的选项,请重新输入!"); } } public static void cgf1(){ System.out.println("数据查询成功!"); System.out.println("请选择:1 继续查询数据, 2.返回上一层"); Scanner in3= new Scanner (System.in); int x3=in3.nextInt(); switch (x3) { case 1: tiaojian(); break; case 2: login(); break; default: System.out.println("没有你要的选项,请重新输入!"); } } public static void tiaojian(){ System.out.println("请选择要查询的字段:1.id 查询 2.name 查询 3.age 查询 4.address 查询"); Scanner tjcx=new Scanner(System.in); int tx3=tjcx.nextInt(); switch(tx3){ case 1: System.out.println("请输入要查询的ID:"); Scanner cid=new Scanner(System.in); Integer id1=cid.nextInt(); try { StudentDao st1=new StudentDao(); st1.queryWithId(id1); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } cgf1(); break; case 2: System.out.println("请输入你要查询的 name:"); Scanner cn1=new Scanner(System.in); Map<String,Object> map = new HashMap<String,Object>(); map.put("key", "name"); map.put("value",cn1.next()); List<Map<String,Object>> li= new ArrayList<Map<String,Object>>(); li.add(map); try { StudentDao l4=new StudentDao(); l4.queryWithParam1(li); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } cgf1(); break; case 3: System.out.println("请输入你要查询的 age:"); Scanner cn2=new Scanner(System.in); String sage=cn2.next(); Map<String,Object> map1 = new HashMap<String,Object>(); map1.put("key", "age"); map1.put("value",sage); List<Map<String,Object>> li1= new ArrayList<Map<String,Object>>(); li1.add(map1); try { StudentDao l4=new StudentDao(); l4.queryWithParam2(li1); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } cgf1(); break; case 4: System.out.println("请输入你要查询的 address:"); Scanner cn3=new Scanner(System.in); String sadd=cn3.next(); Map<String,Object> map2 = new HashMap<String,Object>(); map2.put("key", "address"); map2.put("value",sadd); List<Map<String,Object>> li2= new ArrayList<Map<String,Object>>(); li2.add(map2); try { StudentDao l4=new StudentDao(); l4.queryWithParam3(li2); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } cgf1(); break; } } }
6.java JDBC 事务管理
6.1事务的基本概念:事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功。
6.2数据库开启事务命令
方式一:利用SQL语句管理事务
start transaction;--开启事务,这条语句之后的sql语句将处在一个事务当中,这些sql语句并不会立即执行
Commit--提交事务,一旦提交事务,事务中的所有sql语句才会执行。
Rollback -- 回滚事务,将之前所有的sql取消。
方式二:在数据库中存在一个自动提交变量,通过show variables like '%commit%'-----autocommit 值是on,说明开启了事务自动提交。
可以 set autocommint = off(set autocommint=0),关闭自动提交,此时输入的sql语句就不会自动提交了,需要手动roolback或commit
6.3使用事务
(1)当Jdbc程序向数据库获得一个Connection对象时,默认情况下这个Connection对象会自动向数据库提交在它上面发送的SQL语句。若想关闭这种默认提交方式,让多条SQL在一个事务中执行,可使用下列语句:
(2)JDBC控制事务语句
Connection.setAutoCommit(false); // 相当于start transaction
Connection.rollback(); rollback
Connection.commit(); commit
(3)事务管理实例代码:
(1)当Jdbc程序向数据库获得一个Connection对象时,默认情况下这个Connection对象会自动向数据库提交在它上面发送的SQL语句。若想关闭这种默认提交方式,让多条SQL在一个事务中执行,可使用下列语句:
(2)JDBC控制事务语句
Connection.setAutoCommit(false); // 相当于start transaction
Connection.rollback(); rollback
Connection.commit(); commit
7.事务的特性(ACID)
(1)原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
(2)一致性(Consistency)
事务前后数据的完整性必须保持一致。
(3)隔离性(Isolation)
事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。
(4)持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
8.事务隔离级别(Transaction Isolation Levels)
JDBC定义了五种事务隔离级别:
TRANSACTION_NONE JDBC 驱动不支持事务
TRANSACTION_READ_UNCOMMITTED 允许脏读、不可重复读和幻读。
TRANSACTION_READ_COMMITTED 禁止脏读,但允许不可重复读和幻读。
TRANSACTION_REPEATABLE_READ 禁止脏读和不可重复读,单运行幻读。
TRANSACTION_SERIALIZABLE 禁止脏读、不可重复读和幻读。
9.事务隔离性的设置语句
数据库共定义了四种隔离级别:
Serializable:可避免脏读、不可重复读、虚读情况的发生。(串行化)
Repeatable read:可避免脏读、不可重复读情况的发生。(可重复读)不可以避免虚读
Read committed:可避免脏读情况发生(读已提交)
Read uncommitted:最低级别,以上情况均无法保证。(读未提交)
set [global/session] transaction isolation level 设置事务隔离级别
select @@tx_isolation查询当前事务隔离级别
安全性来说:Serializable>Repeatable read>Read committed>Read uncommitted
效率来说:Serializable<Repeatable read<Read committed<Read uncommitted
通常来说,一般的应用都会选择Repeatable read或Read committed作为数据库隔离级别来使用。
mysql默认的数据库隔离级别为:REPEATABLE-READ
如何查询当前数据库的隔离级别?select @@tx_isolation;
如何设置当前数据库的隔离级别?set [global/session] transaction isolation level ...;
~此种方式设置的隔离级别只对当前连接起作用。
set transaction isolation level read uncommitted;
set session transaction isolation level read uncommitted;
~此种方式设置的隔离级别是设置数据库默认的隔离级别
set global transaction isolation level read uncommitted;
10.JDBC事务的优缺点
JDBC为使用Java进行数据库的事务操作提供了最基本的支持。通过JDBC事务,我们可以将多个SQL语句放到同一个事务中,保证其ACID特性。JDBC事务的主要优点就是API比较简单,可以实现最基本的事务操作,性能也相对较好。
但是,JDBC事务有一个局限:一个 JDBC 事务不能跨越多个数据库!!!
所以,如果涉及到多数据库的操作或者分布式场景,JDBC事务就无能为力了。
11.java JDBC 事务实例代码如下:
//db 层 package com.j1702.db; import java.sql.*; public class DBUtil { private static final String URL = "jdbc:mysql://127.0.0.1:3306/jdbc_trading"; private static final String USER = "root"; private static final String PASSWORD = ""; private static Connection conn = null; // (static{}(即static块),会在类被加载的时候执行且仅会被执行一次,一般用来初始化静态变量和调用静态方法,) static { try { // 1 加载驱动程序 Class.forName("com.mysql.jdbc.Driver"); // 2 获得数据的连接 conn = DriverManager.getConnection(URL, USER, PASSWORD); } catch (ClassNotFoundException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } catch (SQLException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } } // 静态方法获取连接 public static Connection geConnection() { return conn; } } //model 层 package com.j1702.model; // 转账记录表的映射 public class Transfer { private Integer id; // 主键 private String from_where; // 转账人 private String to_where; // 收款人 private Integer money; // 转账金额 private String time; // 转账时间 // 无参构造 public Transfer(){} // 有参构造 public Transfer( String from_where, String to_where, Integer money) { this.from_where = from_where; this.to_where = to_where; this.money = money; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getFrom_where() { return from_where; } public void setFrom_where(String from_where) { this.from_where = from_where; } public String getTo_where() { return to_where; } public void setTo_where(String to_where) { this.to_where = to_where; } public Integer getMoney() { return money; } public void setMoney(Integer money) { this.money = money; } public String getTime() { return time; } public void setTime(String time) { this.time = time; } } package com.j1702.model; public class User { private Integer id; // 主键 private String name; // 用户名 private Integer money; // 用户余额 public User(){} public User(String name, Integer money){ this.name = name; this.money = money; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getMoney() { return money; } public void setMoney(Integer money) { this.money = money; } } // dao 层 package com.j1702.dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import com.j1702.db.DBUtil; import com.j1702.model.Transfer; public class TransferDao { // 增加 public void addRecord(Transfer t) throws SQLException { Connection conn = DBUtil.geConnection(); String sql = "insert Transfer (from_where,to_where,money) values(?,?,?)"; PreparedStatement ptmt = conn.prepareStatement(sql); ptmt.setString(1, t.getFrom_where()); ptmt.setString(2, t.getTo_where()); ptmt.setInt(3, t.getMoney()); ptmt.execute(); } // 查询 public List<Transfer> queryTransfers() throws Exception { Connection conn = DBUtil.geConnection(); Statement stmt = conn.createStatement(); ResultSet res = stmt.executeQuery("select * from Transfer"); List<Transfer> t_list = new ArrayList<Transfer>(); Transfer t = null; while (res.next()) { t = new Transfer(); t.setFrom_where(res.getString("from_where")); t.setTo_where(res.getString("to_where")); t.setMoney(res.getInt("money")); t.setTime(res.getString("time")); t_list.add(t); } return t_list; } } package com.j1702.dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import com.j1702.db.DBUtil; import com.j1702.model.User; public class UserDao { // 修改(因为转账会发生 money 的增加或减少) public void updateUser(User u) throws SQLException { Connection conn = DBUtil.geConnection(); String sql = "update User set money=? where name=?"; PreparedStatement ptmt = conn.prepareStatement(sql); ptmt.setInt(1, u.getMoney()); ptmt.setString(2, u.getName()); ptmt.execute(); } // 查询 public User queryUser(String name) throws Exception { Connection conn = DBUtil.geConnection(); String sql = "select * from User where name=?"; PreparedStatement ptmt = conn.prepareStatement(sql); ptmt.setString(1, name); ResultSet res = ptmt.executeQuery(); User u = new User(); while (res.next()) { u.setName(res.getString("name")); u.setMoney(res.getInt("money")); } return u; } } package com.j1702.action; import java.util.List; import com.j1702.dao.TransferDao; import com.j1702.model.Transfer; public class TransferAction { // 1 新增转账记录 public void add(Transfer t) throws Exception{ TransferDao dao = new TransferDao(); dao.addRecord(t); } // 2 查询转账记录 public List<Transfer> query() throws Exception{ TransferDao dao = new TransferDao(); return dao.queryTransfers(); } } package com.j1702.action; import java.sql.SQLException; import com.j1702.dao.UserDao; import com.j1702.model.User; public class UserAction { public User query(String name) throws Exception{ UserDao dao = new UserDao(); return dao.queryUser(name); } public void update(User u) throws SQLException{ UserDao dao = new UserDao(); dao.updateUser(u); } } //view 层 package com.j1702.view; import java.sql.Connection; import java.util.List; import java.util.Scanner; import com.j1702.action.TransferAction; import com.j1702.action.UserAction; import com.j1702.db.DBUtil; import com.j1702.model.Transfer; import com.j1702.model.User; public class TestView { public static void main(String[] args) throws Exception { Scanner scan = new Scanner(System.in); while(true){ System.out.println("请操作指令:1-转账;2-查询;其他任意键退出"); String operator = scan.nextLine(); switch (operator) { case "1": { System.out.println("请输入付款人姓名:"); String from_name = scan.nextLine(); System.out.println("请输入收款人姓名:"); String to_name = scan.nextLine(); System.out.println("请输入转账金额:"); Integer money = scan.nextInt(); scan.nextLine(); Transfer tf = new Transfer(from_name, to_name, money); // 处理数据库操作 UserAction ua = new UserAction(); // 查询 User u1 = ua.query(from_name); User u2 = ua.query(to_name); // 操作金额的增减 u1.setMoney(u1.getMoney()-money); u2.setMoney(u2.getMoney()+money); // 更新 需要添加事务 Connection conn = DBUtil.geConnection(); //设置自动提交为false conn.setAutoCommit(false); //需要绑定执行的代码 ua.update(u1); ua.update(u2); // 增加转账记录 TransferAction ta = new TransferAction(); ta.add(tf); //手动提交事务 conn.commit(); } break; case "2": { TransferAction ta = new TransferAction(); List<Transfer> l_t = ta.query(); for (Transfer transfer : l_t) { System.out.println(transfer.getTime() + "\t->\t" + transfer.getFrom_where() + "\t->\t" + transfer.getTo_where() + "\t->\t" + transfer.getMoney() ); } } break; default: { System.out.println("退出!"); scan.close(); System.exit(0); } break; } } } }
12.JTA事务
为什么需要JTA
通常,JDBC事务就可以解决数据的一致性等问题,鉴于他用法相对简单,所以很多人关于Java中的事务只知道有JDBC事务,或者有人知道框架中的事务(比如Hibernate、Spring)等。但是,由于JDBC无法实现分布式事务,而如今的分布式场景越来越多,所以,JTA事务就应运而生。
如果,你在工作中没有遇到JDBC事务无法解决的场景,那么只能说你做的项目还都太小。拿电商网站来说,我们一般把一个电商网站横向拆分成商品模块、订单模块、购物车模块、消息模块、支付模块等。然后我们把不同的模块部署到不同的机器上,各个模块之间通过远程服务调用(RPC)等方式进行通信。以一个分布式的系统对外提供服务。
一个支付流程就要和多个模块进行交互,每个模块都部署在不同的机器中,并且每个模块操作的数据库都不一致,这时候就无法使用JDBC来管理事务。我们看一段代码:
/** 支付订单处理 **/
@Transactional(rollbackFor = Exception.class)
public void completeOrder() {
orderDao.update(); // 订单服务本地更新订单状态
accountService.update(); // 调用资金账户服务给资金帐户加款
pointService.update(); // 调用积分服务给积分帐户增加积分
accountingService.insert(); // 调用会计服务向会计系统写入会计原始凭证
merchantNotifyService.notify(); // 调用商户通知服务向商户发送支付结果通知
}
上面的代码是一个简单的支付流程的操作,其中调用了五个服务,这五个服务都通过RPC的方式调用,请问使用JDBC如何保证事务一致性?我在方法中增加了@Transactional
注解,但是由于采用调用了分布式服务,该事务并不能达到ACID的效果。
JTA事务比JDBC事务更强大。一个JTA事务可以有多个参与者,而一个JDBC事务则被限定在一个单一的数据库连接。下列任一个Java平台的组件都可以参与到一个JTA事务中:JDBC
连接、JDO PersistenceManager
对象、JMS
队列、JMS
主题、企业JavaBeans(EJB
)、一个用J2EE Connector Architecture
规范编译的资源分配器。
JTA的定义
Java事务API(Java Transaction API
,简称JTA ) 是一个Java企业版 的应用程序接口,在Java环境中,允许完成跨越多个XA资源的分布式事务。
JTA和它的同胞Java事务服务(JTS;Java TransactionService),为J2EE平台提供了分布式事务服务。不过JTA只是提供了一个接口,并没有提供具体的实现,而是由j2ee服务器提供商 根据JTS规范提供的,常见的JTA实现有以下几种:
- 1.J2EE容器所提供的JTA实现(JBoss)
- 2.独立的JTA实现:如JOTM,Atomikos.这些实现可以应用在那些不使用J2EE应用服务器的环境里用以提供分布事事务保证。如Tomcat,Jetty以及普通的java应用。
JTA里面提供了 java.transaction.UserTransaction
,里面定义了下面几个方法
begin
:开启一个事务
commit
:提交当前事务
rollback
:回滚当前事务
setRollbackOnly
:把当前事务标记为回滚
setTransactionTimeout
:设置事务的事件,超过这个事件,就抛出异常,回滚事务
这里,值得注意的是,不是使用了UserTransaction
就能把普通的JDBC操作直接转成JTA操作,JTA对DataSource、Connection和Resource 都是有要求的,只有符合XA规范,并且实现了XA规范的相关接口的类才能参与到JTA事务中来,关于XA规范,请看我的另外一篇文章中有相关介绍。这里,提一句,目前主流的数据库都支持XA规范。
要想使用用 JTA 事务,那么就需要有一个实现
javax.sql.XADataSource
、javax.sql.XAConnection
和javax.sql.XAResource
接口的 JDBC 驱动程序。一个实现了这些接口的驱动程序将可以参与 JTA 事务。一个XADataSource
对象就是一个XAConnection
对象的工厂。XAConnection
是参与 JTA 事务的 JDBC 连接。要使用JTA事务,必须使用
XADataSource
来产生数据库连接,产生的连接为一个XA连接。XA连接(
javax.sql.XAConnection
)和非XA(java.sql.Connection
)连接的区别在于:XA可以参与JTA的事务,而且不支持自动提交。
public void JtaTransfer() {
javax.transaction.UserTransaction tx = null;
java.sql.Connection conn = null;
try{
tx = (javax.transaction.UserTransaction) context.lookup("java:comp/UserTransaction"); //取得JTA事务,本例中是由Jboss容器管理
javax.sql.DataSource ds = (javax.sql.DataSource) context.lookup("java:/XAOracleDS"); //取得数据库连接池,必须有支持XA的数据库、驱动程序
tx.begin();
conn = ds.getConnection();
// 将自动提交设置为 false,
//若设置为 true 则数据库将会把每一次数据更新认定为一个事务并自动提交
conn.setAutoCommit(false);
stmt = conn.createStatement();
// 将 A 账户中的金额减少 500
stmt.execute("\
update t_account set amount = amount - 500 where account_id = 'A'");
// 将 B 账户中的金额增加 500
stmt.execute("\
update t_account set amount = amount + 500 where account_id = 'B'");
// 提交事务
tx.commit();
// 事务提交:转账的两步操作同时成功