Friday, July 20, 2012

Adding Hibernate native SQL features into your Spring Data Repository

JPA provides @NamedNativeQuery for you to use native SQL. However, the usage is not so convenient, especially when you need to map multiple entities in your native SQL. You have to define a set of SqlResultSetMapping mapping which is quite error prone.

For those who have used Hibernate native SQL features before, you will find that it is much more easier to use than JPA's @NamedNativeQuery. In recent projects, I am using Spring Data JPA. I have added hibernate native query features to my Spring Data base repostory. You can now perform native query in JPA without SqlResultSetMapping.

1. Add you cusomt annotation @NativeQueries and @NativeQuery

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface NativeQueries {
 NativeQuery[] queries() default  {};
}

@Retention(RetentionPolicy.RUNTIME)
public @interface NativeQuery {
 String name()  default "";
 String sql()  default "";
}

2. Add the method "queryNatively" in base Spring Data Repository.

If you do not know how to add custom behaviour to your Spring data JPA base repository, please see my previous post for how to customize your Spring data JPA base repository for detail. You can see in previous post that I intentionally expose the repository interface (i.e. the springDataRepositoryInterface property) in the GenericRepositoryImpl. This small tricks enable me to access the annotation in the repository interface easily.

public List queryNatively(String nativeQueryName, LinkedHashMap<String,Class<?>> inEntityClasses, Map inParams ){
 SQLQuery query =  this.createHibernateNativeQuery( nativeQueryName,  inParams );
     //add entities
     if (inEntityClasses!=null) {      
      for (Object key: inEntityClasses.keySet()) {
    String entityClassAlias = key.toString();
    Class<?> entityClass = (Class<?>)inEntityClasses.get(key);  
    query.addEntity(entityClassAlias,entityClass);
         }    
     }
             
     //add parameter
     if (inParams != null){
      for (Object key: inParams.keySet()) {
    String queryParamName = key.toString();
    Object queryParamValue = inParams.get(key);
    query.setParameter(queryParamName, queryParamValue);
         }
     }  
     return (query!=null)? query.list() : null ;
  }
    

  private SQLQuery createHibernateNativeQuery (String nativeQueryName, Map inParams ){
     if (GenericRepository.class.isAssignableFrom(getSpringDataRepositoryInterface())) {
      
      Annotation nativeQueryAnn = getSpringDataRepositoryInterface().getAnnotation(NativeQueries.class);
      if(nativeQueryAnn != null){
       NativeQueries nativeQueries = (NativeQueries)nativeQueryAnn;
       NativeQuery[] queries  = nativeQueries.queries();
    for (NativeQuery sqlquery : queries) { 
          
     if (StringUtils.equals(nativeQueryName, sqlquery.name())) {
      String sql  = sqlquery.sql();
      
      Session hiernateSess = em.unwrap(Session.class);
         SQLQuery query = hiernateSess.createSQLQuery(sql);
         
         //add parameter
         if (inParams != null){
          for (Object key: inParams.keySet()) {
        String queryParamName = key.toString();
        Object queryParamValue = inParams.get(key);
        query.setParameter(queryParamName, queryParamValue);
             }
         }  
         
         return query;
     }    
    }
      }
     
     }
     return null;
  }


3. Example Usage

In your repositry interface, define which native SQL queries you want to use through your @NativeQueries and @NativeQuery annotation. The usage is just similar to calling hibernate native query features. You could just see the hibernate alias and property references.

@NativeQueries (
 queries = {
   @NativeQuery(name="query1", 
         sql="SELECT {cat.*}, {mother.*}  FROM CATS c, CATS m WHERE c.MOTHER_ID = c.ID and c.name =  :catName "),
   @NativeQuery(name="query2", 
         sql="SELECT {cat.*} FROM CATS where c.ID =  :catName")       
 }
)
public interface CatRepository extends GenericRepository<Cat, Long> {
}

In your service or business class that inject your repository, you could just simply call the queryNatively() method to perform native SQL query.

@Service
public class CatService {
 
 @Inject
 private CatRepository catRepository;
 
