java程序员入行科目一之CRUD轻松入门教程(三)

Service业务逻辑对象层

什么是业务?

代表用户完成的一个业务功能,这个功能可以由一个或者多个DAO层的调用组成。

需要将这种业务功能也单独的抽取出来一层Service,将一个和多个业务功能在Service层中实现,而对于入口main方法,只需要调用Service层的某个业务方法,即可实现具体内容,main方法只需要提供对应的参数即可,具体实现都在Service和DAO层中完成。

image.png

准备关系表

准备一张Account表:

  • id:int类型,主键自增
  • name:varchar类型,非空
  • card_no:varchar类型,非空
  • pwd:varchar类型,非空
  • money:decimal(10,2)类型,非空不允许为负数
create table account(
  id int primary key auto_increment comment '主键',
  name varchar(16) not null comment '名称',
  card_no varchar(32) not null comment '卡号',
  pwd varchar(32) not null comment '密码',
  money decimal(10,2) UNSIGNED not null comment '余额'
);

准备测试数据

image.png

准备实体类

/**
 * 映射account表
 */
public class Account {

    private int id;

    private String name;

    private String cardNo;

    private String pwd;
  
    private BigDecimal money;

    // 省略 无参,有参,toString,get/set
}

准备DAO接口和实现类

根据咱们转账的业务,可以看出,需要提供两个方法

  • 根据cardNo查询当前account的信息。
  • 根据卡号修改金额money。

AccountDao接口:

package com.jimihua.dao;

import com.jimihua.entity.Account;

/**
 * account表的Dao层操作
 */
public interface AccountDao {

    /**
     * 根据卡号查询具体的account信息
     * @param cardNo
     * @return
     */
    Account findByCardNo(String cardNo);

    /**
     * 根据卡号修改account的余额
     * @param account
     * @return
     */
    int updateMoneyByCardNo(Account account);

}

准备AccountDao的实现类:

package com.jimihua.dao.impl;

import com.jimihua.dao.AccountDao;
import com.jimihua.entity.Account;
import com.jimihua.entity.Person;
import com.jimihua.utils.DatabaseUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class AccountDaoImpl  implements AccountDao {



    @Override
    public Account findByCardNo(String cardNo) throws SQLException {
        //1、获取连接
        Connection conn = DatabaseUtils.getConnection();

        //2、编写SQL语句
        String sql = "select * from account where card_no = ?";

        //3、获取PreparedStatement,传入SQL
        PreparedStatement ps = conn.prepareStatement(sql);

        //4、给占位符赋值
        ps.setObject(1,cardNo);

        //5、执行SQL,获取结果集ResultSet
        ResultSet rs = ps.executeQuery();

        //6、处理结果集封装数据
        Account account = null;
        if(rs.next()){
            account = new Account();
            account.setId(rs.getInt("id"));
            account.setName(rs.getString("name"));
            account.setCardNo(rs.getString("card_no"));
            account.setPwd(rs.getString("pwd"));
            account.setMoney(rs.getBigDecimal("money"));
        }

        //7、释放资源
        DatabaseUtils.closeAll(conn,ps,rs);

        //8、返回结果
        return account;
    }

    @Override
    public int updateMoneyByCardNo(Account account) throws SQLException {
        //1、获取连接
        Connection conn = DatabaseUtils.getConnection();

        //2、编写SQL语句
        String sql = "update account set money = ? where card_no = ?";

        //3、获取PreparedStatement,传入SQL
        PreparedStatement ps = conn.prepareStatement(sql);

        //4、给占位符赋值
        ps.setObject(1,account.getMoney());
        ps.setObject(2,account.getCardNo());

        //5、执行SQL,获取结果
        int count = ps.executeUpdate();

        //6、释放资源
        DatabaseUtils.closeAll(conn,ps);

        //7、返回结果
        return count;
    }
}

准备Service接口和实现类

经过需要做的转账操作,大致分析,需要几个参数

  • fromCardNo,转账人的卡号。
  • toCardNo,被转账人的卡号。
  • fromPwd,转账人的密码。
  • amount,转账金额

提供Service层的接口:

package com.jimihua.service;


import com.jimihua.entity.Account;

import java.math.BigDecimal;

