• No results found

Creating a data access layer

In document Spring (Page 77-82)

Data persistence, ORM, and transactions

JPQL query

2.4 Creating a data access layer

PREREQUISITES

Recipe 2.3, “Object-relational mapping and transactions via Hibernate”

KEY TECHNOLOGIES

Hibernate Background

Persistence is a fairly low-level concern; it’s not part of the domain logic that forms the core concern for your application’s service tier. Unfortunately, so far your service bean focuses squarely on persistence. In this recipe, you’ll learn how to isolate domain logic from persistence logic, which creates a cleaner architecture.

Problem

Separate the domain logic and persistence logic concerns.

Solution

The classical solution to separating the domain logic and persistence logic concerns is called the data access object (DAO) pattern.6 The basic idea is to consolidate persistence-related code in a dedicated architectural tier that sits above the database, and have the service tier (which handles domain logic) defer persistence-related concerns to this persistence-centric tier.

The approach to implementing DAOs is simple: inject the Hibernate SessionFac-tory into each DAO, and then implement persistence methods backed by the sessions you grab from the factory. Because a lot of the persistence operations are common, it’s useful to define a general DAO interface and an abstract base DAO, and then derive corresponding entity-specific interfaces and classes as shown in figure 2.8.

6 Core J2EE Patterns—Data Access Object, Oracle, http://mng.bz/0PSy.

Figure 2.8 Class diagram illustrating the relation-ship between generic framework classes and app-specific implementa-tions

The following listing presents the generic DAO interface that you’ll use throughout the book.

package com.springinpractice.dao;

import java.io.Serializable;

import java.util.List;

public interface Dao<T extends Object> { void create(T t);

T get(Serializable id);

T load(Serializable id);

List<T> getAll();

void update(T t);

void delete(T t);

void deleteById(Serializable id);

void deleteAll();

long count();

boolean exists(Serializable id);

}

There’s not a single way to implement a generic DAO interface, but this one meets the needs for this book. Let’s take a look.

First, note that this interface is in the com.springinpractice.dao package

B

. We put it there instead of in com.springinpractice.ch02 because you’ll use this inter-face throughout the book. To get the sample DAO code, grab the download at https:

//github.com/springinpractice/sip-top/.

A key feature is the use of a generic type parameter

C

. This allows you to adapt derived interfaces to specific domain classes, making them friendlier to use. You include the various CRUD methods such as create()

D

. You can also include general-purpose queries; here you use a getAll() finder method

E

that finds all instances of the relevant type.

Deriving a subinterface is easy. You can add entity-specific methods as you please, as shown in the next listing.

package com.springinpractice.ch02.dao;

import java.util.List;

import com.springinpractice.ch02.model.Contact;

import com.springinpractice.dao.Dao;

public interface ContactDao extends Dao<Contact> { List<Contact> findByEmail(String email);

}

Listing 2.8 Dao.java, a generic DAO interface

Listing 2.9 ContactDao.java: a contact DAO interface

Common DAO package

B

Generic type parameter CRUD operation

C

D

A query

E

You now have a base DAO interface, and you know how to derive entity-specific inter-faces from it. You’re going to do the same thing on the implementation side, defining a generic base DAO class, then subclassing it on a per-entity basis. The implementation is based on Hibernate.

public abstract class AbstractHbnDao<T extends Object>

