JDBC

JDBC

JDBC(Java Data Base Connectivity, java数据库连接)是一种用于执行SQL语句的javaAPI,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。

  1. 加载驱动
  2. 连接数据库
  3. 使用语句操作数据库
  4. 关闭数据库连接,释放资源

加载驱动(以MySQL举例)

Java连接MySQL需要驱动包,最新下载地址为:http://dev.mysql.com/downloads/connector/j/,解压后得到jar库文件,然后在对应的项目中导入该库文件。

MySQL8.0之前版本

驱动名:com.mysql.jdbc.Driver
加载方式:Class.forName("com.mysql.jdbc.Driver")

MySQL8.0之后版本

驱动名:com.mysql.cj.jdbc.Driver
加载方式:Class.forName("com.mysql.cj.jdbc.Driver")

连接关闭数据库

  1. DriverManager:驱动管理类,负责获取数据库的连接
    1. getConnection(String url, String user, String password):试图建立给定数据库的URL连接
  2. 数据库连接地址格式:jdbc:mysql://IP:端口号/数据库名称
  3. Connection接口:与特定的数据库连接(会话)
    1. createStatement():创建Statement执行数据库更删改查等操作
    2. close():立即释放此Connection对象的数据库和JDBC资源,而不是等待他们被自动释放

举个例子:

import java.sql.*;
public class MySQLDemo{
    
    //MySQL8.0以下版本-JDBC数据库名及数据库URL,数据库名称为test_db
    static final String JDBC_DRIVER = "com.mysql.Driver";
    statis final String DB_URL="jdbc:mysql://localhost:3306/test_db"
    
    //MySQL8.0以上版本-JDBC数据库名及数据库URL,数据库名称为test_db
    //static final String JDBC_DRIVER="com.mysql.cj.jdbc.Driver";
    //static final String DB_URL="jdbc:mysql://localhost:3306/test_db"
    
    //数据库的用户名与密码,根据你自己的数据库进行设置
    static final String User ="root";
    static final String Password="123456";

    public static void main(String... args){
        Connection conn = null;
        try{
            //注册JDBC驱动
            Class.forName(JDBC_DRIVER);
            System.out.println("注册驱动成功");
            //连接数据库并得到Connection会话
            conn = DriverManager.getConnection(DB_URL, User, Password);
        }catch(ClassNotFoundException e){
            e.printStackTrace();
            System.out.println("注册驱动失败")
        }finally{
      //最后关闭Connection会话,释放资源
            if(conn !=null){
                try{
                    conn.close();
                }catch(SQLException e){
                    e.printStackTrace();
                }
            }
        }
    }
}

操作数据库(Statement)

  1. 作用:用于执行静态SQL语句并返回它所生成结果的对象;
  2. int executeUpdate(String sql)执行给定SQL语句,该语句可能为INSERT、UPDATE或DELETE语句,或者不返回任何内容的SQL语句(如SQL DDL 语句)
  3. void close():立即释放此Statement对象的数据库和资源,而不是等待该对象自动关闭

封装DBUtil工具类

public class DBUtil{
    
    //MySQL8.0以下版本-JDBC数据库名及数据库URL,数据库名称为test_db
    static final String JDBC_DRIVER = "com.mysql.Driver";
    statis final String DB_URL="jdbc:mysql://localhost:3306/test_db"
    
    //MySQL8.0以上版本-JDBC数据库名及数据库URL,数据库名称为test_db
    //static final String JDBC_DRIVER="com.mysql.cj.jdbc.Driver";
    //static final String DB_URL="jdbc:mysql://localhost:3306/test_db"
    
    //数据库的用户名与密码,根据你自己的数据库进行设置
    static final String User ="root";
    static final String Password="123456";

/**
 *获取数据库连接
 */
    public Connection getConnection() throws Exception{
            //注册JDBC驱动
            Class.forName(JDBC_DRIVER);
            //连接数据库并得到Connection会话
            return DriverManager.getConnection(DB_URL, User, Password);
    }
    
    /**
     *关闭连接
     * @param conn
     * @throws Exception
     */
    public void close(Connection conn) throws Exception{
        if(conn!=null){
            conn.close();
        }
    }
  