/**
 * 在这个AccountService中提供转账的功能~
 */
public interface AccountService {


    /**
     * 转账操作
     * @param fromCardNo   转账人的卡号。
     * @param toCardNo     被转账人的卡号。
     * @param fromPwd      转账人的密码。
     * @param amount       转账金额
     * @return
     */
    String transfer(String fromCardNo, String toCardNo, String fromPwd, BigDecimal amount);


}

实现AccountServiceImpl中的具体转账逻辑

package com.jimihua.service.impl;

import com.jimihua.dao.AccountDao;
import com.jimihua.dao.impl.AccountDaoImpl;
import com.jimihua.entity.Account;
import com.jimihua.service.AccountService;

import java.math.BigDecimal;
import java.sql.SQLException;

public class AccountServiceImpl implements AccountService {

    // 与account的Dao层的交互,需要用的对象
    private AccountDao accountDao = new AccountDaoImpl();


    @Override
    public String transfer(String fromCardNo, String toCardNo, String fromPwd, BigDecimal amount) throws SQLException {
//        1、查看卡号是否存在?
        Account fromAccount = accountDao.findByCardNo(fromCardNo);
        if(fromAccount == null){
            throw new RuntimeException("--------转账人卡号不存在---------");
        }
//        2、匹配你输入的密码是否正确?
        if (!fromAccount.getPwd().equals(fromPwd)) {
            throw new RuntimeException("--------转账人密码错误---------");
        }
//        3、查看余额是否充足?
        if (fromAccount.getMoney().compareTo(amount) < 0) {
            throw new RuntimeException("--------转账人余额不足---------");
        }
//        4、查看被转账方卡号是否存在?
        Account toAccount = accountDao.findByCardNo(toCardNo);
        if (toAccount == null) {
            throw new RuntimeException("--------被转账人卡号不存在---------");
        }
//        5、给转账人卡里扣钱。
        fromAccount.setMoney(fromAccount.getMoney().subtract(amount));
        int fromCount = accountDao.updateMoneyByCardNo(fromAccount);
        if (fromCount != 1) {
            throw new RuntimeException("--------转账人扣钱失败!---------");
        }
//        6、给被转账人卡里加钱。
        toAccount.setMoney(toAccount.getMoney().add(amount));
        int toCount = accountDao.updateMoneyByCardNo(toAccount);
        if (toCount != 1){
            throw new RuntimeException("--------被转账人加钱失败!---------");
        }
        return "转账成功!";
    }
}

在main方法中测试

可以根据Service中的业务逻辑,测试各个判断是否正常的生效。

package com.jimihua;

import com.jimihua.service.AccountService;
import com.jimihua.service.impl.AccountServiceImpl;

import java.math.BigDecimal;
import java.sql.SQLException;

/**
 * 完成转账操作的main方法
 */
public class Demo9 {


    public static void main(String[] args) throws SQLException {
        AccountService accountService = new AccountServiceImpl();
        String result = accountService.transfer("10086", "10010", "123456", new BigDecimal(1000));
        System.out.println(result);
    }
}

事务的处理

JDBC中事务如何操作

在Java中控制事务操作。

在Java中需要使用Connection对象来控制事务。Connection提供了三个方法:

  • conn.setAutoCommit(false);
  • conn.commit();
  • conn.rollback();

转账事务如何控制

现在需要做的事情很简单,只需要在5和6的操作位置追加上事务控制即可。

Connection conn1 = DatabaseUtils.getConnection();
try {
 // 开启事务
 conn1.setAutoCommit(false);
//        5、给转账人卡里扣钱。
//        6、给被转账人卡里加钱。
 // 提交事务
 conn1.commit();
}catch(Exception e){
 // 回滚事务
 conn1.rollback();
}

但是这样一来就存在了一个问题,Service层基于DatabaseUtils拿到的Connection对象和Dao层中基于DatabaseUtils获取到的Connection不是同一个。

Service和Dao不是一个Connection

解决方案:

  • 可以直接将Service层获取到的Connection对象作为参数传递到Dao层,Dao层直接使用Service中传递过来的Connection和数据库交互即可。

