事务的四大特性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 {@Overridepublic Account findAccountById(Integer id) {PreparedStatement st=null;ResultSet rs=null;try{Connection conn=JdbcUtils.getConnection();//3.准备sqlString sql="select id,name,money from account where id=?";st=conn.prepareStatement(sql);//4.设置参数st.setInt(1,id);//5.执行sqlrs=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);}}@Overridepublic void updateAccount(Account account) {PreparedStatement st=null;ResultSet rs=null;try{Connection conn=JdbcUtils.getConnection();//3.准备sqlString 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.执行sqlst.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();@Overridepublic 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();@Overrideprotected 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 {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic 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();}}@Overridepublic void destroy() {}
}