Fms-Java版开发实录:36 业务层和持久层编写

Fms-Java版开发实录:36 业务层和持久层编写

数据对象好了之后是持久层和业务层,再之后就是专心控制器与模板了。

这一次准备一起把持久层和业务层编写掉,顺便也统一一下函数名和新的构想

Journal类的持久层和业务层

Journal类虽然是最简单的类,但其持久层要完成重要的任务,那就是根据合同、合同类别与项目,取出对应的Journal类的集合。

持久层如下:

public interface JournalDao extends JpaRepository<Journal, Integer> {

    // 根据Contract id 查找全部Journal
    Set<Journal> findJournalByContractIdOrderByCreateTime(int cid);

    // 根据SubjectId查找全部Journal
    Set<Journal> findJournalByContract_SubjectIdOrderByCreateTime(int sid);

    // 根据ProjectId查找全部Journal
    Set<Journal> findJournalByContract_Subject_ProjectIdOrderByCreateTime(int pid);
   
}

这里实际上可以根据合同、合同类别和项目的id来查出对应的所有Journal,这实际上是为了方便在业务层中组装BO对象。

这里也学习到了JPA跨表查询的方法。

Journal的业务层则比较简单,主要集中与单体数据对象操作:

@Service
public class JournalService {

    private final JournalDao journalDao;

    @Autowired
    public JournalService(JournalDao journalDao) {
        this.journalDao = journalDao;
    }

    @Transactional
    public Journal getJournalById(int id) {
        return journalDao.findById(id).orElseThrow(() -> new RuntimeException("找不到明细记录 id=" + id));
    }

    @Transactional
    public Set<Journal> getJournalByContractId(int id) {
        return journalDao.findJournalByContractIdOrderByCreateTime(id);
    }

    @Transactional
    public Journal save(Journal journal) {
        return journalDao.save(journal);
    }

    @Transactional
    public void deleteJournal(int id) {
        journalDao.deleteById(id);
    }

}

Contract类的持久层和业务层

作为Journal的上一层数据,除了操作单体Contract对象之外,业务层的重要作用就是生成ContractData这个BO对象,用于在列表和详情中展示。

持久层没有什么特色:

public interface ContractDao extends JpaRepository<Contract, Integer> {

    boolean existsByNumber(String number);
     
    // 根据Subject的Id查找对应的全部合同
    Set<Contract> findContractBySubjectIdOrderByCreateTime(int id);
    
}

业务层则引入了JournalDao,用于组装ContractData对象:

@Service
public class ContractService {

    private final ContractDao contractDao;
    private final JournalDao journalDao;

    @Autowired
    public ContractService(ContractDao contractDao, JournalDao journalDao) {
        this.contractDao = contractDao;
        this.journalDao = journalDao;
    }

    // 查找contract本身的服务

    // 检查合同是否已经存在
    @Transactional
    public boolean existByNumber(String number) {
        return contractDao.existsByNumber(number);
    }

    // 保存合同
    @Transactional
    public Contract saveContract(Contract contract) {
        return contractDao.save(contract);
    }

    // 按照id查找合同
    @Transactional
    public Contract getContractById(int id) {
        return contractDao.findById(id).orElseThrow(() -> new RuntimeException("找不到合同: id=" + id));
    }

    // 按照id删除合同
    @Transactional
    public void deleteContractById(int id) {
        contractDao.deleteById(id);
    }

    // 按照类别来查找对应的合同
    @Transactional
    public Set<Contract> getContractBySubjectId(int pid) {
        return contractDao.findContractBySubjectIdOrderByCreateTime(pid);
    }

