目录

  1 Connection中的重用方法

  2 JDBC事务管理经典案例

1 Connection类中常用的方法回顾

  1.1 Statement createStatement() throws SQLException;

    创建一个Statement实例(即:创建一个SQL执行对象)

  1.2 PreparedStatement prepareStatement(String sql) throws SQLException;

    创建一个PreparedStatement对象(即:创建一个预编译SQL执行对象)

  1.3 void setAutoCommit(boolean autoCommit) throws SQLException;

    设置事务的自动提交(false为关闭自动提交,true为启动自动提交)

  1.4 void commit() throws SQLException;

    手动提交事务

  1.5 void rollback() throws SQLException;

    手动回滚事务

2 需要用到事务回滚的经典案例:银行转账案例

  转出和转入是一个事务,如果转出成功但是转入失败的会就需要进行事务回滚,否则就出出现转出者余额减少但是转入者余额没有增加

  注意:事务的提交与回滚是通过Connection提供的方法来调用的;本质上事务还是依赖数据库的实现;Connection的方法实质上也是调用了数据库事务机制.

  2.1 不使用事务控制的转账业务

    缺点:如果转入成功,但是转入失败的话,会造成转出者余额减少,但是转入者余额不变

    项目结构图

      

  1. package cn.xiangxu.entity;
  2.  
  3. import java.sql.Connection;
  4. import java.sql.PreparedStatement;
  5. import java.util.Scanner;
  6.  
  7. import cn.xiangxu.tools.DBUtil;
  8.  
  9. public class Test {
  10. public static void main(String[] args) {
  11. Scanner scanner = new Scanner(System.in);
  12. System.out.println("请输入转出用户名:");
  13. String outName = scanner.nextLine();
  14. System.out.println("请输入需要转出的资金额度:");
  15. Double money = Double.parseDouble(scanner.nextLine());
  16. System.out.println("请输入转入用户名:");
  17. String inName = scanner.nextLine();
  18. System.out.println("转出账户为:" + outName + "转出金额为:" + money + "转入账户为:" + inName);
  19.  
  20. Connection conn = null;
  21. try {
  22. conn = DBUtil.getConnection(); // 实例化连接对象
  23.  
  24. // conn.setAutoCommit(false); // 关闭自动提交事务功能
  25.  
  26. String sql = "UPDATE client "
  27. + "SET account = account - ? "
  28. + "WHERE name = ? ";
  29. PreparedStatement ps = conn.prepareStatement(sql);
  30. ps.setDouble(1, money);
  31. ps.setString(2, outName);
  32. Integer rs = ps.executeUpdate();
  33. if(rs > 0) {
  34. System.out.println("转出成功");
  35. } else {
  36. System.out.println("转出失败");
  37. return; // 转出失败跳出函数,不再执行下面的语句;但是finally中的语句还是会执行的,因为就算天塌下来finally中的语句都会执行
  38. }
  39.  
  40. System.out.println("======分割线=======");
  41.  
  42. String sql_in = "UPDATE client "
  43. + "SET account = account + ? "
  44. + "WHERE name = ? ";
  45. PreparedStatement ps_in = conn.prepareStatement(sql_in);
  46. ps_in.setDouble(1, money);
  47. ps_in.setString(2, inName);
  48. Integer judge_in = ps_in.executeUpdate();
  49. if(judge_in > 0) {
  50. System.out.println("转入成功");
  51. // conn.commit(); // 转出、转入都成功就提交事务
  52. } else {
  53. System.out.println("转入失败");
  54. // conn.rollback(); // 转出成功、转入失败就回滚事务
  55. }
  56.  
  57. // conn.setAutoCommit(true); // 打开自动提交事务
  58.  
  59. } catch (Exception e) {
  60. // TODO Auto-generated catch block
  61. e.printStackTrace();
  62. } finally {
  63. System.out.println("我是finally中的语句哟");
  64. try {
  65. DBUtil.closeConnection();
  66. } catch (Exception e) {
  67. // TODO Auto-generated catch block
  68. e.printStackTrace();
  69. }
  70. }
  71. }
  72. }

转账业务java源代码

  1. CREATE TABLE client (
  2. id INT (10) PRIMARY KEY,
  3. name VARCHAR (10),
  4. pwd VARCHAR (10),
  5. account INT (20)
  6. );

