Merge two very similar methods - java

I have this theoretical situation:
a form with
2 inputs [attributes attr1, attr2 of object item] **
<h:inputText id="attr1" value="#{bean.item.attr1}"/>
<h:inputText id="attr2" value="#{bean.item.attr2}"/>
2 submit buttons [searching by attr1, attr2 in dtb] in foo.xhtml:
<h:commandButton id="search1" action="#{bean.search1}" />
<h:commandButton id="search2" action="#{bean.search2}" />
and two very similar methods in bean.java:
public void search1(){
try
{
session = DaoSF.getSessionFactory().openSession();
Criteria criteria = session.createCriteria(Foo.class);
criteria.add(Restrictions.like("attr1", item.getAttr1()));
dataList = criteria.list();
}
catch (Exception e) {...}
}
public void search2(){
try
{
session = DaoSF.getSessionFactory().openSession();
Criteria criteria = session.createCriteria(Foo.class);
criteria.add(Restrictions.like("attr2", item.getAttr2()));
dataList = criteria.list();
}
catch (Exception e) {...}
}
Is there some way to merge these two methods to one?
UPDATE: and also merge action of commandButtons?
SOLUTION:
private void search(String field, String value)
action="#{bean.search('attr2', bean.item.attr1)}"

Just pass what's different as a parameter:
public void search(String param, Attr attr){
try
{
session = DaoSF.getSessionFactory().openSession();
Criteria criteria = session.createCriteria(Foo.class);
criteria.add(Restrictions.like(param, attr));
dataList = criteria.list();
}
catch (Exception e) {...}
}

Sure: look for what's the same, and what's different. You don't provide enough info to know whether or not this is possible:
public void search(String sAttr, T attr) {
try {
session = DaoSF.getSessionFactory().openSession();
Criteria criteria = session.createCriteria(Foo.class);
criteria.add(Restrictions.like(sAttr, attr));
dataList = criteria.list();
} catch (Exception e) {...}
}
If the attribute types aren't the same, it's a bit more irritating, and the joy that is Java rears its ugly head.
At some point you'll end up with a method that takes a Criteria and you either create those on-the-fly and pass them in to something that wraps up the try/catch and list, or you end up creating an interface and passing an implementation in to the same.
On a side note, IMO creating side-effect dependencies like setting dataList inside a search method will eventually lead to tears: consider returning the list from the method instead, even if you just set it to a property to get passed along to whatever uses it.

You could create a third method and call it from within those methods:
private void doSearch(String field, String value) {
session = DaoSF.getSessionFactory().openSession();
Criteria criteria = session.createCriteria(Foo.class);
criteria.add(Restrictions.like(field, value));
dataList = criteria.list();
}
public void search1() {
doSearch("attr1", item.getAttr1());
}
public void search2() {
doSearch("attr2", item.getAttr2());
}

Related

Is possibility using Spring Data MongoDb to define custom Criteria?

I have simple interface with one method:
Criteria toCriteria(String key, String value)
And next I'd like to have next implementation
public class EqExpression implements Expression
{
#Override
public Criteria toCriteria(String key, String value)
{
return Criteria.where(key).eq(Pattern.compile(value));
}
}
}
but there isn't $eq operator. So my questions:
Why org.springframework.data.mongodb.core.query.Criteria doesn't have such operator?
Is there a way to implement custom Criteria implementation or is there any workaround?
For me it would be good to have code like
#Override
public Criteria toCriteria(String key, String value)
{
//return new BasicDBObject(key, new BasicDBObject("$eq", value)) converted to Criteria
}
In general, my purpose is to implement rest query language and for each operation like gt, lt I have specific implementation of Expression interface.
Request may looks like name=John&age>20
I am building whole query using next code:
List<Criteria> criterias = new ArrayList<Criteria>();
...
while (matcher.find())
{
String key = matcher.group(1);
String operator = matcher.group(2);
String value = matcher.group(3);
// get from map appropriate implementation
criterias.add(expressions.get(operator).toCriteria(key, value));
}
May be you have any suggestions how to implement it more elegant
using this answer, I was able to create simple $eq Criteria
private Criteria getEqCriteria(String value)
{
// hack Criteria, because new Criteria().is(value) not working!
Criteria c = new Criteria();
try
{
Field _criteria = c.getClass().getDeclaredField("criteria");
_criteria.setAccessible(true);
#SuppressWarnings("unchecked")
LinkedHashMap<String, Object> criteria = (LinkedHashMap<String, Object>) _criteria.get(c);
criteria.put("$eq", value);
Field _criteriaChain = c.getClass().getDeclaredField("criteriaChain");
_criteriaChain.setAccessible(true);
#SuppressWarnings("unchecked")
List<Criteria> criteriaChain = (List<Criteria>) _criteriaChain.get(c);
criteriaChain.add(c);
} catch (Exception e)
{
// Ignore
}
return c;
}

