引言

对于业务逻辑层的开发重复代码很多,尽管有代码生成器,但从代码量总的来说还是比较多,所以就有了以下抽象类及工具,对一些常用操作进行封装。

对通用新增、删除、编辑、查询,代码操作进行封装简化。你只需要写你的业务逻辑代码就可以了。

对特有树状结构特有字段如(所有父级编码、所有排序号编码、是否是叶子节点、当前节点层次)进行更新,比如,通过所有父级编码可快速查询到所有子级的数据;通过所有排序号,可快速对整个树结构进行排序;通过是否叶子节点快速得知是否有下级;根据当前层次快速知道当前节点在树中的级别。

对通用数据权限进行简化封装,将颗粒度降到人员身上,支持人员与数据,角色与数据权限定制。数据权限不仅仅支持公司、部门、角色,还可以通过配置支持你的业务字段数据信息过滤,如订单的区域、内容管理的栏目、故障单类型等等。

对事务处理使用Spring事务@Transactional注解,进行方法级别的事务控制,不用单独处理事务及回滚。如配置传播行为,进行事务继承,子事务,事务回滚行为等,配置隔离级别读取未提交的数据等。

基类及接口的继承关系

TreeService -> CrudService -> QueryService -> BaseService

TreeDao -> CrudDao -> QueryDao -> BaseDao

TreeEntity -> DataEntity -> BaseEntity

QueryService查询抽象基类

/**
* 新建实体对象
* @return
*/
protected T newEntity(); /**
* 新建实体对象(带一个String构造参数)
* @return
*/
protected T newEntity(String id); /**
* 获取单条数据
* @param id 主键
* @return
*/
public T get(String id); /**
* 获取单条数据
* @param entity
* @return
*/
public T get(T entity); /**
* 获取单条数据,如果获取不到,则实例化一个空实体
* @param id 主键编号
* @param isNewRecord 如果是新记录,则验证主键编号是否存在。
* 如果存在抛出ValidationException异常。
* @return
*/
public T get(String id, boolean isNewRecord); /**
* 获取单条数据,如果获取不到,则实例化一个空实体(多个主键情况下调用)
* @param pkClass 主键类型数组
* @param pkValue 主键数据值数组
* @param isNewRecord 如果是新记录,则验证主键编号是否存在。
* 如果存在抛出ValidationException异常。
* @return
*/
public T get(Class<?>[] pkClass, Object[] pkValue, boolean isNewRecord); /**
* 列表查询数据
* @param entity
* @return
*/
public List<T> findList(T entity); /**
* 分页查询数据
* @param page 分页对象
* @param entity
* @return
*/
public Page<T> findPage(Page<T> page, T entity); /**
* 查询列表总数
* @param entity
* @return
*/
public long findCount(T entity);

CrudService增删改抽象基类

该类继承QueryService抽象类

/**
* 保存数据(插入或更新)
* @param entity
*/
@Transactional(readOnly = false)
public void save(T entity) /**
* 插入数据
* @param entity
*/
@Transactional(readOnly = false)
public void insert(T entity); /**
* 更新数据
* @param entity
*/
@Transactional(readOnly = false)
public void update(T entity); /**
* 更新状态(级联更新子节点)
* @param entity
*/
@Transactional(readOnly = false)
public void updateStatus(T entity); /**
* 删除数据
* @param entity
*/
@Transactional(readOnly = false)
public void delete(T entity);

TreeService树结构抽象基类

该类继承CrudService抽象类

