Spring MVC:通用 DAO 和服务类

Posted

技术标签:

【中文标题】Spring MVC:通用 DAO 和服务类【英文标题】:Spring MVC: Generic DAO and Service classes 【发布时间】:2014-12-04 23:18:30 【问题描述】:

我正在使用 Spring MVC 编写网络。我使用通用 DAO 编写了所有 DAO。现在我想重写我的服务类。如何写“通用服务”?

有我的 DAO:

/* ################################# DAO ################################ */
package net.example.com.dao;

import java.util.List;

public interface GenericDao<T>        
        public T findById(int id);     
        public List<T> findAll();      
        public void update(T entity);  
        public void save(T entity);    
        public void delete(T entity);


/* ------------------------------------------------------ */

package net.example.com.dao;

import java.io.Serializable;
import java.util.List;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;

@Scope("prototype")
public abstract class GenericHibernateDaoImpl<T extends Serializable> implements GenericDao<T> 

        private Class<T> clazz;

        @Autowired
        private SessionFactory sessionFactory;

        public final void setClazz(Class<T> clazzToSet) 
                this.clazz = clazzToSet;               
        

        @SuppressWarnings("unchecked")
        public T findById(int id) 
                return (T) getCurrentSession().get(clazz, id);
        

        @SuppressWarnings("unchecked")
        public List<T> findAll() 
                return getCurrentSession().createQuery("FROM " + clazz.getName()).list();              
        

        public void update(T entity) 
                getCurrentSession().update(entity);            
        

        public void save(T entity) 
                getCurrentSession().save(entity);              
        

        public void delete(T entity) 
                getCurrentSession().delete(entity);            
        

        protected final Session getCurrentSession()
                return sessionFactory.getCurrentSession();
        


/* ------------------------------------------------------ */

package net.example.com.dao;

import net.example.com.entity.Country;

public interface CountryDao extends GenericDao<Country> 

    public Country findByName(String name);    
    public Country findByCode(String code);



/* ------------------------------------------------------ */

package net.example.com.dao;

import org.springframework.stereotype.Repository;

import net.example.com.entity.Country;

@Repository
public class CountryDaoImpl extends GenericHibernateDaoImpl<Country> implements CountryDao 

        @Override
        public Country findByName(String name) 
                return (Country) getCurrentSession()
                                .createQuery("FROM Country WHERE name = :name")
                                .setString("name", name).uniqueResult();
        

        @Override
        public Country findByCode(String code) 
                return (Country) getCurrentSession()
                                .createQuery("FROM Country WHERE code = :code")
                                .setString("code", code).uniqueResult();
        



/* ################################# DAO ################################ */

和服务:

/* ################################# SERVICE ################################ */

package net.example.com.service;

import java.util.List;

public interface GenericManager<T>  // GenericManager<T> is the same as GenericDao<T>

        public T findById(int id);     
        public List<T> findAll();      
        public void update(T entity);  
        public void save(T entity);    
        public void delete(T entity);


/* ------------------------------------------------------ */

package net.example.com.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import net.example.com.dao.GenericDao;

@Service
public abstract class GenericManagerImpl<T> implements GenericManager<T> 

        @Autowired
        protected GenericDao<T> dao;

        @Override
        public T findById(int id) 
                return dao.findById(id);
        

        @Override
        public List<T> findAll() 
                return dao.findAll();
        

        @Override
        public void update(T entity) 
                dao.update(entity);
        

        @Override
        public void save(T entity) 
                dao.save(entity);
        

        @Override
        public void delete(T entity) 
                dao.delete(entity);    
        

/* ------------------------------------------------------ */

package net.example.com.dao;

import net.example.com.entity.Country;

public interface CountryManager extends GenericDao<Country>  // CountryManager is the same as CountryDao

    public Country findByName(String name);    
    public Country findByCode(String code);


/* ------------------------------------------------------ */

package net.example.com.service;

import java.util.List;

import javax.transaction.Transactional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import net.example.com.dao.CountryDao;
import net.example.com.entity.Country;

