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)看不到 findByName
和 findByCode
方法。我明白为什么。但是我该如何重写呢?
【问题讨论】:
【参考方案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 和服务类的主要内容,如果未能解决你的问题,请参考以下文章
Spring Transactions 和通用 DAO 和服务的最佳实践
在DAO,服务层架构中使用spring MVC和Hibernate的正确方法是啥