[[service]]
= 业务逻辑层 / Service

== 规范
* 业务逻辑层需要注意避免**循环依赖**，当你意识到可能会存在循环依赖时，应当新建一个新的Service对象来组合你的业务逻辑。
* 所有与业务相关的代码都应在Service层处理，Controller层只关心对象的传递和权限。
* 业务层通过异常的方式将相关的错误信息向上传递
* 编写业务代码时应通过日志记录关键的信息。
* 禁止空的catche代码块

== BaseService
BaseService 通过泛型实现操作某个具体的Entity对象
[source,java]
----

public class BaseService<T, ID extends Serializable, R extends SupportRepository<T, ID>>
		implements CrudService<T, ID>, DataRuleService<T, Serializable> {

	private final static Logger logger = LoggerFactory.getLogger(BaseService.class);

	private Class<T> clazz;
	protected R repository;
	protected boolean isReparable = false;
	protected DataRuleWapper dataRuleWapper;

	@SuppressWarnings("unchecked")
	public BaseService() {
		Class<T> clazz = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
		this.clazz = clazz;
		this.isReparable = Repairable.class.isAssignableFrom(clazz);
	}

	@Autowired
	public <S extends R> void setRepository(S repository) {
		this.repository = repository;
	}

	@Autowired
	public void setDataRuleWapper(DataRuleWapper dataRuleWapper) {
		this.dataRuleWapper = dataRuleWapper;
	}

	@Override
	public <S extends T> S save(S entity) {
		return repository.saveAndFlush(entity);
	}

	@Override
	public <S extends T> Iterable<S> save(Iterable<S> entities) {
		return repository.save(entities);
	}

	@Override
	public T findOne(ID id) {
		return repository.findOne(id);
	}

	@Override
	public T getOne(ID id) {
		return repository.findOne(id);
	}

	@Override
	public boolean exists(ID id) {
		return repository.exists(id);
	}

	@Override
	public Iterable<T> findAll() {
		return repository.findAll();
	}

	@Override
	public Iterable<T> findAll(Iterable<ID> ids) {
		return repository.findAll(ids);
	}

	@Override
	public long count() {
		return repository.count();
	}

	@Override
	public void delete(ID id) {
		delete(id, false);
	}

	@Override
	public void delete(List<ID> ids) {
		for (ID id : ids) {
			delete(id, false);
		}
	}

	@Override
	public void delete(ID[] ids) {
		delete(Arrays.asList(ids), false);
	}

	@Override
	public void delete(T entity) {
		delete(entity, false);
	}

	@Override
	public void delete(Iterable<? extends T> entities) {
		delete(entities, false);
	}

	@Override
	public void deleteAll() {
		deleteAll(false);
	}

	public Iterable<T> findAll(T entity){
		return repository.findAll(Example.of(entity));
	}

	public Page<T> findAll(T entity, Pageable pageable){
		return repository.findAll(Example.of(entity), pageable);
	}

	/**
	 * 解析form表单提交的查询entity，生成复杂查询（全部，过滤已删除数据，不带支持权限）
	 * @param entity
	 * @param pageable
	 * @return
	 */
	@Override
	public List<T> findAllWithForm(T entity) {
		return repository.findAll(handleQueryAnnotation(entity));
	}

	/**
	 * 解析form表单提交的查询entity，生成复杂查询（分页，过滤已删除数据，不带支持权限）
	 * @param entity
	 * @param pageable
	 * @return
	 */
	@Override
	public Page<T> findAllWithForm(T entity, Pageable pageable) {
		return repository.findAll(handleQueryAnnotation(entity), pageable);
	}
	/**
	 * 解析form表单提交的查询entity，生成复杂查询（全部，过滤已删除数据，支持权限）
	 * @param entity
	 * @param pageable
	 * @return
	 */
	@Override
	public List<T> findAllWithFormAndDataRule(T entity) {
		return repository.findAll(withDataRule(handleQueryAnnotation(entity)));
	}
	
	/**
	 * 解析form表单提交的查询entity，生成复杂查询（分页，过滤已删除数据，支持权限）
	 * @param entity
	 * @param pageable
	 * @return
	 */
	@Override
	public Page<T> findAllWithFormAndDataRule(T entity, Pageable pageable) {
		return repository.findAll(withDataRule(handleQueryAnnotation(entity)), pageable);
	}

	@Override
	public void delete(ID id, boolean isPhysical) {
		if (!checkDeleteable(id)) {
			return;
		}

		if (isReparable && !isPhysical) {
			tagDelete(id);
		} else {
			repository.delete(id);
		}
	}

	@Override
	public void delete(List<ID> ids, boolean isPhysical) {
		for (ID id : ids) {
			delete(id, isPhysical);
		}
	}

	@Override
	public void delete(ID[] ids, boolean isPhysical) {
		delete(Arrays.asList(ids), isPhysical);
	}

	@Override
	public void delete(T entity, boolean isPhysical) {
		if (!checkDeleteable(entity)) {
			return;
		}

		if (isReparable && !isPhysical) {
			tagDelete(entity);
		} else {
			repository.delete(entity);
		}
	}

	@Override
	public void delete(Iterable<? extends T> entities, boolean isPhysical) {
		for (T entity : entities) {
			if (!checkDeleteable(entity)) {
				return;
			}
		}

		if (isReparable && !isPhysical) {
			for (T entity : entities) {
				tagDelete(entity);
			}
		} else {
			repository.delete(entities);
		}
	}

	@Override
	public void deleteAll(boolean isPhysical) {
		if (isReparable && !isPhysical) {
			throw new RuntimeException("The Reparable class cannot be tag delete all");
		} else {
			repository.deleteAll();
		}
	}

	protected void tagDelete(ID id) {
		T entity = findOne(id);
		tagDelete(entity);
	}

	protected void tagDelete(T entity) {
		if (null == entity) {
			return;
		}

		if (isReparable) {
			try {
				((Repairable) entity).setIsDeleted(true);
				save(entity);
			} catch (ClassCastException e) {
				throw new RuntimeException("The given class " + entity.getClass() + " is not a Repairable("
						+ Repairable.class + ") class");
			}
		} else {
			throw new RuntimeException(
					"The given class " + entity.getClass() + " is not a Repairable(" + Repairable.class + ") class");
		}
	}

	protected boolean checkDeleteable(T entity) {
		return true;
	}

	protected boolean checkDeleteable(ID id) {
		return true;
	}

	@Override
	public T update(ID id, T entity) {
		T entity2 = findOne(id);
		try {
			new BeanUtils().copyProperties(entity2, entity, true);
			entity2 = save(entity2);
		} catch (IllegalAccessException | InvocationTargetException e) {
			e.printStackTrace();
		}

		return entity2;
	}

	@Override
	public Specification<T> withDataRule(Specification<T> specification) {
		if (null != dataRuleWapper) {
			Specifications<T> specifications = Specifications.where(specification)
					.and(dataRuleWapper.getDataRuleSpecification(clazz));
			return specifications;
		} else {
			logger.warn("DataRuleWapper not been providored.");
			return specification;
		}
	}

	/**
	 * 解析实体类上的Query*注解生成Specification
	 * 
	 * @param entity
	 * @return
	 */
	@Override
	public Specification<T> handleQueryAnnotation(T entity) {
		if (entity == null) {
			return null;
		}
		Boolean isReparable = this.isReparable;
		return new Specification<T>() {

			@Override
			public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
				List<Predicate> predicates = Lists.newArrayList();
				if (isReparable) {
					Predicate isDeletePredicate = builder.equal(root.get("isDeleted"), false);
					predicates.add(isDeletePredicate);
				}

				predicates.add(LofterQueryPredicateBuilder.getPredicate(root, builder, entity));
				Predicate[] p = new Predicate[predicates.size()];
				return builder.and(predicates.toArray(p));
			}

		};
	}

	@Override
	public List<T> findAllWithDataRule(Specification<T> specification) {
		return findAll(withDataRule(specification));
	}

	@Override
	public Page<T> findAllWithDataRule(Specification<T> specification, Pageable pageable) {
		return findAll(withDataRule(specification), pageable);
	}

	@Override
	public T findOne(Specification<T> spec) {
		return repository.findOne(spec);
	}

	@Override
	public List<T> findAll(Specification<T> spec) {
		return repository.findAll(spec);
	}

	@Override
	public Page<T> findAll(Specification<T> spec, Pageable pageable) {
		return repository.findAll(spec, pageable);
	}

	@Override
	public List<T> findAll(Specification<T> spec, Sort sort) {
		return repository.findAll(spec, sort);
	}

	@Override
	public long count(Specification<T> spec) {
		return repository.count(spec);
	}
}


