I have an application that uses Spring Boot Data jpa . So far i am using a repository like this
public interface StudentRepository extends CrudRepos
JPA 2 introduces a criteria API that can be used to build queries programmatically.
You can extend a new interface from JpaSpecificationExecutor
public interface CustomerRepository extends
CrudRepository<Customer, Long>,
JpaSpecificationExecutor<Customer> {
default List<Customer> findCustomers() {
return findAll(CustomerSpecs.findCustomers());
}
Then create a customer specs
public final class CustomerSpecs {
public static Specification<Customer> findCustomers() {
return new Specification<Customer>() {
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query,
CriteriaBuilder builder) {
LocalDate date = new LocalDate().minusYears(2);
return builder.lessThan(root.get("birthday"), date);
}
};
}
The above can be simplified using lambdas as follows
public interface CustomerRepository extends
CrudRepository<Customer, Long>,
JpaSpecificationExecutor<Customer> {
default List<Customer> findCustomers() {
return findAll(
(root, query, cb) -> {
LocalDate date = new LocalDate().minusYears(2);
return builder.lessThan(root.get("birthday"), date);
}
);
}
For more details, refer this spring doc here
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#specifications
It's very complicated to create specifications using JPA Criteria because the API is very verbose and instrusive.
With the Lambda JDK 8, you can create high very typed-queries using simples predicates.
@Test
public void testSimpleSpec() {
String expected =
"select e0.id, e0.name "
+ "from Customer e0 "
+ "where (e0.regionCode = :p0)";
Consumer<Query<Customer>> regionCode1 =
q -> q.where(i -> i.getRegionCode()).eq(1L);
NativeSQLResult result = new QueryBuilder()
.from(Customer.class)
.whereSpec(regionCode1)
.select(i -> i.getId())
.select(i -> i.getName())
.to(new NativeSQL())
;
String actual = result.sql();
Assert.assertEquals(expected, actual);
Assert.assertEquals(result.params().get("p0"), 1L);
}
You can isolate the conditions and to reuse in others queries, ie, specifications.
https://github.com/naskarlab/fluent-query
https://github.com/naskarlab/fluent-query-eclipselink
With Spring-boot-jpa
you are able to use entityManager
nearly everywhere. The most commom way is to create an own interface
for custom methods.
public interface StudentCustomRepository {
void anyCustomMethod();
Student getStudentByName(String name);
}
Then implement this interface to a service class where you are able to autowire and use the entityManager
:
@Service
public class StudentCustomRepositoryServiceImpl implements StudentCustomRepository {
@PersistenceContext
private EntityManager em;
@Override
public void anyCustomMethod(){
//here use the entityManager
}
@Override
StudentEntity getStudentByName(String name){
Criteria crit = em.unwrap(Session.class).createCriteria(StudentEntity.class);
crit.add(Restrictions.eq("name", name));
List<StudentEntity> students = crit.list();
return students.get(0);
}
}
You can also decide to implement your StudentRepository
to your new StudentCustomRepositoryServiceImpl
class.
you can refer to Query creation into spring data JPA documentation and take a look at the table, JPA give multiple Query creation from method names where you can avoid using a String query
According to Spring doc HibernateTemplate:
NOTE: Hibernate access code can also be coded in plain Hibernate style. Hence, for newly started projects, consider adopting the standard Hibernate style of coding data access objects instead, based on SessionFactory.getCurrentSession(). This HibernateTemplate primarily exists as a migration helper for Hibernate 3 based data access code, to benefit from bug fixes in Hibernate 4.x.
While according to Hibernate doc:
New development should focus on the JPA javax.persistence.criteria.CriteriaQuery API. Eventually, Hibernate-specific criteria features will be ported as extensions to the JPA javax.persistence.criteria.CriteriaQuery.
So its better to use JPQL Criteria:JPA Criteria API Queries
Example:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Country> q = cb.createQuery(Country.class);
Root<Country> c = q.from(Country.class);
q.select(c);
where entityManager should be @Autowired. For detail info, see above link
From the docs
To enrich a repository with custom functionality you first define an interface and an implementation for the custom functionality. Use the repository interface you provided to extend the custom interface.
Define an interface like so
public interface StudentRepositoryCustom {
List<String> nameByCourse(String coursename);
}
Then define a custom implementation of this interface like so
@Service
class StudentRepositoryImpl implements StudentRepositoryCustom {
@PersistenceContext
private EntityManager em;
public List<String> nameByCourse(String coursename) {
CriteriaBuilder cb = em.getCriteriaBuilder();
//Using criteria builder you can build your criteria queries.
}
}
Now you can extend this custom repository implementaion in your JPA repository like so.
public interface StudentRepository extends CrudRepository<StudentEntity, Integer>, StudentRepositoryCustom {
}
Learn more about criteria query and criteria builder here