事务的四大特性ACID
原子性:强调事务的不可分割.多条语句要么都成功,要么都失败。
一致性:强调的是事务的执行的前后,数据要保持一致
隔离性:并发访问数据库时,一个事务的执行不应该受到其他事务的干扰.
持久性:一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
事务的隔离级别
READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读 仍有可能发生
REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修 改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务 之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
JdbcUtils 事务工具类
public class JdbcUtils {
//准备Properties集合
private static Properties config=new Properties();
//连接池
private static DataSource dataSource;
//当前线程
private static ThreadLocal<Connection> tl=new ThreadLocal<>();
static {
try {
//1.读取properties文件的内容
InputStream in = JdbcUtils.class.getClassLoader().
getResourceAsStream("datasource.properties");
config.load(in);
//2.创建连接池
DruidDataSource druidDataSource=new DruidDataSource();
druidDataSource.setDriverClassName(config.getProperty("jdbc.driverClassName"));
druidDataSource.setUrl(config.getProperty("jdbc.url"));
druidDataSource.setUsername(config.getProperty("jdbc.username"));
druidDataSource.setPassword(config.getProperty("jdbc.password"));
dataSource=druidDataSource;
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("创建数据库连接池失败!");
}
}
/**
* 获取链接
* @return
*/
public static Connection getConnection(){
try {
//1.先从当前线程获取链接
Connection connection = tl.get();
//2.如果当前线程连接为空,代表第一次调用
if(connection==null){
//2.1获取一个新的连接
connection=dataSource.getConnection();
//2.2 绑定连接到当前线程
tl.set(connection);
}
return connection;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("获取连接失败!");
}
}
/**
* 开启事务
*/
public static void startTransaction(){
try {
//1.获取链接
Connection connection = getConnection();
//2.开启事务
connection.setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("开启事务");
}
}
/**
* 提交事务
*/
public static void commitTransaction(){
try {
//1.获取链接
Connection connection = tl.get();
//2.如果当前线程有连接才提交事务
if (connection != null) {
connection.commit();
}
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("提交事务失败!");
}
}
/**
* 回滚事务
*/
public static void rollback(){
try {
//1.获取链接
Connection connection = tl.get();
//2.如果当前线程有连接才回滚
if (connection != null) {
connection.rollback();
connection.commit();
}
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("回滚事务失败!");
}
}
/**
* 关闭链接,移除当前线程绑定的连接
*/
public static void closeTransaction(){
try {
//1.获取链接
Connection connection = tl.get();
if (connection != null) {
//移除当前线程绑定的连接
tl.remove();
//关闭链接
connection.close();
}
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("关闭链接失败!");
}
}
/**
* 关闭资源
* @param st
* @param rs
*/
public static void close(Statement st, ResultSet rs){
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs=null;
}
if(st!=null){
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
st=null;
}
}
}
dao
public interface AccountDao {
/**
* 根据id查询账户信息
* @param id
* @return
*/
Account findAccountById(Integer id);
/**
* 修改账户信息
* @param account
*/
void updateAccount(Account account);
}
public class AccountDaoImpl implements AccountDao {
@Override
public Account findAccountById(Integer id) {
PreparedStatement st=null;
ResultSet rs=null;
try{
Connection conn=JdbcUtils.getConnection();
//3.准备sql
String sql="select id,name,money from account where id=?";
st=conn.prepareStatement(sql);
//4.设置参数
st.setInt(1,id);
//5.执行sql
rs=st.executeQuery();
//6.解析结果
Account account=null;
if(rs.next()){
account=new Account();
account.setId(id);
account.setName(rs.getString("name"));
account.setMoney(rs.getDouble("money"));
}
return account;
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("查询失败!");
}finally {
JdbcUtils.close(st,rs);
}
}
@Override
public void updateAccount(Account account) {
PreparedStatement st=null;
ResultSet rs=null;
try{
Connection conn=JdbcUtils.getConnection();
//3.准备sql
String sql="update account set money=?,name=? where id=?";
st=conn.prepareStatement(sql);
//4.设置参数
st.setDouble(1,account.getMoney());
st.setString(2,account.getName());
st.setInt(3,account.getId());
//5.执行sql
st.executeUpdate();
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("修改失败!");
}finally {
JdbcUtils.close(st,rs);
}
}
}
service
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao=new AccountDaoImpl();
@Override
public void transfer(Integer sourceId, Integer targetId, Double money) {
//1.查询源账户信息
Account sAccount = accountDao.findAccountById(sourceId);
//2.查询目标账户信息
Account tAccount = accountDao.findAccountById(targetId);
//3.转账的业务
if (sAccount.getMoney() < money) {
throw new RuntimeException("余额不足!");
}
//4.源账户扣钱
sAccount.setMoney(sAccount.getMoney() - money);
accountDao.updateAccount(sAccount);
//int x = 1 / 0;
//5.目标账户加钱
tAccount.setMoney(tAccount.getMoney() + money);
accountDao.updateAccount(tAccount);
}
}
servlet
@WebServlet("/accountServlet")
public class AccountServlet extends HttpServlet {
private AccountService accountService=new AccountServiceImpl();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
//1.开启事务
JdbcUtils.startTransaction();
//2.执行业务
AccountService accountService = new AccountServiceImpl();
accountService.transfer(1, 2, 3.0);
//3.提交事务
JdbcUtils.commitTransaction();
}catch (Exception e){
e.printStackTrace();
//回滚事务
JdbcUtils.rollback();
}finally {
//关闭资源,移除当前线程绑定的连接
JdbcUtils.closeTransaction();
}
}
}
过滤器解决事务
@WebFilter("/*")
public class TransactionFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
try{
//1.开启事务
JdbcUtils.startTransaction();
//2.执行业务 放行
chain.doFilter(req,res);
//3.提交事务
JdbcUtils.commitTransaction();
}catch (Exception e){
e.printStackTrace();
//回滚事务
JdbcUtils.rollback();
}finally {
//关闭资源,移除当前线程绑定的连接
JdbcUtils.closeTransaction();
}
}
@Override
public void destroy() {
}
}