11

I have an abstract DAO class which uses parameterized types E (Entity) and K (Primary Key). In every entity I have a @NamedQuery. I want to dynamically invoke this named query without knowing its exact name and parameter name.

As an example, imagine the following entity City

@Entity(name="CITY")
@NamedQuery(
    name="findCityByname",
    query="FROM CITY c WHERE name = :CityName"
)
public class City { 
    // ...
}

and this CityDao

public class CityDao extends AbstractDao<City, Long> {
    public CityDao() {
        super(City.class);
    }   
}

How should I implement the findByName() method in AbstractDao so that I don't need to know the exact name and parameter name?

public abstract class AbstractDao<E, K> implements Dao<E, K> {

    @PersistenceContext
    protected EntityManager entityManager;
    protected Class<E> entityClass;

    protected AbstractDao(Class<E> entityClass) {
        this.entityClass = entityClass; 
    }

    @Override
    public E findByName(String name) {
        try {
            return (E) entityManager
                .createNamedQuery("findCityByName")
                .setParameter("CityName", name)
                .getSingleResult();
        } catch(Exception e) {
            return null;
        }
    }

    // ...
}
Arjan Tijms
  • 37,782
  • 12
  • 108
  • 140
yoav.str
  • 1,547
  • 6
  • 33
  • 73

4 Answers4

13

The naming convention for named queries is usually <Entity Name>.findBy<PropertyAndAnotherProperty>, "City.findByName" in your example, so I would try to change the named queries to follow this pattern. The parameter to this query should then also have the same name, or you could use positional parameters. Your find method would then turn into

@Override
public E findByName(String name) {
    E entity = null;
    try {
        return (E)entityManager.createNamedQuery(myClass.getSimpleName() + ".findByName")
                               .setParameter("name", name)
                               .getSingleResult();
    } catch (Exception ex) {
        return null;
    }
}
Jörn Horstmann
  • 33,639
  • 11
  • 75
  • 118
1

The simplest method is to pass the name of the query to the constructor of the abstract DAO:

public DaoAbstreact(Class myClass, String findByNameQueryName) {
    this.myClass = myClass; 
    this.findByNameQueryName = findByNameQueryName;
}

Then define a public static final String in City to hold the name:

public class ConcreteCityDao<City,Long> extends DaoAbstreact {    
    ConcreteCityDao(){
        super(City.class, City.FIND_BY_NAME_QUERY_NAME));
    }   
}

Alternatively you could declare DaoAbstreact as abstract and then have a method like this in it:

public abstract String getFindByNameQueryName();

And implement that in ConcreteCityDao.

Finally you could also introduce an enumeration:

public enum NamedEntityType {
    CITY(City.class, "findCityByname"), 
    PERSON(Person.class, "findPersonByname");

    private final Class<?> entityClass;

    private final String findByNameQueryName;

    private NamedEntityType(Class<?> entityClass, String findByNameQueryName) {
         this.entityClass = entityClass;
         this.findByNameQueryName = findByNameQueryName;
    }

    public Class<?> getEntityClass() {
        return entityClass;
    }

    public String getFindByNameQueryName() {
        return findByNameQueryName;
    }
}

Then your DAO can determine the type from the class passed in. To ensure you don't forget to add an entity to the enumeration you can make each entity implement an interface with a getNamedEntityType() method. Then you can specify that your abstract generic DAO will only accept entities that implement that interface.

Russ Hayward
  • 5,617
  • 2
  • 25
  • 30
0

The obvious way would be to pass values from concrete classes to the abstract superclass using abstract method

public abstract class AbstractDao<E, K extends Serializable> implements Dao <E, K> {
    ...
    protected abstract String getFindByNameQueryName();

    @Override
    public E findByName(String EntityStr) { 
        ... entityManager.createNamedQuery(getFindByNameQueryName()) ...
    }
}

@Override
public class ConcreteCityDao<City,Long> extends DaoAbstreact{
    ...
    protected String getFindByNameQueryName() { 
        return "findCityByName";
    }
}

or as a constructor argument:

public abstract class AbstractDao<E, K extends Serializable> implements Dao<E, K> {
    public AbstractDao(Class<E> myClass, String findByNameQueryName) { ... }
    ...
}

@Override
public class ConcreteCityDao<City, Long> extends DaoAbstreact{
    public ConcreteCityDao() {
        super(City.class, "findCityByName");
    }
}

Though this requires consistent naming of query parameters for different entities.

Also note the minor improvements in these snippets.

axtavt
  • 239,438
  • 41
  • 511
  • 482
0

What you basically seem to want is to annotate the annotations that define the named queries, in such a way that you can programmatically discover what the "findByName" query is (and possible other queries).

Since this is not possible in Java, you could use the fact that @NamedQuery supports query hints, that are defined as being vendor specific. Unknown hints are ignored. You could add your own data here, that the generic DAO can read back from entityClass:

@NamedQuery(
    name="findCityByname",
    query="FROM CITY c WHERE name = :CityName",
    hints=@QueryHint(name="genericDAO.type", value="findByName")
)
Arjan Tijms
  • 37,782
  • 12
  • 108
  • 140