implements Dao<T> {

@Inject private SessionFactory sessionFactory;

private Class<T> domainClass;

protected Session getSession() {

return sessionFactory.getCurrentSession();

private String getDomainClassName() { return getDomainClass().getName();

}

public void create(T t) { Method method = ReflectionUtils.findMethod(

getDomainClass(), "setDateCreated",

Listing 2.10 AbstractHbnDao.java: a generic base class for implementing DAOs

Uses generics

@SuppressWarnings("unchecked") public T get(Serializable id) {

return (T) getSession().get(getDomainClass(), id);

}

@SuppressWarnings("unchecked") public T load(Serializable id) {

return (T) getSession().load(getDomainClass(), id);

}

@SuppressWarnings("unchecked") public List<T> getAll() { return getSession()

.createQuery("from " + getDomainClassName()) .list();

}

public void update(T t) { getSession().update(t); } public void delete(T t) { getSession().delete(t); }

public void deleteById(Serializable id) { delete(load(id)); } public void deleteAll() {

getSession()

.createQuery("delete " + getDomainClassName()) .executeUpdate();

}

public long count() { return (Long) getSession()

.createQuery("select count(*) from " + getDomainClassName()) .uniqueResult();

}

public boolean exists(Serializable id) { return (get(id) != null); } }

In line with the Dao interface, the base class uses generics

B

. You inject the Hibernate SessionFactory

C

and then offer a protected getSession() method

D

, allowing subclasses to perform persistent operations against the Hibernate Session.

Implementing general versions of your CRUD operations and queries requires a reference to the actual domain class. You use reflection to discover and return it

E

.

The various methods are implementations of Dao methods. You have, for example, a CRUD method

F

and a query

G

. The create() method sets the dateCreated prop-erty if it exists.

The following listing shows how to extend AbstractHbnDao to create an entity-specific DAO.

package com.springinpractice.ch02.dao.hbn;

import java.util.List;

import org.springframework.stereotype.Repository;

import com.springinpractice.ch02.dao.ContactDao;

Listing 2.11 HbnContactDao.java: Hibernate-based DAO for contacts Query

G

import com.springinpractice.ch02.model.Contact;

import com.springinpractice.dao.hibernate.AbstractHbnDao;

@Repository

public class HbnContactDao extends AbstractHbnDao<Contact>

implements ContactDao { @SuppressWarnings("unchecked")

public List<Contact> findByEmail(String email) { return getSession()

.getNamedQuery("findContactsByEmail") .setString("email", "%" + email + "%") .list();

} }

Clearly, AbstractHbnDao makes life much simpler. You don’t have to implement the core persistence methods, because they’re available in AbstractHbnDao. Instead, you can focus on entity-specific extensions such as findByEmail(). Note the use of the flu-ent Query interface in this implemflu-entation of the findByEmail() method.

Also note the use of the @Repository annotation. This allows Spring to component-scan the bean, among other things.

The next listing shows the effect of your refactoring on the ContactServiceImpl bean.

package com.springinpractice.ch02.service.impl;

import java.util.List;

import javax.inject.Inject;

import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;

import com.springinpractice.ch02.dao.ContactDao;

import com.springinpractice.ch02.model.Contact;

import com.springinpractice.ch02.service.ContactService;

@Service

@Transactional

public class ContactServiceImpl implements ContactService { @Inject private ContactDao contactDao;

public void createContact(Contact contact) { contactDao.create(contact);

}

public List<Contact> getContacts() { return contactDao.getAll();

}

public List<Contact> getContactsByEmail(String email) { return contactDao.findByEmail(email);

}

public Contact getContact(Long id) { return contactDao.get(id);

}

Listing 2.12 ContactServiceImpl.java without persistence code

public void updateContact(Contact contact) { contactDao.update(contact);

}

public void deleteContact(Long id) { contactDao.deleteById(id);

} }

As you can see, this “service” is a pass-through layer to the DAO. In a more realistic application, you’d expect to see service beans include more domain logic. Sometimes this is domain logic proper, and sometimes it’s other non-domain logic that for what-ever reason hasn’t been externalized. Examples are validation, security, messaging, and workflow. In any event, you’ve successfully moved the persistence concern out of the service bean into a dedicated tier.

This recipe was just a refactoring; rerun the application to ensure that it still func-tions before continuing on to the next recipe. Don’t forget to add

<context:component-scan

base-package="com.springinpractice.ch02.dao.hbn" />

to beans-service.xml so Spring can find the HbnContactDao you created.

Discussion

You’ve defined general persistence operations in the Dao interface. AbstractHbnDao implements this interface using Hibernate’s Session API. You create entity-specific interfaces by extending Dao, and you create entity-specific implementations by extend-ing AbstractHbnDao.

The next two recipes are optional because they present material that you don’t use elsewhere in the book. But we strongly recommend that you review them, because they present an official framework for building DAOs, similar to the earlier ones, based on JPA and Spring Data JPA. Spring Data JPA is a fairly recent addition to the Spring portfolio, and we didn’t have time to rework all the examples in the book to use JPA and Spring Data JPA instead of Hibernate and custom DAOs. But we would have liked to have done so. Look at the next two recipes, and consider using the approach described in your own projects.

In document Spring (Page 77-82)