SQL语句

  1. package cn.xiangxu.tools;
  2.  
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import java.sql.Connection;
  6. import java.sql.SQLException;
  7. import java.util.Properties;
  8.  
  9. import org.apache.commons.dbcp.BasicDataSource;
  10.  
  11. public class DBUtil {
  12. /*
  13. * ThreadLocal用于线程跨方法共享数据使用
  14. * ThreadLocal内部有一个Map, key为需要共享数据的线程本身,value就是其需要共享的数据
  15. */
  16. private static ThreadLocal<Connection> tl; // 声明一个类似于仓库的东西
  17. private static BasicDataSource dataSource; // 声明一个数据库连接池对象
  18.  
  19. // 静态代码块,在类加载的时候执行,而且只执行一次
  20. static {
  21. tl = new ThreadLocal<Connection>(); // 实例化仓库对象
  22. dataSource = new BasicDataSource(); // 实例数据库连接池对象
  23.  
  24. Properties prop = new Properties(); // 创建一个Properties对象用(该对象可以用来加载配置文件中的属性列表)
  25. InputStream is = DBUtil.class.getClassLoader().getResourceAsStream("config/mysql.properties"); // 读取配置文件信息
  26. try {
  27. prop.load(is); // 加载配置文件中的属性列表
  28.  
  29. String driverClassName = prop.getProperty("driverClassName"); // 获取属性信息
  30. String url = prop.getProperty("url");
  31. String username = prop.getProperty("username");
  32. String password = prop.getProperty("password");
  33. Integer maxActive = Integer.parseInt(prop.getProperty("maxActive"));
  34. Integer maxWait = Integer.parseInt(prop.getProperty("maxWait"));
  35.  
  36. dataSource.setDriverClassName(driverClassName); // 初始化数据库连接池(即:配置数据库连接池的先关参数)
  37. dataSource.setUrl(url);
  38. dataSource.setUsername(username);
  39. dataSource.setPassword(password);
  40. dataSource.setMaxActive(maxActive);
  41. dataSource.setMaxWait(maxWait);
  42.  
  43. is.close(); // 关闭输入流,释放资源
  44. } catch (IOException e) {
  45. // TODO Auto-generated catch block
  46. e.printStackTrace();
  47. }
  48.  
  49. }
  50.  
  51. /**
  52. * 创建连接对象(注意:静态方法可以直接通过类名来调用)
  53. * @return 连接对象
  54. * @throws Exception
  55. */
  56. public static Connection getConnection() throws Exception {
  57. try {
  58. Connection conn = dataSource.getConnection(); // 创建连接对象(利用数据库连接池进行创建)
  59. tl.set(conn); // 将连接对象放到仓库中
  60. return conn;
  61. } catch (Exception e) {
  62. // TODO Auto-generated catch block
  63. e.printStackTrace();
  64. throw e;
  65. }
  66. }
  67.  
  68. /**
  69. * 关闭连接对象(注意:静态方法可以通过类名直接调用)
  70. * @throws Exception
  71. */
  72. public static void closeConnection() throws Exception {
  73. Connection conn = tl.get(); // 从仓库中取出连接对象
  74. tl.remove(); // 清空仓库
  75. if(conn != null) { // 判断连接对象是否释放资源
  76. try {
  77. conn.close();
  78. } catch (Exception e) {
  79. // TODO Auto-generated catch block
  80. e.printStackTrace();
  81. throw e;
  82. }
  83. }
  84. }
  85.  
  86. }

数据库连接池的java源代码

  1. # zhe shi zhu shi , yi ban bu yong zhong wen
  2. # deng hao liang bian mei you kong ge, mo wei mei you fen hao
  3. # hou mian bu neng you kong ge
  4. driverClassName=com.mysql.jdbc.Driver
  5. url=jdbc:mysql://localhost:3306/test
  6. username=root
  7. password=182838
  8. maxActive=100
  9. maxWait=3000

