@iamfox
2015-07-16T03:21:13.000000Z
字数 28543
阅读 1852
由于英迈思乐虎网交付的代码对持久层的封装原始且难用,进行查询需要编写大量的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**/@Embeddablepublic 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**/@Embeddablepublic 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**/@Embeddablepublic 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@Transactionalpublic class VirtualAccountServiceImpl extendsBaseServiceImpl<VirtualAccount, VirtualAccountQO, VirtualAccountDao> {@Autowiredprivate 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@Transactionalpublic class VirtualAccountServiceImpl extendsBaseServiceImpl<VirtualAccount, VirtualAccountQO, VirtualAccountDao> {@Autowiredprivate 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@Transactionalpublic class VirtualAccountServiceImpl extendsBaseServiceImpl<VirtualAccount, VirtualAccountQO, VirtualAccountDao> {@Autowiredprivate 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@Transactionalpublic class VirtualAccountServiceImpl extendsBaseServiceImpl<VirtualAccount, VirtualAccountQO, VirtualAccountDao> {@Autowiredprivate 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@Transactionalpublic class UserServiceImpl extends BaseServiceImpl<User, UserQO, UserDao> {@Autowiredprivate UserDao userDao;@Overrideprotected 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@Transactionalpublic class UserGradeServiceImpl extends BaseServiceImpl<UserGrade, UserGradeQO, UserGradeDao> {@Autowiredprivate UserGradeDao userGradeDao;@Overrideprotected 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;@Repositorypublic class UserDao extends BaseDao<User, UserQO> {@Autowiredprivate UserGradeDao gradeDao;@Overrideprotected 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;}@Overrideprotected 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;@Repositorypublic class UserGradeDao extends BaseDao<UserGrade, UserGradeQO> {@Overrideprotected Criteria buildCriteria(Criteria criteria,UserGradeQO qo) {if (qo != null) {// 这里可以将在UserGradeQO的注解中没有完善的更多查询条件加入criteria,实现更复杂的查询// ...return criteria;}return criteria;}@Overrideprotected 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);
效果:
selectthis_.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_fromVA_VIRTUAL_ACCOUNT this_wherethis_.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。
效果:
selectthis_.ID as y0_,this_.NAME as y1_fromVA_VIRTUAL_ACCOUNT this_wherethis_.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);
效果:
selectthis_.ID as y0_,this_.NAME as y1_fromVA_VIRTUAL_ACCOUNT this_wherethis_.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());
效果:
selectcount(*) as y0_fromVA_VIRTUAL_ACCOUNT this_wherethis_.ENABLE='Y'
selectthis_.ID as y0_,this_.NAME as y1_fromVA_VIRTUAL_ACCOUNT this_wherethis_.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);
效果:
selectthis_.ID as y0_,this_.NAME as y1_fromVA_VIRTUAL_ACCOUNT this_wherethis_.AVAIABLE_AMOUNT>=0and 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);
效果:
selectthis_.ID as y0_,this_.NAME as y1_fromVA_VIRTUAL_ACCOUNT this_wherethis_.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);
效果:
selectthis_.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_fromVA_VIRTUAL_ACCOUNT this_inner joinVA_HY_CURRENCY hycurrency2_on this_.CURRENCY_ID=hycurrency2_.IDwherethis_.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:
@Overrideprotected 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;}
开发人员要写的所有查询代码已经全部在上面了,没有其他要写的了。
效果:
selectthis_.ID as y0_,this_.NAME as y1_,hc1_.ID as y2_,hc1_.NAME as y3_fromVA_VIRTUAL_ACCOUNT this_left outer joinVA_HY_CURRENCY hc1_on this_.CURRENCY_ID=hc1_.IDwherethis_.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);
效果:
selectthis_.ID as y0_,this_.NAME as y1_,hc1_.ID as y2_,hc1_.NAME as y3_fromVA_VIRTUAL_ACCOUNT this_left outer joinVA_HY_CURRENCY hc1_on this_.CURRENCY_ID=hc1_.IDwherethis_.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);
效果:
selectthis_.ID as y0_,this_.NAME as y1_fromVA_VIRTUAL_ACCOUNT this_wherethis_.NAME like '%分%'and this_.ID='001'limit 1
save(...)或update(...)或delete(),其中又以查询最为复杂,如果封装做得不好会出现诸多问题,比如N+1。Dao中对Criteria增加补充条件搞定,最后2%通过HQL或SQL搞定。极大减少在数据库操作上耗费的开发时间。NULL值条件自动忽略。