/**
* 根据父节点获取子节点最后一条记录
*/
public T getLastByParentCode(T entity); /**
* 保存数据(插入或更新)
* 实现自动保存字段:所有父级编号、所有排序号、是否是叶子节点、节点的层次级别等数据
* 实现级联更新所有子节点数据:同父级自动保存字段
*/
@Transactional(readOnly = false)
public void save(T entity); /**
* 更新parent_codes、tree_sorts、tree_level字段值
*/
@Transactional(readOnly = false, isolation = Isolation.READ_UNCOMMITTED) // 可读取未提交数据
private void updateParentCodes(T entity); /**
* 更新当前节点排序号
*/
@Transactional(readOnly = false)
public void updateTreeSort(T entity); /**
* 更新tree_leaf字段值
*/
@Transactional(readOnly = false, isolation = Isolation.READ_UNCOMMITTED) // 可读取未提交数据
private void updateTreeLeaf(T entity); /**
* 修正本表树结构的所有父级编号
* 包含:数据修复(parentCodes、treeLeaf、treeLevel)字段
*/
@Transactional(readOnly = false) // 可读取未提交数据
public void updateFixParentCodes(); /**
* 按父级编码修正树结构的所有父级编号
* 包含:数据修复(parentCodes、treeLeaf、treeLevel)字段
*/
@Transactional(readOnly = false) // 可读取未提交数据
public void updateFixParentCodes(String parentCode); /**
* 预留接口事件,更新子节点
* @param childEntity 当前操作节点的子节点
* @param parentEntity 当前操作节点
*/
protected void updateChildNode(T childEntity, T parentEntity); /**
* 更新状态(级联更新子节点)
* @param entity
*/
@Transactional(readOnly = false)
public void updateStatus(T entity); /**
* 删除数据(级联删除子节点)
* @param entity
*/
@Transactional(readOnly = false)
public void delete(T entity); /**
* 将不同级别无序的树列表进行排序,前提是sourcelist每一级是有序的<br>
* 举例如下:<br>
* List<T> targetList = Lists.newArrayList();<br>
* List<T> sourcelist = service.findList(category);<br>
* service.execTreeSort(targetList, sourcelist, "0");<br>
* @param sourceList 源数据列表
* @param targetList 目标数据列表
* @param parentCode 目标数据列表的顶级节点
*/
public void execTreeSort(List<T> sourceList, List<T> targetList, String parentCode); /**
* 将简单列表code,parentCode转换为嵌套列表形式code,childList[code,childList[...]]<br>
* 举例如下:<br>
* List<T> targetList = Lists.newArrayList();<br>
* List<T> sourcelist = service.findList(category);<br>
* service.execChildListBulid(targetList, sourcelist, "0");<br>
* @param sourceList 源数据列表
* @param targetList 目标数据列表
* @param parentCode 目标数据列表的顶级节点
*/
public void execChildListBulid(List<T> sourceList, List<T> targetList, String parentCode);

数据权限调用

本次对数据权限这块进行了全面的升级,让数据权限颗粒度细化到人员身上,支持人员与权限和角色与权限。

调用示例:

/**
* 添加数据权限过滤条件
*/
public void addDataScopeFilter(T entity){
// 举例1:公司数据权限过滤,实体类@Table注解extWhereKeys="dsf"
company.getSqlMap().getDataScope().addFilter("dsf", "Company", "a.company_code");
// 举例2:部门数据权限过滤,实体类@Table注解extWhereKeys="dsf"
office.getSqlMap().getDataScope().addFilter("dsf", "Office", "a.office_code");
// 举例3:角色数据权限过滤,实体类@Table注解extWhereKeys="dsf"
role.getSqlMap().getDataScope().addFilter("dsf", "Role", "a.role_code");
// 举例4:用户、员工数据权限根据部门过滤,实体类@Table注解extWhereKeys="dsfOffice"
empUser.getSqlMap().getDataScope().addFilter("dsfOffice",
"Office", "e.office_code", "a.create_by");
// 举例5:用户、员工数据权限根据公司过滤,实体类@Table注解extWhereKeys="dsfCompany"
empUser.getSqlMap().getDataScope().addFilter("dsfCompany",
"Company", "e.company_code", "a.create_by");
}

API接口:

/**
* 数据范围过滤
* @param sqlMapKey sqlMap的键值,举例:如设置“dsf”数据范围过滤,则:<br>
* exists方式对应:sqlMap.dsf; join方式对应:sqlMap.dsfForm 和 sqlMap.dsfWhere
* @param ctrlTypes 控制类型,多个用“,”隔开,举例:<br>
* 控制角色:Role<br>
* 控制部门:Office<br>
* 控制公司:Company<br>
* @param bizCtrlDataFields 业务表对应过滤表别名或需要过滤的表别名加权限字段,多个使用“,”分隔,
* 长度必须与tableTypes保持一致,举例:<br>
* 业务表控制角色:a.role_code<br>
* 业务表控制部门:a.office_code<br>
* 业务表控制公司:a.company_code<br>
* @param bizCtrlUserField 业务表对应表别名或用户字段。过滤只可以查看本人数据。
* 不设置的话,如果没有范围权限,则查不到任何数据,举例:<br>
* 业务表a.create_by"
* @example
* 1)在Service中调用如下两种方式:<br>
* // 添加数据权限过滤条件(控制角色)<br>
* entity.getSqlMap().getDataScope().addFilter("dsf", "Role", "a.role_code");<br>
* // 添加数据权限过滤条件(控制公司和部门)<br>
* entity.getSqlMap().getDataScope().addFilter("dsf",<br>
* "Office,Company", "a.office_code,a.company_code");<br>
* 2)MyBatis \@Table 注解中调用如下:<br>
* 采用 EXISTS方式调用 : \@Table(extWhereKeys="dsf")
* 采用JOIN方式调用 : \@Table(extFromKeys="dsf",extWhereKeys="dsf")
* 3)MyBatis Mapper 中调用如下两种方式:<br>
* 采用 EXISTS方式调用 : 将 ${sqlMap.dsf} 放在Where后<br>
* 采用JOIN方式调用 : 将 ${sqlMap.dsfFrom} 放在Form后 ,将 ${sqlMap.dsfWhere} 放在Where后<br>
*/
public QueryDataScope addFilter(String sqlMapKey, String ctrlTypes, String bizCtrlDataFields, String bizCtrlUserField);

数据库事务

事务管理对于企业应用来说是至关重要的,当出现异常情况,它也可以保证数据的一致性。

JeeSite主要使用Spring的@Transactional注解,也称声明式事务管理,是建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需通过基于@Transactional注解的方式,便可以将事务规则应用到业务逻辑中。

注解属性

属性 类型 描述
传播性 枚举型 可选的传播性设置(默认值:Propagation.REQUIRED)
隔离性 枚举型 可选的隔离性级别(默认值:Isolation.ISOLATION_DEFAULT)
只读性 布尔型 读写型事务 vs. 只读型事务
超时 int型 事务超时(以秒为单位)
回滚异常类(rollbackFor) Class 类的实例,必须是Throwable 的子类 异常类,遇到时必须进行回滚。默认情况下checked exceptions不进行回滚,仅unchecked exceptions(即RuntimeException的子类)才进行事务回滚。
不回滚异常类(noRollbackFor) Class 类的实例,必须是Throwable的子类 异常类,遇到时必须不回滚。

事务传播行为

所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在Propagation定义中包括了如下几个表示传播行为的常量:

  • Propagation.REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务,这是默认值。
  • Propagation.REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

事务隔离级别

隔离级别是指若干个并发的事务之间的隔离程度。Isolation 接口中定义了五个表示隔离级别的常量:

  • Isolation.DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是Isolation.READ_COMMITTED。
  • Isolation.READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
  • Isolation.READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
  • Isolation.REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
  • Isolation.SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

转载于:https://my.oschina.net/thinkgem/blog/1538766

