jdbc 编程(二)及 事务

 

JDBC处理大文本,JDBC处理图片,JDBC进行批处理    数据库连接池

参考文献:https://www.cnblogs.com/shanheyongmu/p/5909539.html

参考文献:https://www.cnblogs.com/shanheyongmu/p/5909922.html

 

1. 事务(Transaction,简写为tx):

  所谓事务是用户定义的一组操作,使数据从一种状态变换到另一种状态,要么同时成功,要么同时失败。

2. 事务的操作:

  先定义开始一个事务,然后对数据作修改操作,这时如果提交(commit),这些修改就永久地保存下来,如果回退(rollback),数据库管理系统将放弃您所作的所有修改而回到开始事务时的状态。

3. 事务的ACID属性:

  Atomic原子性、Consistency一致性、Isolation   [ˌaɪsəˈleɪʃn] 隔离性和 Durability 持久性。

  1、原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 
  2、一致性(Consistency):事务必须使数据库从一个一致性状态变换到另外一个一致性状态。 
  3、隔离性(Isolation):事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。 
  4、持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。)

4. 并发下事务会产生的问题

  1、脏读

    所谓脏读,就是指事务A读到了事务B还没有提交的数据,比如银行取钱,事务A开启事务,此时切换到事务B,事务B开启事务-->取走100元,此时切换回事务A,事务A读取的肯定是数据库里面的原始数据,因为事务B取走了100块钱,并没有提交,数据库里面的账务余额肯定还是原始余额,这就是脏读。

  2、不可重复读

    所谓不可重复读,就是指在一个事务里面读取了两次某个数据,读出来的数据不一致。还是以银行取钱为例,事务A开启事务-->查出银行卡余额为1000元,此时切换到事务B事务B开启事务-->事务B取走100元-->提交,数据库里面余额变为900元,此时切换回事务A,事务A再查一次查出账户余额为900元,这样对事务A而言,在同一个事务内两次读取账户余额数据不一致,这就是不可重复读。

  3、幻读

    所谓幻读,就是指在一个事务里面的操作中发现了未被操作的数据。比如学生信息,事务A开启事务-->修改所有学生当天签到状况为false,此时切换到事务B,事务B开启事务-->事务B插入了一条学生数据,此时切换回事务A,事务A提交的时候发现了一条自己没有修改过的数据,这就是幻读,就好像发生了幻觉一样。幻读出现的前提是并发的事务中有事务发生了插入、删除操作。

 

5. MySQL事务隔离级别

  ① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。

  ② Repeatable read (可重复读):可避免脏读、不可重复读的发生。

  ③ Read committed (读已提交):可避免脏读的发生。

  ④ Read uncommitted (读未提交):最低级别,任何情况都无法保证

  以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低。像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。

  在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读);而在Oracle数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed级别。

 

  1. 在MySQL数据库中查看当前事务的隔离级别:    select @@tx_isolation; 

   2.在MySQL数据库中设置事务的隔离 级别:  记住:设置数据库的隔离级别一定要是在开启事务之前!

            set [glogal | session] transaction isolation level 隔离级别名称;        

            set tx_isolation=’隔离级别名称;
   注意:   session 表示设置当前会话事务隔离级别,

          比如MyBatis,getSqlSession()的时候,只针对这一次拿到的Session有效;比如CMD命令行,只对这一次的窗口有效。      

       glogal表示设置全局事务隔离级别:针对此后所有的会话有效,当前已经存在的会话不受影响

 

  3. 如果是使用JDBC对数据库的事务设置隔离级别的话,也应该是在调用Connection对象的setAutoCommit(false)方法之前。调用Connection对象的setTransactionIsolation(level)即可设置当前链接的隔离级别,至于参数level,可以使用Connection对象的字段:

 

   在JDBC中设置隔离级别的部分代码:

 

 后记:隔离级别的设置只对当前链接有效。对于使用MySQL命令窗口而言,一个窗口就相当于一个链接,当前窗口设置的隔离级别只对当前窗口中的事务有效;对于JDBC操作数据库来说,一个Connection对象相当于一个链接,而对于Connection对象设置的隔离级别只对该Connection对象有效,与其他链接Connection对象无关。

6. 如何在代码中去处理事务:

  1.在JDBC中,事务是默认提交的.  必须先设置事务为手动提交.

    connection对象.setAutoCommit(false);//设置事务为手动提交.

  2.手动的提交事务.

    connection对象.commit();

  3.若出现异常必须回滚事务:

     不回滚事务,总余额依然是正确的. 若不回滚事务,不会释放数据库资源.

     connection对象.rollback();

 

 

7. 事务示例:

 

银行转帐功能: bank / money

 

过儿和姑姑:

过儿 : 10000块钱

姑姑 : 0块钱

 

转账:过儿要给姑姑转1000块钱

 

