概述
/*
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