Get parameter name of class to pass it to other method in java

I would like to pass parameter name as a parameter to other method, f.e:
I have class:
public class Foo() {
public Bar bar;
public Bar anotherBar;
public Bar yetAnotherBar;
public void doSomethingWithBar() {
common.doingSomething(
getMostImportantBarParameterName()
);
}
}
And in this class I would to have method:
public String getMostImportantBarParameterName() {
return Foo.bar;
}
but instead of returning value of bar, I would like to get a name of parameter bar, so it should just return "bar".
For now I have to do this that way:
public String getMostImportantBarParameterName() {
return "bar";
}
Why I wanna achieve something like that?
I am trying as much I can to avoid using strings in my code, cause in refactorization process I will bypass (skip) it accidentally.
But if I will have "hard coded" parameters that way, when I will later rename this parameter it will be automatically replaced in all instances by Eclipse IDE (Using LALT+LSHIFT+R)
Also my method: common.doingSomething() use parameter in runtime, So I won't get compilation error, which it makes hard to maintain this method.
I don't write unit test, cause I can't yet.
Please give me some help on this. Thanks
----------------- EDIT ------------------------
Real life usage.
I would like to have method to access database records in generic way.
Common database operation in my application is:
Getting records from TableName where Parameter = SomeValue
So I would like to have generic method for that in generic entity listed below:
#MappedSuperclass
public abstract class GenericModel<T extends GenericModel> {
#Transient protected Class<T> entityClass;
private List<T> getByParameterAndValue(String parameter, String value) {
List<T> entities = new ArrayList<T>();
String sqlString = "SELECT e FROM " + entityClass.getSimpleName() + " e WHERE e."+ parameter + " = :value";
TypedQuery<T> query = JPA.em().createQuery(sqlString, entityClass).setParameter("value", value);
try {
entities = query.getResultList();
} catch (NoResultException e1) {
entities = null;
} catch (Exception e) {
Index.toLog("error","Unsupported error in Generic model class in " + entityClass);
}
return entities;
}
which is extended by real entities f.e.:
public class User extends GenericModel<User> {
public String name;
public String email;
public String date;
public String department;
public List<User> getUsersByDepartments(String dep) {
return getByParameterAndValue("department", dep);
}
}
The problem is that in JPA TypedQuery:
TypedQuery<User> query = em.createQuery("SELECT u FROM User u WHERE u.department = :department", User.class);
return query.setParameter("department", department).getSingleResult();
First of all, I think you should reconsider your approach. Using field names like this (either by reflection or hard coded Strings) is not very robust. In general, reflection should be avoided if possible.
What are you trying to achieve? What will common.doingSomething be doing with the field name?
It might be better to model the importance explicitly with an accessor:
class Foo {
private Bar bar;
private Bar anotherBar;
private Bar yetAnotherBar;
public Bar getMostImportantBar() {
return bar;
}
}
To answer your question about generics. You can either select the field by its index or by its name. Both are not robust, for when you change the field name, the String used to get it via reflection will not change with it, and if you change the order of the fields, the index will be wrong.
Here's how to do it:
Class foo = Foo.class;
Field[] fields = foo.getFields();
// get by index
Field firstField = fields[0];
String firstFieldName = firstField.getName();
// get by name
Field barField = foo.getField("bar");
String barFieldName = barField.getName();
EDIT (after reading updated question):
In any Object Relational Mapping solution there is a boundary where the object-oriented realm ends and the relational realm begins. With your solution you are pulling that boundary a bit further into your code, in order to gain ease of use for your specific model classes and queries. The consequence of that is that you get more 'boiler plate' style code as part of your application (the GenericModel class) and that the boundary becomes more visible (the reference to a field by index or name using reflection). This type of code is generally harder to understand, test and maintain. On the other hand, once you get it right it doesn't change that often (if your assumption about the query type you usually need turns out to be valid).
So I think this is not a ridiculous use case for reflection, even though I myself would probably still stick to JPA and accept the similarity of the queries. With a good JPA framework, expressing these queries does not incur a lot of code.
About the hard-coded field names vs indexes, I advise you to go with the field names because they are easier to understand and debug for your successors. I would make sure the field name is expressed in the model class where the field resides, to make it as clear as possible that the two belong together, similar to the example you gave:
public class User extends GenericModel<User> {
public static final String FIELD_NAME = "name";
public static final String FIELD_EMAIL = "email";
public static final String FIELD_DATE = "date";
public static final String FIELD_DEPARTMENT = "department";
private String name;
private String email;
private String date;
private String department;
// the byXXX naming scheme is a quite common shorthand for lookups
public List<User> byDepartment(String department) {
return getByParameterAndValue(FIELD_DEPARTMENT, department);
}
BTW I think getByParameterAndValue cannot be private (must be at least default). Also I don't think you should initialize List<T> entities = new ArrayList<T>() at the start. You can do that in the catch(Exception e) to avoid unnecessary initialization if the query succeeds or returns no results. An your fields should be private (shown above).
Of course, this approach still results in one lookup method for each field. A different solution is to create a service for this and leave the model objects aenemic (without behavior):
public class DaoService {
public <T extends GenericModel> List<T> get(Class<T> entityClass, String fieldName, String value) {
List<entityClass> entities;
String sqlString = "SELECT e FROM " + entityClass.getSimpleName() + " e WHERE e."+ fieldName+ " = :value";
TypedQuery<T> query = JPA.em().createQuery(sqlString, entityClass).setParameter("value", value);
try {
entities = query.getResultList();
} catch (NoResultException e) {
entities = null;
} catch (Exception e) {
entities = new ArrayList<T>()
}
return entities;
}
}
Usage:
List<User> = daoService.get(User.class, User.FIELD_DEPARTMENT, value);
Here's another (slightly wild) idea I just had. Each model class is also a query template:
public abstract class ModelQuery<T extends ModelQuery> {
// TODO set from constructor
private Class<T> entityClass;
private Field[] allFields = entityClass.getFields();
private List<T> getByTemplate() {
List<Field> queryFields = new ArrayList<Field>();
String sql = selectFieldsAndCreateSql(queryFields);
TypedQuery<T> query = setQueryParameters(queryFields, sql);
return executeQuery(query);
}
private String selectFieldsAndCreateSql(List<Field> queryFields) throws IllegalAccessException {
StringBuilder sql = new StringBuilder();
sql.append("SELECT e FROM ")
.append(entityClass.getSimpleName())
.append("e WHERE ");
for (Field field : allFields) {
if (field.get(this) != null) {
sql.append("e.")
.append(field.getName())
.append(" = :")
.append(field.getName());
// keep track of the fields used in the query
queryFields.add(field);
}
}
return sql.toString();
}
private TypedQuery<T> setQueryParameters(List<Field> queryFields, String sql) throws IllegalAccessException {
TypedQuery<T> query = JPA.em().createQuery(sql, entityClass);
for (Field field : queryFields) {
query.setParameter(field.getName(), field.get(this));
}
return query;
}
private List<T> executeQuery(TypedQuery<T> query) {
List<T> entities;
try {
entities = query.getResultList();
} catch (NoResultException e1) {
entities = null;
} catch (Exception e) {
entities = new ArrayList<T>();
}
return entities;
}
}
Usage:
User userQuery = new User();
userQuery.setDepartment("finance");
List<User> results = userQuery.getByTemplate();
I guess there are more ways to skin this cat. Good luck with finding your optimal solution!
To get private field names
use foo.getDeclaredFields(); instead of foo.getFields();
Here are also you have some minor issue
fields[0] means, the first declared field, in which 0 is again hard coded
If you change the order of declaration then again it could be a trouble for you, which will never get refracted
I would recommend using
1.) The Class.forName() SPI logic where you can inject the expected business logic on the fly.
2.) The Spring DI with interfaces and implementations using auto wiring