Ps:首先上述方式可以解决,但是会污染到Dao层接口和实现类的方法声明的信息……

  • 可以采用ThreadLocal的方式,ThreadLocal可以将Connection对象在Service层获取,并且存储到ThreadLocal中,当Dao层需要和数据库交互时,优先从ThreadLocal中尝试获取Connection对象。

基于ThreadLocal的方案,直接去修改DatabaseUtils中提供的getConnection的静态方法

private static final ThreadLocal<Connection> tl = new ThreadLocal<>();

/**
 * 获取Connection对象方法
 * @return
 */
public static Connection getConnection() {
    //1、先尝试从ThreadLocal中获取。
    Connection conn = tl.get();
    if (conn == null) {
        //2、没获取到,基于DriverManager获取Connection,然后添加到ThreadLocal中
        try {
            conn = DriverManager.getConnection(PROP.getProperty("jdbc.url"),PROP.getProperty("jdbc.username"),PROP.getProperty("jdbc.password"));
        } catch (SQLException e) {
            throw new RuntimeException("获取Connection出错!");

        }
        tl.set(conn);
    }
    //3、获取到了,直接返回
    return conn;
}

image.png

Dao层释放Connection资源需要前置到Service

在前面的ThreadLocal封装好之后,会出现一个诡异的错误

image.png

经过ThreadLocal的整改后,让整个Service和Dao中使用的Connection对象只有一个。

但是咱们将释放资源的操作放在了Dao层中完成,可是Service一次业务可能会涉及到多次和Dao层交互,如果第一个Dao层操作完,直接释放掉了Connection,就会导致后续的Dao操作无法完成,事务也无法正常开启。

问题的解决方案很简单,将Dao层释放Connection资源的操作前置到Service层。

但是Dao依然要正常的释放Statement,ResultSet。

所以要再次改造DatabaseUtils中的closeAll方法。

  • 每个资源的close和非空判断要单独走。
  • 当释放connection资源时,记得要将ThreadLocal中存储的Connection给remove掉。
/**
 * 针对查询操作的释放资源
 * @param connection
 * @param statement
 * @param resultSet
 */
public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
    try {
        if(resultSet != null)
            resultSet.close();

        if(statement != null)
            statement.close();

        if (connection != null) {
            connection.close();
            tl.remove();
        }
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }

}
/**
 * 针对写操作的释放资源
 * @param connection
 * @param statement
 */
