Spring Generic Dao class name

2019-01-12 08:12发布

问题:

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.

回答1:

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
    }


回答2:

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



回答3:

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)