 public List<Cat> searchCat( String catName) {
    
  List<Cat> catList;
 
  // Add entity mapping for your query
  HashMap<String, Object> inParams = new HashMap<String, Object>();
  inParams.put("catName", "Felix");
  
  
  // Prepare parameters for your native sql
  LinkedHashMap<String, Object> entityMap = new LinkedHashMap<String, Object>();
  entityMap.put("cat", Cat.class);
  entityMap.put("mother",Mother.class);
 

 
  catList = catRepository.queryNatively(
    "query1", "",entityParam);
 
  return catList;
 }
}

Monday, July 16, 2012

Inject SLF4J logger by annotation

Actually, the following class is not written by me. It is written my colleage. I know he is referencing from another post from Java Geeks. I found this is quite handy so I put it here as a reference.

The main idea is make use of the BeanPostProcessors interface. We first create an "LoggableInjector" class which implement the BeanPostProcessors interface.
This injector class gets the SLF4J logger and assign to the beans property after the bean creation by the Spring IOC continaer.

1. Create Loggable annotation
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.FIELD)  
@Documented  
public @interface Loggable {
 //for slf4j
}  

2. Create LoggableInjector class to add logger to the bean
@Component
public class LoggableInjector implements BeanPostProcessor {        
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {  
  return bean;  
 }  
      
 public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {  
  ReflectionUtils.doWithFields(bean.getClass(), new FieldCallback() {  
   public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {  
    // make the field accessible if defined private  
    ReflectionUtils.makeAccessible(field);  
    if (field.getAnnotation(Loggable.class) != null) {
     Logger log = LoggerFactory.getLogger(bean.getClass());      
     field.set(bean, log);  
    }  
   }  
  });  
  return bean;  
 }  
} 

3. Usage Example

@Service
public class ReportServiceImpl {

 @Loggable
 private Logger logger;
 
}

Saturday, July 14, 2012

Adding Hibernate Entity Level Filtering feature to Spring Data JPA Repository

Those who have used data filtering features of hibernate should know that it is very powerful. You could define a set of filtering criteria to an entity class or a collection. Spring data JPA is a very handy library but it does not have fitering features. In this post, I will demonstarte how to add the hibernate filter features at entity level. We can just use annotation in your repositoy interface to enable this features.


Step 1. Define filter at entity level as usual. Just use hibernate @FilterDef annotation.


@Entity
@Table(name = "STUDENT")
@FilterDef(name="filterBySchoolAndClass", parameters={@ParamDef(name="school", type="string"),@ParamDef(name="class", type="integer")})
public class Student extends GenericEntity implements Serializable {
  ...
}


Step2. Define two custom annotations.

These two annotations are to be used in your repository interfaces. You could apply the hibernate filter defined in step 1 to specific query through these annotations.


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EntityFilter {
 FilterQuery[] filterQueries() default  {};
}

@Retention(RetentionPolicy.RUNTIME)
public @interface FilterQuery {
 String name()  default "";
 String jpql()  default "";
}



Step3. Add a method to your Spring data JPA base repository.

This method will read the annotation you defined (i.e. @FilterQuery) and apply hibernate filter to the query by just simply unwrap the EntityManager. You could specify the parameter in your hibernate filter and also the parameter in you query in this method.

If you do not know how to add custom method to your Spring data JPA base repository, please see my previous post for how to customize your Spring data JPA base repository for detail. You can see in previous post that I intentionally expose the repository interface (i.e. the springDataRepositoryInterface property) in the GenericRepositoryImpl. This small tricks enable me to access the annotation in the repository interface easily.


public List<T> doQueryWithFilter( String filterName, String filterQueryName, Map inFilterParams, Map inQueryParams){
    if (GenericRepository.class.isAssignableFrom(getSpringDataRepositoryInterface())) {
       Annotation entityFilterAnn = getSpringDataRepositoryInterface().getAnnotation(EntityFilter.class);
       if(entityFilterAnn != null){
        EntityFilter entityFilter = (EntityFilter)entityFilterAnn;
        FilterQuery[] filterQuerys  = entityFilter.filterQueries() ;
        for (FilterQuery fQuery : filterQuerys) { 
         if (StringUtils.equals(filterQueryName, fQuery.name())) {
          String jpql = fQuery.jpql();
          Filter filter = em.unwrap(Session.class).enableFilter(filterName);
          
          //set filter parameter
          for (Object key: inFilterParams.keySet()) {
           String filterParamName = key.toString();
           Object filterParamValue = inFilterParams.get(key);
           filter.setParameter(filterParamName, filterParamValue);
                }
          
          //set query parameter
          Query query= em.createQuery(jpql);
          for (Object key: inQueryParams.keySet()) {
           String queryParamName = key.toString();
           Object queryParamValue = inQueryParams.get(key);
           query.setParameter(queryParamName, queryParamValue);
                }
          return query.getResultList();
         }
        }
       }
      }
     }
     return null;
    }