----

通过继承BaseService就可以实现对对象的基本操作,继承时指定Entity，Entity的id类型，Entity对应的Repository

[source,java]
----
package net.parim.spark.core.system.service;

import org.springframework.stereotype.Service;

import net.parim.spark.common.service.BaseService;
import net.parim.spark.core.system.entity.Site;
import net.parim.spark.core.system.repository.jpa.SiteRepository;

@Service
public class SiteService extends BaseService<Site, Long, SiteRepository> {

}

----

== JPA Specification 复杂查询
一般来说，简单的查询无法满足企业管理员的使用需求，在之前的开发框架下，为了满足各种复杂的查询的需求，需要开发人员需要去手工编写sql，并对各种条件进行判断，耗费大量的精力。同时在软件开发领域有个不变的真理，bug的数量与代码的行数成正比。 

为了减少手工编写sql，我们在SpringDataJpa的基础上做了些封装，提供了一组注解并在BaseService中新增了几个方法，希望各位程序猿（媛）们在理解的基础上灵活的使用。

=== 设计思路
做封装，首先需要考虑的就是兼容性，不能影响之前已经开发的代码的运行；其次是易用性，使用起来要简单直接，屏蔽复杂度；最后，代码的耦合不能过大，最大程度上减少代码的侵入性。
在以上前提下想到了通过注解定义语意，并使用Entity接收查询数据，后台通过特定方法去解析接收到的实体生成对应的复杂查询Specification，最后调用SpringDataJpa来查询数据。