  /**
   * 同时关闭Statement和Connection
   */
  public void close(Statement stmt, Connection conn) throws Exception{
    if(stmt !=null){
      stmt.close();
    }
    if(conn !=null){
      conn.close();
    }
  }
}

下面执行连接数据库均采用上述的DBUtil工具类。

创建测试数据

接下来我们在MySQL中创建test_db数据库并创建websites数据表,表结构如下:

CREATE TABLE `websites` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` char(20) NOT NULL DEFAULT '' COMMENT '站点名称',
  `url` varchar(255) NOT NULL DEFAULT '',
  `alexa` int(11) NOT NULL DEFAULT '0' COMMENT 'Alexa 排名',
  `country` char(10) NOT NULL DEFAULT '' COMMENT '国家',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;

测试插入:

public class TestInsert{
    public static void main(String... args) throws Exception{
        DBUtil dbUtil = new DBUtil();
    //SQL语句,这里的1也可以设置为null,因为为它设置了自增
        String sql="INSERT INTO `websites` VALUES ('1', 'Google', 'https://www.google.cm/', '1', 'USA')";
        Connection conn = dbUtil.getConnection();//获取数据库连接
        Statement stmt = conn.createStatement();//获取Statement
    //进行SQL语句进行插入,其中返回结果表示数据库操作的数量,这里插入一条数据返回的就是1,插入N条返回N。
        int result = stmt.executeUpdate(sql);
        System.out.println("操作的结果:"+ result+"数据");
        stmt.close();关闭stmt
        dbUtil.close(conn);//关闭Connection
    }
}

这里我们将需要插入的数据直接封装在SQL语句中,但是实际开发的时候是由外部传入的,我们可以进行修改为:

public class TestInsert{
    
    public static int addWebsite(String name, String url, int alexa, String country) throws Exception{
            DBUtil dbUtil = new DBUtil();
    //SQL语句,这里的1也可以设置为null,因为为它设置了自增
        String sql="INSERT INTO `websites` VALUES (null, '"+name+"', '"+website+"', '"+alexa+"', '"+country+"')";
        Connection conn = dbUtil.getConnection();//获取数据库连接
        Statement stmt = conn.createStatement();//获取Statement
    //进行SQL语句进行插入,其中返回结果表示数据库操作的数量,这里插入一条数据返回的就是1,插入N条返回N。
        int result = stmt.executeUpdate(sql);
        System.out.println("操作的结果:"+ result+"数据");
        stmt.close();关闭stmt
        dbUtil.close(conn);//关闭Connection
        return result;
    }
  
    public static void main(String... args) throws Exception{
        int result=addWebsite("Google", "https://www.google.com/", 1, "USA");
        if(result==1){
            System.out.println("添加成功");
        }else{
            System.out.println("添加失败");
        }
    }
}

但是传入那么多参数并不符合面向对象的思想,我们应该将网站信息封装成Java类,以对象的形式进行操作,具体如下:

class Website{
    private int id;
    private String name;
    private String url;
    private int alexa;
    private String country;
    //下面是对应的构造方法、Getter和Setter方法,请自行添加
}

添加完Website类之后,TestInsert添加AddWebsite2方法,操作如下:

public static int addWebsite2(Website website) throws Exception{
  DBUtil dbUtil = new DBUtil();
    //SQL语句,这里的1也可以设置为null,因为为它设置了自增
        String sql="INSERT INTO 'websites' VALUES (null, '"+website.getName()+"', '"+website.getUrl()+"', '"+website.getAlexa()+"', '"+website.getCountry()+"')";
        Connection conn = dbUtil.getConnection();//获取数据库连接
        Statement stmt = conn.createStatement();//获取Statement
    //进行SQL语句进行插入,其中返回结果表示数据库操作的数量,这里插入一条数据返回的就是1,插入N条返回N。
        int result = stmt.executeUpdate(sql);
        System.out.println("操作的结果:"+ result+"数据");
        stmt.close();关闭stmt
        dbUtil.close(conn);//关闭Connection
        return result;
}

修改main方法如下:

public static void main(String... args) throws Exception{
    Website website = new Website("Google", "http://www.google.com/", 1, "USA");
    int result=addWebsite2(website);    
    System.out.println(result==1?"添加成功":"添加失败");
}

其中更新、删除操作进本相同,不同的是数据库语句,这里就不过多的举例子!

PreparedStatement实现增删改

PreparedStatement是Statement的子接口,属于预处理操作。与Statement不同的是,PreparedStatement在操作时,是先在数据表中准备好一条SQL语句,但是此SQL语句的具体内容暂时不设置,而是在之后进行设置。

插入

我们仍然使用上面的数据库,使用PreparedStatement插入一个网站,具体如下:

DBUtil dbUtil = new DBUtil();

public static int addWebsite(Website website) throws Exception{
    Connection conn = dbUtil.getConnection();
    String sql = "INSERT INTO websites values(null, ?, ?, ?, ?)";
  PreparedStatement pstmt = conn.preparedStatement(sql);
  pstmt.setString(1, website.getName());//从1开始
  pstmt.setString(2, website.getUrl());
  pstmt.setInt(3, website.getAlexa());
  pstmt.setString(4, website.getCountry());
    //所以SQL语句的四个问号分别对应name、URL、alexa、country
  int result = conn.executeUpdate();
  dbUtil.close(pstmt, conn);
  return result;
}

更新

现在数据库中已经存在了(id=1,name=Google,url="https://www.google.com/",alexa=1,country=USA)的数据了。如果我们要将更改这条数据,用PreparedStatement接口应该怎么做呢?

DBUtil dbUtil = new DBUtil();

public static int updateWebsite(Website website) throws Exception{
    Connection conn = dbUtil.getConnection();
  String sql ="UPDATE websites set name=?,url=?,alexa=?,country=? where id=?";
  PreparedStatement pstmt = conn.preparedStatement(sql);
  pstmt.putString(1, website.getName());
  pstmt.putString(2, website.getUrl());
  pstmt.putInt(3, website.getAlexa());
  pstmt.putString(4, website.getCountry());
  pstmt.putInt(5, website.getId());
  int result =pstmt.executeUpdate();
  dbUtil.close(pstmt, conn);
  return result;
} 

删除

假设我们要删除id=1的数据,代码如下:

DBUtil dbUtil = new DBUtil();
//删除指定id的websiste
public static int deleteWebsite(int id) throws Exception{
  Connection conn = dbUtil.getConnection();
  String deleteSql ="DELETE from websites where id=?";
  PreparedStatement pstmt = conn.preparedStatement(deleteSql);
  pstmt.putInt(1, id);
  int result=pstmt.executeUpdate();
  dbUtil.close(pstmt, conn);
  return result;
}

ResultSet结果集

当我们查询数据库时,返回的是一个二维的结果集,我们需要使用ResultSet来遍历结果,获取每一行的数据。

ResultSet光标最初位于第一行之前,第一次调用next()方法使第一行成为当前行,第二次调用使第二行成为当前行,以此类推。

  1. boolean next():将光标从当期位置向前移一行;
  2. String getString(int columnIndex):以Java编程语言中String的形式获取ResultSet对象的当前行中指定列的值;
  3. String getString(int columnLabel):以Java编程语言中String的形式获取ResultSet对象的当前行中指定列的值。

实战

DBUtil dbUtil = new DBUtil();

public List<Website> queryAllWebsite()throws Exception{
    Connection conn = dbUtil.getConnection();
  String querySql = "SELECT * from websites";
  PreparedStatement pstmt = conn.preparedStatement(querySql);
  ResultSet rs = pstmt.executeQuery();//executeQuery执行查询,返回ResultSet结果集
    List<Website> websites = new ArrayList();
  while(rs.next()){
    int id = rs.getInt(1);//获取第一列的值:id
    String name=rs.getString(2);//获取第二列的值:name
    String url=rs.getString(3);//获取第三列的值:url
    int alexa=rs.getInt(4);//获取第四列的值:alexa
    String country=rs.getString(5);//获取第五列的值:country
    Website website=new Website(id, name, url, alexa, country);
    websites.add(website);
  }
  dbUtil.close(pstmt, conn);
  return websites;
}

在while语句中我们也可以直接使用字段名称进行查询,操作如下:

int id = rs.getInt("id");
String name=rs.getString("name");
String url=rs.getString("url");
int alexa=rs.getInt("alexa");
String country=rs.getString("country");

处理大数据对象

  • CLOB:(character large object)可以存储字符大数据对象,比如长篇小说;
  • BLOB:(binary large object)可以存放二进制大数据对象,比如图片、电影、音乐;

CLOB实战

因为CLOB是存储字符大数据对象,我们可以设置字段的类型为TEXT或者LONGTEXT

  1. websites表中增加about字段,字段类型为longtext
  2. 假设在电脑的C盘根目录下新增一个helloworld.txt文本文件,内容不限;
  3. 在Website类中增加类型为File的about字段。

实例代码如下:

public class TestClob{
  
  DBUtil dbUtil = new DBUtil();
  //插入网站
  public int addWebsite(Website website) throws Exception{
    Connection conn = dbUtil.getConnection();
    String sql = "INSERT INTO websites values(null, ?,?,?,?,?)";//多了about的?
    PreparedStatement pstmt = conn.preparedStatement(sql);
    pstmt.putString(1, website.getName());
    pstmt.putString(2, website.getUrl());
    pstmt.putInt(3, website.getAlexa());
    pstmt.putString(4, website.getCountry());
    File about=website.getAbout();
    InputStream in = new FileInputStream(about);
    pstmt.setAsciiStream(5, in, in.length());//这里不是putString而是setAsciiStream
    int result=pstmt.excuteUpdate();
    dbUtil.close(pstmt, conn);
    return result;
    }
  //读取网站
  public void queryWebsite(int id)throws Exception{
    Connection conn = dbUtil.getConnection();
    String sql="SELECT * from websites where id=?";
    PreparedStatement pstmt = conn.preparesStatement(sql);
    pstmt.putString(1, id);
    ResultSet rs = pstmt.excuteQuery();
    while(rs.next()){
      int id = rs.getInt("id");//依次获取name|country|alexa|url即可
      
      Clob clob = rs.getClob("about");//先通过getClob获取Clob对象
      String about=clob.getSubString(1,(int)clob.length());//利用getSubString获取内容
      //也可以通过getAsciiStream流的形式获取内容
        System.out.println("id:"+id+"\tabout:" + about);
    }
    dbUtil.close(pstmt, conn);
  }
  
  public static void main(String... args) throws Exception{
    Website website = new Website("Google","http://www.google.com/", 1, "USA");
    File fAbout = new File("C:/helloworld.txt");
    website.setAbout(fAbout);//设置文件
    int result = addWebsite(website);
    System.out.println(result==1?"添加成功":"添加失败");
  }
}

BLOB实战

因为Blob是存储二级制文件,包括图片、音乐等,数据库中可以申明为BLOB、LONGBLOB类型。

  1. 在websites表中增加logo字段(网站logo),字段类型为BLOB;
  2. 假设在电脑的C盘根目录下存放网站logo的图片名为logo.jpg;
  3. 在Website类中增加类型为File的logo字段

实例代码如下:

public class TestClob{
  
  DBUtil dbUtil = new DBUtil();
  //插入网站
  public int addWebsite(Website website) throws Exception{
    Connection conn = dbUtil.getConnection();
    String sql = "INSERT INTO websites values(null, ?,?,?,?,?,?)";//多了一个logo的?
    PreparedStatement pstmt = conn.preparedStatement(sql);
    pstmt.putString(1, website.getName());
    pstmt.putString(2, website.getUrl());
    pstmt.putInt(3, website.getAlexa());
    pstmt.putString(4, website.getCountry());
    File about=website.getAbout();
    InputStream in = new FileInputStream(about);
    pstmt.setAsciiStream(5, in, in.length());//这里不是putString而是setAsciiStream
    
    File logo = website.getLogo();
    InputStream logoIn= new FileInputStream(logo);
    pstmt.setBinaryStream(6, logoIn, logoIn.length());//这里是setBinaryStream
    int result=pstmt.excuteUpdate();
    dbUtil.close(pstmt, conn);
    return result;
    }
  //读取网站
  public void queryWebsite(int id)throws Exception{
    Connection conn = dbUtil.getConnection();
    String sql="SELECT * from websites where id=?";
    PreparedStatement pstmt = conn.preparesStatement(sql);
    pstmt.putString(1, id);
    ResultSet rs = pstmt.excuteQuery();
    while(rs.next()){
      int id = rs.getInt("id");//依次获取name|country|alexa|url即可
      //获取about
      Clob clob = rs.getClob("about");//先通过getClob获取Clob对象
      String about=clob.getSubString(1,(int)clob.length());//利用getSubString获取内容
      //获取网站logo
      Blob bLogo = rs.getBlob("logo");
      //定义文件输出流,将文件保存到D盘根目录
      FileOutputStream out=new FileOutputStream(new File("D:/logo.jpg"));
      out.write(bLogo.getBytes(1, ((int)bLogo).length()));
      out.close();
        System.out.println("id:"+id+"\tabout:" + about);
    }
    dbUtil.close(pstmt, conn);
  }
  
  public static void main(String... args) throws Exception{
    Website website = new Website("Google","http://www.google.com/", 1, "USA");
    File fAbout = new File("C:/helloworld.txt");
    website.setAbout(fAbout);//设置文件
    File fLogo = new File("C:/logo.jpg");
    website.setLogo(fLogo);
    int result = addWebsite(website);
    System.out.println(result==1?"添加成功":"添加失败");
  }
}

CallableStatement调用存储过程

CallableStatement主要是调用数据库中的存储过程,是PreparedStatement的子接口。使用CallableStatement可以接收存储过程的返回值。

  • void registerOutParameter(int parameterIndex, int sqlType):按顺序为止parameterIndex将OUT参数注册为JDBC类型sqlType

首先添加存储过程,根据website的id获取website的名称,操作如下:

//创建存储过程
DELIMITER &&
CREATE PROCEDURE pro_getSiteNameById(IN siteId INT, OUT sn CHAR(20))
BEGIN
    select name into sn from websites where id=siteId;
END
&&
DELIMITER ;

//测试存储过程
CALL pro_getSiteNameById(1, @siteName);//执行
SELECT @siteName;//输出

CallableStatement调用存储过程,代码如下:

public class TestProcedure{
    DBUtil dbUtil = new DBUtil();
    public String getSiteNameById(int siteId) throws Exception{
        Connection conn = dbUtil.getConnection();
        String sql="{CALL pro_getSiteNameById(?,?)}";
        CallableStatement cstmt = conn.prepareCall(sql);//prepareCall得到CallableStatement
        cstmt.setInt(1, siteId);//设置第一个参数
        cstmt.registerOutParameter(2, Types.CHAR);//注册第二个参数类型
        cstmt.execute();
        String siteName = cstmt.getString("sn");
        dbUtil.close(cstmt,conn);
        return siteName;
    }
}

使用元数据分析数据库

  • DatabaseMetaData:获取数据库基本信息,包括版本、名称以及表信息等
    • String getDatabaseProductName():数据库产品的名称
    • int getDriverMajorVersion():获取JDBC驱动的主版本号
    • int getDriverMinorVersion():获取JDBC驱动的次版本号
    • String getDriverName:获取驱动名称
  • ResultSetMetaData:获取ResultSet对象中列的基本信息
    • int getColumnCount():返回此ResultSet对象中的列数
    • String getColumnName(int column):获取指定列的名称
    • int getColumnTypeName(int column):获取指定列的SQL类型名称

实例代码如下:

public void aboutDatabaseMetaData() throws Exception{
  DBUtil dbUtil = new DBUtil();
  Connection conn = dbUtil.getConnection();
  DatabaseMetaData metaData = conn.getMetaData();//获取元数据
  System.out.println("数据库名称:"+ metaData.getDatabaseProductName());
  System.out.println("数据库主版本号:" + metaData.getDriverMajorVersion());
}

public void aboutResultMetaData() throws Exception{
  DBUtil dbUtil = new DBUtil();
  Connection conn = dbUtil.getConnection();
  String sql ="select * from websites";
  PreparedStatement pstmt = conn.preparedStatement();
  ResultSet rs = pstmt.executeQuery(sql);
  ResultSetMetaData metaData = rs.getMetaData();
  int columnCount = metaData.getColumnCount();
  System.out.println("website 共有:" + columnCount+" 列");
  for(int i=0; i<columnCount; i++)
  {
    System.out.println("第"+i+"列名称为:" + metaData.getColumnName(i)
                       +"\t 列的类型为:" + metaData.getColumnTypeName(i));
  }
}

JDBC处理事务

所谓事务就是所有的操作要么一起成功,要么一起失败。事务本身具备:

  1. 原子性:是不可再分割的最小单元,相当于一个个小的数据库操作,这些操作必须同时成功,如果一个失败则一切操作将全部失败;
  2. 一致性:数据库操作前后是一致的,保证数据有效性。如果事务正常操作则系统维持有效性,如果出现了错误,则回到最原始状态,也要维持有效性;
  3. 隔离性:多个事务可以同时进行且彼此之间无法访问,只有当事务完成最终操作时,才可以看到结果;
  4. 持久性:事务完成后,它对系统的影响是持久的。该修改即使出现致命的系统故障也将一直保持。

创建账户表

首先创建账户表account,包含id、账户名称和账户余额,代码如下:

CREATE TABLE `account` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` char(20) NOT NULL DEFAULT '' COMMENT '站点名称',
  `accountBalance` INTEGER NOT NULL DEFAULT '0' COMMENT '账户余额',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;