数据库信息文件

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  2. <modelVersion>4.0.0</modelVersion>
  3. <groupId>cn.xiangxu</groupId>
  4. <artifactId>testJDBC</artifactId>
  5. <version>0.0.1-SNAPSHOT</version>
  6. <dependencies>
  7. <dependency>
  8. <groupId>mysql</groupId>
  9. <artifactId>mysql-connector-java</artifactId>
  10. <version>5.1.37</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>junit</groupId>
  14. <artifactId>junit</artifactId>
  15. <version>4.12</version>
  16. </dependency>
  17. <dependency>
  18. <groupId>commons-dbcp</groupId>
  19. <artifactId>commons-dbcp</artifactId>
  20. <version>1.4</version>
  21. </dependency>
  22. </dependencies>
  23. </project>

maven依赖文件

  2.2 利用事务控制的转账业务

  1. package cn.xiangxu.entity;
  2.  
  3. import java.sql.Connection;
  4. import java.sql.PreparedStatement;
  5. import java.sql.SQLException;
  6. import java.util.Scanner;
  7.  
  8. import cn.xiangxu.tools.DBUtil;
  9.  
  10. public class Test {
  11. public static void main(String[] args) {
  12. Scanner scanner = new Scanner(System.in);
  13. System.out.println("请输入转出用户名:");
  14. String outName = scanner.nextLine();
  15. System.out.println("请输入需要转出的资金额度:");
  16. Double money = Double.parseDouble(scanner.nextLine());
  17. System.out.println("请输入转入用户名:");
  18. String inName = scanner.nextLine();
  19. System.out.println("转出账户为:" + outName + "转出金额为:" + money + "转入账户为:" + inName);
  20.  
  21. Connection conn = null;
  22. try {
  23. conn = DBUtil.getConnection(); // 实例化连接对象
  24.  
  25. conn.setAutoCommit(false); // 关闭自动提交事务功能
  26.  
  27. String sql = "UPDATE client "
  28. + "SET account = account - ? "
  29. + "WHERE name = ? ";
  30. PreparedStatement ps = conn.prepareStatement(sql);
  31. ps.setDouble(1, money);
  32. ps.setString(2, outName);
  33. Integer rs = ps.executeUpdate();
  34. if(rs > 0) {
  35. System.out.println("转出成功");
  36. } else {
  37. System.out.println("转出失败");
  38. return; // 转出失败跳出函数,不再执行下面的语句;但是finally中的语句还是会执行的,因为就算天塌下来finally中的语句都会执行
  39. }
  40.  
  41. System.out.println("======分割线=======");
  42.  
  43. String sql_in = "UPDATE client "
  44. + "SET account = account + ? "
  45. + "WHERE name = ? ";
  46. PreparedStatement ps_in = conn.prepareStatement(sql_in);
  47. ps_in.setDouble(1, money);
  48. ps_in.setString(2, inName);
  49. Integer judge_in = ps_in.executeUpdate();
  50. if(judge_in > 0) {
  51. System.out.println("转入成功");
  52. conn.commit(); // 转出、转入都成功就提交事务
  53. } else {
  54. System.out.println("转入失败");
  55. conn.rollback(); // 转出成功、转入失败就回滚事务
  56. }
  57.  
  58. conn.setAutoCommit(true); // 打开自动提交事务
  59.  
  60. } catch (Exception e) {
  61. // TODO Auto-generated catch block
  62. try {
  63. conn.rollback(); // 捕获到异常后也需要进行事务回滚
  64. } catch (SQLException e1) {
  65. // TODO Auto-generated catch block
  66. e1.printStackTrace();
  67. }
  68. e.printStackTrace();
  69. } finally {
  70. System.out.println("我是finally中的语句哟");
  71. try {
  72. DBUtil.closeConnection();
  73. } catch (Exception e) {
  74. // TODO Auto-generated catch block
  75. e.printStackTrace();
  76. }
  77. }
  78. }
  79. }