how to group by columns without projection in hibernate criteria?

i am trying to fetch only max(assetHistoryId) but my below code returing 3 columns max(assetHistoryId), eventId, and assetIdentifier in result.
how to group the columns with out projection using criteria.
you can find my code below.
final Criteria agcriteria = createCriteria(someclass.class);
agcriteria.add(Restrictions.in("eventId", listOfEventIds));
agcriteria.add(Restrictions.ne("action", "T"));
agcriteria.add(
Restrictions.between("modifyDate", lastProcessedTime,
batchStartTime));
agcriteria.setProjection(Projections.projectionList()
.add(Projections.groupProperty("assetIdentifier"))
.add(Projections.groupProperty("eventId"))
.add(Projections.max("assetHistoryId")));
val = agcriteria.list();
please help me any one ?
If i understand you correctly, you want only max(assetHistoryId) without any other column details.
You can try something like this:
Criteria agcriteria = createCriteria(someclass.class);
agcriteria.setProjection(Projections.projectionList()
.add(Projections.max("assetHistoryId")));
You can add restrictions to it, if any... like this: agcriteria.add(Criteria c); or the same set of conditions
agcriteria.add(Restrictions.in("eventId", listOfEventIds));
agcriteria.add(Restrictions.ne("action", "T"));
agcriteria.add(
Restrictions.between("modifyDate", lastProcessedTime,
batchStartTime));
Ok, boys and girls. I know it's a necro and Hibernate Criteria Api was deprecated long ago. But still there are systems which use this API, so hope it will be useful.
I could not find a way to do it with built-in hibernate projections, so I've decided to make my own ones. First of all we will need to create a new projection class which will produce nothing in SELECT clause, but still have it in group clause.
public class NoPropertyGroupProjection extends SimpleProjection {
private String propertyName;
protected NoPropertyGroupProjection(String propertyName) {
this.propertyName = propertyName;
}
#Override
public boolean isGrouped() {
return true;
}
#Override
public Type[] getTypes(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
return new Type[] { };
}
#Override
public String toSqlString(Criteria criteria, int position, CriteriaQuery criteriaQuery) throws HibernateException {
return "";
}
#Override
public String toGroupSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
return StringHelper.join( ", ", criteriaQuery.getColumns( propertyName, criteria ) );
}
#Override
public String toString() {
return propertyName;
}
}
That's a copy of PropertyProjection from the version of Hibernate I have with some changes.
It won't work alone (it is just much complicated to force it work alone), but in most cases we still need something to be selected.
So the next thing we need is to fix ProjectionList as it will break with empty column we're trying to pass it. So, here's the next class. Shame elements list is private, but we have sufficient getters to achieve our goal.
public class ProjectionListWithOnlyGroupBySupport extends ProjectionList {
#Override
public String toSqlString(Criteria criteria, int loc, CriteriaQuery criteriaQuery) throws HibernateException {
final StringBuilder buf = new StringBuilder();
String separator = "";
for ( int i = 0; i < this.getLength(); i++ ) {
Projection projection = this.getProjection(i);
String addition = projection.toSqlString( criteria, loc, criteriaQuery );
if (!"".equals(addition)) {
buf.append(separator).append(addition);
loc += getColumnAliases(loc, criteria, criteriaQuery, projection).length;
separator = ", ";
}
}
return buf.toString();
}
private static String[] getColumnAliases(int loc, Criteria criteria, CriteriaQuery criteriaQuery, Projection projection) {
return projection instanceof EnhancedProjection
? ( (EnhancedProjection) projection ).getColumnAliases( loc, criteria, criteriaQuery )
: projection.getColumnAliases( loc );
}
}
Again, small adjustments for the original class. Now we have everything needed to accomplish our goal. But for convenience we will create one more class.
public final class AdvancedProjections {
public static NoPropertyGroupProjection groupBy(String propertyName) {
return new NoPropertyGroupProjection( propertyName );
}
public static ProjectionList projectionList() {
return new ProjectionListWithOnlyGroupBySupport();
}
}
After we've created all these classes, we can change the code from the question:
final Criteria agcriteria = createCriteria(someclass.class);
agcriteria.add(Restrictions.in("eventId", listOfEventIds));
agcriteria.add(Restrictions.ne("action", "T"));
agcriteria.add(
Restrictions.between("modifyDate", lastProcessedTime,
batchStartTime));
agcriteria.setProjection(AdvancedProjections.projectionList()
.add(Projections.max("assetHistoryId"))
.add(AdvancedProjections.groupBy("assetIdentifier"))
.add(AdvancedProjections.groupBy("eventId")));
val = agcriteria.list();
Voila!