Usage example:

In your repositry, define which query you would like to apply hibernate filter through your @EntityFilter and @FilterQuery annotation.

@EntityFilter (
 filterQueries = {
   @FilterQuery(name="query1", 
       jpql="SELECT s FROM Student LEFT JOIN FETCH s.Subject where s.subject = :subject" ),
   @FilterQuery(name="query2", 
       jpql="SELECT s FROM Student LEFT JOIN s.TeacherSubject where s.teacher =  :teacher")       
 }
)
public interface StudentRepository extends GenericRepository<Student, Long> {
}


In your service or business class that inject your repository, you could just simply call the doQueryWithFilter() method to enable the filtering function.


@Service
public class StudentService {

 @Inject
 private StudentRepository studentRepository;

 public List<Student> searchStudent( String subject, String school, String class) {
   
  List<Student> studentList;

  // Prepare parameters for query filter
  HashMap<String, Object> inFilterParams = new HashMap<String, Object>();
  inFilterParams.put("school", "Hong Kong Secondary School");
  inFilterParams.put("class", "S5");

  // Prepare parameters for query
  HashMap<String, Object> inParams = new HashMap<String, Object>();
  inParams.put("subject", "Physics");

  studentList = studentRepository.doQueryWithFilter(
    "filterBySchoolAndClass", "query1",
    inFilterParams, inParams);

  return studentList;
 }
}








Saturday, July 7, 2012

Customizing Spring Data JPA Repository

Spring Data is a very convenient library. However, as the project as quite new, it is not well featured. By default, Spring Data JPA will provide implementation of the DAO based on SimpleJpaRepository. In recent project, I have developed a customize repository base class so that I could add more features on it. You could add vendor specific features to this repository base class as you like.

Configuration
You have to add the following configuration to you spring beans configuration file. You have to specified a new repository factory class. We will develop the class later.


<jpa:repositories base-package="example.borislam.dao" 
factory-class="example.borislam.data.springData.DefaultRepositoryFactoryBean/>



Just develop an interface extending JpaRepository. You should remember to annotate it with @NoRepositoryBean.


@NoRepositoryBean
public interface GenericRepository <T, ID extends Serializable> 
 extends JpaRepository<T, ID> {    
}



Define Custom repository base implementation class
Next step is to develop the customized base repository class. You can see that I just one property (i.e. springDataRepositoryInterface) inside this customized base repository. I just want to get more control on the behaviour of the customized behaviour of the repository interface. I will show how to add more features of this base repository class in the next post.





import static org.springframework.data.jpa.repository.query.QueryUtils.toOrders;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.hibernate.Filter;
import org.hibernate.Session;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.util.Assert;

