4

I tried to search but didn't find accurate solution. I have Address entity. With every new Address request, first I want to check whether same exist in database or not. My application is for warehouse & there are chances that same address request will come multiple time.

Address Entity

@Entity
@NamedQuery(name="Address.findAll", query="SELECT a FROM Address a")
public class Address implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;

    private String firstname;

    private String lastname;

    private String address1;

    private String address2;

    private String address3;

    private String city;

    private String postcode;

    @JsonProperty(value="county")
    private String state;

    private String country;

    private String telephoneno;

    private String mobileno;    

    private String email;

    //bi-directional many-to-one association to Collection
    @OneToMany(mappedBy="address")
    @JsonIgnore
    private List<Collection> collections;

    //bi-directional many-to-one association to Delivery
    @OneToMany(mappedBy="address")
    @JsonIgnore
    private List<Delivery> deliveries;


    public Address() {
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getAddress1() {
        return this.address1;
    }

    public void setAddress1(String address1) {
        this.address1 = address1;
    }

    public String getAddress2() {
        return this.address2;
    }

    public void setAddress2(String address2) {
        this.address2 = address2;
    }

    public String getAddress3() {
        return this.address3;
    }

    public void setAddress3(String address3) {
        this.address3 = address3;
    }

    public String getCity() {
        return this.city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getCountry() {
        return this.country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getEmail() {
        return this.email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPostcode() {
        return this.postcode;
    }

    public void setPostcode(String postcode) {
        this.postcode = postcode;
    }

    public String getState() {
        return this.state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getFirstname() {
        return firstname;
    }

    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }

    public String getLastname() {
        return lastname;
    }

    public void setLastname(String lastname) {
        this.lastname = lastname;
    }

    public String getTelephoneno() {
        return telephoneno;
    }

    public void setTelephoneno(String telephoneno) {
        this.telephoneno = telephoneno;
    }

    public String getMobileno() {
        return mobileno;
    }

    public void setMobileno(String mobileno) {
        this.mobileno = mobileno;
    }

    public List<Collection> getCollections() {
        return this.collections;
    }

    public void setCollections(List<Collection> collections) {
        this.collections = collections;
    }

    public Collection addCollection(Collection collection) {
        getCollections().add(collection);
        collection.setAddress(this);

        return collection;
    }

    public Collection removeCollection(Collection collection) {
        getCollections().remove(collection);
        collection.setAddress(null);

        return collection;
    }

    public List<Delivery> getDeliveries() {
        return this.deliveries;
    }

    public void setDeliveries(List<Delivery> deliveries) {
        this.deliveries = deliveries;
    }

    public Delivery addDelivery(Delivery delivery) {
        getDeliveries().add(delivery);
        delivery.setAddress(this);

        return delivery;
    }

    public Delivery removeDelivery(Delivery delivery) {
        getDeliveries().remove(delivery);
        delivery.setAddress(null);

        return delivery;
    }


}

I know one solution could be declaring a method in repository with And including all fields. for e.g.

public Address findByFirstnameAndLastnameAndAddress1AndAddress2AndAddress3AndCityAndPostcode....();

But I want to know if better way to do this. Is there anything using which I just pass new Address object to check same Address exist in database or not.

EDIT

Based on Manish's answer, following is what I understand:

1> Create interface ExtendedJpaRepository as mentioned in answer.

2> Create implementation class for this interface as below (Ref : Spring Data Jpa Doc)

public class MyRepositoryImpl<T, ID extends Serializable>
  extends SimpleJpaRepository<T, ID> implements MyRepository<T, ID> {
        List<T> findByExample(T example){
            //EclipseLink implementation for QueryByExample
        }
  }

3> Then for every repository interface, extend ExtendedJpaRepository. This should make findByExample readily available in every repository.

4> Create a custom repository factory to replace the default RepositoryFactoryBean as stated in step 4 of Spring data JPA doc.

5> Declare beans of the custom factory.(Step-5 of Spring Data JPA Doc )

hemu
  • 3,199
  • 3
  • 44
  • 66
  • have you simply tried `findByAddress(Address address)` that's working in my project – Manu Zi Feb 06 '15 at 06:46
  • Yes I tried. But gave me error stating that "no address filed found in Address entity" at the time of starting tomcat. Tomcat tries to create object for `AddressDataRepository` & at that point it throws error. How your code worked ? Did you created `Address` variable in your `Address pojo ? – hemu Feb 06 '15 at 06:56
  • ok, i have look again. sorry my memories were treacherous. I was a bit mixed up. in address it's the same like you, findBy..Attributes... – Manu Zi Feb 06 '15 at 07:02

2 Answers2

3

What you are looking for is called Query-by-Example. As explained in this post, this feature was considered for JPA 2.0 but not included in the final version. That post also explains that most JPA providers have the necessary functionality to implement this feature.

You can create your custom JPA repository implementation that provides this functionality out-of-the-box. The details are provided in the Spring Data JPA documentation.

A starting point would be to create a new interface, such as:

public interface ExtendedJpaRepository<T, ID extends Serializable>
    extends JpaRepository<T, ID> {
  List<T> findByExample(T example);
}

Then, plug in an implementation for this interface that uses your underlying JPA provider. Finally, configure your custom implementation to be used for all your repository interfaces.

After that, you should be able to call addressRepository.findByExample(address), provided AddressRepository extends ExtendedJpaRepository.

Community
  • 1
  • 1
manish
  • 19,695
  • 5
  • 67
  • 91
  • You stated this - `"Then, plug in an implementation for this interface that uses your underlying JPA provider. Finally, configure your custom implementation to be used for all your repository interfaces."` How would I achieve this for eclipseLink using MySql ? – hemu Feb 24 '15 at 12:22
  • Implementation of `findByExample` is provider specific. [This post](http://stackoverflow.com/questions/2880209/jpa-findbyexample) shows how you can do it with some of the common JPA providers, including EclipseLink. JPA is database independent so you will not have to worry about MySQL. – manish Feb 24 '15 at 12:24
  • OK. I have written what I understood in `EDIT` section of question above. Please let me know if I understand it correctly. – hemu Feb 24 '15 at 12:53
  • [Here](https://github.com/manish-in-java/spring-jpa-extension) is a sample working application. – manish Feb 24 '15 at 13:35
2

You can use Specifications that Spring-data gives you out of the box. and be able to use criteria API to build queries programmatically.To support specifications you can extend your repository interface with the JpaSpecificationExecutor interface

public interface CustomerRepository extends SimpleJpaRepository<T, ID>, JpaSpecificationExecutor {

}

The additional interface(JpaSpecificationExecutor ) carries methods that allow you to execute Specifications in a variety of ways.

For example, the findAll method will return all entities that match the specification:

List<T> findAll(Specification<T> spec);

The Specification interface is as follows:

public interface Specification<T> {
  Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
            CriteriaBuilder builder);
}

Okay, so what is the typical use case? Specifications can easily be used to build an extensible set of predicates on top of an entity that then can be combined and used with JpaRepository without the need to declare a query (method) for every needed combination. Here's an example: Example 2.15. Specifications for a Customer

public class CustomerSpecs {

  public static Specification<Customer> isLongTermCustomer() {
    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('dateField'), date);
      }
    };
  }

  public static Specification<Customer> hasSalesOfMoreThan(MontaryAmount value) {
    return new Specification<Customer>() {
      public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
            CriteriaBuilder builder) {

         // build query here
      }
    };
  }
}

You expressed some criteria on a business requirement abstraction level and created executable Specifications. So a client might use a Specification as follows: List customers = customerRepository.findAll(isLongTermCustomer());

You can also combine Specification Example 2.17. Combined Specifications

MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
  where(isLongTermCustomer()).or(hasSalesOfMoreThan(amount)));

As you can see, Specifications offers some glue-code methods to chain and combine Specifications. Thus extending your data access layer is just a matter of creating new Specification implementations and combining them with ones already existing.

And you can Create Complex Specifications, here is an example

public class WorkInProgressSpecification {

    public static Specification<WorkInProgress> findByCriteria(final SearchCriteria searchCriteria){

        return new Specification<WorkInProgress>() {

            @Override
            public Predicate toPredicate(Root<WorkInProgress> root,
                    CriteriaQuery<?> query, CriteriaBuilder cb) {

                List<Predicate> predicates = new ArrayList<Predicate>();

                if(searchCriteria.getView()!=null && !searchCriteria.getView().isEmpty()){
                    predicates.add(cb.equal(root.get("viewType"), searchCriteria.getView()));
                }if(searchCriteria.getFeature()!=null && !searchCriteria.getFeature().isEmpty()){
                    predicates.add(cb.equal(root.get("title"), searchCriteria.getFeature()));
                }if(searchCriteria.getEpic()!=null && !searchCriteria.getEpic().isEmpty()){
                    predicates.add(cb.equal(root.get("epic"), searchCriteria.getEpic()));
                }if( searchCriteria.getPerformingGroup()!=null && !searchCriteria.getPerformingGroup().isEmpty()){
                    predicates.add(cb.equal(root.get("performingGroup"), searchCriteria.getPerformingGroup()));
                }if(searchCriteria.getPlannedStartDate()!=null){
                        System.out.println("searchCriteria.getPlannedStartDate():" + searchCriteria.getPlannedStartDate());
                    predicates.add(cb.greaterThanOrEqualTo(root.<Date>get("plndStartDate"), searchCriteria.getPlannedStartDate()));
                }if(searchCriteria.getPlannedCompletionDate()!=null){
                    predicates.add(cb.lessThanOrEqualTo(root.<Date>get("plndComplDate"), searchCriteria.getPlannedCompletionDate()));
                }if(searchCriteria.getTeam()!=null && !searchCriteria.getTeam().isEmpty()){
                    predicates.add(cb.equal(root.get("agileTeam"), searchCriteria.getTeam()));
                }

                return cb.and(predicates.toArray(new Predicate[]{}));
            }
        };
    }
}

Here is the JPA Respositories docs

iamiddy
  • 3,015
  • 3
  • 30
  • 33