Spring Data JPA: simplify JPA programming

#Spring Data JPA

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.

##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.

<pre> &lt;jpa:repositories base-package="com.hantsylabs.example.spring.jpa">&lt;/jpa:repositories> </pre>

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.

<pre> @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&lt;Signup> signups = new HashSet&lt;Signup>(); //getters and setters } </pre>

An address is added and an one-to-many relation is included.

Address is a Embeddable class.

<pre> @Embeddable public class Address { private String addressLine1; private String addressLine2; private String zipCode; private String city; private String country; //getters and setters } </pre>

Signup is a standalone @Entity class.

<pre> @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. } </pre>

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.

<pre> @Repository public interface ConferenceRepository extends JpaRepository&lt;Conference, Long>{ } </pre>

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,

<pre> @Autowired ConferenceRepository conferenceRepository </pre>

Then call save method directly.

<pre> Conference conference = newConference(); conference.setSlug("test-jud"); conference.setName("Test JUD"); conference.getAddress().setCountry("US"); conference = conferenceRepository.save(conference); </pre>

All query method based on naming convention motioned in before post also can be used here.

For example,

<pre> public Conference findBySlug(String slug); </pre>

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.

<pre> public Conference findBySlug(String slug){ return em.createQuery("from Conference where slug=:slug").setParameter("slug", slug).getSingleResult(); } </pre>

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.

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 package org.springframework.data.jpa.repository) to execute custom JPQL, simply add it on the method.

For example,

<pre> @Query("from Conference where name=?") public Conference searchByConferenceName(String name); </pre>

It also supports named parameter, you must add an extra @Param to specify the parameter name to the method argument.

<pre> @Query("from Conference where name=:name") public Conference searchByNamedConferenceName(@Param("name") String name); </pre>

If the method declaration is annotated with @Query but without any attributes, or without any annotation, for example.

<pre> @Query public Conference searchByMyNamedQuery(String name); </pre>

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 <Entity ClassName>.<queryMethodName>.

<pre> @NamedQuery(name = "Conference.searchByMyNamedQuery", query = "from Conference where name=?") </pre>

Example codes of using these APIs.

<pre> confs = conferenceRepository.searchByConferenceName("Test JUD"); confs = conferenceRepository.searchByNamedConferenceName("Test JUD"); confs = conferenceRepository.searchByMyNamedQuery("Test JUD"); confs = conferenceRepository.searchByDescription("Boston"); confs = conferenceRepository.findByDescriptionLike("%Boston%"); </pre>

##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 Conference identified by id.

<pre> @Query("update Conference conf set conf.description=?1 where conf.id=?2 ") @Modifying public void modifyConferenceDescrition(String description, Long id); </pre>

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.

  1. Define your own interface.

<pre> public interface ConferenceRepositoryCustom { List&lt;Conference> searchByDescription(String like); } </pre>

  1. Extend the ConferenceRepositoryCustom interface.

<pre> @Repository public interface ConferenceRepository extends ConferenceRepositoryCustom, JpaRepository&lt;Conference, Long>{ } </pre>

  1. Provide an implementation class of ConferenceRepositoryCustom.

<pre> public class ConferenceRepositoryImpl implements ConferenceRepositoryCustom { @PersistenceContext EntityManager em; @Override public List<Conference> searchByDescription(String d) { return em.createQuery("from Conference where description like :description", Conference.class) .setParameter("description", "%"+d+"%") .getResultList(); } } </pre>

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.

<pre> @Autowired ConferenceRepository conferenceRepository; //save ...from standard JpaRepository conferenceRepository.save(...); //searchByDescription... from your ConferenceRepositoryCustom conferenceRepository.searchByDescription(..); </pre>

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.

<pre> &lt;jpa:repositories base-package="com.hantsylabs.example.spring.jpa" repository-impl-postfix="MyCust">&lt;/jpa:repositories> </pre>

Now, you can name your custom interface as ConferenceRepositoryMyCust.

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.

  1. Modify ConferenceRepository and add another interface JpaSpecificationExecutor to extend.

<pre> @Repository public interface ConferenceRepository extends JpaRepository<Conference, Long>, JpaSpecificationExecutor<Conference>{ } </pre>

JpaSpecificationExecutor provides some methods which can accept a Specification as method argument.

  1. 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.

<pre> Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb); </pre>

For example, we want to find all upcoming references, we can create a Specification for it.

<pre> public class UpcomingConferences implements Specification&lt;Conference> { @Override public Predicate toPredicate(Root&lt;Conference> root, CriteriaQuery&lt;?> query, CriteriaBuilder cb) { return cb.greaterThan(root.get("startedDate").as(Date.class), cb.currentTimestamp()); } } </pre>

  1. Now you can call findAll method from JpaSpecificationExecutor in your Service or Controller directly.

<pre> @Override public List<Conference> findUpcomingConferences() { return conferenceRepository.findAll(new UpcomingConferences()); } </pre>

As you see, the where clause logic of query is moved to a certain Specification 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 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.

<pre> &lt;plugin> &lt;groupId>org.bsc.maven&lt;/groupId> &lt;artifactId>maven-processor-plugin&lt;/artifactId> &lt;version>2.0.5&lt;/version> &lt;executions> &lt;execution> &lt;id>process&lt;/id> &lt;goals> &lt;goal>process&lt;/goal> &lt;/goals> &lt;phase>generate-sources&lt;/phase> &lt;configuration> &lt;processors> &lt;processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor&lt;/processor> &lt;/processors> &lt;outputDirectory>${project.build.directory}/generated-sources/java/&lt;/outputDirectory> &lt;/configuration> &lt;/execution> &lt;/executions> &lt;dependencies> &lt;dependency> &lt;groupId>org.hibernate&lt;/groupId> &lt;artifactId>hibernate-jpamodelgen&lt;/artifactId> &lt;version>1.2.0.Final&lt;/version> &lt;/dependency> &lt;/dependencies> &lt;/plugin> </pre>

