数据库_jdbc_事务2_ThreadLocal

概述

/*
mysql -uroot -proot
set character_set_client=gb2312;
set character_set_results=gb2312;
use day17;
create table account(
	id int primary key auto_increment,
	name varchar(40),
	money float
)character set utf8 collate utf8_general_ci;
insert into account(name,money) values('林黛玉',1000);
insert into account(name,money) values('薛宝钗',1000);
insert into account(name,money) values('史湘云',1000); 
*/
演示实际开发中的事务管理
      除了Spring,就是ThreadLocal管理事务
导包:
      mysql-connector-java-5.0.8-bin.jar
      commons-dbcp-1.2.2.jar
      commons-pool.jar
      commons-dbutils-1.2.jar
      
实际开发中,Dao只提供基本的update,find方法,不参与具体的业务!
开启事务,提交事务,获得连接,释放连接都由service层自己控制
工具类JdbcUtils提供上述方法



Account位于domain包

package cn.itcast.domain;
/*
mysql -uroot -proot
set character_set_client=gb2312;
set character_set_results=gb2312;
use day17;
create table account(
	id int primary key auto_increment,
	name varchar(40),
	money float
)character set utf8 collate utf8_general_ci;
insert into account(name,money) values('林黛玉',1000);
insert into account(name,money) values('薛宝钗',1000);
insert into account(name,money) values('史湘云',1000); 
*/
public class Account {
	//演示ThreadLocal的事务管理
	private int id;
	private String name;
	private float money;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public float getMoney() {
		return money;
	}
	public void setMoney(float money) {
		this.money = money;
	}
}



JdbcUtils_mine位于utils包

