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.

 

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

 

 
  
 
  
  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.

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,

@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%");

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.

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

  1. Define your own interface.

    public interface ConferenceRepositoryCustom {
    List
    
       
    
       
        
     
        
      searchByDescription(String like);
    }
    
    
       
    
       
  2. Extend the ConferenceRepositoryCustom interface.

    @Repository
    public interface ConferenceRepository extends ConferenceRepositoryCustom, 
     JpaRepository
    
       
    
       
        
     
        
     {
    }
    
    
       
    
       
  3. 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.

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.

    @Repository
    public interface ConferenceRepository extends
    JpaRepository
    
       
    
       
        
     
        
     ,
    JpaSpecificationExecutor
     
        
     
        
          { } 
        
    
       
    
       

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

  2. 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()); } } 
        
    
       
    
       
  3. 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.

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.

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

    @Repository
    public interface ConferenceRepository extends ConferenceRepositoryCustom, 
    JpaRepository
    
       
    
       
        
     
        
     ,
    JpaSpecificationExecutor
     
        
     
        
          , QueryDslPredicateExecutor 
         
           { 
          
        
    
       
    
       

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

  2. 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; } } 
              
             
            
           
          
        
    
       
    
       
  3. 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.

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.

你可能感兴趣的:(spring,Hibernate,jpa,spring-data,spring-data-jpa)