Convert methods to a generic method in DAO layer - java

Currently I'm having some methods in my DAO layer with multiple select queries. What I was thinking is to have a generic method for all these three methods so that it can be used for further also. Here are my methods.
public List<Customer> findAll(){
String sql = "SELECT * FROM CUSTOMER";
List<Customer> customers = getJdbcTemplate().query(sql,
new BeanPropertyRowMapper(Customer.class));
return customers;
}
For finding phone numbers of a customer.
public List<Phone> findPhoneNumbers(int custId){
String sql = "SELECT * FROM PHONE WHERE CUST_ID="+custId;
List<Phone> phoneNumbers = getJdbcTemplate().query(sql,
new BeanPropertyRowMapper(Phone.class));
return phoneNumbers;
}
and so on.
Can these methods can be converted in a single generic method, so that it can be called from my service layer. Any suggestions or ideas would be greatly appreciated.

You can use a generic method and let the caller specify the class.
It's easy for findAll
public <T> List<T> findAll(Class<T> entityClass, String tableName){
String sql = "SELECT * FROM " + tableName;
return getJdbcTemplate().query(sql,
new BeanPropertyRowMapper(entityClass));
return phoneNumbers;
}
You can further improve this by using class metadata, such as specifying an annotations:
#Target(ElementType.Type)
public #interface MappedTable {
String tableName();
}
//apply the annotation to bean classes:
#MappedTable(tableName="CUSTOMERS")
public class Customer {}
Which will allow your findAll method to look like:
public <T> List<T> findAll(Class<T> entityClass){
String sql = "SELECT * FROM " + entityClass
.getAnnotation(MappedTable.class).tableName();
return getJdbcTemplate().query(sql,
new BeanPropertyRowMapper(entityClass));
return phoneNumbers;
}
Implementing findByXyZ will be trickier, but you can use a similar approach that either takes the full query from the caller or that uses additional annotation-based metadata.
Note that the complexity of this kind of code grows very fast. That's why it's a good idea to consider using ORM tools instead of reinventing them (the above is just an idea for simple cases as the one in the question).

public <T> List<T> findAll(String sql, Class<T> clazz) {
return getJdbcTemplate().query(sql, new BeanPropertyRowMapper(clazz));
}
Then you call
String sql = "SELECT * FROM CUSTOMER";
List<Customer> customers = findAll(sql, Customer.class);
Or
String sql = "SELECT * FROM PHONE WHERE CUST_ID="+custId;
List<Phone> phoneNumbers = findAll(sql, Phone.class);
The problem is that you have to construct sql statement before calling the method.
Maybe you want an android sqldatabase-like interface and construct sql statement in the method.
public <T> List<T> findAll(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, Class<T> clazz) {
String sql = // construct sql statement
return getJdbcTemplate().query(sql, new BeanPropertyRowMapper(clazz));
}
I'm not familiar with sql statement, you may find something in the Android SDK SQLiteQueryBuilder.buildQueryString(...).
If you really want to return a single item not a list with one item, you need another method.
public <T> T findOne(String sql, Class<T> clazz) {
// your sql statement should contains something like "limit 1"
List<T> result = findAll(sql, clazz);
return result.isEmpty() ? null : result.get(0);
}
If you need one method but you require to return List<T> for multiple data and T for single data. Try this:
public <T> Object findAll(String sql, Class<T> clazz, bool one) {
List<T> all = getJdbcTemplate().query(sql, new BeanPropertyRowMapper(clazz));
return one ? all.get(0) : all;
}
But I don't really suggest this approach. I don't think it is necessary to return a single item rather than a list with a single item.

You can use inheritance to achieve this
1) class Customer
2)class PhoneNumber extends Customer
3) class Address extends Customer
4) Declaring methods as
public List<T> findPhoneNumbers(Customer customer){
if(customer instanceOf PhoneNumber) {sql ="get phone number query ...."}
else if(customer instanceOf Address) {sql ="get address query ...."}
else {sql = get customer query}
}
Now in the calling method(ServiceLayer), you can pass appropriate object to choose query and get result

Related

How to make hibernate query( which are used for same purpose) generic?