@Service
@Transactional
public class CountryManagerImpl extends GenericManagerImpl<Country> implements CountryManager 

        @Override
        public List<Country> findAll() 
                return dao.findAll();
        

        public Country findById(int id) 
                return dao.findById(id);
        

        @Override
        public Country findByName(String name) 
                return dao.findByName(name); // compiler (and Eclipse) do not see findByName !!!!!!!!!
        

        @Override
        public Country findByCode(String code) 
                return dao.findByCode(code); // compiler (and Eclipse) do not see findByCode !!!!!!!!!
        

        @Override
        public void save(Country country) 
                dao.save(country);
        

        @Override
        public void delete(Country country) 
                dao.delete(country);
        

        @Override
        public void update(Country country) 
                dao.update(country);
        



/* ------------------------------------------------------ */

/* ################################# SERVICE ################################ */

编译器(和 Eclipse)看不到 findByNamefindByCode 方法。我明白为什么。但是我该如何重写呢?

【问题讨论】:

【参考方案1】:

问题是您在 GenericManager 中直接注入 GenericDao,但它们都不是具体的 Spring bean,您将永远无法使用特定的 CountryDao。

您不能自动装配 GenericDao,而只能定义它并提供 setter:

// Add DAO as a genric parameter
public abstract class GenericManagerImpl<T, D extends GenericDao<T>> implements GenericManager<T> 
    private D dao;

    protected void setDao (D dao) 
        this.dao = dao;
    

...

然后,您必须在具体服务中注入一个具体的 spring bean。即在 CountryManagerImpl 中:

// Instantiate your concrete service with your concrete DAO
public class CountryManagerImpl extends GenericManagerImpl<Country, CountryDao> implements CountryManager 

    // Do not redeclare your dao here in order to keep the inherited one

    // Don't forget to inject
    @Inject("countryDao")
    @Override
    protected void setDao (CountryDao dao) 
        this.dao = dao;
    

...


然后,您将拥有一个完整的 spring bean,其中注入了您的具体 CountryDao 类型及其特定方法。

您可以查看我们在 RESThub 项目中针对通用服务所做的工作:https://github.com/resthub/resthub-spring-stack/blob/master/resthub-common/src/main/java/org/resthub/common/service/CrudServiceImpl.java 和一些具体示例:https://github.com/resthub/todo-backbone-example/blob/master/src/main/java/todo/TodoController.java(使用控制器而不是服务,但类似)

希望它会有所帮助。

(抱歉,如果有错别字,我现在无法仔细检查)

而且,顺便说一句,您应该考虑使用 Spring Data 而不是使用 GenericDaos,但您仍然对服务有相同的需求。

【讨论】:

好吧,我可以看到多个错误(有些是我的:-)):DAO 类型应该作为通用类型在您的 GenericManager 中传递(请参阅我的答案中的编辑代码)并且不要忘记@Inject 注解。我更新了我的答案。 不知道不会忘记,你真的应该看看我之前粘贴的两个链接......【参考方案2】:

我仍然不知道为什么人们实际上使用古老的 DAO / 服务 - 带有 Spring Data 的模型;完全没有必要,容易出错等等。

Spring Data JPA 为这些东西提供了一些非常有用的接口:JpaRepository 和 JpaSpecificationExecutor - 它们封装了你想要的一切,你只需要你的标准实体就可以了 - 其他一切都会由 spring 处理,您只需输入您的标准并得到您想要的,而无需重新发明***。 可能是您实际上没有阅读文档吗?它非常有用:

官方介绍:http://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/

文档:http://docs.spring.io/spring-data/jpa/docs/1.7.0.RELEASE/reference/html/

小方法:http://www.cubrid.org/wiki_ngrinder/entry/how-to-create-dynamic-queries-in-springdata

天才本人的例子:https://github.com/spring-projects/spring-data-jpa-examples/tree/master/spring-data-jpa-example

示例类:

public CustomerSpecifications 

  public static Specification<Customer> customerHasBirthday() 
    return new Specification<Customer> 
      public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) 
        return cb.equal(root.get(Customer_.birthday), today);
      
    ;
  

  public static Specification<Customer> isLongTermCustomer() 
    return new Specification<Customer> 
      public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) 
        return cb.lessThan(root.get(Customer_.createdAt), new LocalDate.minusYears(2));
      
    ;
  


public interface CustomerRepository extends JpaRepository<Customer>, JpaSpecificationExecutor 
  // Your query methods here

现在您可以简单地自动装配您的存储库:

@Autowired
CustomerRepository customerRepo;

并像这样检索数据:

List<Customer> customersWithBirthDay = customerRepo.findAll(CustomerSpecifications.customerHasBirthDay());

就这么简单。

【讨论】:

您是否尝试过针对表之间复杂的连接策略进行此操作?我对该 API 的体验非常糟糕。 好吧,你可能只是这个星球上唯一的人 - JPA 标准 API(因为基本上就是这样;周围有无功能的包装器)非常经过现场验证、标准化和备受推崇。如果您对它有“糟糕的经验”,您可能会犯一些错误。如果您习惯于 SQL 语句,那么开始通过标准描述您的数据逻辑有点困难 - 但它非常值得学习。并且:是的,我已经“尝试”了它“针对表之间的复杂连接策略”,它比 DBMS 优化的 SQL 语句更有效。 +1 我同意这里的要点:在 Spring 应用程序(Java EE 也是)中使用 DAO 和服务的典型方式是令人难以置信的无效。太多不必要的、毫无意义的代码只会妨碍您。 Spring Data 更好,尽管仍然不是我的首选方法,即简单地拥有一个封装标准 JPA API 的通用类(例如“AppDatabase”),使其更易于使用(使用“save”等方法, “删除”,“查找(jpql,args)”等)。【参考方案3】:

我认为这只是 java OO 设计的局限。您需要一种参数化的方式来传递谓词以进行搜索,例如:

List<T> findByPredicate(List<Predicate> predicates, Class<T> returnType);

谓词类是这样的

class Predicate 
   String columnName;
   Operator operator;
   String value;

因此您可以表达“name = 'John'”、age >= 21 等

这不是一个理想的解决方案,代码变得不那么可读,您需要将谓词转换为数据库查询,并且需要进行的类型转换很少,容易出现运行时错误。

您可以避免使用 Spring Data 等库重新发明***。你甚至不需要一个通用的 DAO,你只需要提供一个接口方法,比如

List<Person> findByName(String name);

并在应用程序引导时自动生成一个实现。查看 Spring Data JPA 了解更多信息。

【讨论】:

【参考方案4】:

//实现GenericDao和GenericService

//StateDaO

public interface StateDao extends GenericDao<State> 


// StateDaoImpl

@Repository("stateDao")

public class StateDaoImpl extends GenericDaoImpl<State> implements StateDao 

    @Autowired
    SessionFactory sessionFactory;
// another specific businness operation perform


// 状态服务

public interface StateService extends  GenericService<State> 



// StateServiceImpl

@Repository("stateService")

public class StateServiceImpl extends GenericServiceImpl<State, StateDao> implements StateService  

   @Resource
   StateDao stateDao;

//using stateDao object of another specific operation

【讨论】:

【参考方案5】:

试试这个:

public interface GenericDao<T> 

    public List<T> loadAll() throws Exception;
    public Long saveOrUpdate(T domain) throws Exception;
    public void saveOrUpdate(List domainList) throws Exception;
    public void delete(T domain) throws Exception;
    public T get(Serializable id) throws Exception;
    public List<T> getListByCriteria(DetachedCriteria detachedCriteria);
    public List<T> getListByCriteria(DetachedCriteria detachedCriteria,
                                     int offset, int size);
    public List<T> filterListWithCondition(T domain) throws Exception;



