首页 > 代码库 > redis13---事务处理。

redis13---事务处理。

Jedis事务
我们使用JDBC连接Mysql的时候,每次执行sql语句之前,都需要开启事务;在MyBatis中,
也需要使用openSession()来获取session事务对象,来进行sql执行、查询等操作。当我们
对数据库的操作结束的时候,是事务对象负责关闭数据库连接。

事务对象用于管理、执行各种数据库操作的动作。它能够开启和关闭数据库连接,执行sql
语句,回滚错误的操作。

我们的Redis也有事务管理对象,其位于redis.clients.jedis.Transaction下。

Jedis事务的相关代码:

[java] view plain copy
 
 技术分享技术分享
  1. package cn.com.redis;  
  2.   
  3. import redis.clients.jedis.Jedis;  
  4. import redis.clients.jedis.Transaction;  
  5.   
  6. public class Test7 {  
  7.     public static void main(String[] args) {  
  8.         Jedis jedis = new Jedis("192.168.248.129",6379);  
  9.           
  10.         Transaction transaction=jedis.multi();//返回一个事务控制对象  
  11.           
  12.         //预先在事务对象中装入要执行的操作  
  13.         transaction.set("k4", "v4");  
  14.         transaction.set("k5", "v5");  
  15.           
  16.         transaction.exec();//执行  
  17.     }  
  18. }  

我们查看一下redis:
技术分享
发现数据已经加入进去

我们把k4的value和k5的value改为“v44”和“v55”,
然后在transaction.exec()语句后加入transaction.discard()语句:

[java] view plain copy
 
 技术分享技术分享
  1. package cn.com.redis;  
  2.   
  3. import redis.clients.jedis.Jedis;  
  4. import redis.clients.jedis.Transaction;  
  5.   
  6. public class Test7 {  
  7.     public static void main(String[] args) {  
  8.         Jedis jedis = new Jedis("192.168.248.129",6379);  
  9.           
  10.         Transaction transaction=jedis.multi();//返回一个事务控制对象  
  11.           
  12.         //预先在事务对象中装入要执行的操作  
  13.         transaction.set("k4", "v44");  
  14.         transaction.set("k5", "v55");  
  15.   
  16.         transaction.discard();//回滚  
  17.     }  
  18. }  

会发现数据插入操作被回滚,redis中那两个值未被改变:
技术分享

我们模拟一个刷一次信用卡的交易,使用redis的事务来处理一些逻辑:

[java] view plain copy
 
 技术分享技术分享
  1. package cn.com.redis;  
  2.   
  3. import redis.clients.jedis.Jedis;  
  4. import redis.clients.jedis.Transaction;  
  5.   
  6. public class TestTransaction {  
  7.     //模拟信用卡消费和还款  
  8.     public static void main(String[] args) {  
  9.         TestTransaction t = new TestTransaction();  
  10.         boolean retValue = t.transMethod(100);  
  11.         if(retValue){  
  12.             System.out.println("使用信用卡消费成功!");  
  13.         }else{  
  14.             System.out.println("使用信用卡消费失败!");  
  15.         }  
  16.           
  17.     }  
  18.   
  19.     /** 
  20.      * 通俗点讲,watch命令就是标记一个键,如果标记了一个键, 
  21.      * 在提交事务前如果该键被别人修改过,那事务就会失败,这种情况通常可以在程序中 
  22.      * 重新再尝试一次。 
  23.      * 
  24.      * 首先标记了balance,然后检查余额是否足够,不足就取消标记,并不做扣减; 
  25.      * 足够的话,就启动事务进行更新操作。 
  26.      * 如果在此期间键balance被其他人修改,拿在提交事务(执行exec)时就会报错, 
  27.      * 程序中通常可以捕获这类错误再重新执行一次,直到成功。 
  28.      * */  
  29.     private boolean transMethod(int amount) {  
  30.           
  31.         System.out.println("您使用信用卡预付款"+amount+"元");  
  32.           
  33.         Jedis jedis = new Jedis("192.168.248.129",6379);  
  34.           
  35.         int balance = 1000;//可用余额  
  36.         int debt;//欠额  
  37.         int amtToSubtract = amount;//实刷额度  
  38.           
  39.         jedis.set("balance", String.valueOf(balance));  
  40.         jedis.watch("balance");  
  41.         //jedis.set("balance", "1100");//此句不该出现,为了模拟其他程序已经修改了该条目  
  42.         balance = Integer.parseInt(jedis.get("balance"));  
  43.         if(balance < amtToSubtract){//可用余额小于实刷金额,拒绝交易  
  44.             jedis.unwatch();  
  45.             System.out.println("可用余额不足!");  
  46.             return false;  
  47.         }else{//可用余额够用的时候再去执行扣费操作  
  48.             System.out.println("扣费transaction事务开始执行...");  
  49.             Transaction transaction = jedis.multi();  
  50.             transaction.decrBy("balance",amtToSubtract);//余额减去amtToSubtract的钱数  
  51.             transaction.incrBy("debt", amtToSubtract);//信用卡欠款增加amtToSubtract的钱数  
  52.             transaction.exec();//执行事务  
  53.             balance = Integer.parseInt(jedis.get("balance"));  
  54.             debt = Integer.parseInt(jedis.get("debt"));  
  55.             System.out.println("扣费transaction事务执行结束...");  
  56.               
  57.             System.out.println("您的可用余额:"+balance);  
  58.             System.out.println("您目前欠款:"+debt);  
  59.             return true;  
  60.         }  
  61.     }  
  62.       
  63. }  


