Spring Data JPA(Maven archetype id is spring-data-jpa) provides an extra JpaRepository interface which is extended from PagingAndSortingRepository. This module also provide QueryDSL integration.
The configuration is very similar with the one motioned in before JPA post, just declare a DataSource bean, a EntityManagerFactroyBean, and a JpaTransactionManager, etc in your Spring configuration.
An extra step, you have to specify the package of the repositories in your configuration.
or use annotation @EableJpaRepositories to activate the JPA repository support.
I will reuse the Conference entity as example to demonstrate the usage of JpaRepository API.
@Entity @NamedQuery(name = "Conference.searchByMyNamedQuery", query = "from Conference where name=?") public class Conference { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") private Long id; @Version @Column(name = "version") private Integer version; @NotNull private String name; @NotNull private String description; @NotNull @Temporal(TemporalType.TIMESTAMP) @DateTimeFormat(style = "M-") private Date startedDate; @NotNull @Temporal(TemporalType.TIMESTAMP) @DateTimeFormat(style = "M-") private Date endedDate; @NotNull private String slug; private Address address; @OneToMany(cascade = CascadeType.ALL, mappedBy = "conference") private Set signups = new HashSet (); //getters and setters }
An address is added and an one-to-many relation is included.
Address is a Embeddable class.
@Embeddable public class Address { private String addressLine1; private String addressLine2; private String zipCode; private String city; private String country; //getters and setters }
Signup is a standalone @Entity class.
@Entity public class Signup { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") private Long id; @Version private Integer version; @NotNull private String firstName; @NotNull private String lastName; @NotNull @Email private String email; @NotNull private String phone; private String occupation; @Size(max = 2000) private String company; private String comment; @DateTimeFormat(style = "M-") private Date createdDate; @ManyToOne() private Conference conference; //getters and setters. }
JpaRepository is a JPA specific Repository API which is extended the PagingAndSortingRepository from Spring Data Commons.
You can use the methods defined in the super interfaces(CrudRepository and PagingAndSortingRepository) freely. JpaRepository aslo provides some extra methods for JPA operations. Please review the JpaRepository class for more details.
@Repository public interface ConferenceRepository extends JpaRepository { }
You can declare a ConferenceRepository like this. The @Repository annotation on the class is not a must, because Spring Data recognizes the repositories by determining if they are extending the Repository interface.
For example, in order to save a Conference.
Firstly inject ConferenceRepository,
@Autowired ConferenceRepository conferenceRepository
Then call save method directly.
Conference conference = newConference(); conference.setSlug("test-jud"); conference.setName("Test JUD"); conference.getAddress().setCountry("US"); conference = conferenceRepository.save(conference);
All query method based on naming convention motioned in before post also can be used here.
For example,
public Conference findBySlug(String slug);
It means it will query Conference by the slug property, and return an unique Conference object. The result is no difference from the following custom codes, but you do not need write one line code of the detailed implementation, Spring Date severs for you.
public Conference findBySlug(String slug){ return em.createQuery("from Conference where slug=:slug").setParameter("slug", slug).getSingleResult(); }
You can combine multi properties for complex query, such as findByNameAndSlug(String name, String Slug). More info about the rules, please read the official Spring Data JPA documentation.
It is not the all, Spring Data JPA provides more.
In some cases, maybe the convention based method can not satisfy your requirement. Using Spring Data JPA, you have several ways to build complex query.
Spring Data JPA provides a @Query annotation(in package org.springframework.data.jpa.repository) to execute custom JPQL, simply add it on the method.
For example,
@Query("from Conference where name=?") public Conference searchByConferenceName(String name);
It also supports named parameter, you must add an extra @Param to specify the parameter name to the method argument.
@Query("from Conference where name=:name") public Conference searchByNamedConferenceName(@Param("name") String name);
If the method declaration is annotated with @Query but without any attributes, or without any annotation, for example.
@Query public Conference searchByMyNamedQuery(String name);
And the method name also does not follow the convention motioned before, it will search if there is a @NamedQuery defined on the Conference class, the name attribute value should be in form of . .
@NamedQuery(name = "Conference.searchByMyNamedQuery", query = "from Conference where name=?")
Example codes of using these APIs.
confs = conferenceRepository.searchByConferenceName("Test JUD"); confs = conferenceRepository.searchByNamedConferenceName("Test JUD"); confs = conferenceRepository.searchByMyNamedQuery("Test JUD"); confs = conferenceRepository.searchByDescription("Boston"); confs = conferenceRepository.findByDescriptionLike("%Boston%");
You can execute a modifying query(such as perform a batch deletion or update) on the method by adding an extra annotation @Modifying.
For example, to update the description of an specific Conference identified by id.
@Query("update Conference conf set conf.description=?1 where conf.id=?2 ") @Modifying public void modifyConferenceDescrition(String description, Long id);
You can also use the traditional way(I means use JPA without Spring Data JPA) to execute a custom JPQL in your implementation class.
Define your own interface.
public interface ConferenceRepositoryCustom { List searchByDescription(String like); }
Extend the ConferenceRepositoryCustom interface.
@Repository public interface ConferenceRepository extends ConferenceRepositoryCustom, JpaRepository { }
Provide an implementation class of ConferenceRepositoryCustom.
public class ConferenceRepositoryImpl implements ConferenceRepositoryCustom { @PersistenceContext EntityManager em; @Override public List searchByDescription(String d) { return em.createQuery("from Conference where description like :description", Conference.class) .setParameter("description", "%"+d+"%") .getResultList(); } }
The codes seem a little wired, Spring Data JPA must find some way to combine the official interface and you home use interface, and expose it to the caller.
Now you can inject ConferenceRepository and use both methods of them.
@Autowired ConferenceRepository conferenceRepository; //save ...from standard JpaRepository conferenceRepository.save(...); //searchByDescription... from your ConferenceRepositoryCustom conferenceRepository.searchByDescription(..);
By default, the Custom postfix in your custom interface is a must, but you can change it to any characters as you expected. In the <jpa:repositories, specify a repository-impl-postfix attribute.
Now, you can name your custom interface as ConferenceRepositoryMyCust.
Spring Data JPA provides a simple Specification interface to envelope the usage of the JPA Criteria API which is introduced in JPA 2.0.
Modify ConferenceRepository and add another interface JpaSpecificationExecutor to extend.
@Repository public interface ConferenceRepository extends JpaRepository , JpaSpecificationExecutor { }
JpaSpecificationExecutor provides some methods which can accept a Specification as method argument.
Write your own Specification class.
Open the Specification class, you will find it just includes one method toPredicate, which returns a WHERE clause in form of Predicate for given Root and CriteriaQuery.
Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb);
For example, we want to find all upcoming references, we can create a Specification for it.
public class UpcomingConferences implements Specification { @Override public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { return cb.greaterThan(root.get("startedDate").as(Date.class), cb.currentTimestamp()); } }
Now you can call findAll method from JpaSpecificationExecutor in your Service or Controller directly.
@Override public List findUpcomingConferences() { return conferenceRepository.findAll(new UpcomingConferences()); }
As you see, the where clause logic of query is moved to a certain Specification class, the codes becomes more maintainable than before.
JPA 2.0 provides new Criteria API, and also provides a type safe MetaModel to access the entity properties.
You have to use the compiler APT processor to generate the MetaModel classes at compile time. Almost all JPA providers(EclipseLink, Hibernate, OpenJPA) provide similar tools for generating the metamodel classes.
For Hibernate, it is provided in hibernate-jpamodelgen.
You can simply add the org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor in the APT configuration of the maven compiler plugin.
Or use a standalone plugin to configure it to get more flexibility.
org.bsc.maven maven-processor-plugin 2.0.5 process process generate-sources org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor ${project.build.directory}/generated-sources/java/ org.hibernate hibernate-jpamodelgen 1.2.0.Final
Run mvn compile
, the MetaModel classes will be generated under the folder generated-sources/java.
The metamodel class of Conference is named Conference_.
@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor") @StaticMetamodel(Conference.class) public abstract class Conference_ { public static volatile SingularAttribute id; public static volatile SingularAttribute endedDate; public static volatile SetAttribute signups; public static volatile SingularAttribute address; public static volatile SingularAttribute startedDate; public static volatile SingularAttribute description; public static volatile SingularAttribute name; public static volatile SingularAttribute slug; public static volatile SingularAttribute version; }
Now you can replace the hard code “startedDate” in before UpcomingConferences with the typesafe variant.
@Override public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { return cb.greaterThan(root.get(Conference_startedDate).as(Date.class), cb.currentTimestamp()); }
QueryDSL provides a series of fluent APIs for JPA, JDBC, Mongo, Lucence etc. The usage of integration steps is very similar with JPA Criteria API.
Spring Data JPA provides a QueryDSL specific QueryDslPredicateExecutor for QueryDSL integration, make ConferenceRepository extend it.
@Repository public interface ConferenceRepository extends ConferenceRepositoryCustom, JpaRepository , JpaSpecificationExecutor , QueryDslPredicateExecutor {
QueryDslPredicateExecutor provides some methods which can accept a QueryDSL specific Predicate object as argument.
You can build the Predicate via the Metamodels which can generated by QueryDSL APT tools.
Declare a the QueryDSL official maven plugin in your pom.xml.
com.mysema.maven apt-maven-plugin 1.0.9 process target/generated-sources/java com.mysema.query.apt.jpa.JPAAnnotationProcessor com.mysema.querydsl querydsl-apt ${querydsl.version} com.mysema.querydsl querydsl-jpa apt ${querydsl.version}
Run mvn compile
to compile the project, it will generate the metamodel codes.
@Generated("com.mysema.query.codegen.EntitySerializer") public class QConference extends EntityPathBase { private static final long serialVersionUID = -226677720; private static final PathInits INITS = PathInits.DIRECT; public static final QConference conference = new QConference("conference"); public final QAddress address; public final StringPath description = createString("description"); public final DateTimePath endedDate = createDateTime("endedDate", java.util.Date.class); public final NumberPath id = createNumber("id", Long.class); public final StringPath name = createString("name"); public final SetPath signups = this. createSet("signups", Signup.class, QSignup.class, PathInits.DIRECT); public final StringPath slug = createString("slug"); public final DateTimePath startedDate = createDateTime("startedDate", java.util.Date.class); public final NumberPath version = createNumber("version", Integer.class); public QConference(String variable) { this(Conference.class, forVariable(variable), INITS); } @SuppressWarnings("all") public QConference(Path path) { this((Class)path.getType(), path.getMetadata(), path.getMetadata().isRoot() ? INITS : PathInits.DEFAULT); } public QConference(PathMetadata metadata) { this(metadata, metadata.isRoot() ? INITS : PathInits.DEFAULT); } public QConference(PathMetadata metadata, PathInits inits) { this(Conference.class, metadata, inits); } public QConference(Class type, PathMetadata metadata, PathInits inits) { super(type, metadata, inits); this.address = inits.isInitialized("address") ? new QAddress(forProperty("address")) : null; } }
Now you can use this class to build your Predicate.
For example, query all in progress conferences.
public static Predicate inProgressConferences() { QConference conf = QConference.conference; final Date now = new Date(); BooleanBuilder builder = new BooleanBuilder(); return builder.and(conf. startedDate.before(now)) .and(conf.endedDate.after(now)) .getValue(); }
Call findAll(Predicate) method provided in QueryDslPredicateExecutor to get the result.
conferenceRepository.findAll(inProgressConferences());
Some other query examples.
List confs = (List ) conferenceRepository .findAll(QConference.conference.address.country.eq("US")); confs = (List ) conferenceRepository .findAll(QConference.conference.name.eq("Test JUD")); confs = (List ) conferenceRepository .findAll(QConference.conference.description.contains("Boston")); confs = (List ) conferenceRepository .findAll(QConference.conference.signups.any().email .eq("[email protected]"));
As you see, compare to Specification and JPA Criteria API, QueryDSL Predicate is more friendly and more close to the nature language.
Personally, I think the QueryDSL integration is the most attractive feature in Spring Data JPA, it provides true typesafe, fluent APIs for JPA programming.