zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    ASP.NET MVC布局
    C#微信扫码支付Demo
    ASP.NET MVC用户登录(Memcache存储用户登录信息)
    Memcached分布式缓存快速入门
    Log4Net日志配置
    ASP.NET MVC自定义异常处理
    Spring.Net快速入门:控制翻转、依赖注入、面向切面编程
    C#微信公众号开发入门教程
    APS.NET MVC4生成解析二维码简单Demo
    Entity Framwork学习笔记
  • 原文地址:https://www.cnblogs.com/gshao/p/10242254.html
Copyright © 2011-2022 走看看