=== 注意事项
* 注解只有定义在实体（带有@Entity注解的类）字段上，并通过使用特定的方法才能生效。
* 未使用注解的字段默认按equal处理
* form表单使用的参数名需要与实体字段名相同，关系查询使用`createdBy.firstName`的形式。
* 关联对象上的注解同样生效
* 关联对象的主键不为空时自动忽略其他字段查询
* 不支持逆向的复杂查询，这种情况下应该使用Mybatis。
* 默认查询未提供对已逻辑删除数据的查询，如有需求请另行实现

=== 相关注解
* QueryLike -- 自动提示简写`@qlk`
* QueryGreaterThan -- 自动提示简写`@qgtt`
* QueryGreaterEqual -- 自动提示简写`@qgte`
* QueryLessThan -- 自动提示简写`@qlt`
* QueryLessEqual -- 自动提示简写`@qlee`
* QueryOr 自动提示简写`@qor`

=== 相关方法
[source,java]
----
/**
	 * 解析form表单提交的查询entity，生成复杂查询（全部，过滤已删除数据，不带支持权限）
	 * @param entity
	 * @param pageable
	 * @return
	 */
	@Override
	public List<T> findAllWithForm(T entity) {
		return repository.findAll(handleQueryAnnotation(entity));
	}

	/**
	 * 解析form表单提交的查询entity，生成复杂查询（分页，过滤已删除数据，不带支持权限）
	 * @param entity
	 * @param pageable
	 * @return
	 */
	@Override
	public Page<T> findAllWithForm(T entity, Pageable pageable) {
		return repository.findAll(handleQueryAnnotation(entity), pageable);
	}
	/**
	 * 解析form表单提交的查询entity，生成复杂查询（全部，过滤已删除数据，支持权限）
	 * @param entity
	 * @param pageable
	 * @return
	 */
	@Override
	public List<T> findAllWithFormAndDataRule(T entity) {
		return repository.findAll(withDataRule(handleQueryAnnotation(entity)));
	}
	
	/**
	 * 解析form表单提交的查询entity，生成复杂查询（分页，过滤已删除数据，支持权限）
	 * @param entity
	 * @param pageable
	 * @return
	 */
	@Override
	public Page<T> findAllWithFormAndDataRule(T entity, Pageable pageable) {
		return repository.findAll(withDataRule(handleQueryAnnotation(entity)), pageable);
	}
