Spring Data JPA
Spring Data JPA(Maven archetype id is spring-data-jpa) provides an extra JpaRepository interface which is extended fromPagingAndSortingRepository. This module also provide QueryDSL integration.
Configuration
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.
<jpa:repositories base-package="com.hantsylabs.example.spring.jpa"></jpa:repositories>
or use annotation @EableJpaRepositories to activate the JPA repository support.
JpaRepository
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<Signup> signups = new HashSet<Signup>(); //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 thePagingAndSortingRepository 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<Conference, Long>{ }
You can declare a ConferenceRepository like this. The @Repositoryannotation on the class is not a must, because Spring Data recognizes the repositories by determining if they are extending theRepository 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 asfindByNameAndSlug(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.
Query
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 packageorg.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 theConference class, the name attribute value should be in form of<Entity ClassName>.<queryMethodName>.
@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%");
Modifying Query
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 Conferenceidentified by id.
@Query("update Conference conf set conf.description=?1 where conf.id=?2 ") @Modifying public void modifyConferenceDescrition(String description, Long id);
Custom JPQL Query
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<Conference> searchByDescription(String like); }
- Extend the ConferenceRepositoryCustom interface.
@Repository public interface ConferenceRepository extends ConferenceRepositoryCustom, JpaRepository<Conference, Long>{ }
- Provide an implementation class ofConferenceRepositoryCustom.
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.
<jpa:repositories base-package="com.hantsylabs.example.spring.jpa" repository-impl-postfix="MyCust"></jpa:repositories>
Now, you can name your custom interface asConferenceRepositoryMyCust.
Specification and Criteria API
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 interfaceJpaSpecificationExecutor to extend.
@Repository public interface ConferenceRepository extends JpaRepository, JpaSpecificationExecutor{ }
JpaSpecificationExecutor provides some methods which can accept aSpecification as method argument.
- Write your own Specification class.
Open the Specification class, you will find it just includes one methodtoPredicate, 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<Conference> { @Override public Predicate toPredicate(Root<Conference> 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 certainSpecification class, the codes becomes more maintainable than before.
Typesafe Criteria API
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 theorg.hibernate.jpamodelgen.JPAMetaModelEntityProcessor in the APT configuration of the maven compiler plugin.
Or use a standalone plugin to configure it to get more flexibility.
<plugin> <groupId>org.bsc.maven</groupId> <artifactId>maven-processor-plugin</artifactId> <version>2.0.5</version> <executions> <execution> <id>process</id> <goals> <goal>process</goal> </goals> <phase>generate-sources</phase> <configuration> <processors> <processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor> </processors> <outputDirectory>${project.build.directory}/generated-sources/java/</outputDirectory> </configuration> </execution> </executions> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-jpamodelgen</artifactId> <version>1.2.0.Final</version> </dependency> </dependencies> </plugin>
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<Conference, Long> id; public static volatile SingularAttribute<Conference, Date> endedDate; public static volatile SetAttribute<Conference, Signup> signups; public static volatile SingularAttribute<Conference, Address> address; public static volatile SingularAttribute<Conference, Date> startedDate; public static volatile SingularAttribute<Conference, String> description; public static volatile SingularAttribute<Conference, String> name; public static volatile SingularAttribute<Conference, String> slug; public static volatile SingularAttribute<Conference, Integer> version; }
Now you can replace the hard code "startedDate" in beforeUpcomingConferences with the typesafe variant.
@Override public Predicate toPredicate(Root<Conference> root, CriteriaQuery<?> query, CriteriaBuilder cb) { return cb.greaterThan(root.get(Conference_startedDate).as(Date.class), cb.currentTimestamp()); }
QueryDSL integration
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 specificQueryDslPredicateExecutor for QueryDSL integration, makeConferenceRepository extend it.
@Repository public interface ConferenceRepository extends ConferenceRepositoryCustom, JpaRepository<Conference, Long>, JpaSpecificationExecutor<Conference>, QueryDslPredicateExecutor<Conference> {
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.
<plugin> <groupId>com.mysema.maven</groupId> <artifactId>apt-maven-plugin</artifactId> <version>1.0.9</version> <executions> <execution> <goals> <goal>process</goal> </goals> <configuration> <outputDirectory>target/generated-sources/java</outputDirectory> <processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor> </configuration> </execution> </executions> <dependencies> <dependency> <groupId>com.mysema.querydsl</groupId> <artifactId>querydsl-apt</artifactId> <version>${querydsl.version}</version> </dependency> <dependency> <groupId>com.mysema.querydsl</groupId> <artifactId>querydsl-jpa</artifactId> <classifier>apt</classifier> <version>${querydsl.version}</version> </dependency> </dependencies> </plugin>
Run
mvn compile
to compile the project, it will generate the metamodel codes.@Generated("com.mysema.query.codegen.EntitySerializer") public class QConference extends EntityPathBase<Conference> { 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<java.util.Date> endedDate = createDateTime("endedDate", java.util.Date.class); public final NumberPath<Long> id = createNumber("id", Long.class); public final StringPath name = createString("name"); public final SetPath<Signup, QSignup> signups = this.<Signup, QSignup>createSet("signups", Signup.class, QSignup.class, PathInits.DIRECT); public final StringPath slug = createString("slug"); public final DateTimePath<java.util.Date> startedDate = createDateTime("startedDate", java.util.Date.class); public final NumberPath<Integer> version = createNumber("version", Integer.class); public QConference(String variable) { this(Conference.class, forVariable(variable), INITS); } @SuppressWarnings("all") public QConference(Path<? extends Conference> 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<? extends Conference> 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 QueryDslPredicateExecutorto get the result.
conferenceRepository.findAll(inProgressConferences());
Some other query examples.
List<Conference> confs = (List<Conference>) conferenceRepository .findAll(QConference.conference.address.country.eq("US")); confs = (List<Conference>) conferenceRepository .findAll(QConference.conference.name.eq("Test JUD")); confs = (List<Conference>) conferenceRepository .findAll(QConference.conference.description.contains("Boston")); confs = (List<Conference>) conferenceRepository .findAll(QConference.conference.signups.any().email .eq("test@test.com"));
As you see, compare to Specification and JPA Criteria API, QueryDSLPredicate is more friendly and more close to the nature language.
Summary
Personally, I think the QueryDSL integration is the most attractive feature in Spring Data JPA, it provides true typesafe, fluent APIs for JPA programming.
评论