JeeSite 4.0 简化业务逻辑层开发的更多相关文章

  1. MVC5 网站开发之四 业务逻辑层的架构和基本功能

    业务逻辑层在Ninesky.Core中实现,主要功能封装一些方法通过调用数据存储层,向界面层提供服务.   目录 奔跑吧,代码小哥! MVC5网站开发之一 总体概述 MVC5 网站开发之二 创建项目 ...

  2. 在 ASP.NET 中创建数据访问和业务逻辑层(转)

    .NET Framework 4 当在 ASP.NET 中处理数据时,可从使用通用软件模式中受益.其中一种模式是将数据访问代码与控制数据访问或提供其他业务规则的业务逻辑代码分开.在此模式中,这两个层均 ...

  3. HL AsySocket 服务开发框架 - 业务逻辑层

    一 概述 Socket服务只是提供一个网络传输服务. 业务逻辑层在整体架构中的位置在那里呢,如图: 网络层将解包后的消息包抛至业务逻辑层,业务逻辑层收到消息包后,解析消息类型,然后转入相应的处理流程处 ...

  4. 9.1.3 .net framework通过业务逻辑层自动生成WebApi的做法

    首先需要说明的是这是.net framework的一个组件,而不是针对.net core的.目前工作比较忙,因此.net core的转换正在编写过程中,有了实现会第一时间贴出来. 接下来进入正题.对于 ...

  5. MyBatis知多少(6)表现层与业务逻辑层

    表现层 表现层负责向最终用户展示应用程序的控制方式以及数据.它还要负责所有信息的布局和格式.今天,商业应用程序最流行的表现方式应该算是Web前端了,它使用HTML和JavaScript并通 过Web浏 ...

  6. ASP.NET MVC+EF框架+EasyUI实现权限管理系列(4)-业务逻辑层的封装

    原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(4)-业务逻辑层的封装 ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇)   (1):框架搭建    (2) ...

  7. [Prodinner项目]学习分享_第三部分_Service层(业务逻辑层)

    前两节讲到怎样生成一个Model和怎样将Model映射到数据库,这一节将讲到业务逻辑层,也就是Service层. 1.Prodinner架构已经构建好的,基本的增删改查. 假设,我现在想操作第二节中讲 ...

  8. JSP业务逻辑层

    经典的三层架构:表示层.业务逻辑层和数据访问层 具体的区分方法 1:数据访问层:主要看你的数据层里面有没有包含逻辑处理,实际上他的各个函数主要完成各个对数据文件的操作.而不必管其他操作. 2:业务逻辑 ...

  9. MVC+Ef项目(4) 抽象业务逻辑层BLL层

    接下来,我们就要到业务逻辑层了,简单的说,业务逻辑层就是调用Repository(可以看做是DAL数据库访问层) 先来看看项目的架构 我们现在就开始来做BLL层.  同样,先编写  UserInfoS ...

随机推荐

  1. CAS单点登录系列之极速入门于实战教程(4.2.7)

    @ 目录 一. SSO简介 1.1 单点登录定义 1.2 单点登录角色 1.3 单点登录分类 二. CAS简介 2.1 CAS简单定义 2.2 CAS体系结构 2.3 CAS原理 三.CAS服务端搭建 ...

  2. Elasticsearch创建mapping

    (put)请求方式 http://192.168.1.200:9200/index_mapping body 参数 { "mappings":{ "properties& ...

  3. MySQL入门,第七部分,单表查询

    首先我们需要了解一下整个数据库的结构 其中Student表中Sno为主键.Study表中Sno和Cno合起来做主键.Course表中Cno为主键 其创建脚本如下: #----------------- ...

  4. 安卓开发学习日记 DAY5——监听事件onClick的实现方法

    今天主要学习了监听事件的是实现方法,就是说,做了某些动作后,怎么监听这个动作并作出相应反应. 方法主要有三种: 1.匿名内部类的方法 2.独立类的方法 3.类似实现接口的方法 以下分别分析: 1.匿名 ...

  5. ES6新特性箭头函数和常用function()对比

    // 无参 var fn1 = function() {} var fn1 = () => {} // 单个参数 var fn2 = function(a) {} var fn2 = a =&g ...

  6. EndNote自动修改导入文献标题

    今天使用EndNote导入文献,插入到word中时发现是标题的名字,导入的文献格式不标准,在百度后终于找到了解决办法,EndNote的强大可见一斑. 这个是我要导入的PDF文件的名字 导入后的名字 设 ...

  7. Python套接字之UDP

    目录 基于UDP的socket 发送消息 接收消息 基于UDP的socket 面向无连接的不可靠数据传输,可以没有服务器端,只不过没有服务器端,发送的数据会被直接丢弃,并不能到达服务器端 发送消息 在 ...

  8. N - Aroma's Search CodeForces - 1293D math+greedy

    作为DIV2的D题来讲,这个题目不算难. 题目大意:再规定的时间内寻找宝藏,第i个宝藏的位置为a*x(i-1)+b,a*y(i-1)+b.然后给出初始位置xs,ys和时间t让求再时间t内能够寻找到多少 ...

  9. Android内存优化—dumpsys meminfo详解

    原创置顶 不死鸟JGC 最后发布于2018-12-24 14:19:28 阅读数 3960 收藏展开dumpsys 介绍Dumpsys用户系统诊断,它运行在设备上,并提供系统服务状态信息 命令格式: ...

  10. Java读源码之CountDownLatch

    前言 相信大家都挺熟悉 CountDownLatch 的,顾名思义就是一个栅栏,其主要作用是多线程环境下,让多个线程在栅栏门口等待,所有线程到齐后,栅栏打开程序继续执行. 案例 用一个最简单的案例引出 ...