----
=== 注解部分
这部分将通过对比的方式来讲解 

==== @QueryLike
[source,java]
----
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface QueryLike {
	StringMatcher matcher() default StringMatcher.CONTAINING;
}
----
===== 语意：模糊查询
===== 作用对象：实体（Entity）的持久化字段
===== 使用限制：只能作用于__String__类型的字段
===== 示例：
前端通过get请求提交参数`name=张`

使用之前的实体：
[source,java]
----
@Entity
@Table(name="sys_user")
public class User extends Repairable {
    @Id
    private Long id;
    private String name;
}
----

对应的模糊查询Specification或者使用mybatis：
[source,java]
----
protected <S extends User> Specification<S> getFindAllSpecification(final S user) {
		return new Specification<S>() {

			@Override
			public Predicate toPredicate(Root<S> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
				Predicate predicate = cb.equal(root.get("site").get("id"), user.getSite().getId());

				if (null != user.getUsername()) {
					Predicate ul = cb.like(root.get("username"), "%" + user.getUsername() + "%");
					Predicate un = cb.like(root.get("displayName"), "%" + user.getUsername() + "%");
					predicate = cb.and(predicate, cb.or(ul, un));
				}

				return predicate;
			}

		};
	}
----

使用之后的实体：
[source,java]
----
@Entity
@Table(name="sys_user")
public class User extends Repairable {
    @Id
    private Long id;
    @QueryLike <1>
    private String name;
}
----
<1> 支持通过StringMatcher设置模糊查询匹配模式
----
public static enum StringMatcher {

	/**
	 * 完全匹配
	 */
	DEFAULT,
	/**
	 * 完全匹配
	 */
	EXACT,
	/**
	 * 匹配模式'abc%'
	 */
	STARTING,
	/**
	 * 匹配模式'%abc'
	 */
	ENDING,
	/**
	 * 默认设置，匹配模式'%abc%'
	 */
	CONTAINING,
	/**
	 * 不支持，不要使用
	 */
	REGEX;

}
----
对应的模糊查询：
[source,java]
----
findAllWithForm(user)或者findAllWithForm(user, pageable)
----
==== @QueryGreaterEqual、@QueryLessEqual、@QueryGreaterThan、@QueryLessThan
===== 语意：比较查询
===== 作用对象：实体（Entity）的持久化字段
===== 使用限制：无,可以单独使用也可以联合使用,四个同时使用时，返回最小范围的结果，比较字段不存在时自动跳过
===== 示例：
前端通过get请求提交参数

`createdDateBegin=2017-11-18 12:00:00&createdDateEnd=2017-11-18 13:00:00`

使用注解之前的实体
[source,java]
----
@Entity
@Table(name="sys_user")
public class User extends Repairable {
    @Id
    private Long id;
    private Date createdDate;
}
----
之前的查询：
[source,java]
----
protected <S extends User> Specification<S> getFindAllSpecification(final S user) {
		return new Specification<S>() {

			@Override
			public Predicate toPredicate(Root<S> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
				Predicate predicate = cb.equal(root.get("site").get("id"), user.getSite().getId());

				if (null != user.getCreatedDateBegin()) {
					Predicate b = cb.greaterThanOrEqual(root.get("createdDate"), user.getCreatedDateBegin());
					predicate = cb.and(predicate, b);
				}
				if (null != user.getCreatedDateEnd()) {
					Predicate e = cb.lessThanOrEqual(root.get("createdDate"), user.getCreatedDateEnd());
					predicate = cb.and(predicate, e);
				}

				return predicate;
			}

		};
	}
----

使用注解之后的实体
[source,java]
----
@Entity
@Table(name="sys_user")
public class User extends Repairable {
    @Id
    private Long id;
    @QueryGreaterEqual(target="createdDateBegin")<1>
    @QueryLessEqual(target="createdDateEnd")<2>
    private Date createdDate;
    @Transient <3> 
    private Date createdDateBegin;<4>
    @Transient
    private Date createdDateEnd;<5>
}
----
<1> 指定表示起始值的字段
<2> 指定表示结束值的字段
<3> 标识字段为游离字段，不生成表字段
<4> 添加游离字段，接收起始条件
<5> 添加游离字段，接收结束条件
对应的模糊查询：
[source,java]
----
findAllWithForm(user)或者findAllWithForm(user, pageable)
----
方法会提供对null的判断,并生成针对`createdDate`的时间范围查询