package cn.itcast.utils;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
/**(关于事务)开源数据库连接池DBCP的使用
DBCP内部增强Connection的close方法使用的是装饰模式!
1,导入两个jar包到工程下的lib目录,变成奶瓶
commons-dbcp-1.2.2.jar
commons-pool.jar
2,设置src下的dbcpconfig.properties配置文件信息如库名day17!
3,定义成员记住DBCP创建出来的数据源(即连接池)
4,静态代码块中用BasicDataSourceFactory创建数据源(即连接池)
5,提供获取(绑定在当前线程上的)连接的方法
5,提供(绑定在当前线程上的连接的)开启事务的方法
5,提供(绑定在当前线程上的连接的)提交事务的方法
6,提供释放连接(并从当前线程中移除该连接)的方法
*/
//工具类
public class JdbcUtils_mine {
	//定义成员DataSource记住DBCP创建出来的数据源(即连接池)
	   private static DataSource ds;
	   //线程局部 (thread-local) 变量
	   private static ThreadLocal<Connection> threadLocal=new ThreadLocal<Connection>();
	   static{
		      try {
		         String pro_name="dbcpconfig.properties";
		          InputStream in=JdbcUtils_mine.class.getClassLoader().getResourceAsStream(pro_name);
		          Properties pro = new Properties();
		          pro.load(in);
		        //DBCP连接池--固定代码:由工厂创建数据源(即连接池)
		          BasicDataSourceFactory factory=new BasicDataSourceFactory();
		          //用类成员DataSource记住根据配置文件创建出来的连接池!
		          ds=factory.createDataSource(pro);
		      } catch (Exception e) {
		         throw new ExceptionInInitializerError(e);
		      }
	   }
	// 静态方法1:获取绑定在当前线程上的连接!如果没有,从池中取出一个绑定到当前线程!
	public static Connection getConnection() {
		Connection conn;
		try {
			//本方法的目的是:得到当前线程上绑定的连接
			conn = threadLocal.get();
			if (conn==null) {
				//如果当前线程上还没有绑定连接(如线程范围内的首次获取)
				//就从DBCP连接池中取出一个连接,并绑定到当前线程上,供线程上后面的dao使用!
				conn=ds.getConnection();
				threadLocal.set(conn);
			}
			//返回绑定在当前线程的连接,目的达到!
			return conn;
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}
	//ThreadLocal处理事务,静态方法2:开启事务
	public static void startTransaction() {
		//获得当前线程上绑定的连接,并开启事务
		Connection conn;
		try {
			//使用上面的静态方法,获得当前线程上绑定的连接
			conn=getConnection();
			//开启事务
			//if (conn!=null) {				conn.setAutoCommit(false);			}
			conn.setAutoCommit(false);	
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}
	//ThreadLocal处理事务,静态方法3:提交事务
	public static void commitTransaction() {
		//获得当前线程上绑定的连接,并提交事务
		Connection conn;
		try {
			//使用上面的静态方法,获得当前线程上绑定的连接
			conn=getConnection();
			//提交事务
			//if (conn!=null) {			conn.commit();			}
			conn.commit();
		}catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}
	//ThreadLocal处理事务,静态方法3:关闭连接,并解除绑定!千万注意!
	public static void freeConnection() {
		//释放当前线程上绑定的连接,即归还给连接池!
		Connection conn;
		try {
         //使用上面的静态方法,获得当前线程上绑定的连接
         conn=getConnection();
         //if (conn!=null) {            conn.close();         }
         conn.close();
      }catch (SQLException e) {
         throw new RuntimeException(e);
      }finally{
         //千万记得关闭连接时,要解除线程上绑定的连接
         //从threadlocal容器(map<线程名,连接>)中移除对应当前线程的连接
         threadLocal.remove();
      }
   }
}



JdbcUtils位于utils包

package cn.itcast.utils;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
/**(关于事务)开源数据库连接池DBCP的使用
DBCP内部增强Connection的close方法使用的是装饰模式!
1,导入两个jar包到工程下的lib目录,变成奶瓶
commons-dbcp-1.2.2.jar
commons-pool.jar
2,设置src下的dbcpconfig.properties配置文件信息如库名day17!
3,定义成员记住DBCP创建出来的数据源(即连接池)
4,静态代码块中用BasicDataSourceFactory创建数据源(即连接池)
5,提供获取(绑定在当前线程上的)连接的方法
5,提供(绑定在当前线程上的连接的)开启事务的方法
5,提供(绑定在当前线程上的连接的)提交事务的方法
6,提供释放连接(并从当前线程中移除该连接)的方法
*/
//工具类
public class JdbcUtils {
   //定义成员DataSource记住DBCP创建出来的数据源(即连接池)
      private static DataSource ds;
      //线程局部 (thread-local) 变量
      private static ThreadLocal<Connection> threadLocal=new ThreadLocal<Connection>();
      static{
            try {
               String pro_name="dbcpconfig.properties";
                InputStream in=JdbcUtils.class.getClassLoader().getResourceAsStream(pro_name);
                Properties pro = new Properties();
                pro.load(in);
              //DBCP连接池--固定代码:由工厂创建数据源(即连接池)
                BasicDataSourceFactory factory=new BasicDataSourceFactory();
                //用类成员DataSource记住根据配置文件创建出来的连接池!
                ds=factory.createDataSource(pro);
            } catch (Exception e) {
               throw new ExceptionInInitializerError(e);
            }
      }
   // 静态方法1:获取绑定在当前线程上的连接!如果没有,从池中取出一个绑定到当前线程!
   public static Connection getConnection() {
      Connection conn;
      try {
         //本方法的目的是:得到当前线程上绑定的连接
         conn = threadLocal.get();
         if (conn==null) {
            //如果当前线程上还没有绑定连接(如线程范围内的首次获取)
            //就从DBCP连接池中取出一个连接,并绑定到当前线程上,供线程上后面的dao使用!
            conn=ds.getConnection();
            threadLocal.set(conn);
         }
         //返回绑定在当前线程的连接,目的达到!
         return conn;
      } catch (SQLException e) {
         throw new RuntimeException(e);
      }
   }
   //ThreadLocal处理事务,静态方法2:开启事务
   public static void startTransaction() {
      //获得当前线程上绑定的连接,并开启事务
      Connection conn;
      try {
         //获得当前线程上绑定的连接
         conn=threadLocal.get();
         if(conn==null){
            conn=ds.getConnection();
            threadLocal.set(conn);
         }
         //此时当前线程上必定绑定了连接,开启事务!
         conn.setAutoCommit(false);
      } catch (SQLException e) {
         throw new RuntimeException(e);
      }
   }
   //ThreadLocal处理事务,静态方法3:提交事务
   public static void commitTransaction() {
      //获得当前线程上绑定的连接,并提交事务
      Connection conn;
      try {
         //得到当前线程上绑定的连接并提交事务
         conn=threadLocal.get();
         //提交事务
         if (conn!=null) {
            //只有当连接存在时,才提交事务!
            conn.commit();
         }
      }catch (SQLException e) {
         throw new RuntimeException(e);
      }
   }
   //ThreadLocal处理事务,静态方法3:关闭连接,并解除绑定!千万注意!
   public static void freeConnection() {
      //释放当前线程上绑定的连接,即归还给连接池!
      Connection conn;
      try {
         //得到当前线程上绑定的连接并释放连接至连接池
         conn=threadLocal.get();
         if (conn!=null) {
            //只有连接不为空时,才释放连接至连接池
            conn.close();
         }
      }catch (SQLException e) {
         throw new RuntimeException(e);
      }finally{
         //千万记得关闭连接时,要解除线程上绑定的连接
         //从threadlocal容器(map<线程名,连接>)中移除对应当前线程的连接
         threadLocal.remove();
      }
   }
}



AccountDao位于dao包

package cn.itcast.dao;
import java.sql.SQLException;
import java.util.List;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import cn.itcast.domain.Account;
import cn.itcast.utils.JdbcUtils;
public class AccountDao {
   //实际开发中,Dao只提供基本的update,find方法,不参与具体的业务!
   //其中使用的连接是从当前线程上获取的连接!
   //开启事务,提交事务,获得连接,释放连接都由service层自己控制(通过工具类JdbcUtils)
   public void update(Account a){
      //为简化开发,不管别的,先new QueryRunner
      //因为关系到事务,所以构造时,不能传给QueryRunner连接池,
      //要由service层自己控制连接的开启事务和提交事务以及关闭连接
      QueryRunner qr=new QueryRunner();
      String sql="update account set money=? where id=?";
      Object[] params={a.getMoney(),a.getId()};
      try {
         //重点来了!由于没有给QueryRunner连接池,
         //所以叫QueryRunner更新的时候要JdbcUtils传一个连接给它!
         //该连接一定要是绑定在ThreadLocal线程上的连接!
         //因为Dao不会也不知道什么时候开启和关闭事务,
         //Dao只提供基本的update,find方法,不参与具体的业务!
         //只有在service层才知道将什么放到事务中执行!
         //所有开启事务和关闭事务,由service层控制!
         qr.update(JdbcUtils.getConnection(),sql, params);
      } catch (SQLException e) {
         throw new RuntimeException(e);
      }
   }
   //实际开发中,Dao只提供基本的update,find,getAll方法,不参与具体的业务!
   //其中使用的连接是从当前线程上获取的连接!
   //开启事务,提交事务,获得连接,释放连接都由service层自己控制(通过工具类JdbcUtils)
   public Account find(String id){
      //为简化开发,不管别的,先new QueryRunner
      //因为关系到事务,所以构造时,不能传给QueryRunner连接池,
      //要由service层自己控制连接的开启事务和提交事务以及关闭连接
      QueryRunner qr=new QueryRunner();
      String sql="select * from account where id=?";
      //由于只有一个参数,所以不需使用参数数组
      try {
         Account a=(Account) qr.query(JdbcUtils.getConnection(),sql, id, new BeanHandler(Account.class));
         return a;
      } catch (SQLException e) {
         throw new RuntimeException(e);
      }
   }
   //实际开发中,Dao只提供基本的update,find,getAll方法,不参与具体的业务!
   //其中使用的连接是从当前线程上获取的连接!
   //开启事务,提交事务,获得连接,释放连接都由service层自己控制(通过工具类JdbcUtils)
   public List<Account> getAll(){
      //为简化开发,不管别的,先new QueryRunner
      //因为关系到事务,所以构造时,不能传给QueryRunner连接池,
      //要由service层自己控制连接的开启事务和提交事务以及关闭连接
      QueryRunner qr=new QueryRunner();
      String sql="select * from account";
      try {
         List<Account> list=(List<Account>) qr.query(JdbcUtils.getConnection(),sql, new BeanListHandler(Account.class));
         return list;
      } catch (SQLException e) {
         throw new RuntimeException(e);
      }
   }
}


AccountService位于service包

package cn.itcast.service;

import org.junit.Test;

import cn.itcast.dao.AccountDao;
import cn.itcast.domain.Account;
import cn.itcast.utils.JdbcUtils;

//业务层负责:开启事务,提交事务,释放连接!都由service层自己控制(通过工具类JdbcUtils)
public class AccountService {
   @Test
   public void test_1(){
      transfer("1", "2", 100);
   }
   public void transfer(String from_id,String to_id,float money){
      //所有事务开启,提交,连接的关闭操作通过JdbcUtils完成
      JdbcUtils.startTransaction();
      //所有业务更新和查找通过dao完成
      AccountDao dao=new AccountDao();
      Account a=dao.find(from_id);
      Account b=dao.find(to_id);
      a.setMoney(a.getMoney()-money);
      b.setMoney(b.getMoney()+money);
      dao.update(a);
      dao.update(b);
      //int i=1/0;
      //所有事务开启,提交,连接的关闭操作通过JdbcUtils完成
      JdbcUtils.commitTransaction();
      
      //所有事务开启,提交,连接的关闭操作通过JdbcUtils完成
      JdbcUtils.freeConnection();
   }
}

用到的第3方jar包

mysql-connector-java-5.0.8-bin.jar
commons-dbcp-1.2.2.jar
commons-pool.jar
commons-dbutils-1.2.jar


dbcpconfig.properties位于src目录


#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/day17
username=root
password=root
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=50
#<!-- 最大空闲连接 -->
maxIdle=20
#<!-- 最小空闲连接 -->
minIdle=5
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 即等1分钟后仍没连接,这时才告诉人家,呆会再来,暂无连接! -->
maxWait=60000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;] 
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=utf8
#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true
#driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=
#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_COMMITTED









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值