@iamfox
2015-07-16T11:21:13.000000Z
字数 28543
阅读 1606
由于英迈思乐虎网交付的代码对持久层的封装原始且难用,进行查询需要编写大量的SQL拼接,关联查询还要依赖大量的数据库视图,对开发业务逻辑操作的效率和代码可维护性影响巨大,因此我们需要一套新的业务逻辑处理和持久化操作代码规范,通过对Hibernate进一步封装,即保留了Hibernate的开发效率高的特性,也要追求接近于Mybatis的灵活性和SQL性能。
以下以虚拟帐户模块做为演示:
虚拟帐户模块可用于管理平台内的各种可用虚拟货币和电子钱包,如红包、积分、返利余额等。
新建了三个maven工程如下:
工程 | 主要内容 | 依赖 |
---|---|---|
hy-va-server | 包含remote层、service、dao层 | hy-va-pojo、lazypack-common |
hy-va-domain | 包含entity类 | hy-va-pojo、lazypack-common |
hy-va-pojo | 包含远程调用的SOA服务接口,接口传递和返回的参数对象,各种javabean,自定义异常 | lazypack-common |
所有代码已提交至新的git服务器,地址是:http://192.168.19.21:8082/gitblit
开发人员可以用自己的帐号登录获取代码签出地址,帐号规则和默认密码同禅道。
lazypack-common是一个自行开发的工具包,其最重要的功能就是围绕Hibernate封装了一套极简的操作框架,此外还有些常用工具类。
工具及基本封装类工程lazypack-common,jar包及源码包已上传至http://192.168.19.21:8081/nexus
<dependency>
<groupId>lazypack</groupId>
<artifactId>lazypack-common</artifactId>
<version>0.1</version>
</dependency>
看完了上面的效果,现在看一下要完成这样的自动化查询效果,都需要Service层以下提供哪些类。
以帐户表(VA_VIRTUAL_ACCOUNT
),汇银货币种类表(VA_HY_CURRENCY
)为例。
两张表的关联关系是
VA_VIRTUAL_ACCOUNT
与VA_HY_CURRENCY
多对一, 每个币种有多个帐户,每个帐户只会使用一个币种,外键是HY_CURRENCY_ID
。
以这两张表建立两个对应的主要根实体VirtualAccount
,HyCurrency
。
由于表字段过多,全放在一个实体类里会造成阅读和维护困难,因此HyCurrency
实体进行了值对象的拆分,将字段分散到不同的组件对象里,多个对象映射一张表,如下图:
用户的字段被拆分到了VirtualAccountBaseInfo
,VirtualAccountBalance
,VirtualAccountStatus
几个对象中,所有字段映射全注解。与数据库的映射使用了Hibernate的Component机制。以下贴出两个实体类的代码,其余的类似:
package com.lehumall.va.domain.entity.account;
import java.util.Date;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.hibernate.annotations.DynamicUpdate;
import org.springframework.beans.BeanUtils;
import com.lehumall.base.domain.entity.DomainLink;
import com.lehumall.va.command.CreateVirtualAccountCommand;
import com.lehumall.va.domain.entity.M;
import com.lehumall.va.domain.entity.base.HyCurrency;
import com.lehumall.va.dto.HyCurrencyDTO;
import com.lehumall.va.dto.VirtualAccountDTO;
import com.lehumall.va.exception.VirtualAccountException;
import lazypack.common.component.StringIdBaseEntity;
import lazypack.common.util.MoneyUtil;
import lazypack.common.util.UUIDGenerator;
/**
* 虚拟帐户
*
* @author yuxiaoxiang
*
*/
@DynamicUpdate
@Entity
@Table(name = M.TABLE_PREFIX + "VIRTUAL_ACCOUNT")
@SuppressWarnings("serial")
public class VirtualAccount extends StringIdBaseEntity {
/**
* 帐户基本信息
*/
private VirtualAccountBaseInfo baseInfo;
/**
* 帐户币种
*/
@ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
@JoinColumn(name = "CURRENCY_ID", nullable = false)
private HyCurrency currency;
/**
* 所属父帐户,父帐户余额由所有子帐户余额累加
*/
@ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
@JoinColumn(name = "PARENT_ID", nullable = true)
private VirtualAccount parent;
/**
* 帐户归属主体
*/
private DomainLink onwer;
/**
* 帐户余额
*/
private VirtualAccountBalance balance;
/**
* 帐户状态
*/
private VirtualAccountStatus status;
/**
* 创建
*
* @param command
*/
public void create(CreateVirtualAccountCommand command,
HyCurrency hyCurrency, VirtualAccount parentAccount) {
setId(UUIDGenerator.getUUID());
setBaseInfo(new VirtualAccountBaseInfo());
getBaseInfo().setName(command.getName());
getBaseInfo().setInvalidDate(command.getInvalidDate());
getBaseInfo().setProxyAccount(hyCurrency.getProxyCurrency());
getBaseInfo().setCreateDate(new Date());
setOnwer(new DomainLink());
getOnwer().setOutId(command.getSubjectId());
getOnwer().setType(command.getSubjectType());
getOnwer().setOutName(command.getSubjectName());
setCurrency(hyCurrency);
setBalance(new VirtualAccountBalance());
getBalance().setTotalAmount(0D);
getBalance().setAvaiableAmount(0D);
getBalance().setFrozenAmount(0d);
setParent(parentAccount);
setStatus(new VirtualAccountStatus());
getStatus().setEnable(true);
getStatus().setClose(false);
}
/**
* 入帐
*
* @param amount
* @throws VirtualAccountException
*/
public void moneyIn(Double amount) throws VirtualAccountException {
checkEnable();
// MoneyUtil提供的计算方法为将Double转成BigDecimal计算,保证精度
Double totalAmount = MoneyUtil.add(getBalance().getTotalAmount(),
amount);
Double avaiableAmount = MoneyUtil.add(getBalance().getAvaiableAmount(),
amount);
getBalance().setAvaiableAmount(avaiableAmount);
getBalance().setTotalAmount(totalAmount);
}
/**
* 重置帐户为0
*/
public void reset() {
getBalance().setAvaiableAmount(0D);
getBalance().setTotalAmount(0D);
getBalance().setFrozenAmount(0D);
}
/**
* 从可用余额出帐
*
* @param amount
* @throws VirtualAccountException
*/
public void moneyOutFromAvaiable(Double amount)
throws VirtualAccountException {
checkEnable();
if (getBalance().getAvaiableAmount() < amount) {
throw new VirtualAccountException(
VirtualAccountException.AVAIABLE_AMOUNT_NOT_ENOUTH,
"可用余额不足");
}
Double totalAmount = MoneyUtil.sub(getBalance().getTotalAmount(),
amount);
Double avaiableAmount = MoneyUtil.sub(getBalance().getAvaiableAmount(),
amount);
getBalance().setAvaiableAmount(avaiableAmount);
getBalance().setTotalAmount(totalAmount);
}
/**
* 从冻结金额出帐
*
* @param amount
* @throws VirtualAccountException
*/
public void moneyOutFromFrozen(Double amount,
VirtualAccount frozenVirtualAccount) throws VirtualAccountException {
if (amount.doubleValue() != frozenVirtualAccount.getBalance()
.getTotalAmount().doubleValue()) {
throw new VirtualAccountException(
VirtualAccountException.FROZEN_AMOUNT_WRONG, "冻结金额与出帐金额不等");
}
Double totalAmount = MoneyUtil.sub(getBalance().getTotalAmount(),
amount);
Double frozenAmount = MoneyUtil.sub(getBalance().getFrozenAmount(),
amount);
getBalance().setTotalAmount(totalAmount);
getBalance().setFrozenAmount(frozenAmount);
}
/**
* 冻结余额
*
* @param amount
* @throws VirtualAccountException
*/
public void moneyFrozen(Double amount) throws VirtualAccountException {
checkEnable();
if (getBalance().getAvaiableAmount().doubleValue() < amount) {
throw new VirtualAccountException(
VirtualAccountException.AVAIABLE_AMOUNT_NOT_ENOUTH,
"可用余额不足");
}
Double frozenAmount = MoneyUtil.add(getBalance().getFrozenAmount(),
amount);
Double avaiableAmount = MoneyUtil.sub(getBalance().getAvaiableAmount(),
amount);
getBalance().setAvaiableAmount(avaiableAmount);
getBalance().setFrozenAmount(frozenAmount);
}
/**
* 解冻余额
*
* @param amount
* @throws VirtualAccountException
*/
public void moneyUnFreeze(Double amount) throws VirtualAccountException {
checkEnable();
if (getBalance().getAvaiableAmount().doubleValue() < amount) {
throw new VirtualAccountException(
VirtualAccountException.AVAIABLE_AMOUNT_NOT_ENOUTH,
"可用余额不足");
}
Double frozenAmount = MoneyUtil.sub(getBalance().getFrozenAmount(),
amount);
Double avaiableAmount = MoneyUtil.add(getBalance().getAvaiableAmount(),
amount);
getBalance().setAvaiableAmount(avaiableAmount);
getBalance().setFrozenAmount(frozenAmount);
}
/**
* 检查帐户正常状态
*
* @throws VirtualAccountException
*/
public void checkEnable() throws VirtualAccountException {
if (getStatus().getClose()) {
throw new VirtualAccountException(
VirtualAccountException.ACCOUNT_CLOSE, "帐户已关闭");
}
if (!getStatus().getEnable()) {
throw new VirtualAccountException(
VirtualAccountException.ACCOUNT_FORBIDDEN, "帐户禁用中");
}
}
/**
* 启用
*/
public void enable() {
getStatus().setEnable(true);
}
/**
* 禁用
*/
public void disable() {
getStatus().setEnable(false);
}
/**
* 关闭
*/
public void close() {
getStatus().setClose(true);
}
/**
* 将实体转换成返回给控制层和视图层使用的POJO
*/
public VirtualAccountDTO trans2DTO(boolean transCurrency,
boolean transParent) {
VirtualAccountDTO dto = new VirtualAccountDTO();
String[] ignore = new String[6];
ignore[0] = "baseInfo";
ignore[1] = "currency";
ignore[2] = "parent";
ignore[3] = "onwer";
ignore[4] = "balance";
ignore[5] = "status";
BeanUtils.copyProperties(this, dto, ignore);
dto.setBalance(getBalance().trans2DTO());
dto.setBaseInfo(getBaseInfo().trans2DTO());
dto.setOnwer(getOnwer().trans2DTO());
dto.setStatus(getStatus().trans2DTO());
if (transParent) {
VirtualAccountDTO parentDto = getParent().trans2DTO(transCurrency,
transParent);
dto.setParent(parentDto);
}
if (transCurrency) {
HyCurrencyDTO hyCurrencyDTO = getCurrency().trans2DTO();
dto.setCurrency(hyCurrencyDTO);
}
return dto;
}
// setter/getter省略
}
package com.lehumall.va.domain.entity.account;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import org.springframework.beans.BeanUtils;
import com.lehumall.base.domain.entity.M;
import com.lehumall.va.dto.VirtualAccountBalanceDTO;
/**
* 帐户余额
*
* @author yuxiaoxiang
*
*/
@Embeddable
public class VirtualAccountBalance {
/**
* 总金额
*/
@Column(name = "TOTAL_AMOUNT", columnDefinition = M.MONEY_COLUMN)
private Double totalAmount;
/**
* 可用金额
*/
@Column(name = "AVAIABLE_AMOUNT", columnDefinition = M.MONEY_COLUMN)
private Double avaiableAmount;
/**
* 冻结金额
*/
@Column(name = "FROZEN_AMOUNT", columnDefinition = M.MONEY_COLUMN)
private Double frozenAmount;
public VirtualAccountBalanceDTO trans2DTO() {
VirtualAccountBalanceDTO dto = new VirtualAccountBalanceDTO();
BeanUtils.copyProperties(this, dto);
return dto;
}
}
package com.lehumall.va.domain.entity.account;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import org.hibernate.annotations.Type;
import org.springframework.beans.BeanUtils;
import com.lehumall.base.domain.entity.M;
import com.lehumall.va.dto.VirtualAccountBaseInfoDTO;
/**
* 虚拟帐户基本信息
*
* @author yuxiaoxiang
*
*/
@Embeddable
public class VirtualAccountBaseInfo {
/**
* 帐号名称
*/
@Column(name = "NAME")
private String name;
/**
* 创建时间
*/
@Column(name = "CREATE_DATE", columnDefinition = M.DATE_COLUMN)
private Date createDate;
/**
* 失效时间
*/
@Column(name = "INVALID_DATE")
private Date invalidDate;
/**
* 是否是代理帐户
*/
@Type(type = "yes_no")
@Column(name = "PROXY_ACCOUNT")
private Boolean proxyAccount;
public VirtualAccountBaseInfoDTO trans2DTO() {
VirtualAccountBaseInfoDTO dto = new VirtualAccountBaseInfoDTO();
BeanUtils.copyProperties(this, dto);
return dto;
}
}
package com.lehumall.va.domain.entity.account;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import org.hibernate.annotations.Type;
import org.springframework.beans.BeanUtils;
import com.lehumall.va.dto.VirtualAccountStatusDTO;
/**
* 帐户状态
*
* @author yuxiaoxiang
*
*/
@Embeddable
public class VirtualAccountStatus {
/**
* 是否已启用
*/
@Type(type = "yes_no")
@Column(name = "ENABLE")
private Boolean enable;
/**
* 是否已使用完关闭,不会再启用
*/
@Type(type = "yes_no")
@Column(name = "CLOSE")
private Boolean close;
public VirtualAccountStatusDTO trans2DTO() {
VirtualAccountStatusDTO dto = new VirtualAccountStatusDTO();
BeanUtils.copyProperties(this, dto);
return dto;
}
public Boolean getEnable() {
return enable;
}
public void setEnable(Boolean enable) {
this.enable = enable;
}
public Boolean getClose() {
return close;
}
public void setClose(Boolean close) {
this.close = close;
}
}
package com.lehumall.va.domain.entity.base;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.Type;
import org.springframework.beans.BeanUtils;
import com.lehumall.va.domain.entity.M;
import com.lehumall.va.dto.HyCurrencyDTO;
import lazypack.common.component.StringIdBaseEntity;
/**
* 汇银虚拟币种
*
* @author yuxiaoxiang
*
*/
@DynamicUpdate
@Entity
@Table(name = M.TABLE_PREFIX + "HY_CURRENCY")
@SuppressWarnings("serial")
public class HyCurrency extends StringIdBaseEntity {
/**
* 名称
*/
@Column(name = "NAME")
private String name;
/**
* 是否启用
*/
@Type(type = "yes_no")
@Column(name = "ENABLE")
private Boolean enable;
/**
* 基准汇率,多少抵1元人民币
*/
@Column(name = "EXCHANGE_ONE_YUAN", columnDefinition = M.MONEY_COLUMN)
private Double exchangeOneYuan;
/**
* 单位名称
*/
@Column(name = "UNIT")
private String unit;
/**
* 是否是代理币种
*/
@Type(type = "yes_no")
@Column(name = "PROXY_CURRENCY")
private Boolean proxyCurrency;
public HyCurrencyDTO trans2DTO() {
HyCurrencyDTO dto = new HyCurrencyDTO();
BeanUtils.copyProperties(this, dto);
return dto;
}
}
为了能从这些表里做查询,我们要把所有可用的查询条件封装到一个查询条件对象里,以方便框架能自动解析并生成SQL语句。以下列出其中两个类,其中的条件字段可以不断增加。
注:加在查询条件字段上的是一组自定义注解,有了这些注解,框架会自动解析并生成SQL,不需要再手工拼接SQL、HQL或者Criteria条件,Service和Dao也不需要自己写任何的查询方法。QO的具体用法会在后面JUNIT例子中演示。
package com.lehumall.va.qo;
import lazypack.common.annotation.QueryCondition;
import lazypack.common.annotation.QueryConditionType;
import lazypack.common.component.BaseQO;
/**
* 虚拟帐户查询条件
*
* @author yuxiaoxiang
*
*/
@SuppressWarnings("serial")
public class VirtualAccountQO extends BaseQO<String> {
private HyCurrencyQO hyCurrencyQO;
@QueryCondition(name = "currency", type = QueryConditionType.FETCH_EAGER)
private Boolean fetchCurrency = false;
private VirtualAccountQO parentQO;
@QueryCondition(name = "parent", type = QueryConditionType.FETCH_EAGER)
private Boolean fetchParent = false;
/**
* 帐户归属者ID
*/
@QueryCondition(name = "onwer.outId")
private String subjectId;
/**
* 帐户归属者类型
*/
@QueryCondition(name = "onwer.type")
private Integer subjectType;
public final static Integer ONWER_TYPE_USER = 1; // 个人用户
public final static Integer ONWER_TYPE_MERCHANT = 2; // 商户
public final static Integer ONWER_TYPE_PLATFORM = 3; // 平台
/**
* 是否已启用
*/
@QueryCondition(name = "status.enable")
private Boolean enable;
/**
* 是否已使用完关闭,不会再启用
*/
@QueryCondition(name = "status.enable")
private Boolean close;
/**
* 最小总余额
*/
@QueryCondition(name = "totalAmount", type = QueryConditionType.GE)
private Double geTotalAmount;
/**
* 最大总余额
*/
@QueryCondition(name = "totalAmount", type = QueryConditionType.LE)
private Double leTotalAmount;
/**
* 最小可用余额
*/
@QueryCondition(name = "avaiableAmount", type = QueryConditionType.GE)
private Double geAvaiableAmount;
/**
* 最大可用余额
*/
@QueryCondition(name = "avaiableAmount", type = QueryConditionType.LE)
private Double leAvaiableAmount;
}
注:没有
QueryConditionType
属性的注解,都是默认为and
组装和=
判断的条件。框架也有提供or条件的注解,但or
条件的组装注解可能不具有通用性,在某些情况下是or
,在另一些情况下可能会用and
,因此建议在dao
的条件组装通用方法中进行or
的处理。or
条件组装的注解如下:
/**
* 是否已启用
*/
@QueryCondition(name = "status.enable")
@QueryConditionGroup("group1")
private Boolean enable;
/**
* 是否已使用完关闭,不会再启用
*/
@QueryCondition(name = "status.enable")
@QueryConditionGroup("group1")
private Boolean close;
同在一个
QueryConditionGroup
中的这两个查询条件,具有and (enable = xxx or close = xxx)
的效果
package com.lehumall.va.qo;
import lazypack.common.annotation.QueryCondition;
import lazypack.common.annotation.QueryConditionType;
import lazypack.common.component.BaseQO;
/**
* 币种查询条件
*
* @author yuxiaoxiang
*
*/
@SuppressWarnings("serial")
public class HyCurrencyQO extends BaseQO<String> {
@QueryCondition(name = "useType", type = QueryConditionType.FETCH_EAGER)
private boolean fetchUseType;
@QueryCondition(name = "specImageMap", type = QueryConditionType.FETCH_EAGER)
private boolean fetchSpecImageMap;
}
Service
类中的方法只接受两种参数,一种是QO
,一种是Command
,QO
是查询条件对象,用于查询方法,Command
是命令对象,用于增删改方法,它们有不同的父类,父类中定义了一些通用字段。
以下列举了部分命令和处理代码:
package com.lehumall.va.command;
import java.util.Date;
import lazypack.common.component.BaseCommand;
/**
* 创建虚拟帐户
*
* @author yuxiaoxiang
*
*/
@SuppressWarnings("serial")
public class CreateVirtualAccountCommand extends BaseCommand {
/**
* 币种
*/
private String hyCurrencyId;
/**
* 帐号名称
*/
private String name;
/**
* 失效时间
*/
private Date invalidDate;
/**
* 父帐号
*/
private String parentId;
/**
* 帐号归属者名称
*/
private String onwerName;
/**
* 帐号归属者ID
*/
private String onwerId;
/**
* 帐号归属者类型
*/
private Integer onwerType;
public final static Integer TYPE_USER = 1; // 个人用户
public final static Integer TYPE_MERCHANT = 2; // 商户
public final static Integer TYPE_PLATFORM = 3; // 平台
}
@Service
@Transactional
public class VirtualAccountServiceImpl extends
BaseServiceImpl<VirtualAccount, VirtualAccountQO, VirtualAccountDao> {
@Autowired
private VirtualAccountDao virtualAccountDao;
/**
* 创建虚拟帐户
*
* @param command
* @return
* @throws VirtualAccountException
*/
@Transactional(rollbackFor = VirtualAccountException.class)
public VirtualAccount createVirtualAccount(
CreateVirtualAccountCommand command) throws VirtualAccountException {
HyCurrency currency = hyCurrencyDao.get(command.getHyCurrencyId());
VirtualAccount parentAccount = null;
if (command.getParentId() != null) {
parentAccount = virtualAccountDao.load(command.getParentId());
}
VirtualAccount virtualAccount = new VirtualAccount();
virtualAccount.create(command, currency, parentAccount);
virtualAccountDao.save(virtualAccount);
return virtualAccount;
}
}
package com.lehumall.va.command;
import lazypack.common.component.BaseCommand;
/**
* 入帐
*
* @author yuxiaoxiang
*
*/
@SuppressWarnings("serial")
public class MoneyInCommand extends BaseCommand {
/**
* 帐户id
*/
private String accountId;
/**
* 入帐的金额
*/
private Double amount;
}
@Service
@Transactional
public class VirtualAccountServiceImpl extends
BaseServiceImpl<VirtualAccount, VirtualAccountQO, VirtualAccountDao> {
@Autowired
private VirtualAccountDao virtualAccountDao;
/**
* 入帐
*
* @param command
* @return
* @throws VirtualAccountException
*/
public VirtualAccountBalance moneyIn(MoneyInCommand command)
throws VirtualAccountException {
VirtualAccount virtualAccount = virtualAccountDao.get(command
.getAccountId());
if (!virtualAccount.getStatus().getEnable()) {
throw new VirtualAccountException(
VirtualAccountException.ACCOUNT_FORBIDDEN, "帐户禁用中");
}
if (virtualAccount.getStatus().getClose()) {
throw new VirtualAccountException(
VirtualAccountException.ACCOUNT_CLOSE, "帐户已关闭");
}
virtualAccount.moneyIn(command.getAmount());
return virtualAccount.getBalance();
}
}
package com.lehumall.va.command;
import lazypack.common.component.BaseCommand;
/**
* 冻结金额
*
* @author yuxiaoxiang
*
*/
@SuppressWarnings("serial")
public class MoneyFrozenCommand extends BaseCommand {
/**
* 帐户id
*/
private String accountId;
/**
* 冻结的金额
*/
private Double amount;
}
@Service
@Transactional
public class VirtualAccountServiceImpl extends
BaseServiceImpl<VirtualAccount, VirtualAccountQO, VirtualAccountDao> {
@Autowired
private VirtualAccountDao virtualAccountDao;
/**
* 冻结金额
*
* @param command
* @return
* @throws VirtualAccountException
*/
public Object[] moneyFrozen(MoneyFrozenCommand command)
throws VirtualAccountException {
VirtualAccount virtualAccount = virtualAccountDao.get(command
.getAccountId());
virtualAccount.moneyFrozen(command.getAmount());
// 创建存冻结余额的冻结帐户
CreateVirtualAccountCommand createCommand = new CreateVirtualAccountCommand();
createCommand.setHyCurrencyId(virtualAccount.getCurrency().getId());
createCommand.setParentId(virtualAccount.getId());
createCommand.setOnwerId(virtualAccount.getOnwer().getOutId());
createCommand.setOnwerType(virtualAccount.getOnwer().getType());
createCommand.setName(virtualAccount.getBaseInfo().getName() + "冻结帐户");
createCommand.setOnwerName(virtualAccount.getOnwer().getOutName());
VirtualAccount frozenVirtualAccount = createVirtualAccount(createCommand);
frozenVirtualAccount.moneyIn(command.getAmount());
frozenVirtualAccount.moneyFrozen(command.getAmount());
getDao().save(frozenVirtualAccount);
getDao().update(virtualAccount);
Object[] results = new Object[2];
results[0] = virtualAccount.getBalance();
results[1] = frozenVirtualAccount.getId();
return results;
}
}
package com.lehumall.va.command;
import lazypack.common.component.BaseCommand;
/**
* 转帐
*
* @author yuxiaoxiang
*
*/
@SuppressWarnings("serial")
public class MoneyTransferCommand extends BaseCommand {
/**
* 入帐帐户id
*/
private String inAccountId;
/**
* 出帐帐户id
*/
private String outAccountId;
/**
* 冻结id 有值时出帐方使用冻结的金额做扣除,金额要和冻结的对应金额相等 为空时使用出帐方可用余额扣除
*/
private String frozenId;
/**
* 转帐金额
*/
private Double amount;
}
@Service
@Transactional
public class VirtualAccountServiceImpl extends
BaseServiceImpl<VirtualAccount, VirtualAccountQO, VirtualAccountDao> {
@Autowired
private VirtualAccountDao virtualAccountDao;
/**
* 转帐
*
* @param command
* @return
* @throws VirtualAccountException
*/
@Transactional(rollbackFor = VirtualAccountException.class)
public VirtualAccountBalance[] moneyTransfer(MoneyTransferCommand command)
throws VirtualAccountException {
VirtualAccount inAccount = virtualAccountDao.get(command
.getInAccountId());
VirtualAccount outAccount = virtualAccountDao.get(command
.getOutAccountId());
inAccount.moneyIn(command.getAmount());
if (command.getFrozenId() == null) {
// 从可用余额付款
outAccount.moneyOutFromAvaiable(command.getAmount());
} else {
// 从冻结金额付款
VirtualAccount frozenVirtualAccount = virtualAccountDao.get(command
.getFrozenId());
outAccount.moneyOutFromFrozen(command.getAmount(),
frozenVirtualAccount);
}
VirtualAccountBalance[] virtualAccountBalances = new VirtualAccountBalance[2];
virtualAccountBalances[0] = inAccount.getBalance();
virtualAccountBalances[1] = outAccount.getBalance();
return virtualAccountBalances;
}
}
在具体的业务操作Service
实现类中,只有接收Command
对象的写操作,接收QO
对象的查询操作queryUnique(QO qo)
,queryList(QO qo)
,queryPagination(QO qo)
,queryCount(QO qo)
全部都在BaseServiceImpl
中。
每个具体实体泛型对应的Dao
实现类,也只有一个重写BaseDao
的Criteria buildCriteria(Criteria criteria, XXXQO qo)
方法,该方法中组装的Criteria
条件对象可复用于上述四种查询方法。
package com.lehumall.application.service.user;
import lazypack.common.component.BaseServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.lehumall.application.dao.user.UserDao;
import com.lehumall.domain.entity.user.user.User;
import com.lehumall.pojo.qo.UserQO;
@Service
@Transactional
public class UserServiceImpl extends BaseServiceImpl<User, UserQO, UserDao> {
@Autowired
private UserDao userDao;
@Override
protected UserDao getDao() {
return userDao;
}
}
package com.lehumall.application.service.user;
import lazypack.common.component.BaseServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.lehumall.application.dao.user.UserGradeDao;
import com.lehumall.domain.entity.user.usergrade.UserGrade;
import com.lehumall.pojo.qo.UserGradeQO;
@Service
@Transactional
public class UserGradeServiceImpl extends BaseServiceImpl<UserGrade, UserGradeQO, UserGradeDao> {
@Autowired
private UserGradeDao userGradeDao;
@Override
protected UserGradeDao getDao() {
return userGradeDao;
}
}
package com.lehumall.application.dao.user;
import lazypack.common.component.BaseDao;
import org.hibernate.Criteria;
import org.hibernate.sql.JoinType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.lehumall.domain.entity.user.user.User;
import com.lehumall.pojo.qo.UserQO;
@Repository
public class UserDao extends BaseDao<User, UserQO> {
@Autowired
private UserGradeDao gradeDao;
@Override
protected Criteria buildCriteria(Criteria criteria, UserQO qo) {
if (qo != null) {
// 这里可以将在UserQO的注解中没有完善的更多查询条件加入criteria,实现更复杂的查询
// ...
// 将关联表的查询条件纳入条件解析
if(qo.getUserGradeQO() != null){
Criteria gradeCriteria = criteria.createCriteria("grade", "g",
JoinType.LEFT_OUTER_JOIN);
gradeDao.buildCriteriaOut(gradeCriteria, qo.getUserGradeQO());
}
return criteria;
}
return criteria;
}
@Override
protected Class<User> getEntityClass() {
return User.class;
}
}
package com.lehumall.application.dao.user;
import lazypack.common.component.BaseDao;
import org.hibernate.Criteria;
import org.springframework.stereotype.Repository;
import com.lehumall.domain.entity.user.usergrade.UserGrade;
import com.lehumall.pojo.qo.UserGradeQO;
@Repository
public class UserGradeDao extends BaseDao<UserGrade, UserGradeQO> {
@Override
protected Criteria buildCriteria(Criteria criteria,UserGradeQO qo) {
if (qo != null) {
// 这里可以将在UserGradeQO的注解中没有完善的更多查询条件加入criteria,实现更复杂的查询
// ...
return criteria;
}
return criteria;
}
@Override
protected Class<UserGrade> getEntityClass() {
return UserGrade.class;
}
}
以下代码可以直观地看出如何去使用封装好的工具,这些代码通常是写在Action
或Controller
中的代码,不需要往Service
,Dao
中添加任何方法就能运行,依赖lazypack-common
的基本封装类和方法。
全部代码已经通过测试,JUNIT测试类位于hy-lehu-application
工程src/test/java
目录下。
按id查询:
VirtualAccountQO qo = new VirtualAccountQO();
// 按帐号id查询
qo.setId("001");
VirtualAccount va = virtualAccountService.queryUnique(qo);
效果:
select
this_.ID as ID1_8_0_,
this_.AVAIABLE_AMOUNT as AVAIABLE2_8_0_,
this_.FROZEN_AMOUNT as FROZEN_A3_8_0_,
this_.TOTAL_AMOUNT as TOTAL_AM4_8_0_,
this_.CREATE_DATE as CREATE_D5_8_0_,
this_.INVALID_DATE as INVALID_6_8_0_,
this_.NAME as NAME7_8_0_,
this_.PROXY_ACCOUNT as PROXY_AC8_8_0_,
this_.CURRENCY_ID as CURRENC14_8_0_,
this_.OUT_ID as OUT_ID9_8_0_,
this_.OUT_NAME as OUT_NAM10_8_0_,
this_.DOMAIN_TYPE as DOMAIN_11_8_0_,
this_.PARENT_ID as PARENT_15_8_0_,
this_.CLOSE as CLOSE12_8_0_,
this_.ENABLE as ENABLE13_8_0_
from
VA_VIRTUAL_ACCOUNT this_
where
this_.ID='001' limit 1
加上字段投影筛选:
VirtualAccountQO qo = new VirtualAccountQO();
// 按帐号id查询
qo.setId("001");
// 只查两个字段
String[] projs = {"id","baseInfo.name"};
qo.setProjectionProperties(projs);
VirtualAccount va = virtualAccountService.queryUnique(qo);
注:第二个字段写成
baseInfo.userName
是因为name
这个属性放在VirtualAccount
中的名为baseInfo
的值对象中,见3.1.1。
效果:
select
this_.ID as y0_,
this_.NAME as y1_
from
VA_VIRTUAL_ACCOUNT this_
where
this_.ID='001' limit 1
多条查询:
VirtualAccountQO qo = new VirtualAccountQO();
// 查状态为已启用的帐户
qo.setEnable(true);
// 只查两个字段
String[] projs = {"id","baseInfo.name"};
qo.setProjectionProperties(projs);
List<VirtualAccount> vaList = virtualAccountService.queryList(qo);
效果:
select
this_.ID as y0_,
this_.NAME as y1_
from
VA_VIRTUAL_ACCOUNT this_
where
this_.ENABLE='Y'
分页查询:
Pagination pagination = new Pagination();
// 查第1页每页5条
pagination.setPageNo(1);
pagination.setPageSize(5);
VirtualAccountQO qo = new VirtualAccountQO();
// 查状态为已启用的帐户
qo.setEnable(true);
// 只查两个字段
String[] projs = {"id","baseInfo.name"};
qo.setProjectionProperties(projs);
pagination.setCondition(qo);
pagination = virtualAccountService.queryPagination(pagination);
System.out.println(pagination.getList());
效果:
select
count(*) as y0_
from
VA_VIRTUAL_ACCOUNT this_
where
this_.ENABLE='Y'
select
this_.ID as y0_,
this_.NAME as y1_
from
VA_VIRTUAL_ACCOUNT this_
where
this_.ENABLE='Y' limit 1
范围查询:
VirtualAccountQO qo = new VirtualAccountQO();
// 查帐户可用余额大于等于0并且小于等于10的帐户
qo.setGeAvaiableAmount(0D);
qo.setLeAvaiableAmount(10D);
String[] projs = {"id","baseInfo.name"};
qo.setProjectionProperties(projs);
virtualAccountRemoteService.queryList(qo);
效果:
select
this_.ID as y0_,
this_.NAME as y1_
from
VA_VIRTUAL_ACCOUNT this_
where
this_.AVAIABLE_AMOUNT>=0
and this_.AVAIABLE_AMOUNT<=10
立即加载被配置成lazy的关联实体
VirtualAccountQO qo = new VirtualAccountQO();
qo.setId("001");
qo.setFetchCurrency(true);
String[] projs = {"id","baseInfo.name"};
qo.setProjectionProperties(projs);
virtualAccountService.queryUnique(qo);
效果:
select
this_.ID as y0_,
this_.NAME as y1_
from
VA_VIRTUAL_ACCOUNT this_
where
this_.ID='001' limit 1
上面没有出现join效果,这是因为我们筛选了列,并且没有获取HyCurrency实体里的列,所以Hibernate给优化掉了。
修改上面的条件:
VirtualAccountQO qo = new VirtualAccountQO();
qo.setId("001");
qo.setFetchCurrency(true);
// String[] projs = {"id","baseInfo.name"};
// qo.setProjectionProperties(projs);
virtualAccountService.queryUnique(qo);
效果:
select
this_.ID as ID1_8_1_,
this_.AVAIABLE_AMOUNT as AVAIABLE2_8_1_,
this_.FROZEN_AMOUNT as FROZEN_A3_8_1_,
this_.TOTAL_AMOUNT as TOTAL_AM4_8_1_,
this_.CREATE_DATE as CREATE_D5_8_1_,
this_.INVALID_DATE as INVALID_6_8_1_,
this_.NAME as NAME7_8_1_,
this_.PROXY_ACCOUNT as PROXY_AC8_8_1_,
this_.CURRENCY_ID as CURRENC14_8_1_,
this_.OUT_ID as OUT_ID9_8_1_,
this_.OUT_NAME as OUT_NAM10_8_1_,
this_.DOMAIN_TYPE as DOMAIN_11_8_1_,
this_.PARENT_ID as PARENT_15_8_1_,
this_.CLOSE as CLOSE12_8_1_,
this_.ENABLE as ENABLE13_8_1_,
hycurrency2_.ID as ID1_7_0_,
hycurrency2_.ENABLE as ENABLE2_7_0_,
hycurrency2_.EXCHANGE_ONE_YUAN as EXCHANGE3_7_0_,
hycurrency2_.NAME as NAME4_7_0_,
hycurrency2_.PROXY_CURRENCY as PROXY_CU5_7_0_,
hycurrency2_.UNIT as UNIT6_7_0_
from
VA_VIRTUAL_ACCOUNT this_
inner join
VA_HY_CURRENCY hycurrency2_
on this_.CURRENCY_ID=hycurrency2_.ID
where
this_.ID='001' limit 1
两张表的所有字段都出来了,然后我们会需要筛选。要动用到关联实体的QO。
VirtualAccountQO qo = new VirtualAccountQO();
qo.setId("001");
// 立即加载帐户所用货币信息
qo.setFetchCurrency(true);
// 从帐户表查两个字段
String[] projs = {"id","baseInfo.name"};
qo.setProjectionProperties(projs);
// 从货币表查两个字段
HyCurrencyQO qo2 = new HyCurrencyQO();
String[] projs2 = {"id","name"};
qo2.setProjectionProperties(projs2);
qo.setHyCurrencyQO(qo2);
virtualAccountService.queryUnique(qo);
因为操作了关联实体的查询条件,所以我们还要在Dao
里做些额外工作让上面的qo.setUserGradeQO(userGradeQO);
生效。这里只有片段,完整的UserDao
见4.3.3:
@Override
protected Criteria buildCriteria(Criteria criteria, VirtualAccountQO qo) {
if (qo != null) {
if (qo.getHyCurrencyQO() != null) {
// 创建一个子查询Criteria用于组装HyCurrency实体上的查询条件
Criteria hyCurrencyCriteria = criteria.createCriteria("currency", "hc", JoinType.LEFT_OUTER_JOIN);
// 将yCurrencyQO里的查询条件加载到子查询Criteria里
hyCurrencyDao.buildCriteriaOut(hyCurrencyCriteria, qo.getHyCurrencyQO());
}
}
return criteria;
}
开发人员要写的所有查询代码已经全部在上面了,没有其他要写的了。
效果:
select
this_.ID as y0_,
this_.NAME as y1_,
hc1_.ID as y2_,
hc1_.NAME as y3_
from
VA_VIRTUAL_ACCOUNT this_
left outer join
VA_HY_CURRENCY hc1_
on this_.CURRENCY_ID=hc1_.ID
where
this_.ID='001' limit 1
同时按User和UserGrade里的值做条件查询:
VirtualAccountQO qo = new VirtualAccountQO();
qo.setId("001");
qo.setFetchCurrency(true);
String[] projs = {"id","baseInfo.name"};
qo.setProjectionProperties(projs);
HyCurrencyQO qo2 = new HyCurrencyQO();
String[] projs2 = {"id","name"};
qo2.setProjectionProperties(projs2);
qo2.setName("积分");
qo.setHyCurrencyQO(qo2);
virtualAccountService.queryUnique(qo);
效果:
select
this_.ID as y0_,
this_.NAME as y1_,
hc1_.ID as y2_,
hc1_.NAME as y3_
from
VA_VIRTUAL_ACCOUNT this_
left outer join
VA_HY_CURRENCY hc1_
on this_.CURRENCY_ID=hc1_.ID
where
this_.ID='001'
and hc1_.NAME='积分' limit 1
VirtualAccountQO qo = new VirtualAccountQO();
qo.setId("001");
qo.setFetchCurrency(true);
String[] projs = {"id","baseInfo.name"};
qo.setProjectionProperties(projs);
qo.setName("分");
qo.setNameLike(true);
virtualAccountService.queryUnique(qo);
效果:
select
this_.ID as y0_,
this_.NAME as y1_
from
VA_VIRTUAL_ACCOUNT this_
where
this_.NAME like '%分%'
and this_.ID='001'limit 1
save(...)
或update(...)
或delete()
,其中又以查询最为复杂,如果封装做得不好会出现诸多问题,比如N+1。Dao
中对Criteria
增加补充条件搞定,最后2%通过HQL
或SQL
搞定。极大减少在数据库操作上耗费的开发时间。NULL
值条件自动忽略。