public static void closeAll(Connection connection, Statement statement) {
    try {
        if(statement != null)
            statement.close();
  
        if(connection != null) {
            connection.close();
            tl.remove();
        }
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
}

首先将Dao层中的connection资源的释放逻辑去掉,closeAll时,不传递connection对象

image.png

可以在Service层中提交事务或者回滚事务之后,将Connection资源释放掉

package com.jimihua.service.impl;

import com.jimihua.dao.AccountDao;
import com.jimihua.dao.impl.AccountDaoImpl;
import com.jimihua.entity.Account;
import com.jimihua.service.AccountService;
import com.jimihua.utils.DatabaseUtils;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.SQLException;

public class AccountServiceImpl implements AccountService {

    // 与account的Dao层的交互,需要用的对象
    private AccountDao accountDao = new AccountDaoImpl();


    @Override
    public String transfer(String fromCardNo, String toCardNo, String fromPwd, BigDecimal amount) throws SQLException {
//        1、查看卡号是否存在?
        Account fromAccount = accountDao.findByCardNo(fromCardNo);
        if (fromAccount == null) {
            throw new RuntimeException("--------转账人卡号不存在---------");
        }
//        2、匹配你输入的密码是否正确?
        if (!fromAccount.getPwd().equals(fromPwd)) {
            throw new RuntimeException("--------转账人密码错误---------");
        }
//        3、查看余额是否充足?
        if (fromAccount.getMoney().compareTo(amount) < 0) {
            throw new RuntimeException("--------转账人余额不足---------");
        }
//        4、查看被转账方卡号是否存在?
        Account toAccount = accountDao.findByCardNo(toCardNo);
        if (toAccount == null) {
            throw new RuntimeException("--------被转账人卡号不存在---------");
        }


        Connection conn = DatabaseUtils.getConnection();
        try {
            // 开启事务
            conn.setAutoCommit(false);
//        5、给转账人卡里扣钱。
            fromAccount.setMoney(fromAccount.getMoney().subtract(amount));
            int fromCount = accountDao.updateMoneyByCardNo(fromAccount);
            if (fromCount != 1) {
                throw new RuntimeException("--------转账人扣钱失败!---------");
            }
//        6、给被转账人卡里加钱。
            toAccount.setMoney(toAccount.getMoney().add(amount));
            int toCount = accountDao.updateMoneyByCardNo(toAccount);
            if (toCount != 1) {
                throw new RuntimeException("--------被转账人加钱失败!---------");
            }
            // 提交事务
            conn.commit();
            DatabaseUtils.closeAll(conn,null);
        } catch (Exception e) {
            e.printStackTrace();
            // 回滚事务
            conn.rollback();
            DatabaseUtils.closeAll(conn,null);
            return "转账失败!";
        }


        return "转账成功!";
    }
}

事务的控制封装到DatabaseUtils中

在Service层中的这个操作有点恶心,获取Connection,同时还要是提交或者回滚之后,去释放Connection资源,不如将事务的控制再封装到DatabaseUtils中。

/**
 * 开启事务
 */
public static void begin(){
    Connection conn = getConnection();
    try {
        conn.setAutoCommit(false);
    } catch (SQLException e) {
        throw new RuntimeException("开启事务失败!");
    }
}

/**
 * 提交事务
 */
public static void commit(){
    Connection conn = getConnection();
    try {
        conn.commit();
    } catch (SQLException e) {
        throw new RuntimeException("提交事务失败!");
    }finally {
        closeAll(conn,null);
    }
}

/**
 * 回滚事务
 */
public static void rollback(){
    Connection conn = getConnection();
    try {
        conn.rollback();
    } catch (SQLException e) {
        throw new RuntimeException("回滚事务失败!");
    }finally {
        closeAll(conn,null);
    }
}

再次改造Service层代码

package com.jimihua.service.impl;

import com.jimihua.dao.AccountDao;
import com.jimihua.dao.impl.AccountDaoImpl;
import com.jimihua.entity.Account;
import com.jimihua.service.AccountService;
import com.jimihua.utils.DatabaseUtils;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.SQLException;

public class AccountServiceImpl implements AccountService {

    // 与account的Dao层的交互,需要用的对象
    private AccountDao accountDao = new AccountDaoImpl();


    @Override
    public String transfer(String fromCardNo, String toCardNo, String fromPwd, BigDecimal amount) throws SQLException {
//        1、查看卡号是否存在?
        Account fromAccount = accountDao.findByCardNo(fromCardNo);
        if (fromAccount == null) {
            throw new RuntimeException("--------转账人卡号不存在---------");
        }
//        2、匹配你输入的密码是否正确?
        if (!fromAccount.getPwd().equals(fromPwd)) {
            throw new RuntimeException("--------转账人密码错误---------");
        }
//        3、查看余额是否充足?
        if (fromAccount.getMoney().compareTo(amount) < 0) {
            throw new RuntimeException("--------转账人余额不足---------");
        }
//        4、查看被转账方卡号是否存在?
        Account toAccount = accountDao.findByCardNo(toCardNo);
        if (toAccount == null) {
            throw new RuntimeException("--------被转账人卡号不存在---------");
        }


        try {
            // 开启事务
            DatabaseUtils.begin();
//        5、给转账人卡里扣钱。
            fromAccount.setMoney(fromAccount.getMoney().subtract(amount));
            int fromCount = accountDao.updateMoneyByCardNo(fromAccount);
            if (fromCount != 1) {
                throw new RuntimeException("--------转账人扣钱失败!---------");
            }
//        6、给被转账人卡里加钱。
            toAccount.setMoney(toAccount.getMoney().add(amount));
            int toCount = accountDao.updateMoneyByCardNo(toAccount);
            if (toCount != 1) {
                throw new RuntimeException("--------被转账人加钱失败!---------");
            }
            // 提交事务
            DatabaseUtils.commit();
        } catch (Exception e) {
            e.printStackTrace();
            // 回滚事务
            DatabaseUtils.rollback();
            return "转账失败!";
        }
        return "转账成功!";
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鷄米花

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值