JAVA JDBC(存储过程和事务管理)
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)
//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; } } } }View Code 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(); // 事务提交:转账的两步操作同时成功 } catch(SQLException sqle){ try{ // 发生异常,回滚在本事务中的操做 tx.rollback(); // 事务回滚:转账的两步操作完全撤销 stmt.close(); conn.close(); }catch(Exception ignore){ } sqle.printStackTrace(); } }
上面的例子就是一个使用JTA事务的转账操作,该操作相对依赖于J2EE容器,并且需要通过JNDI的方式获取
UserTransaction和
Connection。
标准的分布式事务
一个分布式事务(Distributed Transaction)包括一个事务管理器(
transaction manager)和一个或多个资源管理器(resource manager)。一个资源管理器(resource manager)是任意类型的持久化数据存储。事务管理器(transaction manager)承担着所有事务参与单元者的相互通讯的责任。
JTA的实现方式也是基于以上这些分布式事务参与者实现的,具体的关于JTA的实现细节不是本文的重点,感兴趣的同学可以阅读JTA 深度历险 – 原理与实现
- 看上面关于分布式事务的介绍是不是和2PC中的事务管理比较像?的却,2PC其实就是符合XA规范的事务管理器协调多个资源管理器的一种实现方式。 我之前有几篇文章关于2PC和3PC的,那几篇文章中介绍过分布式事务中的事务管理器是如何协调多个事务的统一提交或回滚的,后面我还会有几篇文章详细的介绍一下和分布式事务相关的内容,包括但不限于全局事务、DTP模型、柔性事务等。
JTA的优缺点
JTA的优点很明显,就是提供了分布式事务的解决方案,严格的ACID。但是,标准的JTA方式的事务管理在日常开发中并不常用,因为他有很多缺点:
- 实现复杂 通常情况下,JTA UserTransaction需要从JNDI获取。这意味着,如果我们使用JTA,就需要同时使用JTA和JNDI。
总结
Java事务的类型有三种:
JDBC事务、
JTA(Java Transaction API)事务、
容器事务,其中JDBC的事务操作用法比较简单,适合于处理同一个数据源的操作。JTA事务相对复杂,可以用于处理跨多个数据库的事务,是分布式事务的一种解决方案。
这里还要简单说一下,虽然JTA事务是Java提供的可用于分布式事务的一套API,但是不同的J2EE平台的实现都不一样,并且都不是很方便使用,所以,一般在项目中不太使用这种较为负责的API。现在业内比较常用的分布式事务解决方案主要有异步消息确保型、TCC、最大努力通知等。关于这几种分布式事务解决方案,我会在后面的文章中介绍。欢迎关注与交流。
- Java-JDBC调用批处理、存储过程、事务
- Java-JDBC调用批处理、存储过程、事务
- Java-JDBC调用批处理、存储过程、事务
- Java-JDBC调用批处理、存储过程、事务
- java JDBC最基本的操作读取、调用存储过程、执行批处理、事务等
- Java-JDBC调用批处理、存储过程、事务
- java自学之路-----jdbc_分页查询,大数据,批处理,存储过程
- java jdbc 调用存储过程方法
- sql server存储过程的条件判断和事务管理
- MySQL存储过程之事务管理
- MySQL存储过程之事务管理
- MySQL存储过程之事务管理
- java通过jdbc访问oracle数据库的存储过程和存储函数
- MySQL存储过程之事务管理
- jdbc的pl/sql的java调用存储过程
- java 调用oracle存储过程传入 数组参数 不用纯jdbc连接
- Java学习笔记2-JDBC存储过程
- MySQL存储过程之事务管理
- 【java工具】使用jdbc访问数据库获取某个存储过程信息及下面参数信息
- MySQL存储过程之事务管理