Is possibility using Spring Data MongoDb to define custom Criteria? - java

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;
}

Related

How to get back a DTO Object from a procedure

I need to execute a procedure on my sql server database that will return me some fields and I wish to transform this fields directly in a List of my DTO Object that will be returned, but i'm new on spring boot and can't get it to work. I tried to do a Converter class but didnt understand much of how it works e probally did it wrong, here is my code on a way i wish it work:
public interface IMyDtoRepository extends JpaRepository<SomeEntity, Long> {
#Query(value = "EXECUTE MyProcedure :param1, :param2, :param3, :param4, :param5)")
public List<MyDtoObject> execMyProcedure(#Param(value = "param1") Integer param1,
#Param(value = "param2") String param2,
#Param(value = "param3") String param3,
#Param(value = "param4") String param4,
#Param(value = "param5") Integer param5);
}
The DtoObject
public class MyDtoObject{
// My Declared Fields...
public MyDtoObject() {
}
public MyDtoObject(/* My Fields */) {
// Setting fields
}
public MyDtoObject(Object[] objects) {
// Setting fields
}
// Getters n Setters...
I omitted the information that i didn't think it was necessary but i can give more explanation if need it
to map the result on your DtoObject with spring-data-jpa your can use : #SqlResultSetMapping
javadoc here
I have a similar method that I use in my DAL. It uses reflection and generics to convert a datatable to whatever type you pass in. Just pass in the datatable you get as a result of your procedure and you're good to go.
public List<T> ConvertDataToTypeList<T>(System.Data.DataTable DataTable) where T : class, new()
{
try
{
System.Type t_Object_Type = typeof(T);
ICollection<PropertyInfo> p_Properties;
lock (Properties_Dictionary)
{
if (!Properties_Dictionary.TryGetValue(t_Object_Type, out p_Properties))
{
p_Properties = t_Object_Type.GetProperties().Where(property => property.CanWrite).ToList();
Properties_Dictionary.Add(t_Object_Type, p_Properties);
}
}
System.Collections.Generic.List<T> l_List = new List<T>(DataTable.Rows.Count);
foreach (var v_Row in DataTable.AsEnumerable())
{
T o_Object = new T();
foreach (var prop in p_Properties)
{
var propType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
var safeValue = v_Row[prop.Name] == null ? null : Convert.ChangeType(v_Row[prop.Name], propType);
prop.SetValue(o_Object, safeValue, null);
}
l_List.Add(o_Object);
}
return l_List;
}
catch
{
return new List<T>();
}
}

Getting column names from a JPA Native Query

I have an administrative console in my web application that allows an admin to perform a custom SQL SELECT query on our database.
Underneath, the application is using Hibernate, but these queries are not HQL, they're pure SQL, so I'm using a Native Query like this:
protected EntityManager em;
public List<Object[]> execute(String query) {
Query q = em.createNativeQuery(query);
List<Object[]> result = q.getResultList();
return result;
}
This works correctly, but it only returns the rows of data, with no extra information. What I would like is to also get the column names, so when I print the results back to the user I can also print a header to show what the various columns are.
Is there any way to do this?
Query query = entityManager.createNamedQuery(namedQuery);
NativeQueryImpl nativeQuery = (NativeQueryImpl) query;
nativeQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
List<Map<String,Object>> result = nativeQuery.getResultList();
And now you have Map<String,Object> . You can see your Column Names
2020
With hibernate 5.2.11.Final is actually pretty easy.
In my example you can see how I get the column names for every row. And how I get values by column name.
Query q = em.createNativeQuery("SELECT columnA, columnB FROM table");
List<Tuple> result = q.getResultList();
for (Tuple row: result){
// Get Column Names
List<TupleElement<Object>> elements = row.getElements();
for (TupleElement<Object> element : elements ) {
System.out.println(element.getAlias());
}
// Get Objects by Column Name
Object columnA;
Object columnB;
try {
columnA = row.get("columnA");
columnB= row.get("columnB");
} catch (IllegalArgumentException e) {
System.out.println("A column was not found");
}
}
This code worked for me
DTO Class :
public class ItemResponse<T> {
private T item;
public ItemResponse() {
}
public ItemResponse(T item) {
super();
this.item = item;
}
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
}
Service Class is in the below
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.springframework.stereotype.Service;
import org.hibernate.transform.AliasToEntityMapResultTransformer;
#Service
public class ServiceClass{
#PersistenceContext
public EntityManager entityManager;
public ItemResponse exceuteQueryResponse(String queryString) {
ItemResponse itemResponse=new ItemResponse();
Query jpaQuery = entityManager.createNativeQuery(queryString);
org.hibernate.Query hibernateQuery =((org.hibernate.jpa.HibernateQuery)jpaQuery).getHibernateQuery();
hibernateQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
List<Map<String,Object>> res = hibernateQuery.list();
itemResponse.setItem(res);
return itemResponse;
}
}
Ryiad's answer DTO adds some confusion, you should have kept it away.
You should have explained that it works only with hibernate.
If like me you needs to keep the order of columns, you can specify your own transformer. i copied the code from hibernate and changed the HashMap to LinkedHashMap:
import java.util.LinkedHashMap;
import java.util.Map;
import org.hibernate.transform.AliasedTupleSubsetResultTransformer;
import org.hibernate.transform.ResultTransformer;
/**
* {#link ResultTransformer} implementation which builds a map for each "row", made up of each aliased value where the
* alias is the map key. Inspired by {#link org.hibernate.transform.AliasToEntityMapResultTransformer}, but kepping the
* ordering of elements.
* <p/>
* Since this transformer is stateless, all instances would be considered equal. So for optimization purposes we limit
* it to a single, singleton {#link #INSTANCE instance}.
*/
public class AliasToEntityMapResultTransformer extends AliasedTupleSubsetResultTransformer {
public static final AliasToEntityMapResultTransformer INSTANCE = new AliasToEntityMapResultTransformer();
/**
* Disallow instantiation of AliasToEntityMapResultTransformer.
*/
private AliasToEntityMapResultTransformer() {
}
#Override
public Object transformTuple(Object[] tuple, String[] aliases) {
Map result = new LinkedHashMap<>(tuple.length);
for (int i = 0; i < tuple.length; i++) {
String alias = aliases[i];
if (alias != null) {
result.put(alias, tuple[i]);
}
}
return result;
}
#Override
public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) {
return false;
}
/**
* Serialization hook for ensuring singleton uniqueing.
*
* #return The singleton instance : {#link #INSTANCE}
*/
private Object readResolve() {
return INSTANCE;
}
}
With this transformer you can used Ryiad's solution with Hibernate:
Query jpaQuery = entityManager.createNativeQuery(queryString);
org.hibernate.Query hibernateQuery =((org.hibernate.jpa.HibernateQuery)jpaQuery).getHibernateQuery();
hibernateQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
List<Map<String,Object>> res = hibernateQuery.list();
After a long time without any answer, And based on my own further research, It seems that it can not be possible, Unfortunately.
If the JPA provider does not support the retrieval of query metadata, another solution could be the use of a SQL parser like JSQLParser, ZQL or General SQL Parser (comercial), which extracts the fields from the SELECT statement.
cast query to hibernate query, then use hibernate method
//normal use, javax.persistence.Query interface
Query dbQuery = entityManager.createNativeQuery(sql);
//cast to hibernate query
org.hibernate.Query hibernateQuery =((org.hibernate.jpa.HibernateQuery)dbQuery)
.getHibernateQuery();
hibernateQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
List<Map<String,Object>> res = hibernateQuery.list();
List<TxTestModel> txTestModels = new ArrayList<>();
res.forEach(e->{
TxTestModel txTestModel = new ObjectMapper().convertValue(e, TxTestModel.class);
// txTestModels.add(new TxTestModel().setIdd((Integer) e.get("idd")).setMmm((String) e.get("mmm")).setDdd((Date) e.get("ddd")));
txTestModels.add(txTestModel);
});
System.out.println(txTestModels.size());
To enforce em.createNativeQuery(..).getResultList() to return List<Tuple> specify it with Tuple.class when creating native queries :
Query q = em.createNativeQuery("SELECT columnA, columnB FROM table", Tuple.class );
List<Tuple> result = q.getResultList();
for (Tuple row: result){
// Get Column Names
List<TupleElement<Object>> elements = row.getElements();
for (TupleElement<Object> element : elements ) {
System.out.println(element.getAlias());
}
// Get Objects by Column Name
Object columnA;
Object columnB;
try {
columnA = row.get("columnA");
columnB= row.get("columnB");
} catch (IllegalArgumentException e) {
System.out.println("A column was not found");
}
}
This worked for me:
final Query emQuery = em.createNativeQuery(query, Tuple.class);
final List<Tuple> queryRows = emQuery.getResultList();
final List<Map<String, Object>> formattedRows = new ArrayList<>();
queryRows.forEach(row -> {
final Map<String, Object> formattedRow = new HashMap<>();
row.getElements().forEach(column -> {
final String columnName = column.getAlias();
final Object columnValue = row.get(column);
formattedRow.put(columnName, columnValue);
});
formattedRows.add(formattedRow);
});
return formattedRows;
This is the working solution
Below example return the objectlist from the query.
Looping the same and from the first object cast it to hasmap, and hashmap.keyset will give you all the coulmn names in a set.
List dataList = session.createSQLQuery("SLECT * FROM EMPLOYEETABLE").setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP).list();
for (Object obj : dataList) {
HashMap<String, Object> hashMap = (HashMap<String, Object>) obj;
Set<String> keySet = hashMap.keySet();
break;
}
I also faced a similar problem working with JPA. There is no direct way in JPA to access the resultset metadata. The solution can be extracting column names from the query itself or use JDBC to get the metadata.

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!

Merge two very similar methods

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());
}

Categories