然后插入两个账户,分别为(张三,1000)和(李四,1000)。当张三向李四转账500的时候,其实出发两个数据库操作:

  • 张三账户的余额变成500
  • 李四账户的月变成1500

必须保证上述两个操作同时成功,才能认为转账操作是成功,如果有一个失败则必须回到转账操作之前的状态(两个人都是1000)。

不用事务(存在风险)

public class Test{
  
  DBUtil dbUtil = new DBUtil();
  /**
   * 账户转出
   */
  public void transferOut(Connection conn, String accountName, int money) throws Exception{
    String sql = "UPDATE account set accountBalance=accountBalance-? where name=?";
    PreparedStatement pstmt = con.preparedStatement(sql);
    pstmt.setInt(1, 500);
    pstmt.setString(2, accountName);
    pstmt.executUpdate(); 
  }
  
  /**
   * 账户转入
   */
  public void transferIn(Connection conn, String accountName, int money) throws Exception{
    String sql = "UPDATE account set accountBalance=accountBalance+? where name=?";
    PreparedStatement pstmt = con.preparedStatement(sql);
    pstmt.setInt(1, 500);
    pstmt.setString(2, accountName);
    pstmt.executUpdate(); 
  }
  
  public static void main(String... args) throws Exception{
    Connection conn = dbUtil.getConnection();
    int money=500;
        transferOut(conn, "张三", money);
    transferIn(conn, "李四", money);
    dbUtil.close(conn);
  }
}