==== @QueryOr
===== 语意：或查询
===== 作用对象：实体（Entity）的**游离** __String__ 类型字段上
===== 使用限制：游离（@Transient注解标识）、String类型
===== 注意事项：目标字段参与或查询后自动忽略本身的equal查询
===== 示例：
前端通过get请求提交参数
`?searchtext=abc`

使用前实体：
[source,java]
----
@Entity
@Table(name = "sys_user")
public class User extends Repairable {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	@Column(unique = true)
	private String username;
	@Column(unique = true)
	private String email;
	private String firstName;
	private String lastName;
	private String displayName;

	@Column(unique = true)
	private String phoneNumber;
	
}

----
使用前查询：
[source,java]
----
protected <S extends User> Specification<S> getFindAllSpecification(final S user, final Boolean withChildGroup) {
		return new Specification<S>() {

			@Override
			public Predicate toPredicate(Root<S> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
				Predicate predicate = cb.equal(root.get("site").get("id"), user.getSite().getId());

				if (null != user.getUsername()) {
					Predicate ul = cb.like(root.get("username"), "%" + user.getUsername() + "%");
					Predicate un = cb.like(root.get("displayName"), "%" + user.getUsername() + "%");
					predicate = cb.and(predicate, cb.or(ul, un));
				}

				if (null != user.getPhoneNumber()) {
					predicate = cb.and(predicate, cb.like(root.get("phoneNumber"), "%" + user.getPhoneNumber() + "%"));
				}

				if (null != user.getEmail()) {
					predicate = cb.and(predicate, cb.like(root.get("email"), "%" + user.getEmail() + "%"));
				}

				if (null != user.getPhoneNumber()) {
					predicate = cb.and(predicate, cb.like(root.get("phoneNumber"), "%" + user.getPhoneNumber() + "%"));
				}

				

				predicate = cb.and(predicate, cb.isFalse(root.get("isDeleted")));

				return predicate;
			}

		};
	}
----

使用后的实体：
[source,java]
----
@Entity
@Table(name = "sys_user")
public class User extends Repairable {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	@Column(unique = true)
	private String username;
	@Column(unique = true)
	private String email;
	private String firstName;
	private String lastName;
	private String displayName;

	@Column(unique = true)
	private String phoneNumber;

	@Transient <1>
	@QueryOr(target = {"username","displayName","phoneNumber","email"})<2>
	private String searchtext;<3>
	
}

----
<1> 标识游离状态
<2> 指定参与或查询的字段，必须为持久化字段
<3> 通用查询参数 

=== 使用进阶
上面几种方式的组合基本可以满足大部分的复杂查询业务，当遇到无法覆盖的业务需求时，可以通过使用Mybatis或者组合Specification的方式实现。以上面的用户查询来说在做复杂查询时会勾选是否包含子组织，这边的方式是通过组合Specification的方式来实现，代码如下：
[source,java]
----
public Page<User> findAll(User entity, Boolean withChildGroup, Pageable pageable) {
	return repository.findAll(withDataRule(
			Specifications.where(getFindAllSpecification(entity, withChildGroup))<1>
						  .and(handleQueryAnnotation(entity)<2>
						  )), pageable);
}

protected <S extends User> Specification<S> getFindAllSpecification(final S user, final Boolean withChildGroup) {
	return new Specification<S>() {

		@Override
		public Predicate toPredicate(Root<S> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
			Predicate predicate = null;
			if (null != user.getUserGroup() && null != user.getUserGroup().getId()) {
				if (BooleanUtils.isTrue(withChildGroup)) {
					predicate = cb.and(predicate, UserGroupSpecification
							.inChildrenOfUserGroupXref(User.class, "userGroup.id", user.getUserGroup().getId())
							.toPredicate((Root<User>) root, query, cb));
				} else {
					predicate = cb.and(predicate,
							cb.equal(root.get("userGroup").get("id"), user.getUserGroup().getId()));
				}
			}
			return predicate;
		}

	};
}
----
<1> 自定业务查询
<2> 使用BaseService提供的方法对注解解析生成基本复杂查询