这一次准备一起把持久层和业务层编写掉,顺便也统一一下函数名和新的构想
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
类的集合-渲染到页面的方式来得到结果,各个层级的操作也很统一。