转账业务的java源代码

  2.3 将关闭自动提交功能、手动提交功能、手动回滚功能封装到一个类中

  1. package cn.xiangxu.tools;
  2.  
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import java.sql.Connection;
  6. import java.sql.SQLException;
  7. import java.util.Properties;
  8.  
  9. import org.apache.commons.dbcp.BasicDataSource;
  10.  
  11. public class DBUtil {
  12. /*
  13. * ThreadLocal用于线程跨方法共享数据使用
  14. * ThreadLocal内部有一个Map, key为需要共享数据的线程本身,value就是其需要共享的数据
  15. */
  16. private static ThreadLocal<Connection> tl; // 声明一个类似于仓库的东西
  17. private static BasicDataSource dataSource; // 声明一个数据库连接池对象
  18.  
  19. // 静态代码块,在类加载的时候执行,而且只执行一次
  20. static {
  21. tl = new ThreadLocal<Connection>(); // 实例化仓库对象
  22. dataSource = new BasicDataSource(); // 实例数据库连接池对象
  23.  
  24. Properties prop = new Properties(); // 创建一个Properties对象用(该对象可以用来加载配置文件中的属性列表)
  25. InputStream is = DBUtil.class.getClassLoader().getResourceAsStream("config/mysql.properties"); // 读取配置文件信息
  26. try {
  27. prop.load(is); // 加载配置文件中的属性列表
  28.  
  29. String driverClassName = prop.getProperty("driverClassName"); // 获取属性信息
  30. String url = prop.getProperty("url");
  31. String username = prop.getProperty("username");
  32. String password = prop.getProperty("password");
  33. Integer maxActive = Integer.parseInt(prop.getProperty("maxActive"));
  34. Integer maxWait = Integer.parseInt(prop.getProperty("maxWait"));
  35.  
  36. dataSource.setDriverClassName(driverClassName); // 初始化数据库连接池(即:配置数据库连接池的先关参数)
  37. dataSource.setUrl(url);
  38. dataSource.setUsername(username);
  39. dataSource.setPassword(password);
  40. dataSource.setMaxActive(maxActive);
  41. dataSource.setMaxWait(maxWait);
  42.  
  43. is.close(); // 关闭输入流,释放资源
  44. } catch (IOException e) {
  45. // TODO Auto-generated catch block
  46. e.printStackTrace();
  47. }
  48.  
  49. }
  50.  
  51. /**
  52. * 创建连接对象(注意:静态方法可以直接通过类名来调用)
  53. * @return 连接对象
  54. * @throws Exception
  55. */
  56. public static Connection getConnection() throws Exception {
  57. try {
  58. Connection conn = dataSource.getConnection(); // 创建连接对象(利用数据库连接池进行创建)
  59. tl.set(conn); // 将连接对象放到仓库中
  60. return conn;
  61. } catch (Exception e) {
  62. // TODO Auto-generated catch block
  63. e.printStackTrace();
  64. throw e;
  65. }
  66. }
  67.  
  68. /**
  69. * 关闭连接对象(注意:静态方法可以通过类名直接调用)
  70. * @throws Exception
  71. */
  72. public static void closeConnection() throws Exception {
  73. Connection conn = tl.get(); // 从仓库中取出连接对象
  74. tl.remove(); // 清空仓库
  75. if(conn != null) { // 判断连接对象是否释放资源
  76. try {
  77. conn.close();
  78. } catch (Exception e) {
  79. // TODO Auto-generated catch block
  80. e.printStackTrace();
  81. throw e;
  82. }
  83. }
  84. }
  85.  
  86. /**
  87. * 在执行SQL语句前关闭JDBC的自动提交事务功能
  88. * @throws SQLException
  89. */
  90. public static void tansBegin() throws SQLException {
  91. try {
  92. tl.get().setAutoCommit(false); // 从仓库中获取连接对象并调用setAutoCommit来关闭自动提交事务功能
  93. } catch(SQLException e) {
  94. e.printStackTrace();
  95. throw e;
  96. }
  97. }
  98.  
  99. /**
  100. * 手动回滚功能
  101. * @throws SQLException
  102. */
  103. public static void transBack() throws SQLException {
  104. tl.get().rollback(); // 从仓库中获取连接对象并调用rollback来实现事务回滚操作
  105. tl.get().setAutoCommit(true); // 回滚启动事务自动提交功能
  106. }
  107.  
  108. /**
  109. * 手动提交功能
  110. * @throws SQLException
  111. */
  112. public static void transCommit() throws SQLException {
  113. tl.get().commit(); // 从仓库中获取连接对象并调用commit来实现事务提交操作
  114. tl.get().setAutoCommit(true); // 提交后启动事务自动提交功能
  115. }
  116.  
  117. }