Currently I am working on Spring Boot + Hibernate and I mostly end up writing repetitive hibernate query every time which are doing same kinda job, only difference is that the entity class different.
Example: here, I am retrieving a list from three different tables improvement_area, development_area and suggested_area. Below are the respective model class.
public List<ImprovementArea> getImprovementAreaList(MainPlanSheet planSheet) {
return HibernateUtils.getSession(sessionFactory)
.createCriteria(ImprovementArea.class).add(Restrictions.eq("planSheet", planSheet))
.list();
}
public List<DevelopmentArea> getDevlopmentAreaList(MainPlanSheet planSheet) {
return HibernateUtils.getSession(sessionFactory)
.createCriteria(DevelopmentArea.class).add(Restrictions.eq("planSheet", planSheet))
.list();
}
public List<SuggestedArea> getSelectedSuggestedGoal(MainPlanSheet planSheet) {
return HibernateUtils.getSession(sessionFactory)
.createCriteria(SuggestedArea.class).add(Restrictions.eq("planSheet", planSheet))
.list();
}
as you could see all the three query is doing the same job, retrieving data from the respective model class.
Can someone please help me or suggest a better approach to make it generic?
Now another query added which accept Integer as parameter:
public List<OnBoardedEmployee> getOnBoardedEmployees(Integer empId) {
return HibernateUtils.getSession(sessionFactory)
.createCriteria(OnBoardedEmployee.class)
.add(Restrictions.eq("emId", empId))
.list();
}
In this scenario how can I make one generic Instead of four different query?
You can simply pass the class as an argument:
public <T> List<T> getDevlopmentByEntity(MainPlanSheet planSheet, Class<T> entity) {
return HibernateUtils.getSession(sessionFactory)
.createCriteria(entity)
.add(Restrictions.eq("planSheet", planSheet))
.list();
}
And invoke it like:
List<DevelopmentArea> developmentAreas = getSelectedSuggestedGoal(planSheet, DevelopmentArea.class);
Edit: the last snippet can be rewritten as:
public <T> List<T> getDevlopmentByEntity(String param, Object value, Class<T> entity) {
return HibernateUtils.getSession(sessionFactory)
.createCriteria(entity)
.add(Restrictions.eq(param, value))
.list();
}

How do I query for a Set<MyObject> using DBUtils?

Looking over the DBUtils API docs, I cannot see if it's possible to query for a Set.
Which implementation of ResultSetHandler i should use for query Set of objects?
I don't think there is a default implementation for Set. You can create a generalized handler for Set as shown below.
public class SetHandler<T> implements ResultSetHandler<Set<T>> {
private final RowProcessor rp = new BasicRowProcessor();
private final Class<T> type;
public SetHandler(Class<T> type) {
this.type = type;
}
#Override
public Set<T> handle(ResultSet rs) throws SQLException {
Set<T> set = new HashSet<>();
while (rs.next()) {
set.add((T) this.rp.toBean(rs,type));
}
return set;
}
}
One down side is that toBean method tries to find ResulSet Column-Bean Property mapping for every row in the ResultSet where as toBeanListmethod(used by BeanListHandler) find this mapping only once per list.
There is a BeanMapHandler which returns HashMap and it internally uses toBean method and hence I think for Sets/Maps we have to rely on toBean method or write a custom RowProcessor.

How to assign all the '?' with object's data in JDBC template?

I am new in spring boot.
I have created on method in spring boot, please see the below function:
public ArrayList<ShipmentDetailsVO> getShipmentStatus(
ShipmentDetailsVO shpmntpert) {
return jdbcTemplate.query("select * from SELECT_SEARCH_DETAILS(?,?,?,?,?,?,?,?)",new ResultSetExtractor<ArrayList<ShipmentDetailsVO>>(){
#Override
public ArrayList<ShipmentDetailsVO> extractData(ResultSet rs) throws SQLException,
DataAccessException {
shipmentDao = new ArrayList<ShipmentDetailsVO>();
while(rs.next()) {
shipmentDetDaoObj =new ShipmentDetailsVO();
shipmentDetDaoObj.setContractNumber(rs.getString(1));
System.out.println("hello" + rs.getString(1));
shipmentDetDaoObj.setOrderNumber(rs.getString(2));
System.out.println(rs.getString(2));
shipmentDetDaoObj.setShipmentNumber(rs.getString(3));
shipmentDetDaoObj.setShipmentControlNo(rs.getString(4));
shipmentDetDaoObj.setStatusCode(rs.getString(5));
shipmentDetDaoObj.setStatusDateStr(rs.getString(6));
shipmentDetDaoObj.setLastUpdatedtStr(rs.getString(7));
shipmentDetDaoObj.setResendFlag(false);
shipmentDetDaoObj.setSourceSystem(rs.getString(8));
shipmentDetDaoObj.setDestinationSystem(rs.getString(9));
shipmentDetDaoObj.setRfid(rs.getString(10));
shipmentDetDaoObj.setUid(rs.getString(11));
shipmentDetDaoObj.setShipmentSeqId(rs.getString(12));
shipmentDao.add(shipmentDetDaoObj);
}
return shipmentDao;
}
});
}
I don't have any idea how to assign all the '?' with object's(shpmntpert) data. Can any one kindly help on the same.
From JdbcTemplate documentation
You can find one of the query the signature
public <T> T query(String sql, Object[] args, ResultSetExtractor<T> rse)
throws DataAccessException
With an explanation of the interesting parameters
sql - SQL query to execute
args - arguments to bind to the query
So you can see that the array is used to bing the values. That means that for a query like
select * from table where name = ? and weight= ?
you need to generate an array with those values in the correct order
new Object[]{name, weight}
An other signature show you that they provide some setter for PreparedStatement named PreparedStatementSetter.
public <T> T query(String sql,
PreparedStatementSetter pss,
ResultSetExtractor<T> rse)
throws DataAccessException
Where the PreparedStatementSetter interface have only one methods to implements
void setValues(PreparedStatement ps)
That will let you set the values like you want fron this setter. You could pass the instance to an implementation of this interface or directly in the code like
final MyInstance m = new MyInstance();
jdbcTemplate.query("select * from table where name = ? and weight= ?",
new PreparedStatementSetter(){
#Override
public void setValues(PreparedStatement ps){
ps.setString(1, m.getName);
ps.setInt(2, m.getWeight);
}
}, myResultSetExtractor);
Note : This is a quick reading of the documentation knowing that I never used this API. But from what I have read, this should work just fine.

