SpringBoot concatenate search parameters browser url - spring-boot

I am starting working with Spring Boot. My aim is to make a limited search retrieving data from a database. I want to add multiple parameters in the query of the url.
So far I was able using the seek: http://localhost:8080/wsr/search/, to get a full search of the data in the database. But what I want is delimit the search under several conditions adding parameters in the url in the browser as for instance:
http://localhost:8080/data/search/person?name=Will&address=Highstreet&country=UK
http://localhost:8080/data/search/person?name=Will&name=Angie
http://localhost:8080/data/search/person?name=Will&name=Angie&country=UK
The problem I found is that I can't find the way to work with more than one condition. The only thing I got to make it work, is:
http://localhost:8080/data/search/person?name=Will
I surfed the web but no results for this exact problem, too much information but impossible to find this.
The code I have is:
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "address")
private String address;
#Column(name = "country")
private String country;
public Value() {
}
public Value(int id, String name, String address, String country) {
this.id = id;
this.name = name;
this.address = address;
this.country = country;
}
//all getters and setters
}
public class Implementation {
#Autowired
private DataBase dataBase;
public List<Value> findById(#PathVariable final int id) {
return dataBase.findById(id);
}
public List<Value> findByName(#PathVariable final String name) {
return dataBase.findByName(name);
}
public List<Value> findByAddress(#PathVariable final String address) {
return dataBase.findByAddress(address);
}
public List<Value> findByCountry(#PathVariable final String country) {
return dataBase.findByCountry(country);
}
}
//#Component
#RepositoryRestResource(collectionResourceRel = "person", path = "data")
public interface DataBase extends JpaRepository<Value, Integer>{
public List<Value> findAll();
#RestResource(path = "ids", rel = "findById")
public List<Value> findById(#Param("id") int id) throws ServiceException;
#RestResource(path = "name", rel = "findByName")
public List<Value> findByName(#Param("name") String name) throws ServiceException;
#RestResource(path = "address", rel = "findByAddress")
public List<Value> findByAddress(#Param("address") String address) throws ServiceException;
#RestResource(path = "country", rel = "findByCountry")
public List<Value> findByCountry(#Param("country") String country) throws ServiceException;
}
Hope you can help me putting me in the correct way of what should do or is wrong. If possible some code will also be highly appreciated.
Best regards

You can use #RequestParam("nameParameter")annotation to map all the parameters you want. Let's say you have url like :
http://localhost:8080/data/search/person?name=Will&country=UK
then you can have an api like:
...
#RequestMapping(value = "/person")
public String api(#RequestParam("name") String name, #RequestParam("country") String country)
...

Related

JPA: How can I read particular fields of an Entity?

I use Spring JPA ( Hibernate ) and have bunch of entities which are mapped onto tables.
When I use an entity to write I need many fields in it (see an example below). But when I read, I wanna sometimes read only particular fields like first/last name. How can I perform it using Spring data JPA ? ( because due to CrudRepository nature it returns the whole entity)
#Entity
#Table(name="PERSON")
#AttributeOverride(name = "id", column = #Column(name = "ID_PERSON"))
public class Person extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name="LAST_NAME", length = 100, nullable = false)
private String lastName;
#Column(name="FIRST_NAME", length = 50, nullable = false)
private String firstName;
#Column(name="MIDDLE_NAME", length = 50)
private String middleName;
#Column(name="BIRTHDAY", nullable = false)
#Temporal(value = TemporalType.DATE)
private Date birthday;
#ManyToOne(fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "ID_SEX")
private Sex sex;
public Person() {
super();
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Sex getSex() {
return sex;
}
public void setSex(Sex sex) {
this.sex = sex;
}
}
There are various possibilities.
With Spring Data JPA you can use projection (that's the name when you only select certain fields/columns of an entity/table).
You can return List of Object[] or a DTO or an Interface.
For example with interface it looks like this:
interface NamesOnly {
String getFirstname();
String getLastname();
}
interface PersonRepository extends Repository<Person, UUID> {
Collection<NamesOnly> findByLastname(String lastname);
}
As you can see the return value most not be of type Person.
Please check out the documentation:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
I was faced with a similar issue and I resorted to this:
Let's say you have your entity FooEntity related to repository FooRepository
To only get certain fields, let's say firstName and lastName using key I had to create a custom query in the FooRepository
In this manner
#Query("select new FooEntity(f.firstName, f.lastName) from FooEntity f where f.key = :key")
Optional<FooEntity> findCustomByKey(#Param("key") BigInteger key);
You also have to ensure that the FooEntity has the constructor accepting the values that you only want to be set or returned in this manner:
public FooEntity(String firstName, String lastName){
// Ensure the constructor is not called with null values
notNull(firstName, "Method called with null parameter (firstName)");
notNull(lastName, "Method called with null parameter (lastName)");
this.firstName = firstName;
this.lastName = lastName;
}
Please the full code below:
public class FooEntity implements Serializable {
#Id
#Column(name = "key")
private BigInteger key;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "birth_date")
private Date birthDate;
#Column(name = "hash")
private String hash;
public FooEntity(String firstName, String lastName){
// Ensure the constructor is not called with null values
notNull(firstName, "Method called with null parameter (firstName)");
notNull(lastName, "Method called with null parameter (lastName)");
this.firstName = firstName;
this.lastName = lastName;
}
// Getters and Setters
}
public interface FooRepository extends JpaRepository<FooEntity, BigInteger>{
#Query("select new FooEntity(f.firstName, f.lastName) from FooEntity f where f.key = :key")
Optional<FooEntity> findCustomById(#Param("key") BigInteger key); // This one only returns two set fields firstName and LastName and the rest as nulls
Optional<FooEntity> findById(BigInteger key) // This one returns all the fields
}

I've a field which is not primary key id. how can i fetch data using JPA repository through that non primary key?

I've a model User. There's a field which is contact and it's a non primary key. How can i fetch data using this key? it's an unique key.
This is my model.
#Entity
#Table(name = "tbl_user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
long userId;
#Column(name = "name")
String name;
#Column(name = "email")
String email;
#Column(name = "contact")
String contact;
#Column(name = "category")
String category;
public long getUserId() {
return userId;
}
public void setUserId(long userId) {
this.userId = userId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getContact() {
return contact;
}
public void setContact(String contact) {
this.contact = contact;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public User() {
}
}
This is the method inside Service layer.
public User getUserByContact(String contact) {
Optional<User> result = userRepository.findByContact(contact);
User user = result.get();
return user;
}
This is the repository.
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
#Query("select u from User u where u.contact = ?1")
User findByContact(String contact);
}
I'm getting an error on "select u from User u where u.contact = ?1" this portion under User and it's saying "cant resolve symbol User". Would appreciate any sort of help.
Leave out the #Query part, that part is not needed if you extend a JpaRepository. As stated in the documentation JPA derives the query from the method name.
I'm not sure whether this is an issue, but in your entity class you use a long for id and in your repository definition (JpaRepository<User, Long>) you use a Long. Correct me if this is not problematic.
If you want to use #Query, then the right approach would be to use #Param to define the variable
#Query("select u from User u where u.contact = :contactVar ", nativeQuery = true)
User findByContact(#Param("contactVar")String contactVar);

How to cache only when the json is valid

I have a spring rest api application that is using HATEOAS/PagingAndSortingRepository to do most of the heavy lifting.
I have implemented caching using guava but I am having issues where when the user cancels the request midway through an api call, it caches the incomplete json and re-serves it for 60 seconds.
I am trying to use the unless="" parameter of the #Cacheable annotation. Previously, I just used unless="#result == null" but that does not handle incomplete or invalid json.
This does not seem to work either. So now I am trying to use com.google.gson.JsonParser to parse the result and invalidate if applicable.
Repository
#RepositoryRestResource(path = "products", collectionResourceRel = "products")
public interface ProductEntityRepository extends PagingAndSortingRepository<ProductEntity, String> {
JsonParser parser = new JsonParser();
#Cacheable(value = CacheConfig.STORE_CACHE)
ProductEntity findByName(String name);
}
Cache Config
public final static String PRODUCTS_CACHE = "products";
#Bean
public Cache productsCache() {
return new GuavaCache(PRODUCTS_CACHE, CacheBuilder.newBuilder()
.expireAfterWrite(60, TimeUnit.SECONDS)
.build());
}
How do I detect invalid json in the unless="" parameter?
I figured out my own issue!
When I interrupted the api request to localhost/products and re-requested, I finally saw an error about not being able to fetch a onetomany mapping. I believe the error was lazy initialization error for a collection.
I solved this issue by adding #LazyCollection(LazyCollectionOption.FALSE) to my models where the #OneToMany and #ManyToOne mappings were decalared.
For example:
#Entity(name = "product")
#Table(name = "products", schema = "${DB_NAME}", catalog = "")
public class ProductEntity {
private Integer id;
private String name;
private List shipments = new ArrayList<>();
#Id
#Column(name = "id", nullable = false)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#Basic
#Column(name = "name", nullable = false, length = 10)
public String getName() { return name; }
public void setName(String name) {
this.name = name;
}
#OneToMany(mappedBy = "shipmentID", targetEntity=ShipmentEntity.class)
#LazyCollection(LazyCollectionOption.FALSE)
public Collection<ShipmentEntity> getShipments() { return shipments; }
public void setShipments(Collection<ShipmentEntity> shipments) { this.shipments = shipments; }
}

How I can get one column value from a table?

I use Spring boot and Spring Data.
I have a contact entity with the id and firstName columns.
#Entity
#Table(name = "Contact")
public class Contact {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private int id;
private String firstName;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
I use repository with Spring Data to find my data.
public interface contactRepository extends CrudRepository<Contact, Long> {
}
My controller, example with getAll:
#RequestMapping(value = "/getAllContact", produces = "application/json")
public List<Contact> getAllClients(){
return repo.getAll();
}
My controller works but I don't know how to return all values in column firstName in my controller. I tried with a query, It works but it only returns a list of values and not the json:
#Query(value = "SELECT firstName FROM Contact" )
List<Contact> findAllFirstName();
Example:
["Pierre", "Jean"]
And i want this (in Json):
[{"firstName ": "Pierre" },{"firstName ":"Jean"}]
How do I do this?
Use the projection and excerpt support in Spring Data Rest. Whilst adding in JsonIgnore annotations does work, it's inflexible as you can only ignore at compile time not run time.
See
http://docs.spring.io/spring-data/rest/docs/current/reference/html/#projections-excerpts
Make sure jackson libraries are in your classpath. Then add #ResponseBody in your controller method to return json output. Also add #JsonIgnore in id in your entity to exclude it from json output.
#RequestMapping(value = "/getAllContact", produces = "application/json")
#ResponseBody
public List<Contact> getAllClients(){
return repo.getAll();
}
#Entity
#Table(name = "Contact")
public class Contact {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
#JsonIgnore
private int id;
private String firstName;
.....
}

Spring Data REST and custom entity lookup (Provided id of the wrong type)

I have a model that looks something like this:
#Entity
public class MyModel {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(unique = true, nullable = false)
#RestResource(exported = false)
private int pk;
#Column(unique = true, nullable = false)
private String uuid = UUID.randomUUID().toString();
#Column(nullable = false)
private String title;
public int getPk() {
return pk;
}
public void setPk(int pk) {
this.pk = pk;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
As you can see I have an auto-incrementing PK as my ID for the model, but also a random UUID. I want to use the PK in the database as the primary key, but want to use the UUID as a public facing ID. (To be used in URLs etc.)
My repository looks like this:
#RepositoryRestResource(collectionResourceRel = "my-model", path = "my-model")
public interface MyModelRepository extends CrudRepository<MyModel, String> {
#RestResource(exported = false)
MyModel findByUuid(#Param("uuid") String id);
}
As you can see I've set the repository to use a String as the ID.
Finally I set the entity lookup in a config file like this:
#Component
public class RepositoryEntityLookupConfig extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.withEntityLookup().forRepository(MyModelRepository.class, MyModel::getUuid, MyModelRepository::findByUuid);
}
}
This works perfectly well for GET and POST requests, but for some reason I get an error returned on PUT and DELETE methods.
o.s.d.r.w.RepositoryRestExceptionHandler : Provided id of the wrong type for class MyModel. Expected: class java.lang.Integer, got class java.lang.String
Anyone know what might be causing this? I don't understand why it's expecting an Integer.
I may be doing something stupid as I'm quite new to the framework.
Thanks for any help.
The identifier of your domain object is obviously of type int. That means, your repository needs to be declared as extends CrudRepository<MyModel, Integer>.

Resources