2

I have configured a custom generic service DAO for my spring / hibernate project - the idea being that I can reuse it easily from my controllers.

It essentially looks like this:

public class DefaultService<T> {

private Class<T> e;

public String className(Class<T> e) {
    String clip = e.getName();
    clip = clip.substring(clip.lastIndexOf('.') + 1, clip.length());
    return clip;
}
public List<T> getAll(Integer status) {
    Session session = sessionFactory.getCurrentSession();
    Query query = session.createQuery("FROM " + className(e) + " WHERE status = " + status);
    return query.list();
}
...

Which gets referenced by:

@Autowired
public DefaultService<Address> addressService;
addressService.get(1);

However the String clip = e.getName() line throws a Null pointer exception. I can get this to work if I move the class into the attributes section (so addressService.get(Address.class, 1) but I find this somewhat untidy, especially when there are multiple different classes being called upon.

Is there some way to get the class to generate a value correctly without repeatedly adding it into all my functions?

Thanks in advance.

Toby
  • 1,561
  • 2
  • 17
  • 30

3 Answers3

5

I did something similar, you need the generic class to be a constructor argument as well, mine uses hibernate entities, but you could pass in the string of table name.

public class DomainRepository<T> {

    @Resource(name = "sessionFactory")
    protected SessionFactory sessionFactory;

 public DomainRepository(Class genericType) {
        this.genericType = genericType;
    }

 @Transactional(readOnly = true)
    public T get(final long id) {
        return (T) sessionFactory.getCurrentSession().get(genericType, id);
    }

You can then subclass (if you need to) to customize or simply set up you bean in the spring config like below t :

<bean id="tagRepository" class="com.yourcompnay.data.DomainRepository">
        <constructor-arg value="com.yourcompnay.domain.Tag"/>
</bean>

So in your code you could then reference tagRepository like so (no other cod eis needed than that posted above, and below) :

@Resource(name = "tagRepository")
private DomainRepository<Tag> tagRepository;

Also, I would call it a repository not a service, a service deals with different types and their interactions (not just one). And for specifically your example using SQL strings :

public final String tableName;
public DomainRepository(String tableName) {
      this.tableName = tableName;
}
public List<T> getAll(Integer status) {
    Session session = sessionFactory.getCurrentSession();
    Query query = session.createQuery("FROM " + tableName + " WHERE status = " + status);
    return query.list();
}

and have your beans defined like so

<bean id="addressRepository" class="com.yourcompnay.data.DomainRepository">
  <constructor-arg value="address"/>
</bean>

And then you can alsow create subclasses youself where necessary :

public class PersonRepository extends DomainRepository<Person> {
    public PersonRepository(){
        super("person"); //assumes table name is person
    }
NimChimpsky
  • 43,542
  • 55
  • 186
  • 295
  • The first line of my full class is @Repository, so i'll start calling it that from now on :) Since it is the name of the class I want so that I can create custom database lookups, and looking at your code, is all I need to add in theory `public void e(Class genericType) {this.e = genericType;}`? – Toby Feb 21 '13 at 13:41
  • see update, if the subclassed file is empty don't subclass ... onyl subclass where necessary. in My app I have a number of repositories, which are simply instances of the generic repository, and then one or two reposotries which have been subclassed (where they have more complex requirements). th esolution I provided allows you to subclass ... I'll show you that aswel – NimChimpsky Feb 21 '13 at 13:49
  • Ok, so I've remodelled my application to follow this (begrudgingly creating all those extra repositories for each model). One final question - what does the bean do? It seems to work just fine without it, although I did need to remove the 'final' modifier of 'tableName' and add a `public DomainRepository() {}` method... – Toby Feb 27 '13 at 13:17
  • @Toby huh ? You only have to create one DomainRepository class, the bean defintion instantiates and instance of DomainReposiory using the constructor argument and Generic argument provided,which you reference in your code...you don't *have* to subclass, begrudgingly or not, but it can useful if you have a number of methods that are specific to certain types/tables. – NimChimpsky Feb 27 '13 at 13:31
  • Ah I see, so it's like an either/or situation? Well I've been using some horridble workarounds to try and keep the dao as generic as possible, so it's probably best to have a seperate instance of each actual repository - its only a dozen so it's not so bad. Last question: which is better or does it make no difference; `@Autowired public PageRepos pagerep; or `@Autowired public DomainRepository pagerep;'? Many thanks – Toby Feb 27 '13 at 13:43
  • @Toby horses for courses, they do different things. The first would be used if you if have subclassed domain repository. See my updated answer. In my app, I have only one concrete subclass of DomainRepository, but actually use about 8 different instances of it, as declared in my Bean config file – NimChimpsky Feb 27 '13 at 13:46
  • That beans have to be placed @ web.xml? @NimChimpsky you can edit the answer to clear that question? – Filipe Feb 17 '14 at 18:19
  • @LuizFilipe beans are never placed in web xml. They must be defined in application context, or if controllers in the web context. The answer already contains the xml snippet required. – NimChimpsky Feb 18 '14 at 08:01
0

As I understand you got NPE because you did not set any value for this field. So you can resolve this problem by 2 ways:

  1. Set manually class object as in comment NimChimpsky.
  2. Get class type dynamically. E.g, if you use Spring try this one:

protected Class getEntityClass() { return GenericTypeResolver.resolveTypeArguments(getClass(), DefaultService.class)[0]; }

or some workaround here

ybondar
  • 367
  • 2
  • 16
  • Should this work with my current code? I have a good basic understanding of what is going on, but I can't quite grasp how this line would fit in? – Toby Feb 21 '13 at 13:33
  • Yes, this method should be helpful for you. This line shows how get Class type of your concrete generic type. Than you can work with this class object: get name and other things. – ybondar Feb 21 '13 at 15:07
0

It's better to define a specific class for Address service

public class AddressService extends DefaultService<Address>{
  public String getClassName(){
   return "Address";
  }
}

where

public String getClassName();

is an abstract method declared in DefaultService, and used (like your method className()) in your data access logic.

Using this approach, you will be able to add specific data access logic (example, getUsersByAddress)

Laabidi Raissi
  • 3,115
  • 1
  • 19
  • 27