tags: [小小商城, SSH]
categories: 技术实战

提要

本文是 小小商城-SSH版的 细节详解系列 之一,项目 github:https://github.com/xenv/S-mall-ssh 本文代码大部分在 github 中 可以找到
在 SSH 开发中,Service层会有许多重复的,调用dao层的,增改删查的方法。对于重复的方法,我们可以用一个 BaseService 抽象出来。如图所示
普通Service的增改删查操作统一被抽象到 BaseService,普通Service继承BaseService后,只需实现少量独有的代码即可
这样,在 Action 层调用的时候,只需直接 categoryService.get(id) 就可以完成获取操作。

具体实现

BaseService实现的难点在于,不知道 继承它的 普通 Service 对应的 实体类 是哪个,从而不能从 Hibernate 中 获取到相应的 数据库查询器,那么,怎么来解决这个问题呢?
其实很简单,利用 泛型 ,子类在继承父类的时候实现泛型,初始化时,父类就可以读取到子类的泛型信息,从而实现了 子类 向 父类 传 数据。
父类根据子类携带的实体类信息,初始化 Hibernate 数据库查询器,子类在泛型继承的时候,就自动会调用子类指定的查询器,从而做到了抽象的效果。
在 小小商城 项目中,核心实现是这两个文件,Service4DAOImpl.java 和 BaseServiceImpl.java 下面,我会一步步教大家实现。

初始化时读取子类的泛型信息,创建 Hibernate 查询器

public class Service4DAOImpl<P> implements Service4DAO {
    protected DAO dao;

    protected Class clazz;

    public Service4DAOImpl() {
        ParameterizedType t = (ParameterizedType) (getClass().getGenericSuperclass());
        if (t != null) {
            clazz = (Class) t.getActualTypeArguments()[0];
        }
    }

    @Autowired
    public void setDao(DAO dao) {
        this.dao = dao;
    }

    public Criteria createCriteria() {
        return getSession().createCriteria(clazz).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
    }

    public Session getSession() {
        return dao.getSessionFactory().getCurrentSession();
    }

}
通过反射,子类继承该类时,会自动读取到 子类 携带的 实体类 ,然后我们将 hibernate 的 dao 自动注入进来,使用 createCriteria 就可以获取到相应的查询器
比如 ProductService 中 extends Service4DAOImpl ,我们就可以自动为其创建一个 Product 实体类的查询器了。

实现 BaseService ,抽象增改删查方法

public class BaseServiceImpl<P> extends Service4DAOImpl<P> implements BaseService {
    
    @Override
    public List list(Object... paramAndObjects) {
        Criteria c= createCriteria();

        if(paramAndObjects.length%2 !=0){
            return null;
        }
        for (int i=0;i<paramAndObjects.length;i+=2){
            if(paramAndObjects[i+1] == null){
                c.add(Restrictions.isNull(paramAndObjects[i].toString()));
                continue;
            }
            if(paramAndObjects[i].equals("pagination") && paramAndObjects[i+1] instanceof Pagination) {
                c.setFirstResult(((Pagination)paramAndObjects[i+1]).getStart());
                c.setMaxResults(((Pagination)paramAndObjects[i+1]).getCount());
                continue;
            }
            if(paramAndObjects[i].equals("desc") && paramAndObjects[i+1] instanceof String){
                c.addOrder(Order.desc(paramAndObjects[i+1].toString()));
                continue;
            }
            if(paramAndObjects[i].equals("asc") && paramAndObjects[i+1] instanceof String){
                c.addOrder(Order.asc(paramAndObjects[i+1].toString()));
                continue;
            }
            if(paramAndObjects[i].equals("max") && NumberUtils.isDigits(paramAndObjects[i+1].toString())){
                c.setMaxResults(Integer.valueOf(paramAndObjects[i+1].toString()));
                continue;
            }
            if(paramAndObjects[i].toString().contains("_like") && paramAndObjects[i+1] instanceof String){
                String keyword = "%"+paramAndObjects[i+1].toString()+"%";
                c.add(Restrictions.like(StringUtils.removeEnd(paramAndObjects[i].toString(),"_like"),keyword));
                continue;
            }
            if(paramAndObjects[i].toString().contains("_gt") && NumberUtils.isDigits(paramAndObjects[i+1].toString())){
                c.add(Restrictions.gt(StringUtils.removeEnd(paramAndObjects[i].toString(),"_gt"),paramAndObjects[i+1]));
                continue;
            }
            c.add(Restrictions.eq(paramAndObjects[i].toString(),paramAndObjects[i+1]));
        }
        c.addOrder(Order.desc("id"));
        return c.list();
    }


    @Override
    public Integer add(Object object) {
        Session session = getSession();
        return (Integer)session.save(object);
    }


    @Override
    public void update(Object object) {
        Session session = getSession();
        session.update(object);
    }


    @Override
    public void delete(Object object) {
        Session session = getSession();
        try {
            //获取对象的setDeleteAt方法,插入一个时间
            Method  setDeleteAt = object.getClass().getMethod("setDeleteAt",Date.class);
            setDeleteAt.invoke(object,new Date());
        } catch (Exception e) {
            e.printStackTrace();
        }
        session.update(object);
    }

}
这里节选了项目里的文件的一部分代码,我们先看 update(Object object) ,在获取到 seesion 之后,我们直接调用 update 方法即可
在list方法中,我又对查询器进行进一步抽象,这样,我们在调用的时候,就可以顺带指定分页、顺序、搜索条件等,更加方便

普通Service 附带 上 泛型 信息

这样,我们直接继承 BaseService 即可,无需再写任何代码
@Service
public class ProductServiceImpl extends BaseServiceImpl implements ProductService {

}

在 Action 中调用

那么,我们应该怎么调用这些 Service 呢,其实非常简单,在注入了 productService 之后,只需直接调用list方法即可
@Action("category")
public String category(){
    fill(category);
    products = productService.list("category",category,handleSort()[0],handleSort()[1],"stock_gt",0);
    return "categoryPage";
}
那么,大功告成,Service 和 Action 都省下了 大量重复的代码。