Spring MVC: Generic DAO and Service classes
Asked Answered
M

5

9

I am writting web in Spring MVC. I wrote all DAOs using Generic DAO. Now I would like to rewrite my Service classes. How can I write "Generic Service"?

There are my DAOs:

/* ################################# 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 ################################ */

and Services:

/* ################################# 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 ################################ */

Compiler (and Eclipse) do not see findByName and findByCode methods. I understand why. But how can I rewrite it?

Martz answered 9/10, 2014 at 21:40 Comment(0)
R
2

The problem is that your inject directly your GenericDao in your GenericManager but none of them is a concrete Spring bean and you will never be able to use your specific CountryDao.

You must not autowire GenericDao but only define it and provide 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;
    }

...

}

Then, you will have to inject a concrete spring bean in your concrete services. i.e. in 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;
    }

...

}

You will have then a full spring bean injected with your concrete CountryDao type and its specific methods.

You can take a look at what we did on RESThub project regarding generic services : https://github.com/resthub/resthub-spring-stack/blob/master/resthub-common/src/main/java/org/resthub/common/service/CrudServiceImpl.java and some concrete example : https://github.com/resthub/todo-backbone-example/blob/master/src/main/java/todo/TodoController.java (with a Controller instead of a Service but it is similar)

Hope it will help.

(and sorry if there is some typos, I cannot double check right now)

and, BTW, you should consider using Spring Data instead of using GenericDaos but you will still have the same needs regarding your Services.

Raposa answered 9/10, 2014 at 22:11 Comment(2)
Well, I can see multiple mistakes (some are mine :-)): DAO type should be passed as a generic Type in your GenericManager (see edited code in my answer) and don't forget the @Inject annotation. I updated my answer.Raposa
Not sure to not forget something, you really should take a look to the two links I paste before ...Raposa
L
1

i still dont know why people actually use archaic DAO / service - models with Spring Data; totally unnecessary, error-prone and whatnot.

Spring Data JPA has some extremely useful interfaces for that stuff : JpaRepository and JpaSpecificationExecutor - these encapsulate EVERYTHING you would want, you only need your standard Entities and thats it - everything else will be handled by spring, you simply input your criterias and get exactly what you want, without reinventing the wheel. Could it be that you didnt actually read the documentation? Its very useful :

official introduction : http://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/

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

small howto : http://www.cubrid.org/wiki_ngrinder/entry/how-to-create-dynamic-queries-in-springdata

examples from the genius himself : https://github.com/spring-projects/spring-data-jpa-examples/tree/master/spring-data-jpa-example

example classes :

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
}

and now you can simply Autowire your repository :

@Autowired
CustomerRepository customerRepo;

and retrieve data like this :

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

its that easy.

Lubricator answered 9/10, 2014 at 22:14 Comment(3)
Have you tried this against complex join policies between tables ? I have had very bad experience with that API.Inverness
well you might just be the only person on this planet - the JPA criteria API (because thats basically what it is; with functionless wrappers around it) is very field-proven, standardized and well-respected. If you had "bad experience" with it you probably made several mistakes. Its kinda difficult do start describing your data-logic via criterias if you're used to SQL statements - but its well worth the learning effort. And : Yes i have "tried" it "against complex join policies between tables", it works even better than DBMS-optimized SQL statements.Lubricator
+1 I agree with the essence of the point here: the typical way DAOs and Services are used in Spring apps (Java EE too) is mind-boggingly innefective. So much unnecessary, pointless code that only gets in the way. Spring Data is better, although still not my preferred approach, which is to simply have a single general-purpose class (say, "AppDatabase") which wraps the standard JPA API, making it easier to use (with methods like "save", "remove", "find(jpql, args)", etc.).Breaking
F
0

I think it's just the limitation of the java OO design. You need a parametrized way to pass the predicates for searching, something like:

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

Where the predicate class is something like this

class Predicate {
   String columnName;
   Operator operator;
   String value;
}

Hence you can express "name = 'John'", age >= 21, etc

This is not an ideal solution, code become less human readable, you will need to transform the predicates into database queries and there are few type casting needs to be done which is prone to runtime error.

You can avoid reinventing the wheel with library like Spring Data. You don't even need a generic DAO, you just need to supply an interface method like

List<Person> findByName(String name);

and an implementation will be automatically generated at application bootstrap. Have a look at Spring Data JPA for more.

Frutescent answered 9/10, 2014 at 21:56 Comment(0)
B
0

Try This:

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);
    }

}
Briefless answered 23/8, 2015 at 10:19 Comment(1)
Welcome to Stack Overflow! Since this is a rather long block of code, I think your answer could be improved if you commented on what exactly you changed to fix the OP's issue.Meow
B
0

//Implementing GenericDao and 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

}

// StateService

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
}
Briefless answered 23/8, 2015 at 10:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.