Converting Mongo DBObject to Entity POJO using Reflection

First I want to say that yes - I know there are ORMs like Morphia and Spring Data for MongoDB. I'm not trying to reinvent the weel - just to learn. So basic idea behind my AbstractRepository is to encapsulate logic that's shared between all repositories. Subclasses (repositories for specific entities) passes Entity class to .
Converting entity beans (POJOs) to DBObject using Reflection was pretty streightforward. Problem comes with converting DBObject to entity bean. Reason? I need to convert whatever field type in DBObject to entity bean property type. And this is where I'm stuck. I'm unable to get entity bean class in AbstractRepository method T getEntityFromDBObject(DBObject object)
I could pass entity class to this method but that would defeat the purpose of polymorphism. Another way would be to declare private T type property and then read type using Field. Defining additional property just so I can read doesn't sound right.
So the question is - how would you map DBObject to POJO using reflection using less parameteres possible. Once again this is the idea:
public abstract class AbstractRepository<T> {
T getEntityFromDBObject(DBObject object) {
....
}
}
And specific repository would look like this:
public class EntityRepository extends AbstractRepository<T> {
}
Thanks!
Note: Ignore complex relations and references. Let's say it doesn't need to support references to another DBObjects or POJOs.
You need to build an instance of type T and fill it with the data that comes in ´DBObject´:
public abstract class AbstractRepository<T> {
protected final Class<T> entityClass;
protected AbstractRepository() {
// Don't remember if this reflection stuff throws any exception
// If it does, try-catch and throw RuntimeException
// (or assign null to entityClass)
// Anyways, it's impossible that such exception occurs here
Type t = this.getClass().getGenericSuperclass();
this.entityClass = ((Class<T>)((ParameterizedType)t).getActualTypeArguments()[0]);
}
T getEntityFromDBObject(DBObject object) {
// Use reflection to create an entity instance
// Let's suppose all entities have a public no-args constructor (they should!)
T entity = (T) this.entityClass.getConstructor().newInstance();
// Now fill entity with DBObject's data
// This is the place to fill common fields only, i.e. an ID
// So maybe T could extend some abstract BaseEntity that provides setters for these common fields
// Again, all this reflection stuff needs to be done within a try-catch block because of checked exceptions
// Wrap the original exception in a RuntimeException and throw this one instead
// (or maybe your own specific runtime exception for this case)
// Now let specialized repositories fill specific fields
this.fillSpecificFields(entity, object);
return entity;
}
protected abstract void fillSpecificFields(T entity, DBObject object);
}
If you don't want to implement the method .fillSpecificFields() in every entity's repository, then you'd need to use reflection to set every field (including common ones such as ID, so don't set them manually).
If this is the case, you already have the entity class as a protected attribute, so it's available to every entity's repository. You need to iterate over ALL its fields, including the ones declared in superclasses (I believe you have to use method .getFields() instead of .getDeclaredFields()) and set the values via reflection.
As a side note, I really don't know what data comes in that DBObject instance, and in what format, so please let me know if extracting fields' values from it results to be non trivial.
First I want to apologies for answering to your comments almost two months later. I did managed to figure it out on my own and here is how I've implemented it (and tested) so maybe someone will make a use of it:
public abstract class AbstractRepository<T> {
#Inject
private MongoConnectionProvider provider;
// Keeps current repository collection name
protected String collectionName;
#PostConstruct
public abstract void initialize();
public String getCollectionName() {
return this.collectionName;
}
protected void setCollectionName(String collectionName) {
this.collectionName = collectionName;
}
protected DBCollection getConnection() {
DB conn = this.provider.getConnection();
DBCollection collection = conn.getCollection(this.collectionName);
return collection;
}
private void putFieldToDbObject(T source, DBObject target, Field field) {
// TODO: Think more elegant solution for this
try {
field.setAccessible(true);
// Should we cast String to ObjectId
if (field.getName() == "id" && field.get(source) != null
|| field.isAnnotationPresent(DBRef.class)) {
String key = field.getName().equals("id") ? "_id" : field.getName();
target.put(key, new ObjectId(field.get(source).toString()));
} else {
if(!field.getName().equals("id")) {
target.put(field.getName(), field.get(source));
}
}
} catch (IllegalArgumentException | IllegalAccessException exception) {
// TODO Auto-generated catch block
exception.printStackTrace();
} finally {
field.setAccessible(false);
}
}
#SuppressWarnings("rawtypes")
protected DBObject getDbObject(T entity) {
DBObject result = new BasicDBObject();
// Get entity class
Class entityClass = entity.getClass();
Field[] fields = entityClass.getDeclaredFields();
// Update DBobject with entity data
for (Field field : fields) {
this.putFieldToDbObject(entity, result, field);
}
return result;
}
#SuppressWarnings({ "unchecked", "rawtypes" })
public T getEntityFromDBObject(DBObject object) throws MappingException {
Type superclass = this.getClass().getGenericSuperclass();
Type entityClass = ((ParameterizedType) superclass).getActualTypeArguments()[0];
T entity;
try {
entity = ((Class<T>) entityClass).newInstance();
// Map fields to entity properties
Set<String> keySet = object.keySet();
for(String key : keySet) {
String fieldName = key.equals("_id") ? "id" : key;
Field field = ((Class<T>) entityClass).getDeclaredField(fieldName);
field.setAccessible(true);
if(object.get(key).getClass().getSimpleName().equals("ObjectId")) {
field.set(entity, object.get(key).toString());
} else {
// Get field type
Type fieldType = field.getType();
Object fieldValue = object.get(key);
Class objectType = object.get(key).getClass();
if(!fieldType.equals(objectType)) {
// Let's try to convert source type to destination type
try {
fieldValue = (((Class) fieldType).getConstructor(objectType)).newInstance(object.get(key));
} catch (NoSuchMethodException exception) {
// Let's try to use String as "man-in-the-middle"
String objectValue = object.get(key).toString();
// Get constructor for destination type that take String as parameter
Constructor constructor = ((Class) fieldType).getConstructor(String.class);
fieldValue = constructor.newInstance(objectValue);
}
}
field.set(entity, fieldValue);
}
field.setAccessible(false);
}
} catch (InstantiationException | IllegalAccessException | NoSuchFieldException | SecurityException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException e) {
throw new MappingException(e.getMessage(), MappingExceptionCode.UNKNOWN_ERROR);
}
return entity;
}
public List<T> getAll() {
DBCollection conn = this.getConnection();
DBCursor cursor = conn.find();
List<T> result = new LinkedList<T>();
while (cursor.hasNext()) {
DBObject obj = cursor.next();
try {
result.add(this.getEntityFromDBObject(obj));
} catch (MappingException e) {
}
}
return result;
}
public T getOneById(String id) {
DBObject idRef = new BasicDBObject().append("_id", new ObjectId(id));
DBCollection conn = this.getConnection();
DBObject resultObj = conn.findOne(idRef);
T result = null;
try {
result = this.getEntityFromDBObject(resultObj);
} catch (MappingException e) {
}
return result;
}
public void save(T entity) {
DBObject object = this.getDbObject(entity);
DBCollection collection = this.getConnection();
collection.save(object);
}
}
You've stumbled onto the problem of object mapping. There are a few libraries out there that look to help with this. You might check out ModelMapper (author here).