DBUtil

  1. package cn.xiangxu.entity;
  2.  
  3. import java.sql.Connection;
  4. import java.sql.PreparedStatement;
  5. import java.sql.SQLException;
  6. import java.util.Scanner;
  7.  
  8. import cn.xiangxu.tools.DBUtil;
  9.  
  10. public class Test {
  11. public static void main(String[] args) {
  12. Scanner scanner = new Scanner(System.in);
  13. System.out.println("请输入转出用户名:");
  14. String outName = scanner.nextLine();
  15. System.out.println("请输入需要转出的资金额度:");
  16. Double money = Double.parseDouble(scanner.nextLine());
  17. System.out.println("请输入转入用户名:");
  18. String inName = scanner.nextLine();
  19. System.out.println("转出账户为:" + outName + "转出金额为:" + money + "转入账户为:" + inName);
  20.  
  21. Connection conn = null;
  22. try {
  23. conn = DBUtil.getConnection(); // 实例化连接对象
  24.  
  25. DBUtil.tansBegin(); // 关闭自动提交事务功能
  26.  
  27. String sql = "UPDATE client "
  28. + "SET account = account - ? "
  29. + "WHERE name = ? ";
  30. PreparedStatement ps = conn.prepareStatement(sql);
  31. ps.setDouble(1, money);
  32. ps.setString(2, outName);
  33. Integer rs = ps.executeUpdate();
  34. if(rs > 0) {
  35. System.out.println("转出成功");
  36. } else {
  37. System.out.println("转出失败");
  38. return; // 转出失败跳出函数,不再执行下面的语句;但是finally中的语句还是会执行的,因为就算天塌下来finally中的语句都会执行
  39. }
  40.  
  41. System.out.println("======分割线=======");
  42.  
  43. String sql_in = "UPDATE client "
  44. + "SET account = account + ? "
  45. + "WHERE name = ? ";
  46. PreparedStatement ps_in = conn.prepareStatement(sql_in);
  47. ps_in.setDouble(1, money);
  48. ps_in.setString(2, inName);
  49. Integer judge_in = ps_in.executeUpdate();
  50. if(judge_in > 0) {
  51. System.out.println("转入成功");
  52. DBUtil.transCommit(); // 转出、转入都成功就提交事务
  53. } else {
  54. System.out.println("转入失败");
  55. DBUtil.transBack(); // 转出成功、转入失败就回滚事务
  56. }
  57.  
  58. } catch (Exception e) {
  59. // TODO Auto-generated catch block
  60. try {
  61. DBUtil.transBack();// 捕获到异常后也需要进行事务回滚
  62. } catch (SQLException e1) {
  63. // TODO Auto-generated catch block
  64. e1.printStackTrace();
  65. }
  66. e.printStackTrace();
  67. } finally {
  68. System.out.println("我是finally中的语句哟");
  69. try {
  70. DBUtil.closeConnection();
  71. } catch (Exception e) {
  72. // TODO Auto-generated catch block
  73. e.printStackTrace();
  74. }
  75. }
  76. }
  77. }

转账业务java源代码

    