此代码就是模拟用户使用信用卡刷了100元的东西,此时应该减去信用卡的可用余额100元,
增加100元的欠款。

运行结果:
技术分享

redis的结果:
技术分享
证明我们的操作是成功的。

加watch命令是为了在事务执行的过程中,防止其它的操作打断事务,或者是影响事务的计算结果,
导致“幻读”、“脏数据”等异常情况的发生。watch命令建立了一个键,一旦发现执行过程中该
键被别人修改过,那事务就会失败,程序中通常可以捕获这类错误再重新执行一次,直到成功。
所以watch命令可以保证数据的同步安全。

为了证明watch命令的用途,我们把上面代码里面的jedis.set("balance", "1100");注释释放,
然后transMethod方法抛出打断异常:throws InterruptedException,main方法捕获打断异常,
然后弹出相应警告框。

[java] view plain copy
 
 技术分享技术分享
  1. package cn.com.redis;  
  2.   
  3. import java.util.List;  
  4.   
  5. import redis.clients.jedis.Jedis;  
  6. import redis.clients.jedis.Transaction;  
  7.   
  8. public class TestTransaction {  
  9.     //模拟信用卡消费和还款  
  10.     public static void main(String[] args) {  
  11.         TestTransaction t = new TestTransaction();  
  12.         boolean retValue=http://www.mamicode.com/false;  
  13.         boolean Interrupted = false;  
  14.           
  15.         try {  
  16.             retValue = t.transMethod(100);  
  17.         } catch (InterruptedException e) {  
  18.             Interrupted = true;  
  19.             System.out.println("事务被打断,请重新执行!");  
  20.         }finally{  
  21.             if(retValue){  
  22.                 System.out.println("使用信用卡消费成功!");  
  23.             }else{  
  24.                 if(!Interrupted){  
  25.                     System.out.println("使用信用卡消费失败!余额不足!");  
  26.                 }  
  27.             }  
  28.         }  
  29.     }  
  30.   
  31.     /** 
  32.      * 通俗点讲,watch命令就是标记一个键,如果标记了一个键, 
  33.      * 在提交事务前如果该键被别人修改过,那事务就会失败,这种情况通常可以在程序中 
  34.      * 重新再尝试一次。 
  35.      * 
  36.      * 首先标记了balance,然后检查余额是否足够,不足就取消标记,并不做扣减; 
  37.      * 足够的话,就启动事务进行更新操作。 
  38.      * 如果在此期间键balance被其他人修改,拿在提交事务(执行exec)时就会报错, 
  39.      * 程序中通常可以捕获这类错误再重新执行一次,直到成功。 
  40.      * */  
  41.     private boolean transMethod(int amount) throws InterruptedException{  
  42.           
  43.         System.out.println("您使用信用卡预付款"+amount+"元");  
  44.           
  45.         Jedis jedis = new Jedis("192.168.248.129",6379);  
  46.           
  47.         int balance = 1000;//可用余额  
  48.         int debt;//欠额  
  49.         int amtToSubtract = amount;//实刷额度  
  50.           
  51.         jedis.set("balance", String.valueOf(balance));  
  52.         jedis.watch("balance");  
  53.         jedis.set("balance", "1100");//此句不该出现,为了模拟其他程序已经修改了该条目  
  54.         balance = Integer.parseInt(jedis.get("balance"));  
  55.         if(balance < amtToSubtract){//可用余额小于实刷金额,拒绝交易  
  56.             jedis.unwatch();  
  57.             System.out.println("可用余额不足!");  
  58.             return false;  
  59.         }else{//可用余额够用的时候再去执行扣费操作  
  60.             System.out.println("扣费transaction事务开始执行...");  
  61.             Transaction transaction = jedis.multi();  
  62.             transaction.decrBy("balance",amtToSubtract);//余额减去amtToSubtract的钱数  
  63.             transaction.incrBy("debt", amtToSubtract);//信用卡欠款增加amtToSubtract的钱数  
  64.             List<Object> result = transaction.exec();//执行事务  
  65.               
  66.             if(result==null){//事务提交失败,说明在执行期间数据被修改过  
  67.                   
  68.                 System.out.println("扣费transaction事务执行中断...");  
  69.                 throw new InterruptedException();  
  70.                   
  71.             }else{//事务提交成功  
  72.                 balance = Integer.parseInt(jedis.get("balance"));  
  73.                 debt = Integer.parseInt(jedis.get("debt"));  
  74.                 System.out.println("扣费transaction事务执行结束...");  
  75.                   
  76.                 System.out.println("您的可用余额:"+balance);  
  77.                 System.out.println("您目前欠款:"+debt);  
  78.                   
  79.                 return true;  
  80.             }  
  81.         }  
  82.     }  
  83.       
  84. }  

再运行一下,看一下效果:
技术分享

这就说明了,如果在watch命令执行后和事务提交之前,如果数据发生了修改操作,事务执行就不会成功,

redis13---事务处理。