以上操作是存在风险的并不能保证两个操作能够同时成功。

采用事务操作

转入和转出操作与上面是一致的,主要是修改main方法,代码如下:

public static void main(String... args){
  Connection conn;
  try{
    conn = dbUtil.getConnection();
    conn.setAutoCommit(false);//首先将自动提交进行关闭
    System.out.println("张三开始向李四转账");
    int money=500;
    transferOut(conn, "张三", money);
    transferIn(conn, "李四", money);
    System.out.println("转账成功");
  }catch(Exception e){
    if(conn !=null){
      try{
        conn.rollback();  //当操作失败的时候回滚到之前的状态
      }catch(Exception e){
        e.printStackTrace();
      }
    }
  }finally{
    if(conn !=null){
      try{
        conn.commit();//提交事务
        conn.close();
      }catch(Exception e){
        e.printStackTrace();
      }
    }
  }
}

setAutoCommit、rollback、commit三个方法的结合实现了事务的操作!

保存点

假设存在这种情况,张三将钱转给第三方,由第三方转给李四,也就是张三负责转,至于李四是否收到并不关心。

那么我们可以使用保存点来实现这个功能,具体操作如下:

public static void main(String... args){
  Connection conn;
  SavePoint sp;
  try{
    conn = dbUtil.getConnection();
    conn.setAutoCommit(false);//首先将自动提交进行关闭
    System.out.println("张三开始向李四转账");
    int money=500;
    transferOut(conn, "张三", money);
    sp = conn.setSavePoint();//设置一个保存点
    transferIn(conn, "李四", money);
    System.out.println("转账成功");
  }catch(Exception e){
    if(conn !=null){
      try{
        //当操作失败的时候回滚到sp这个保存点的位置
        conn.rollback(sp);  
      }catch(Exception e){
        e.printStackTrace();
      }
    }
  }finally{
    if(conn !=null){
      try{
        conn.commit();//提交事务
        conn.close();
      }catch(Exception e){
        e.printStackTrace();
      }
    }
  }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351