genericdaohibernate add getbyfield other than id

I am using org.appfuse.dao.hibernatepackage and I have used all the method in the GenericDaoHibernate<T,PK> class.
I found these methods
public List<T> getAll();
public List<T> getAllDistinct();
public List<T> search(String searchTerm);
public T get(PK id);
public boolean exists(PK id);
public T save(T object);
public void remove(T object);
public void remove(PK id);
public List<T> findByNamedQuery(String queryName, Map<String, Object> queryParams);
public void reindex();
public void reindexAll(boolean async);
I have some model classes, services and methods.
Now I want to get list of object using some other fieled in the model class other than id(I have some common fields in many model classes).
I need to write similar methods in all the services and daos. So i was thinking is it possible to create a common method in generic dao.
The following I tried, but it didn't work.
public T getbyClientKey(Long clientkey) {
Session sess = getSession();
IdentifierLoadAccess byId = sess.byId(persistentClass);
List<T> entity = (List<T>) byId.load(clientkey);
if (entity == null) {
log.warn("Uh oh, '" + this.persistentClass + "' object with client '" + clientkey + "' not found...");
throw new ObjectRetrievalFailureException(this.persistentClass, clientkey);
}
return entity;
}
I knew this will be error. and it showed TypeCastingException, because return type of byId.load(id) is object only, not List.
So how can I create a method like that?
If so, I think I can create method for remove() also(But that's not necessary for me now, may be in future).
The Javadoc for IdentifierLoadAccess is pretty clear in how the load method should behave:
Return the persistent instance with the given identifier, or null if there is no such persistent instance.
This means it should return just one object, not a List of objects. Try casting it to T instead.
If you want to query your entity (that is, retrieve items by any other means than primary key), you most likely want to implement the search(String) method.
If you want to query your entity (that is, retrieve items by any other means than primary key), take a look at the UserDaoHibernate that is shipped with AppFuse. It contains a method loadUserByUsername() which is implemented like this:
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List users = getSession().createCriteria(User.class).add(Restrictions.eq("username", username)).list();
if (users == null || users.isEmpty()) {
throw new UsernameNotFoundException("user '" + username + "' not found...");
} else {
return (UserDetails) users.get(0);
}
}
Obviously, if you want to return all items, it should be modified a bit (this one is made up):
public List<UserDetails> loadLockedUsers() {
List<UserDetails> users = (List<UserDetails>) getSession().createCriteria(User.class).add(Restrictions.eq("account_locked", true)).list();
return users;
}

JPA - FindByExample