@SuppressWarnings("unchecked")
@NoRepositoryBean
public class GenericRepositoryImpl<T, ID extends Serializable> 
 extends SimpleJpaRepository<T, ID>  implements GenericRepository<T, ID> , Serializable{
 
 private static final long serialVersionUID = 1L;

 static Logger logger = Logger.getLogger(GenericRepositoryImpl.class);
 
    private final JpaEntityInformation<T, ?> entityInformation;
    private final EntityManager em;
    private final DefaultPersistenceProvider provider;
     
    private  Class<?> springDataRepositoryInterface; 
 public Class<?> getSpringDataRepositoryInterface() {
  return springDataRepositoryInterface;
 }

 public void setSpringDataRepositoryInterface(
   Class<?> springDataRepositoryInterface) {
  this.springDataRepositoryInterface = springDataRepositoryInterface;
 }

 /**
     * Creates a new {@link SimpleJpaRepository} to manage objects of the given
     * {@link JpaEntityInformation}.
     * 
     * @param entityInformation
     * @param entityManager
     */
    public GenericRepositoryImpl (JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager , Class<?> springDataRepositoryInterface) {
     super(entityInformation, entityManager);
     this.entityInformation = entityInformation;
     this.em = entityManager;
     this.provider = DefaultPersistenceProvider.fromEntityManager(entityManager);
     this.springDataRepositoryInterface = springDataRepositoryInterface;
     }

    /**
     * Creates a new {@link SimpleJpaRepository} to manage objects of the given
     * domain type.
     * 
     * @param domainClass
     * @param em
     */
    public GenericRepositoryImpl(Class<T> domainClass, EntityManager em) {
        this(JpaEntityInformationSupport.getMetadata(domainClass, em), em, null);  
    }
 
    public <S extends T> S save(S entity)
    {     
        if (this.entityInformation.isNew(entity)) {
            this.em.persist(entity);
            flush();
            return entity;
          }
  entity = this.em.merge(entity);
  flush();
        return entity;
    }

   
    public T saveWithoutFlush(T entity)
    {
      return 
       super.save(entity);
    }
    
    public List<T> saveWithoutFlush(Iterable<? extends T> entities)
    {
     List<T> result = new ArrayList<T>();
  if (entities == null) {
   return result;
  }

  for (T entity : entities) {
   result.add(saveWithoutFlush(entity));
  }
  return result;
    }
} 




import static org.springframework.data.jpa.repository.utils.JpaClassUtils.isEntityManagerOfType;
import java.io.Serializable;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.spi.PersistenceProvider;

import org.hibernate.ejb.HibernateQuery;
import org.springframework.data.jpa.repository.query.QueryExtractor;

/**
* 
* @author Boris lam
* This class is use when you use  DefaultRepositoryFactory to override default repository factory class.
* 
*/

public enum DefaultPersistenceProvider implements QueryExtractor, Serializable{

/**
* Hibernate persistence provider.
*/
HIBERNATE("org.hibernate.ejb.HibernateEntityManager") {

public String extractQueryString(Query query) {

return ((HibernateQuery) query).getHibernateQuery()
.getQueryString();
}


/**
* Return custom placeholder ({@code *}) as Hibernate does create
* invalid queries for count queries for objects with compound keys.
* 
* @see HHH-4044
* @see HHH-3096
*/
@Override
protected String getCountQueryPlaceholder() {

return "*";
}
},

/**
* EclipseLink persistence provider.
*/
ECLIPSELINK("org.eclipse.persistence.jpa.JpaEntityManager") {

public String extractQueryString(Query query) {
//return ((JpaQuery<?>) query).getDatabaseQuery().getJPQLString();
return null;
}

},

/**
* OpenJpa persistence provider.
*/
OPEN_JPA("org.apache.openjpa.persistence.OpenJPAEntityManager") {

public String extractQueryString(Query query) {
//return ((OpenJPAQuery) query).getQueryString();
return null;
}
},

/**
* Unknown special provider. Use standard JPA.
*/
GENERIC_JPA("javax.persistence.EntityManager") {

public String extractQueryString(Query query) {

return null;
}


@Override
public boolean canExtractQuery() {

return false;
}
};

private String entityManagerClassName;


/**
* Creates a new {@link PersistenceProvider}.
* 
* @param entityManagerClassName the name of the provider specific
*            {@link EntityManager} implementation
*/
private DefaultPersistenceProvider(String entityManagerClassName) {

this.entityManagerClassName = entityManagerClassName;
}


/**
* Determines the {@link PersistenceProvider} from the given
* {@link EntityManager}. If no special one can be determined
* {@value #GENERIC_JPA} will be returned.
* 
* @param em
* @return
*/
public static DefaultPersistenceProvider fromEntityManager(EntityManager em) {

for (DefaultPersistenceProvider provider : values()) {
if (isEntityManagerOfType(em, provider.entityManagerClassName)) {
return provider;
}
}

return GENERIC_JPA;
}


/*
* (non-Javadoc)
* 
* @see
* org.springframework.data.jpa.repository.query.QueryExtractor#canExtractQuery
* ()
*/
public boolean canExtractQuery() {

return true;
}


/**
* Returns the placeholder to be used for simple count queries. Default
* implementation returns {@code *}.
* 
* @return
*/
protected String getCountQueryPlaceholder() {

return "x";
}

}