分析:转钱需要提供两条sql,但是程序员也会出错,比较代码写错了.

 

  ①update bank set money = money +1000 where name = '姑姑'

 

  ②代码出错,     会导致过儿账户钱少了,而姑姑账户没有收到钱,钱去哪了?

 

  ③update bank set money =  money -1000 where name= '过儿'

解决办法 代码:

数据库:

CREATE TABLE `bank` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `money` double DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

 

com.domain包下的  实体类 Bank 

package com.domain;

public class Bank {
	private String name;
	private double money;
	public Bank(String name, double money) {
		super();
		this.name = name;
		this.money = money;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public double getMoney() {
		return money;
	}
	public void setMoney(double money) {
		this.money = money;
	}
	public Bank() {
		// TODO Auto-generated constructor stub
	}
	@Override
	public String toString() {
		return "Bank [name=" + name + ", money=" + money + "]";
	}
}

  

com.dao 包下的 IBankDao 接口

package com.dao;
import com.domain.Bank;

public interface IBankDao {
	void transMoney(Bank b1,Bank b2, double money);
}

  

com.dao .impl 包 的  BankDaoImpl 实现类

package com.dao.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import com.Util.JDBCUtil;
import com.dao.IBankDao;
import com.domain.Bank;

public class BankDaoImpl implements IBankDao {
// 单利模式获取 BankDaoImpl 类对象
	private  BankDaoImpl (){}
	private static BankDaoImpl instance = null;
	public static  BankDaoImpl getInstance(){
		if(instance == null){
			return instance = new BankDaoImpl();
		}
		return null;
	}
	@Override
	public void transMoney(Bank b1, Bank b2, double money) {
		Connection conn = null;
		PreparedStatement ps = null;
		try {
			conn = JDBCUtil.getInstance().getConnection();
			
		//在JDBC中,事务是默认提交的true.  必须先设置事务为手动提交false
			conn.setAutoCommit(false);
			ps = conn.prepareStatement("update bank set money = money - ? where name = ? ");
			ps.setDouble(1, money);
			ps.setString(2, b1.getName() );
			ps.executeUpdate();
			
//			int a = 1/0;  //错误代码
			
			ps = conn.prepareStatement("update bank set money = money + ? where name = ?");
			ps.setDouble(1, money);
			ps.setString(2, b2.getName());
			ps.executeUpdate();
			conn.commit();        //手动进行提交
			
		} catch (Exception e) {
			try {
				conn.rollback();  //数据回滚
			} catch (SQLException e1) {
				e1.printStackTrace();
			}   
		}
		JDBCUtil.getInstance().closeAll(null,ps,conn);
	}
}

 

测试类

public class TestBank {
	@Test
	public void trans(){
		Bank b1 = new Bank("过儿",10000);
		Bank b2 = new Bank("姑姑",0);
		BankDaoImpl.getInstance().transMoney(b1, b2, 1000);
	}
}

  

 8. 如何在JDBC中保存数据的时候获取自动生成的主键呢?

Statement方式:

int executeUpdate(String sql,  int autoGeneratedKeys):执行SQL:

         参数:autoGeneratedKeys,是否需要返回自动生成的主键.常量值:Statement.RETURN_GENERATED_KEYS.

ResultSet getGeneratedKeys():获取自动生成的主键

示列代码如下:

@Test
    public void statementTest() throws Exception {
        String sql = "insert into student (name, age) values ('zhangsan', 30)";
        Connection conn = JdbcUtil.getConn();
        Statement st = conn.createStatement();
        // statement.executeUpdate(sql);
        st.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
        ResultSet rs = st.getGeneratedKeys();
        if (rs.next()) {
            Long id = rs.getLong(1);
            System.out.println(id);
        }
        JdbcUtil.close(conn, st, null);
    }

 

  

PreparedStatement方式:

PreparedStatement prepareStatement(String sql,int autoGeneratedKeys)  :

创建PreparedStatement对象,病指定是否需要返回生成的主键. 常量值:Statement.RETURN_GENERATED_KEYS

示列代码:

public class PreparedStatementTest {
    @Test
    public void preparedStatement() throws Exception {
        String sql = "insert into student (name,age) values (?,?)";
        Connection conn = JdbcUtil.getConn();
        PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
        ps.setString(1, "张三");
        ps.setInt(2, 24);
        ps.executeUpdate();
        ResultSet rs = ps.getGeneratedKeys();
        if (rs.next()) {
            Long id = rs.getLong(1);
            System.out.println(id);
        }
        JdbcUtil.close(conn, ps, null);
    }
}

 

  

部分类容参考:https://www.cnblogs.com/741162830qq/p/6733982.html

       https://www.cnblogs.com/fjdingsd/p/5273008.html

       https://www.cnblogs.com/xrq730/p/5087378.html

posted @ 2019-01-09 01:14  甘劭  阅读(281)  评论(0编辑  收藏  举报