    // 返回ContractData的功能
    @Transactional
    public ContractData getContractDataById(int id) {
        Contract contract = getContractById(id);
        ContractData contractData = new ContractData(contract);

        // 用该合同对应的所有journals,累计到contractData对象上去
        Set<Journal> journals = journalDao.findJournalByContractIdOrderByCreateTime(id);

        journals.forEach(eachJournal -> {
            // 累计对应journal的货币资金
            contractData.setCash(contractData.getCash().add(eachJournal.getCash()));
            // 累计对应journal的开发成本
            contractData.setDevelopmentCost(contractData.getDevelopmentCost().add(eachJournal.getDevelopmentCost()));
            // 累计对应journal的预付账款
            contractData.setPrepaid(contractData.getPrepaid().add(eachJournal.getPrepaid()));
            // 累计对应journal的进项税金
            contractData.setInputVAT(contractData.getInputVAT().add(eachJournal.getInputVAT()));
            // 累计对应journal的其他资产
            contractData.setOtherAsset(contractData.getOtherAsset().add(eachJournal.getOtherAsset()));
            // 累计对应journal的应付账款
            contractData.setAccountPayable(contractData.getAccountPayable().add(eachJournal.getAccountPayable()));
            // 累计对应journal的其他应付款
            contractData.setOtherPayable(contractData.getOtherPayable().add(eachJournal.getOtherPayable()));
            // 累计对应journal的其他负债
            contractData.setOtherLiability(contractData.getOtherLiability().add(eachJournal.getOtherLiability()));
            // 累计对应journal的其他损益
            contractData.setOtherPL(contractData.getOtherPL().add(eachJournal.getOtherPL()));
        });

        return contractData;
    }

}

这个业务层的核心就是通过合同的Id来组装一个包含了单体合同与聚合其所属的Journal类计算结果的一个BO,可以想到,合同类别与项目的业务层,也会具有类似的操作,都需要使用到JournalDao中的方法。

Subject类的持久层和业务层

这个其实和Contract的持久层和业务层非常类似了,可以看到我也是重新规划了这些类,让这些类看起来基本上都是相同的功能和类似风格的方法名称。这里就直接放代码了。

public interface SubjectDao extends JpaRepository<Subject, Integer> {
    Set<Subject> findSubjectByProjectIdOrderByCreateTime(int id);
}
@Service
public class SubjectService {

    private final SubjectDao subjectDao;
    private final JournalDao journalDao;

    @Autowired
    public SubjectService(SubjectDao subjectDao, JournalDao journalDao) {
        this.subjectDao = subjectDao;
        this.journalDao = journalDao;
    }

    // 删除类别
    @Transactional
    public void deleteSubject(int id) {
        subjectDao.deleteById(id);
    }

    // 保存类别
    @Transactional
    public Subject save(Subject subject) {
        return subjectDao.save(subject);
    }

    // 按照项目id来查找对应的类别
    @Transactional
    public Set<Subject> getBudgetByProjectId(int pid) {
        return subjectDao.findSubjectByProjectIdOrderByCreateTime(pid);
    }

    // 按照id查找类别
    @Transactional
    public Subject getSubjectById(int id) {
        return subjectDao.findById(id).orElseThrow(() -> new RuntimeException("找不到对应的合同类别 id=" + id));
    }


    // 组装SubjectData
    @Transactional
    public SubjectData getSubjectDataById(int id) {
        Subject subject = getSubjectById(id);
        SubjectData subjectData = new SubjectData(subject);

        // 用该subject对应的所有journals,累计到subjectData对象上去
        Set<Journal> journals = journalDao.findJournalByContract_SubjectIdOrderByCreateTime(id);

        journals.forEach(eachJournal -> {
            // 累计对应journal的货币资金
            subjectData.setCash(subjectData.getCash().add(eachJournal.getCash()));
            // 累计对应journal的开发成本
            subjectData.setDevelopmentCost(subjectData.getDevelopmentCost().add(eachJournal.getDevelopmentCost()));
            // 累计对应journal的预付账款
            subjectData.setPrepaid(subjectData.getPrepaid().add(eachJournal.getPrepaid()));
            // 累计对应journal的进项税金
            subjectData.setInputVAT(subjectData.getInputVAT().add(eachJournal.getInputVAT()));
            // 累计对应journal的其他资产
            subjectData.setOtherAsset(subjectData.getOtherAsset().add(eachJournal.getOtherAsset()));
            // 累计对应journal的应付账款
            subjectData.setAccountPayable(subjectData.getAccountPayable().add(eachJournal.getAccountPayable()));
            // 累计对应journal的其他应付款
            subjectData.setOtherPayable(subjectData.getOtherPayable().add(eachJournal.getOtherPayable()));
            // 累计对应journal的其他负债
            subjectData.setOtherLiability(subjectData.getOtherLiability().add(eachJournal.getOtherLiability()));
            // 累计对应journal的其他损益
            subjectData.setOtherPL(subjectData.getOtherPL().add(eachJournal.getOtherPL()));
        });

        return subjectData;
    }
}