As a simple example here, I just override the default save method of the SimpleJPARepository. The default behaviour of the save method will not flush after persist. I modified to make it flush after persist. On the other hand, I add another method called saveWithoutFlush() to allow developer to call save the entity without flush. 

Define Custom repository factory bean
The last step is to create a factory bean class and factory class to produce repository based on your customized base repository class.



import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;

public class DefaultRepositoryFactoryBean <T extends JpaRepository<S, ID>, S, ID extends Serializable>
  extends JpaRepositoryFactoryBean<T, S, ID> {
    /**
     * Returns a {@link RepositoryFactorySupport}.
     * 
     * @param entityManager
     * @return
     */
    protected RepositoryFactorySupport createRepositoryFactory(
            EntityManager entityManager) {

        return new DefaultRepositoryFactory(entityManager);
    }
}


/**
 * 
 * The purpose of this class is to override the default behaviour of the spring JpaRepositoryFactory class.
 * It will produce a GenericRepositoryImpl object instead of SimpleJpaRepository. 
 * 
 */

import static org.springframework.data.querydsl.QueryDslUtils.QUERY_DSL_PRESENT;

import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.query.QueryExtractor;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.QueryDslJpaRepository;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.util.Assert;


public  class DefaultRepositoryFactory extends JpaRepositoryFactory{
    
 private final EntityManager entityManager;
    private final QueryExtractor extractor;


    public DefaultRepositoryFactory(EntityManager entityManager) {
     super(entityManager);
        Assert.notNull(entityManager);
        this.entityManager = entityManager;
        this.extractor = DefaultPersistenceProvider.fromEntityManager(entityManager);
    }
    
    @SuppressWarnings({ "unchecked", "rawtypes" })
    protected <T, ID extends Serializable> JpaRepository<?, ?> getTargetRepository(
            RepositoryMetadata metadata, EntityManager entityManager) {

        Class<?> repositoryInterface = metadata.getRepositoryInterface();
       
        JpaEntityInformation<?, Serializable> entityInformation =
                getEntityInformation(metadata.getDomainType());

        if (isQueryDslExecutor(repositoryInterface)) {
            return new QueryDslJpaRepository(entityInformation, entityManager);
        } else {
            return new GenericRepositoryImpl(entityInformation, entityManager, repositoryInterface); //custom implementation
        }
    }
 
    @Override
    protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {

        if (isQueryDslExecutor(metadata.getRepositoryInterface())) {
            return QueryDslJpaRepository.class;
        } else {
            return GenericRepositoryImpl.class;
        }
    }
    
    /**
     * Returns whether the given repository interface requires a QueryDsl
     * specific implementation to be chosen.
     * 
     * @param repositoryInterface
     * @return
     */
    private boolean isQueryDslExecutor(Class<?> repositoryInterface) {

        return QUERY_DSL_PRESENT
                && QueryDslPredicateExecutor.class
                        .isAssignableFrom(repositoryInterface);
    }   
}







Conclusion
You could now add more features to base repository class. In your program, you could now create your own repository interface extending GenericRepository instead of JpaRepository.


public interface MyRepository <T, ID extends Serializable>
  extends GenericRepository <T, ID> {
   void someCustomMethod(ID id);  
}


In next post, I will show you how to add hibernate filter features to this GenericRepository.


Friday, July 6, 2012

Welcome to Programming Peacefully!

I am an information technology professional from Hong Kong. In Hong Kong, it is so ridiculous that many IT professionals do not like programming. I had even heard that some analyst programmers said that they "hate" programming. Many of them will go to the area of project management and their technical skills may become outdated. 

I keep this blog just want to share some of my experience in software development and to exchange knowledge from technology lovers all over the world. If you think that my programme is written badly, please feel free to comment on it. I always want to get improvement on my technical skills :).

Apart from programming, I also love music. I like to play a musical instrument called "Ukulele". If you are interest in it, we can also share something about ukulele in this blog. :)