Hibernate queries call toString() method

I have a list of names: List<Name>, Name has two methods:
toString() returns "Name: Tom"
getName() returns "Tom"
To query the database for persons with certain names, I'd do
// Query a single database entry
Query query = em.createQuery("FROM person WHERE name = :name");
query.setParameter("name", names.get(0).getName());
When I want to query multiple entries (using WHERE ... IN (...) I'd have to do this:
// Convert the list of name instances to a list of strings
List<String> nameStrings = new ArrayList<String>(names.size());
for (Name name : names) {
nameStrings.add(name.getName());
}
// Query multiple database entries
Query query = em.createQuery("FROM person WHERE name in (:name)");
query.setParameter("name", nameStrings); // JPA
query.setParameterList("name", nameStrings); // Hibernate
Do I have to build a second list? I'd rather do this:
// Query a single database entry
Query query = em.createQuery("FROM person WHERE name = :name");
query.setParameter("name", names.get(0));
and
// Query for multiple database entries
Query query = em.createQuery("FROM person WHERE name in (:name)");
query.setParameter("name", names); // JPA
query.setParameterList("name", names); // Hibernate
Short answer is yes, you need to build the second list. You need to provide a List of Strings (or rather a Collection), a List<Name> will never match this. I'd recommend making some kind of utility method that takes a List<Name> and return a List<String> of the names.
public static List<String> toNameStrings(List<Name> names) {
List<String> list = new ArrayList<String>(names.size());
for (Name name : names) {
list.add(name.getName());
}
return list;
}
I haven't been able to test the following, and I'm not sure I want to recommend it in any way, but I think you could make a List implementation that would return different object types depending on the state it's in. Using this you'd be able to do something like list.setStringMode(false) to use it as a List<Name> (though Generics are going out the window at this stage, they'll all be returned as Object), then list.setStringMode(true), to use it as a List<String>. It would look something like the following:
public class NameAndStringList extends ArrayList<Object> implements List<Object>
{
private boolean stringMode = false;
#Override
public boolean add(Object object)
{
return super.add(toName(object));
}
// Do the same for add(index, element)
// Do the same for set(index, element)
// Do the same for remove(object)
#Override
public boolean addAll(Collection<? extends Object> collection)
{
final List<Name> convertedCollection = new ArrayList<Name>();
for (Object object : collection)
{
convertedCollection.add(toName(object));
}
return super.addAll(convertedCollection);
}
// Do the same for addAll(index, collection)
// Do the same for removeAll(index, collection)
// Do the same for retainAll(index, collection)
#Override
public boolean contains(Object o)
{
return super.contains(toName(o));
}
// Do the same for containsAll(collection)
// Do the same for indexOf(object)
// Implement Iterator that checks the stringMode variable before returning value.
// Override all iterator methods to retrieve custom Iterator implementation.
// Override subList(fromIndex, toIndex) to make subList be an instance of NameAndStringList as well.
#Override
public Object get(int index)
{
if (stringMode)
{
return ((Name) super.get(index)).getName();
}
return super.get(index);
}
// Implement setStringMode(boolean)
protected Object toNameString(Object object)
{
if (object instanceof Name)
{
// Convert to String here
}
return object;
}
protected Name toName(Object object)
{
if (object instanceof String)
{
// Convert to Name here.
}
return object;
}
}
Note that this relies on you being able to convert a String to a Name as well as vice-versa, but you could always get rid of that bit if you know you'll always populate it yourself using Name instances. The general idea here is that the List stores Name instances, but is free to return String instances since it's a List<Object>. Again, I'm not sure I'd recommend this approach, but it should meet your requirements, or at least as close as I can get atm.

Categories