public class GenericDaoImpl<T> extends HibernateDaoSupport implements GenericDao<T> 

        @Autowired
        SessionFactory sessionFactory;

        private Class<T> entityClass;
        private mysqlIntegrityConstraintViolationException sqlException = new MySQLIntegrityConstraintViolationException("Duplicate Record inserted");

        @Autowired
        public void setSession(SessionFactory sessionFactory)
        this.setSessionFactory(sessionFactory);
        

        public GenericDaoImpl() 
            entityClass = (Class<T>) ((ParameterizedType) getClass()
                          .getGenericSuperclass()).getActualTypeArguments()[0];
        

        public List<T> loadAll() throws Exception
            Session session = getHibernateTemplate().getSessionFactory().openSession();
            List<T> list = session.createQuery("from "+entityClass.getName()).list();
            session.close();
            return list;
        

        public void delete(T domain) throws Exception 

                Session session = sessionFactory.openSession();
                Transaction tx = session.beginTransaction();

                session.delete(domain);
                tx.commit();
                session.close();

        

        public Long saveOrUpdate(T domain) throws Exception 

            try 
                Session session = sessionFactory.openSession();
                Transaction tx = session.beginTransaction();

                session.saveOrUpdate(domain);
                tx.commit();
                Serializable ids = session.getIdentifier(domain);
                session.close();
                return (Long)ids;

             catch (ConstraintViolationException  e) 
                throw new ConstraintViolationException("Duplicate Record inserted", sqlException, "");
             

        

        public void saveOrUpdate(List domainList) throws Exception 
            try 
                Session session = sessionFactory.openSession();
                Transaction tx = session.beginTransaction();

                Object dom  = null;

                for(int i =0; i<domainList.size(); i++) 

                    dom = domainList.get(i);
                    session.saveOrUpdate(dom);

                     if ( i % 10 == 0 )  
                          //10, same as the JDBC batch size
                          //flush a batch of inserts and release memory:
                         session.flush();
                         session.clear();
                     

                

                tx.commit();
                session.close();

             catch (ConstraintViolationException  e) 
                throw new ConstraintViolationException("Duplicate Record inserted", sqlException, "");
             

        

        public T get(Serializable id) throws Exception

                Session session = getHibernateTemplate().getSessionFactory().openSession();
                T o = (T) session.get(entityClass, id);
                return (T)o;

        

        public List<T> getListByCriteria(DetachedCriteria detachedCriteria,
                                         int offset, int size) 
            return (List<T>) getHibernateTemplate().findByCriteria(detachedCriteria, offset, size);
        

        public List<T> getListByCriteria(DetachedCriteria detachedCriteria) 
            return (List<T>) getHibernateTemplate().findByCriteria(detachedCriteria);
        

        public List<T> filterListWithCondition(T domain) throws Exception 
            return (List<T>) getHibernateTemplate().findByExample(domain);
        



public interface GenericService<T> 

    public List<T> loadAll() throws Exception;
    public Long saveOrUpdate(T domain) throws Exception;
    public void saveOrUpdate(List domainList) throws Exception;
    public void delete(T domain) throws Exception;
    public T get(Serializable id) throws Exception;
    public List<T> getListByCriteria(DetachedCriteria detachedCriteria);
    public List<T> getListByCriteria(DetachedCriteria detachedCriteria, int offset, int size);
    public List<T> filterListWithCondition(T domain) throws Exception;



public class GenericServiceImpl<T, T2 extends GenericDao<T>> implements GenericService<T> 

    @Autowired
    private T2 genericDao;

    @Override
    public List<T> loadAll() throws Exception 
        return genericDao.loadAll();
    

    @Override
    public Long saveOrUpdate(T domain) throws Exception
        return genericDao.saveOrUpdate(domain);
    

    @Override
    public void delete(T domain) throws Exception 
        genericDao.delete(domain);
    

    @Override
    public T get(Serializable id) throws Exception 
        return genericDao.get(id);
    

    @Override
    public List<T> getListByCriteria(DetachedCriteria detachedCriteria) 
        return genericDao.getListByCriteria(detachedCriteria);
    

    @Override
    public List<T> getListByCriteria(DetachedCriteria detachedCriteria,
            int offset, int size) 
        return genericDao.getListByCriteria(detachedCriteria, offset, size);
    

    @Override
    public List<T> filterListWithCondition(T domain) throws Exception 
        return genericDao.filterListWithCondition(domain);
    

    @Override
    public void saveOrUpdate(List domainList) throws Exception 
        genericDao.saveOrUpdate(domainList);
    


【讨论】:

欢迎来到 Stack Overflow!由于这是一段相当长的代码,我认为如果您评论一下您为解决 OP 问题所做的具体更改,您的答案可能会得到改进。

以上是关于Spring MVC:通用 DAO 和服务类的主要内容,如果未能解决你的问题,请参考以下文章

Java框架之spring—jdbcTemplate

Spring Transactions 和通用 DAO 和服务的最佳实践

在DAO,服务层架构中使用spring MVC和Hibernate的正确方法是啥

Spring 项目中控制器和服务类的通用类

java web项目DAO层通用接口BaseDao与实现类BaseDaoImpl

MVC 中所有控制器的通用业务服务层