Project类的持久层和业务层

不用解释了,直接上代码。。。

public interface ProjectDao extends JpaRepository<Project, Integer> {
    Set<Project> findAllByProjectNameContaining(String target);
}
@Service
public class ProjectService {

    private final ProjectDao projectDao;
    private final JournalDao journalDao;

    @Autowired
    public ProjectService(ProjectDao projectDao, JournalDao journalDao) {
        this.projectDao = projectDao;
        this.journalDao = journalDao;
    }

    // 查找所有项目
    @Transactional
    public List<Project> findAllProjects() {
        return projectDao.findAll(Sort.by(Sort.Direction.ASC, "createTime"));
    }

    // 保存项目
    @Transactional
    public Project save(Project project) {
        return projectDao.save(project);
    }

    // 按照id查找项目
    @Transactional
    public Project getProjectById(int id) {
        return projectDao.findById(id).orElseThrow(() -> new RuntimeException("找不到项目 id=" + id));
    }

    // 删除项目
    @Transactional
    public void deleteProject(int id) {
        projectDao.deleteById(id);
    }

    // 搜索项目名称
    @Transactional
    public Set<Project> projectSearchResult(String target) {
        return projectDao.findAllByProjectNameContaining(target);
    }
    
    // 组装ProjectData
    @Transactional
    public ProjectData getProjectDataById(int id) {
        Project project = getProjectById(id);
        ProjectData projectData = new ProjectData(project);

        // 用该subject对应的所有journals,累计到subjectData对象上去
        Set<Journal> journals = journalDao.findJournalByContract_Subject_ProjectIdOrderByCreateTime(id);

        journals.forEach(eachJournal -> {
            // 累计对应journal的货币资金
            projectData.setCash(projectData.getCash().add(eachJournal.getCash()));
            // 累计对应journal的开发成本
            projectData.setDevelopmentCost(projectData.getDevelopmentCost().add(eachJournal.getDevelopmentCost()));
            // 累计对应journal的预付账款
            projectData.setPrepaid(projectData.getPrepaid().add(eachJournal.getPrepaid()));
            // 累计对应journal的进项税金
            projectData.setInputVAT(projectData.getInputVAT().add(eachJournal.getInputVAT()));
            // 累计对应journal的其他资产
            projectData.setOtherAsset(projectData.getOtherAsset().add(eachJournal.getOtherAsset()));
            // 累计对应journal的应付账款
            projectData.setAccountPayable(projectData.getAccountPayable().add(eachJournal.getAccountPayable()));
            // 累计对应journal的其他应付款
            projectData.setOtherPayable(projectData.getOtherPayable().add(eachJournal.getOtherPayable()));
            // 累计对应journal的其他负债
            projectData.setOtherLiability(projectData.getOtherLiability().add(eachJournal.getOtherLiability()));
            // 累计对应journal的其他损益
            projectData.setOtherPL(projectData.getOtherPL().add(eachJournal.getOtherPL()));
        });

        return projectData;
    }
    
}

这样重构之后,整个业务层和持久层的逻辑清晰多了,每个业务层使用自己的Dao操作单体数据,使用JournalDao来提供聚合所有Journal的运算。

从整体来看,控制器只需要拿到Id就可以计算出一个任意层级数据的聚合结果,可以用查找所需的列表-生成BO类的集合-渲染到页面的方式来得到结果,各个层级的操作也很统一。

LICENSED UNDER CC BY-NC-SA 4.0
Comment