Run mvn compile, the MetaModel classes will be generated under the folder generated-sources/java.

The metamodel class of Conference is named Conference_.

<pre> @Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor") @StaticMetamodel(Conference.class) public abstract class Conference_ { public static volatile SingularAttribute&lt;Conference, Long> id; public static volatile SingularAttribute&lt;Conference, Date> endedDate; public static volatile SetAttribute&lt;Conference, Signup> signups; public static volatile SingularAttribute&lt;Conference, Address> address; public static volatile SingularAttribute&lt;Conference, Date> startedDate; public static volatile SingularAttribute&lt;Conference, String> description; public static volatile SingularAttribute&lt;Conference, String> name; public static volatile SingularAttribute&lt;Conference, String> slug; public static volatile SingularAttribute&lt;Conference, Integer> version; } </pre>

Now you can replace the hard code "startedDate" in before UpcomingConferences with the typesafe variant.

<pre> @Override public Predicate toPredicate(Root&lt;Conference> root, CriteriaQuery&lt;?> query, CriteriaBuilder cb) { return cb.greaterThan(root.get(Conference_startedDate).as(Date.class), cb.currentTimestamp()); } </pre>

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.

  1. Spring Data JPA provides a QueryDSL specific QueryDslPredicateExecutor for QueryDSL integration, make ConferenceRepository extend it.

<pre> @Repository public interface ConferenceRepository extends ConferenceRepositoryCustom, JpaRepository&lt;Conference, Long>, JpaSpecificationExecutor&lt;Conference>, QueryDslPredicateExecutor&lt;Conference> { </pre>

QueryDslPredicateExecutor provides some methods which can accept a QueryDSL specific Predicate object as argument.

  1. 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.

<pre> &lt;plugin> &lt;groupId>com.mysema.maven&lt;/groupId> &lt;artifactId>apt-maven-plugin&lt;/artifactId> &lt;version>1.0.9&lt;/version> &lt;executions> &lt;execution> &lt;goals> &lt;goal>process&lt;/goal> &lt;/goals> &lt;configuration> &lt;outputDirectory>target/generated-sources/java&lt;/outputDirectory> &lt;processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor&lt;/processor> &lt;/configuration> &lt;/execution> &lt;/executions> &lt;dependencies> &lt;dependency> &lt;groupId>com.mysema.querydsl&lt;/groupId> &lt;artifactId>querydsl-apt&lt;/artifactId> &lt;version>${querydsl.version}&lt;/version> &lt;/dependency> &lt;dependency> &lt;groupId>com.mysema.querydsl&lt;/groupId> &lt;artifactId>querydsl-jpa&lt;/artifactId> &lt;classifier>apt&lt;/classifier> &lt;version>${querydsl.version}&lt;/version> &lt;/dependency> &lt;/dependencies> &lt;/plugin> </pre>

Run mvn compile to compile the project, it will generate the metamodel codes.

<pre> @Generated("com.mysema.query.codegen.EntitySerializer") public class QConference extends EntityPathBase&lt;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&lt;java.util.Date> endedDate = createDateTime("endedDate", java.util.Date.class); public final NumberPath&lt;Long> id = createNumber("id", Long.class); public final StringPath name = createString("name"); public final SetPath&lt;Signup, QSignup> signups = this.&lt;Signup, QSignup>createSet("signups", Signup.class, QSignup.class, PathInits.DIRECT); public final StringPath slug = createString("slug"); public final DateTimePath&lt;java.util.Date> startedDate = createDateTime("startedDate", java.util.Date.class); public final NumberPath&lt;Integer> version = createNumber("version", Integer.class); public QConference(String variable) { this(Conference.class, forVariable(variable), INITS); } @SuppressWarnings("all") public QConference(Path&lt;? extends Conference> path) { this((Class)path.getType(), path.getMetadata(), path.getMetadata().isRoot() ? INITS : PathInits.DEFAULT); } public QConference(PathMetadata&lt;?> metadata) { this(metadata, metadata.isRoot() ? INITS : PathInits.DEFAULT); } public QConference(PathMetadata&lt;?> metadata, PathInits inits) { this(Conference.class, metadata, inits); } public QConference(Class&lt;? extends Conference> type, PathMetadata&lt;?> metadata, PathInits inits) { super(type, metadata, inits); this.address = inits.isInitialized("address") ? new QAddress(forProperty("address")) : null; } } </pre>

  1. Now you can use this class to build your Predicate.

For example, query all in progress conferences.

<pre> 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(); } </pre>

Call findAll(Predicate) method provided in QueryDslPredicateExecutor to get the result.

<pre> conferenceRepository.findAll(inProgressConferences()); </pre>

Some other query examples.

<pre> List&lt;Conference> confs = (List&lt;Conference>) conferenceRepository .findAll(QConference.conference.address.country.eq("US")); confs = (List&lt;Conference>) conferenceRepository .findAll(QConference.conference.name.eq("Test JUD")); confs = (List&lt;Conference>) conferenceRepository .findAll(QConference.conference.description.contains("Boston")); confs = (List&lt;Conference>) conferenceRepository .findAll(QConference.conference.signups.any().email .eq("test@test.com")); </pre>

As you see, compare to Specification and JPA Criteria API, QueryDSL Predicate 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.

转载于:https://my.oschina.net/hantsy/blog/135485

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值