Rich domain-model in JPA using Spring and no AOP

I was looking for a way to enrich my domain-model by having dependencies automatically injected into my domain-objects.

There is a solution to this problem using Hibernate. This approach was based on inserting an Interceptor into Hibernate that injected dependencies during object-instantiation. Hibernate allowed the client to insert an instance of an Interceptor. I was hoping to find a similar solution for JPA.

During my search, I found another approach that was not relying on any persistence-mechanism. This approach used a runtime-generated proxy for the appropriate layer that did injections based on configuration. First of all, I was looking for a solution using Spring Framework, and this approach was implemented with Nuts. I also suspect that this approach would trigger lazy-loading in JPA.

I then created my abstraction for enriching my domain-objects:

public interface DependencyInjector {
  void performInjection(Object existingBean);
}

I then turned to JPA's EntityListeners. I quickly realised that the persistence-provider was instantiating the EntityListeners, so I settled on having the DependencyInjector as a static instance-variable on my EntityListener.

The EntityListener then looked like:

public class DependencyInjectionEntityListener {
  private static DependencyInjector _injector;
	
  public static void setInjector(DependencyInjector injector) {
    _injector = injector;
  }

  @PrePersist
  @PostLoad
  public void performInjection(Object entity) {
    if (_injector == null)
      throw new IllegalStateException("EntityListener cannot" 
      + " be used without an injector");
    _injector.performInjection(entity);
  }
}

If I wanted to intercept JPA at other events than @PrePersist and @PostLoad I could add them to the method in my EntityListener.

Now, the static variables in this class is not easy to configure with Spring, so I create this configurer-class:

public class DependencyInjectionEntityListenerConfigurer
      implements InitializingBean {
  private DependencyInjector injector;

  public void setInjector(DependencyInjector injector) {
    if (injector == null)
      throw new NullPointerException("Injector cannot be null");
    this.injector = injector;
  }

  public void afterPropertiesSet() throws Exception {
    DependencyInjectionEntityListener.setInjector(injector);
  }
}

To finish this solution I create a simple Autowire-by-type DependencyInjector using Spring:

public class AutowireDependencyInjector 
    implements DependencyInjector, BeanFactoryAware {
  private AutowireCapableBeanFactory beanFactory;

  public void setBeanFactory(BeanFactory beanFactory) {
    if (!(beanFactory instanceof AutowireCapableBeanFactory))
      throw new IllegalArgumentException("BeanFactory must"
      + " be an AutowireCapableBeanFactory");
    this.beanFactory = (AutowireCapableBeanFactory) beanFactory;
  }

  public void performInjection(Object existingBean) {
    beanFactory.autowireBeanProperties(existingBean,
      AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false);
  }
}

To test this approach I create a simple domain-model with one domain-object and a service.

The domain-object looks like this:

@Entity
@EntityListeners(DependencyInjectionEntityListener.class)
public class DomainObject {
  @Id 
  private long id;

  DomainObject() {
  }

  public DomainObject(long id) {
    this.id = id;
  }

  public long getId() {
    return id;
  }

  @Transient
  private SomeService service;

  public void setService(SomeService service) {
    this.service = service;
  }

  public boolean hasService() {
    return service != null;
  }
}

The service has to be marked as @Transient, as it will not be persisted.

The hasService()-method is my way of testing that this approach works.

The service looks like this:

public interface SomeService {
  void performService(DomainObject test);
}

I also need a repository for holding my DomainObjects:

public interface DomainObjectRepository {
  DomainObject findDomainObject(long id);
  void persistDomainObject(DomainObject test);
  void flushAndClear();
}

The flushAndClear()-method is there to be able to clear the JPA-cache and make sure that the @PostLoad-callback will be initiated.

I create a simple implementation of this repository with JPA:

public class DomainObjectRepositoryJpaImpl 
  implements DomainObjectRepository {

  @PersistenceContext
  private EntityManager em;

  public DomainObject findDomainObject(long id) {
    return em.find(DomainObject.class, id);
  }

  public void persistDomainObject(DomainObject domainObject) {
    em.persist(domainObject);
  }

  public void flushAndClear() {
    em.flush();
    em.clear();
  }
}

I create a tiny implementation of my bogus-service, just for fun:

public class SomeServiceImpl implements SomeService {
  
  public void performService(DomainObject domainObject) {
    System.out.println("Performing service on domain-object "
    + domainObject.getId());
  }
}

My persistence.xml-file lists my DomainObject, and I have a spring-config that wires this this up.

Now, all I have to do is to make an integration-test that verifies that this works. The test looks like this:

public class DomainObjectInjectionTest extends 
    AbstractTransactionalDataSourceSpringContextTests {
	
  @Override
  protected String[] getConfigLocations() {
    return new String[] { "classpath:beans.xml" };
  }

  DomainObjectRepository domainObjectRepository;

  public void setDomainObjectRepository(DomainObjectRepository
      domainObjectRepository) {
    this.domainObjectRepository = domainObjectRepository;
  }
    
  public void testInjectionOnPersist() {
    DomainObject domainObject = new DomainObject(1L);
    assertFalse(domainObject.hasService());
    domainObjectRepository.persistDomainObject(domainObject);
    assertTrue(domainObject.hasService());
  }    	
	
  public void testInjectionOnLoad() {
    DomainObject domainObject = new DomainObject(1L);
    domainObjectRepository.persistDomainObject(domainObject);
    domainObjectRepository.flushAndClear();
    domainObject = null;
    domainObject = domainObjectRepository.findDomainObject(1L);
    assertTrue(domainObject.hasService());
  }
}

The test passes, and I'm happy. The obvious drawback is the fact that the EntityListener must be configured statically, but this is a limitation of the JPA-spec. The solution is, on the other hand, quite simple and doesn't involve any AOP-magic.

You can download this example (see the attached file) as a maven-project, that is ready to unpack and run.


Kommentarer

WOW, this is exactely what I

WOW, this is exactely what I was look for about using Spring and no AOP. Thanks a lot, pal

dissertation writing service || buy essay


I was doing my term paper

I was doing my term paper but I can't stop thinking about fixing my blog and then I saw this article. I find it really interesting but I guess I got to start to write my essay first then I can study how to insert these codes


excellent article. i was

excellent article. i was looking for rich-domain-model solution which is aop-less some time ago without success. reading your article provides a good solution for me which can be easily integrated in an existing application.