JDBC03 利用JDBC实现事务提交与回滚【调用Connection中的方法实现事务管理】的更多相关文章

  1. 【转】批量复制操作(SqlBulkCopy)的出错处理:事务提交、回滚

    原文地址:http://blog.csdn.net/westsource/article/details/6658109 默认情况下,批量复制操作作为独立的操作执行. 批量复制操作以非事务性方式发生, ...

  2. RocketMQ源码分析之RocketMQ事务消息实现原下篇(事务提交或回滚)

    摘要: 事务消息提交或回滚的实现原理就是根据commitlogOffset找到消息,如果是提交动作,就恢复原消息的主题与队列,再次存入commitlog文件进而转到消息消费队列,供消费者消费,然后将原 ...

  3. mysql事务提交和回滚机制

    应用场景:   银行取钱,从ATM机取钱,分为以下几个步骤       1 登陆ATM机,输入密码:    2 连接数据库,验证密码:    3 验证成功,获得用户信息,比如存款余额等:    4 用 ...

  4. 对mysql事务提交、回滚的错误理解

    一.起因 begin或者START TRANSACTION开始一个事务 rollback事务回滚 commit 事务确认 人们对事务的解释如下:事务由作为一个单独单元的一个或多个SQL语句组成,如果其 ...

  5. 关于SAP的事务提交和回滚(LUW)

    1 Sap的更新的类型 在sap中,可以使用CALL FUNCTION ... IN UPDATE TASK将多个数据更新绑定到一个database LUW中.程序使用COMMIT WORK提交修改请 ...

  6. 关于jave在oracle驱动下事务提交与回滚问题

    一直以来,都觉得Connection假设设置了setAutoCommit(false)后.启动手工事务提交.必须手工进行commit或者rollback才行.今天正好遇到一个问题.结果大跌眼镜. 于是 ...

  7. MySQL事务提交与回滚

    提交 为了演示效果,需要打开两个终端窗口,使用同一个数据库,操作同一张表 step1:连接 终端1:查询商品分类信息 select * from goods_cates; step2:增加数据 终端2 ...

  8. J2EE分布式事务中的提交、回滚方法调用异常。

    这个是昨天上班的时候,写一个后台程序的调试程序时碰到的问题,和项目经理纠结了一天,最后搞定了.于是今天上班正好闲着,花了几乎一天的时间去网上找各种相关的资料.目前了解的内容如此: 根据使用的weblo ...

  9. nestd事务如果报错了 则回滚到外部事物保存点 且外部事物如果没异常的话 会正常提交 nested事务并不会提交;如果外部事物报错了 内部事务会一同回滚

    nestd事务如果报错了 则回滚到外部事物保存点 且外部事物如果没异常的话 会正常提交 nested事务并不会提交:如果外部事物报错了 内部事务会一同回滚

随机推荐

  1. AI探索(四)NumPy库的使用

    NumPy(Numerical Python) 是 Python 语言的一个扩展程序库,支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库. umPy 是一个运行速度非常快的数学库, ...

  2. CMC 实例管理

    有人问我,用户用的BW-QUERY看报表挺快的,用了BO发现很慢. 我心想,不会是什么高级优化吧,我可不会. 发现用WEBI时看报表很慢.那这个还是好解决的. 前面说那种情况,解决方法我只知道一种上H ...

  3. ES6 类(Class)基本用法和静态属性+方法详解

    原文地址:http://blog.csdn.net/pcaxb/article/details/53759637 ES6 类(Class)基本用法和静态属性+方法详解 JavaScript语言的传统方 ...

  4. New Concept English three (50)

    31 39 The New Year is a time for resolutions. Mentally, at least, most of us could compile formidabl ...

  5. Gym - 100623J Just Too Lucky (数位dp)

    给定n∈[1,1e12],求1到n的所有整数中,各位数字之和能整除它本身的数的个数. 这道题与UVA-11361类似,假如设dp[u][lim][m1][m2]为枚举到第u位(从低到高数),是否受限, ...

  6. UVA - 11107 Life Forms (广义后缀自动机)

    题意:给你n个字符串,求出在超过一半的字符串中出现的所有子串中最长的子串,按字典序输出. 对这n个字符串建广义后缀自动机,建完后每个字符串在自动机上跑一遍,沿fail树向上更新所有子串结点的出现次数( ...

  7. LeetCode Maximum Length of Pair Chain

    原题链接在这里:https://leetcode.com/problems/maximum-length-of-pair-chain/description/ 题目: You are given n  ...

  8. LeetCode Perfect Number

    原题链接在这里:https://leetcode.com/problems/perfect-number/#/description 题目: We define the Perfect Number ...

  9. bzoj 3887: Grass Cownoisseur Tarjan+Topusort

    题目: 给一个有向图,然后选一条路径起点终点都为1的路径出来,有一次机会可以沿某条边逆方向走,问最多有多少个点可以被经过?(一个点在路径中无论出现多少正整数次对答案的贡献均为1) 题解: 首先考虑简单 ...

  10. 3143 codevs 二叉树的序遍历

    题目描述 Description 求一棵二叉树的前序遍历,中序遍历和后序遍历 输入描述 Input Description 第一行一个整数n,表示这棵树的节点个数. 接下来n行每行2个整数L和R.第i ...