Does anyone have a good example for how to do a findByExample in JPA that will work within a generic DAO via reflection for any entity type? I know I can do it via my provider (Hibernate), but I don't want to break with neutrality...
Seems like the criteria API might be the way to go....but I am not sure how to handle the reflection part of it.
Actually, Query By Example (QBE) has been considered for inclusion in the JPA 2.0 specification but is not included, even if major vendors support it. Quoting Mike Keith:
I'm sorry to say that we didn't actually get to do QBE in JPA 2.0. Criteria API does not have any special operators for it so entity equality is just like in JP QL, based on PK value. Sorry, but hopefully we'll be more successful on that front in the next go-round. For now it is one of those vendor features that every vendor supports, but is not in the spec yet.
Just in case, I've added (non generic) sample code for the major vendors below for documentation purposes.
EclipseLink
Here is a sample of using QBE in the EclipseLink JPA 2.0 reference implementation:
// Create a native EclipseLink query using QBE policy
QueryByExamplePolicy policy = new QueryByExamplePolicy();
policy.excludeDefaultPrimitiveValues();
ReadObjectQuery q = new ReadObjectQuery(sampleEmployee, policy);
// Wrap the native query in a standard JPA Query and execute it
Query query = JpaHelper.createQuery(q, em);
return query.getSingleResult();
OpenJPA
OpenJPA supports this style of query through its extended OpenJPAQueryBuilder interface:
CriteriaQuery<Employee> q = cb.createQuery(Employee.class);
Employee example = new Employee();
example.setSalary(10000);
example.setRating(1);
q.where(cb.qbe(q.from(Employee.class), example);
Hibernate
And with Hibernate's Criteria API:
// get the native hibernate session
Session session = (Session) getEntityManager().getDelegate();
// create an example from our customer, exclude all zero valued numeric properties
Example customerExample = Example.create(customer).excludeZeroes();
// create criteria based on the customer example
Criteria criteria = session.createCriteria(Customer.class).add(customerExample);
// perform the query
criteria.list();
Now, while it should be possible to implement something approaching in a vendor neutral way with JPA 2.0 Criteria API and reflection, I really wonder if it's worth the effort. I mean, if you make any of the above snippets generic and put the code in a DAO method, it would be quite easy to switch from one vendor to another if the need should arise. I agree it's not ideal, but still.
References
What about findByExample in JPA book?
Dynamic, typesafe queries in JPA 2.0
This is quite crude and i'm not convinced it's a good idea in the first place. But anyway, let's try to implement QBE with the JPA-2.0 criteria API.
Start with defining an interface Persistable:
public interface Persistable {
public <T extends Persistable> Class<T> getPersistableClass();
}
The getPersistableClass() method is in there because the DAO will need the class, and i couldn't find a better way to say T.getClass() later on. Your model classes will implement Persistable:
public class Foo implements Persistable {
private String name;
private Integer payload;
#SuppressWarnings("unchecked")
#Override
public <T extends Persistable> Class<T> getPersistableClass() {
return (Class<T>) getClass();
}
}
Then your DAO can have a findByExample(Persistable example) method (EDITED):
public class CustomDao {
#PersistenceContext
private EntityManager em;
public <T extends Persistable> List<T> findByExample(T example) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException {
Class<T> clazz = example.getPersistableClass();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<T> cq = cb.createQuery(clazz);
Root<T> r = cq.from(clazz);
Predicate p = cb.conjunction();
Metamodel mm = em.getMetamodel();
EntityType<T> et = mm.entity(clazz);
Set<Attribute<? super T, ?>> attrs = et.getAttributes();
for (Attribute<? super T, ?> a: attrs) {
String name = a.getName();
String javaName = a.getJavaMember().getName();
String getter = "get" + javaName.substring(0,1).toUpperCase() + javaName.substring(1);
Method m = cl.getMethod(getter, (Class<?>[]) null);
if (m.invoke(example, (Object[]) null) != null)
p = cb.and(p, cb.equal(r.get(name), m.invoke(example, (Object[]) null)));
}
cq.select(r).where(p);
TypedQuery<T> query = em.createQuery(cq);
return query.getResultList();
}
This is quite ugly. It assumes getter methods can be derived from field names (this is probably safe, as example should be a Java Bean), does string manipulation in the loop, and might throw a bunch of exceptions. Most of the clunkiness in this method revolves around the fact that we're reinventing the wheel. Maybe there's a better way to reinvent the wheel, but maybe that's where we should concede defeat and resort to one of the methods listed by Pascal above. For Hibernate, this would simplify the Interface to:
public interface Persistable {}
and the DAO method loses almost all of its weight and clunkiness:
#SuppressWarnings("unchecked")
public <T extends Persistable> List<T> findByExample(T example) {
Session session = (Session) em.getDelegate();
Example ex = Example.create(example);
Criteria c = session.createCriteria(example.getClass()).add(ex);
return c.list();
}
EDIT: Then the following test should succeed:
#Test
#Transactional
public void testFindFoo() {
em.persist(new Foo("one",1));
em.persist(new Foo("two",2));
Foo foo = new Foo();
foo.setName("one");
List<Foo> l = dao.findByExample(foo);
Assert.assertNotNull(l);
Assert.assertEquals(1, l.size());
Foo bar = l.get(0);
Assert.assertNotNull(bar);
Assert.assertEquals(Integer.valueOf(1), bar.getPayload());
}
You should check the solution proposed by Springfuse using Spring Data & JPA 2.
http://www.springfuse.com/2012/01/31/query-by-example-spring-data-jpa.html
Some sample source code here (under repository sub package):
https://github.com/jaxio/generated-projects
Found this project: https://github.com/jaxio/jpa-query-by-example
https://github.com/superbiger/sbiger-jpa-qbe
I'think query by example with single table like mybatis is easy to use
base on jpa we can also support Join/GroupBy like this:
/*
SQL:
select * from
user
where
id=1
or id=2
group by
id,
name
order by
id asc,
name asc
limit ?
*/
public List<User> findAll(){
Example<User> example = ExampleBuilder.create();
example.or()
.andEqual("id", 1)
.orEqual("id", 2);
example.groupBy("id","name");
example.asc("id","name");
return userReponsitory.findAll(example, new PageRequest(0, 1));
}
Features now:
Support and/or logic operation
Support is(Empty/Boolean/Null)
Support Equal/NotEqual/In/NotIn/Like/NotLike
Support gt/ge/lt/le/between
Support join query
Support group by
Support custom specification.
Support pagination
more features coming soon……
Criteria API is your best bet. You'll need a JPA-2.0 provider for that, though. So if you have an entity like this:
#Entity
public class Foo {
#Size(max = 20)
private String name;
}
The following unit test should succeed (i tested it with EclipseLink, but it should work with any of the JPA-2.0 providers):
#PersistenceContext
private EntityManager em;
#Test
#Transactional
public void testFoo(){
Foo foo = new Foo();
foo.setName("one");
em.persist(foo);
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Foo> c = cb.createQuery(Foo.class);
Root<Foo> f = c.from(Foo.class);
c.select(f).where(cb.equal(f.get("name"), "one"));
TypedQuery<Foo> query = em.createQuery(c);
Foo bar = query.getSingleResult();
Assert.assertEquals("one", bar.getName());
}
Also, you might want to follow the link to the tutorial referenced here.
you can use this https://github.com/xiaod0510/jpa-findbyexample
if your entity is Contact:
#Entity
public class Contact {
#Id
#GeneratedValue
private Long id;
#Column
private String name;
#Column
private Date birthday;
//Getter and Setter
}
public interface ContactRepository
extends
JpaSpecificationExecutor<Contact> {
}
just create your own Example like this:
public class ContactExample extends BaseExample<ContactExample, Contact> {
public final Attr<Long> id = new Attr<Long>("id");
public final Attr<String> name = new Attr<String>("name");
public final Attr<Date> birthday = new Attr<Date>("birthday");
//default builder
public static ContactExample where() {
ContactExample example = new ContactExample();
example.operatorType = OperatorType.and;
return example;
}
}
and now you can query by example :
ContactRepository.findOne(ContactExample
.where()//default is and
.id.eq(1l)
);
the example implements the interface "Specification",more information on that github
Maybe the answer is too late. But check this. It might be of help.
https://sourceforge.net/projects/simplejpaquery/
First, include the jar into the classpath. You will have a class called com.afifi.simpleJPAQuery.entities.utility.JPAUtil.
This class uses reflection to deduct the query from the bean.
Suppose you have an entity bean as follows:
#Entity
public class Person {
#Id
private Integer personNo;
private String personName;
public Integer getPersonNo() {
return personNo;
}
public void setPersonNo(Integer personNo) {
this.personNo = personNo;
}
public String getPersonName() {
return personName;
}
public void setPersonName(String personName) {
this.personName = personName;
}
}
Then if you want to query by person name for instance, you need to do as follows:
//initiate entity manager (em)
Person p=new Person();
p.setPersonName("John");
String sortString="";
List<Person> result= JPAUtil.findByExample(em,p,sortString);
The result will get all the records where the person name contained the word "John".
if you want to limit the results, you can do something like:
List<Person> result= JPAUtil.findByExample(em, p, sortString, start, size);
This library has other methods like:
getResultCount: to get the count of the result
createSqlStatement: to get the sql statement that is being used
getSqlWhereString: to get just the where string used
It has the native forms of these functions:
findByExampleNative, getResultCountNative, createSqlStatementNative and getSqlWhereStringNative
The library also has QueryAnnotations class that contains annotations that can be added to the Entity bean properties to give more control on how you want to query using the bean.

Categories