Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic implementation with lots of database tables

The current application I'm working on is a big one. The database consists of 300+ tables and it's growing. At the moment it's a desktop application but we're moving it to the web.

Technologies we're using for this are Spring (MVC) + Hibernate for backend and ZK framework on the front. Having 300+ tables in database I ended up creating that much POJO's as well. Using Spring's DAO pattern this requires project to have 300+ DAO objects and 300+ Service classes as well.

This is how I'm doing it at the moment:

POJO:

@Entity
@Table(name="test")
public class Test implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;
    private Integer version;

    private String m_name;


    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @Column(name="jpa_id")
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Version
    @Column(name="jpa_version", insertable=false, updatable=false)
    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }

    @Column(name="name", length=30)
    public String getM_name() {
        return m_name;
    }

    public void setM_name(String m_name) {
        this.m_name = m_name;
    }
}

Interface for DAO objects:

public interface IDao<T> {
    public List<T> getAll();
}

To avoid copy/paste I have created a generic DAO class that will be extended by all DAO objects:

@Repository
public class GenericDAO<T extends Serializable> implements IDao<T> {

    @Autowired
    protected SessionFactory sessionFactory;

    protected Class<T> entity;

    @SuppressWarnings("unchecked")
    public List<T> getAll() {
        List<T> result = (List<T>) getSessionFactory().getCurrentSession().createQuery("from " + entity.getName()).list();
        return result;
    }

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public void setEntity(Class<T> entity) {
        this.entity = entity;
    }
}

DAO object:

@Repository
public class TestDAO extends GenericDAO<Test> {

    public TestDAO() {
        setEntity(Test.class);
    }   
}

Service interface:

public interface IService<T> {
    public List<T> getAll();
}

Service implementation:

@Service
public class TestService implements IService<Test> {

    @Autowired
    private IDao<Test> testDAO;

    @Transactional(readOnly=true)
    public List<Test> getAll() {
        return testDAO.getAll();
    }

    public void setTestDAO(IDao<Test> testDAO) {
        this.testDAO = testDAO;
    }
}

I have 2 questions:

  1. How to write a generic service like the GenericDAO class above to avoid c/p?

  2. If you look at the DAO implementaion the only thing there is a constructor. Is there a way to have "one" DAO class that handles all POJO's and one Service class that handles all / one DAO object?

like image 793
Mirko Filipovic Avatar asked Jan 15 '23 01:01

Mirko Filipovic


1 Answers

Question 1

This is not going to be an exact answer to your question, but bear with me...

Using Spring's DAO pattern this requires project to have 300+ DAO objects and 300+ Service classes as well.

This is a common misconception, and it's wrong.

First of all, it is quite unlikely that your 300+ tables are all independent from each other. More likely, many of those table provide 1-1, 1 - n and n - m relations. In such cases, it is better to only have a DAO for the owning part of the relation. The other parts should be stored and retrieved using JPA's cascading capabilities.

Furthermore, the service layer is not meant as an extra irrelevant layer on top of your DAO's; it is meant to provide a unified conceptual domain model, where each action by a user is mapped to exactly one method on the service layer.

Let's look at the following example:

There are three tables: author, book and chapter. There is a 1-n relation between book and chapter, and an n-m relation between book and author. In this example, authors and books are considered strong entities, while chapters are considered weak entities (their existence depends on the existence of books).

In this example, we would indeed have three JPA-annotated POJOs. However, we would only have two DAO's: the AuthorDAO and the BookDAO. There would not be a ChapterDAO, as all access to chapters should go through the BookDAO.

interface BookDAO {
    findAll();
    findById(BookID id)

    saveBook(Book book);
    addChapter(BookID id, Chapter chapter); 
    // etcetera
}

Now let's look at the eventual goal of the example. Let's say it would be an application to provide an RSS feed of new books. There would also be an Admin interface to add new books.

In this case, we would get two Service classes – but they are in no way related to your DAO's!

interface RssService {
    Collection<Book> getRecentBooks();
}

interface AdminService {
    addBook(AddBookCommand cmd);
    addAuthor(AddAuthorToBookCommand cmd);
}

The last point I'm going to make is a point of contention, and others will disagree with me. The current AdminService provides no means to provide a list of all books in the system. To solve this, there are two options:

  • Add getAllBooks and getAllAuthors methods to the AdminService.
  • Just use the existing DAOs in your views.

I would prefer the latter, but as said, others will disagree with this.

The point to note however, is that however we implement these services, they are in no way directly related to your DAOs.

And now I can answer your question in full:

How to write a generic service like the GenericDAO class above to avoid c/p?

You don't. Generic services defeat the entire purpose of having a service layer.

Question 2

If you look at the DAO implementaion the only thing there is a constructor. Is there a way to have "one" DAO class that handles all POJO's

Yes, but once again I advise against it. It first, your DAOs might be identical, but as your project progresses, DAOs will specialize and they will need specific methods. For example:

interface BookDAO {
    // generic stuff
    ...
    // end generic stuff
    getBooksWithMinimumChapters(int minimumChapterCount);
}

interface AuthorDAO {
    // generic stuff
    ...
    // end generic stuff

    getAuthorsWithMultipleBooks(int minimalBookCount); 
}

So for now, just leave them as they are - a single constructor and a bunch of generic inherited methods.

like image 88
publysher Avatar answered Jan 22 '23 04:01

publysher