上篇博文链接:点击传送
此篇博文是对上篇博文中代码进行的改造
主要是加入了事务机制,简单来说就是将用到数据库的部分进行了事务的统一处理,使得用到的都是同一个事务(要把自动提交事务关闭,手动地进行提交,加入了ThreadLocal也保证了事务的统一性)
加入了ThreadLocal:ThreadLocal是一种基于当前线程安全的存取机制。我们把值存到ThreadLocal对象中时,只要当前线程还在,那么ThreadLocal对象中的值就还在。
数据库部分
与上篇博文中的数据库无异
前端页面部分
与上篇博文中的无异
引入MVC
MVC是一种基于Web开发的设计模式。该模式最重要的作用是分层开发思想。
M:model 模型 与数据相关的操作
V:view 视图 与页面相关的操作
C:controller 控制器 与调度相关的操作
MVC在开发中具体对应的层
M层(模型层):service+dao
V层(视图层):一些页面,例如jsp
C层(控制器层):controller(也就是相当于servlet)
引入service层
引入了service层进行业务的处理
之前的业务处理写在了controller中会很杂乱,现在为了使得体现分层的开发思想,用service层来处理业务,controller来处理调度
面向接口编程
TaccountService.java
package com.service;
public interface TaccountService {
//处理业务的接口
public void taccount(String zcAccount,String zrAccount,String zzBalanceStr);
}
TaccountServiceImpl.java
package com.service.impl;
import java.sql.Connection;
import java.sql.SQLException;
import com.dao.TaccountDao;
import com.dao.impl.TaccountDaoImpl;
import com.service.TaccountService;
import com.util.DBUtil;
public class TaccountServiceImpl implements TaccountService {
@Override
public void taccount(String zcAccount, String zrAccount, String zzBalanceStr) {
int zzBalance = Integer.valueOf(zzBalanceStr);
/*
表单数据取得后,按照6步操作,来完成转账业务逻辑
(1)验证转出账号有没有
(2)验证转入账号有没有
(3)根据转出账号取出转出账号余额,看看钱够不够
(4)更新转出账号余额(扣钱)
(5)根据转入账号取出转入账号余额
(6)更新转入账号余额(加钱)
*/
TaccountDao taccountDao = new TaccountDaoImpl();
Connection conn = null;
try{
//此时还没有conn,就会创建一个并保存到t(ThreadLocal)对象中
conn = DBUtil.getConn();
System.out.println("service:"+conn);
//把自动提交事务变成手动提交事务
conn.setAutoCommit(false);
//下面开始进行业务处理时,保证是同一个conn事务
System.out.println("开始转账");
//开始处理业务逻辑
//开始接触dao层
//dao层中用到conn时,用的都是service创建(t对象中)的conn,保证了事务统一性
//(1)验证转出账号有没有
if(taccountDao.checkAccount(zcAccount)){
//(2)验证转入账号有没有
if(taccountDao.checkAccount(zrAccount)){
//(3)根据转出账号取出转出账号余额,看看钱够不够
int zcBalance = taccountDao.getBalanceByAccount(zcAccount);
if(zcBalance >= zzBalance){
//(4)更新转出账号余额(扣钱)
taccountDao.updateBalanceByAccount(zcAccount, zcBalance-zzBalance);
//(5)根据转入账号取出转入账号余额
int zrBalance = taccountDao.getBalanceByAccount(zrAccount);
//(6)更新转入账号余额(加钱)
taccountDao.updateBalanceByAccount(zrAccount, zrBalance+zzBalance);
}
}
}
//手动提交事务
conn.commit();
/*异常处理优化,就是说,如果在dao层进行操作时,
例如进行数据库查询账户是否存在,SQL语句出现错误,就会抛出一个运行时异常(要进行一个手动抛出)
然后在处理业务时(运行taccountDao.checkAccount())会接收到这个异常,
就跳到catch进行回滚处理
*/
//使用Exception可以接收出现的所有异常,然后进行回滚处理
}catch(Exception e){
try {
//回滚,当出现运行时异常时就跳转到这进行回滚(一般是在进行数据操作(dao层)时出现异常)
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally{
try {
DBUtil.myClose(conn, null, null);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
工具类部分
因为引入ThreadLocal,所以会进行一个改造
package com.util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DBUtil {
private DBUtil(){}
static{
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private static final String URL = "jdbc:mysql://localhost:3306/test";
private static final String USER = "root";
private static final String PASSWORD = "root";
//指定了泛型,是Connection的
private static ThreadLocal<Connection> t = new ThreadLocal<Connection>();
//创建/取得连接
/**
* service层是进行业务处理的,dao层是进行数据操作的,所以是先进行service层再到dao层的
* 所以肯定是service层先用到conn
* 当service层调用到getConn方法时,创建一个连接返回给service层,同时将此连接存放到t对象中
* 当dao层调用到getConn方法时,t对象中一定有conn了(service层执行时创建并保存的conn,同时也是service层开启事务的conn)
* 将conn从t中取得,直接返回即可
*/
public static Connection getConn() throws SQLException{
//这个就是进入到dao层操作后,要用到getConn()方法时conn拿到的存放在t(ThreadLocal)里面的conn
Connection conn = t.get();
//也就是service层中第一次用到getConn()时是没有conn的,就创建并保存到ThreadLocal对象中
if (conn == null) {
//创建并保存conn
conn = DriverManager.getConnection(URL, USER, PASSWORD);
t.set(conn);
}
return conn;
}
//关闭资源
public static void myClose(Connection conn,PreparedStatement ps,ResultSet rs) throws SQLException{
//关闭的顺序为按照 创建的顺序 逆序关闭
if(rs!=null){
rs.close();
}
if(ps!=null){
ps.close();
}
if(conn!=null){
conn.close();
//必须加
t.remove();
}
}
}
dao层
数据操作部分,也就是进行jdbc的部分
面向接口编程
TaccountDao.java
package com.dao;
public interface TaccountDao {
//验证账号有没有
public boolean checkAccount(String account);
//根据账号取余额
public int getBalanceByAccount(String account);
//根据账号更新余额
public void updateBalanceByAccount(String account,int balance);
}
TaccountDaoImpl.java
package com.dao.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.dao.TaccountDao;
import com.util.DBUtil;
//对接口进行实现,然后对里面的方法进行重写来实现几个操作
public class TaccountDaoImpl implements TaccountDao{
//验证账号有没有
@Override
public boolean checkAccount(String account){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
String sql = "select count(*) from t_account where account=?";
boolean flag = true;
try{
//这个conn都是service层创建(t对象中)的conn
conn = DBUtil.getConn();
//验证事务统一性时做的验证处理
System.out.println("checkAccount:"+conn);
ps = conn.prepareStatement(sql);
ps.setString(1,account);
rs = ps.executeQuery();
if(rs.next()){
int count = rs.getInt(1);
if(count!=1){
flag = false;
}
}
}catch(SQLException e){
e.printStackTrace();
//手动抛出异常,也就是说,如果发生异常,就手动抛出,然后在service层运行到这个方法时进行接收,然后进行回滚
throw new RuntimeException();
}finally{
try {
DBUtil.myClose(null, ps, rs);
} catch (SQLException e) {
e.printStackTrace();
}
}
return flag;
}
//根据账户取余额
@Override
public int getBalanceByAccount(String account) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
String sql = "select balance from t_account where account=?";
int balance = 0;
try{
//这个conn都是service层创建(t对象中)的conn
conn = DBUtil.getConn();
System.out.println("getBalanceByAccount:"+conn);
ps = conn.prepareStatement(sql);
ps.setString(1,account);
rs = ps.executeQuery();
if(rs.next()){
balance = rs.getInt(1);
}
}catch(SQLException e){
e.printStackTrace();
//手动抛出异常
throw new RuntimeException();
}finally{
try {
DBUtil.myClose(null, ps, rs);
} catch (SQLException e) {
e.printStackTrace();
}
}
return balance;
}
//根据账户更新余额
@Override
public void updateBalanceByAccount(String account, int balance) {
Connection conn = null;
PreparedStatement ps = null;
String sql = "update t_account set balance=? where account=?";
try{
//这个conn都是service层创建(t对象中)的conn
conn = DBUtil.getConn();
System.out.println("updateBalanceByAccount:"+conn);
ps = conn.prepareStatement(sql);
ps.setInt(1,balance);
ps.setString(2,account);
ps.executeUpdate();
}catch(SQLException e){
e.printStackTrace();
//手动抛出异常
throw new RuntimeException();
}finally{
try {
DBUtil.myClose(null, ps, null);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
事务统一性验证结果:可以看到都是同一个事务,也就是service层用到getConn()创建并保存到ThreadLocal对象中的conn,后面接触到dao层时,用到的conn都是ThreadLocal对象中的conn,以此保证了事务的统一性
controller层
调度层
TaccountController.java (实则是一个servlet)
package com.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.service.TaccountService;
import com.service.impl.TaccountServiceImpl;
public class TaccountController extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("开始执行转账操作");
//接收表单参数
String zcAccount = request.getParameter("zcAccount");
String zrAccount = request.getParameter("zrAccount");
String zzBalanceStr = request.getParameter("zzBalance");
TaccountService ts = new TaccountServiceImpl();
//处理业务 ---- 这里就是调度
//相当于接收请求,然后把这个请求交给别的地方进行处理
ts.taccount(zcAccount, zrAccount, zzBalanceStr);
response.sendRedirect(request.getContextPath() + "/success.jsp");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
结束语
天晴的时候,记得给自己储备一点阳光。