I've got some difficulties with my JPA Rest Project.
I have build my repositories for each of my entity (my tables in my database), and it works fine.
For example, a part of my entity "Personne" :
#Entity
public class Personne {
private Long id;
private String nom;
private String prenom;
private Date dateNaissance;
private String telDomicile;
private String telPortable;
private String telAutre;
private String telCommentaire;
private String fax;
private String mail;
private String commentaire;
private Timestamp dateSuppr;
private String sexe;
private Patient patientById;
private Adresse adresseByAdresseId;
#Id
#JsonProperty(value = "dataId")
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
And myRepository with a #Query :
#Transactional
#RepositoryRestResource(collectionResourceRel = "personne", path = "personne", excerptProjection = InlinePersonne.class)
public interface PersonneRepo extends JpaRepository<Personne, Long> {
#Query("from Personne p where p.nom = ?1 and p.prenom = ?2")
public Personne customRequest(String nom, String prenom);
}
My problem : the return result is always a type "Personne".
I would like to make a native request that sends me back an object, with customized properties.
Example of the wished return :
{object :
{name : String,
surname : String,
age : int },
adresse :{
city : String,
street : String
}
}
Is it possible to do that ?
I really need it because I have to make complex requests on many tables.
Thank you.
You could use interface-base projections:
First you create interfaces that reflect the fields you need:
interface PersonSummary {
String getName();
String getSurename();
int getAge();
AddressSummary getAddress();
interface AddressSummary {
String getCity();
String getStreet();
}
}
Then you indicate your custom query what interface it needs to extend and instantiate to populate the information:
public interface PersonneRepo extends JpaRepository<Personne, Long> {
// All your other abstract method
// Brand new query
#Query("Select p.name, p.surname, p.age, p.city, p.street from Personne p where p.nom = ?1 and p.prenom = ?2")
public PersonSummary customRequest(String nom, String prenom);
}
You would be receiving an object like this:
{
name : String,
surname : String,
age : int,
address :{
city : String,
street : String
}
}
You would need to test how flexible is this functionality in the terms of the composition complexity of the object you want to receive.
Related
Assume we have entity Animal. There are animals in DB with 'amount' = null, it's a valid case to save animal without the 'amount'.
Is there a way to convert field 'amount' to 0 in case it's null in query?
The simplest workaround seems to convert amount null to '0' earlier
when saving, but it's not allowed.
As another workaround we can do this mapping to '0' after fetching
it from the repository. When sorting by amount in asc order, null values will be at the beginning, in desc order they will be at the end. And after
converting to '0' everything will be at the right place. But it seems that can cause problems with pagination in future
What is the proper way to do it in Query?
Spring Data Jpa 2.2.9.RELEASE, Postgresql 42.2.16.
#Repository
public interface AnimalRepository extends JpaRepository<AnimalEntity, Long> {
#Query(value = "SELECT animal FROM AnimalEntity animal" +
" WHERE animal.ownerId = :ownerId" +
" and function('replace', upper(animal.name), '.', ' ') like function('replace', upper(concat('%', :name,'%')), '.', ' ') "
)
Page<AnimalEntity> findAllLikeNameAndOwnerSorted(String ownerId, String name, Pageable pageable);
}
#Entity
#Table(name = "animal")
public class AnimalEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Integer amount;
private String name;
private String ownerId;
}
UPDATE
Also important to mention. The solution I suggested with replacing nulls with zero is incorrect, because of the different null ordering in Postgresql and HSQLDB.
But it will work in tests, if you're using HSQLDB.
Animal entities in DB test sample: [
Animal(name=Cat, amount=599999.99),
Animal(name=Dog, amount=null),
Animal(name=John, amount=5000)
]
Hsqldb amount desc query result:
[
Animal(name=Cat, amount=599999.99),
Animal(name=John, amount=5000),
Animal(name=Dog, amount=null)
]
Postgresql amount desc query result:
[
Animal(name=Dog, amount=null)
Animal(name=Cat, amount=599999.99),
Animal(name=John, amount=5000)
]
The JPA supports the COALESCE function. Thus you can set up the desired value via this function.
SELECT COALESCE(amount,0) AS desiredAmount FROM AnimalEntity animal
The code should look like this:
#Entity
#Table(name = "animal")
public class AnimalEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer amount;
public AnimalEntity() {
}
public AnimalEntity(Integer amount, String name) {
this.amount = amount;
this.name = name;
}
public Long getId() {
return id;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And the repository:
#Repository
public interface AnimalRepository extends JpaRepository<AnimalEntity, Long> {
#Query(
value = "SELECT animal.id AS id, COALESCE(animal.amount,0) AS amount, UPPER(animal.name) AS name FROM animal animal WHERE animal.name = :name",
nativeQuery = true)
Page<AnimalEntity> findAllLikeNameAndOwnerSorted(String name, Pageable pageable);
}
Also I have prepared the test:
#SpringBootTest
class AnimalRepositoryTest {
#Autowired
private AnimalRepository animalRepository;
#Test
void findAllLikeNameAndOwnerSorted() {
AnimalEntity animalEntity = new AnimalEntity(null, "dog");
animalRepository.save(animalEntity);
AnimalEntity animalEntity2 = new AnimalEntity(1, "CAT");
animalRepository.save(animalEntity2);
System.out.println(animalEntity2.getId());
Pageable sortedByName = PageRequest.of(0, 3, Sort.by("id"));
Page<AnimalEntity> animals = animalRepository.findAllLikeNameAndOwnerSorted("dog", sortedByName);
animals.forEach(System.out::println);
}
}
You can check the commit: https://gitlab.com/chlupnoha/meth/-/commit/76abbc67c33b2369231ee89e0946cffda0460ec9 - it is experiment project.
I have a simple node like this below
#Document("users")
public class User {
#Id // db document field: _key
private String id;
#ArangoId // db document field: _id
private String arangoId;
private String firstName;
private String lastName;
private String country;
public User() {
super();
}
public User(String id) {
this.id = id;
}
public User(String id, String country) {
this.id = id;
this.country = country;
}
// getter & setter
#Override
public String toString() {
return "User [id=" + id + ", name=" + firstName + ", surname=" + lastName + "]";
}
public String getId() {
return id;
}
}
here is the repository class but the method getListOfCountryAndNumUsers returns null even though i have inserted users with different countries into the database.
public interface UserRepository extends ArangoRepository<User, String> {
#Query("FOR u IN users COLLECT country = u.country WITH COUNT INTO length RETURN
{\"country\" : country, \"count\" : length }")
Iterable<CountryAndNumUsers> getListOfCountryAndNumUsers();
}
I think the problem could be with the the syntax of my query in the query annotation. I didnt see any direct example of using collect operation in the spring data arango db part of arangodb documentation here but I saw the collect operation in the section "high level operations" of arangoDb documentation here
Please Help. Thanks. !
So I discovered my error. It was in a class I didn't add in the question. That is the class for the return object of the method getListOfCountryAndNumUsers()
i.e class CountryAndNumUsers.
public class CountryAndNumUsers {
private String country;
private Integer numberOfUsers;
public CountryAndNumUsers(String country, Integer numberOfUsers) {
this.country = country;
this.numberOfUsers = numberOfUsers;
}
public String getCountry() {
return country;
}
public Integer getNumberOfUsers() {
return numberOfUsers;
}
}
so there was a mapping mismatch since the query returns an object with different field names. I changed the query to this below so that it matches
#Query("FOR u IN users COLLECT country = u.country WITH COUNT INTO length RETURN {\"country\" : country, \"numberOfUsers\" : length }")
Is there a way in the query by example to specify which field condition needs to be fulfilled and which one not?
If I have an entity:
#Entity
public class Person extends EntityBase {
#Size(max = 50)
#NotNull
private String firstName;
#NotNull
#Size(max = 80)
private String lastName;
#NotNull
#Size(max = 100)
private Status status;
}
Is there a way that I query all persons that have a specific status and either firstName or lastName equal to something?
Something like status = Active AND (firstName = "name" OR lastName = "name")
If I do:
Example<Person> example = Example.of(new Person("name", "name", Status.Active));
repo.findAll(example);
I have possiblity with the matchers to specify whether all fields should match or any field should match, can I do combination?
If it's not possible with query by example, what other way do you recommend?
Repository.java
#Repository
public interface PersonRepository extends JpaRepository<Person, Long> {
List<Person> findByStatusAndFirstNameOrLastName(Status status, String firstName, String lastName);
}
PersonService.java
#Service
public class PersonService{
#Autowired
PersonRepository repo;
public List<Person> findByStatusAndFirstNameOrLastName(){
repo.findByStatusAndFirstNameOrLastName(Status.Active,"name","name");
}
}
I have the above domain structure where I have list of Companies in the product and the aim is not make entry in mongoDB when I have exact match for companies & productId already present in the DB.
#Entity
public class Mobile {
#Id
private Integer id;
private String imei;
private Product productInfo;
// ...
}
#Entity
public class Product {
#Id
private Integer id;
private String productId;
private List<Company<?>> companies;
// ...
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(name= "samsung", value = Samsung.class),
#JsonSubTypes.Type(name= "htc",value = Htc.class)})
public class Company<T> implements Serializable {
private static final long serialVersionUID = -8869676577723436716L;
private T companyInfo;
private String type;
// ...
}
I am using mongo template and I have tried to use find as shown below but id didn't work
template.find(Query.query(Criteria.where("product.companies").is(companList),Mobile.class);
I'm trying to update a number of fields at the same time in my "User" document. However, I only want to update some of the fields and not replace the entire document and it's the latter that I cannot seem to avoid. The method I have for doing this looks like so:
public void mergeUser(User user) {
Update mergeUserUpdate = new Update();
mergeUserUpdate.set("firstName", user.getFirstName());
mergeUserUpdate.set("lastName", user.getLastName());
mergeUserUpdate.set("username", user.getUsername());
mongoTemplate.updateFirst(new Query(Criteria.where("_id").is(user.getId())), mergeUserUpdate, User.class);
}
My user object does contain other fields - a password field being one of them - but if this was set to a value before it is promptly replaced with an empty string or removed entirely. So in the database, this:
{
"_id" : ObjectId("4fc34563c3276c69248271d8"),
"_class" : "com.test.User",
"password" : "d26b7f5c0ed888e46889dd1e3d217816d070510596f495e156e9efe4b035fec5a1fe1be643955359",
"username" : "john#gmail.com",
"alias" : "john"
}
gets replaced by this after I call the mergeUser method:
{
"_id" : ObjectId("4fc34563c3276c69248271d8"),
"_class" : "com.test.User",
"username" : "john#gmail.com",
"firstName" : "John",
"lastName" : "Doe",
"address" : {
"addressLine1" : ""
}
}
If I look at the Update object I see it contains the following:
{$set={firstName=John, lastName=Doe, username=john#gmail.com}}
This looks correct to me and from my understanding of the MongoDB $set function, this should only set the values that are specified. I was therefore expecting the password field to remain unchanged and the other fields added or altered accordingly.
As a general discussion point, I'm ultimately trying to achieve some kind of "merge" functionality whereby Spring will auto-magically check which fields are present in the supplied User object and only update the database with the values that are filled in, not all the fields. That should be theoretically possible I would have thought. Anyone know of a nice way to do this?
Here's my user object just in case:
/**
* Represents an application user.
*/
#Document(collection = "users")
public class User {
#Id
private String id;
#NotEmpty( groups={ChangePasswordValidationGroup.class} )
private String password;
#Indexed
#NotEmpty
#Email
private String username;
private String firstName;
private String lastName;
private Date dob;
private Gender gender;
private Address address;
public enum Gender {
MALE, FEMALE
}
// /////// GETTERS AND SETTERS ///////////
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 getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Date getDob() {
return dob;
}
public void setDob(Date dob) {
this.dob = dob;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Gender getGender() {
return gender;
}
public void setGender(Gender gender) {
this.gender = gender;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
The code above does work just fine. I made a silly mistake whereby after updating correctly I proceed to save the user object again, which replaces it with a new document.
Having another solution to update an Entity without some fields using Spring MongoTemplate:
DBObject userDBObject = (DBObject) mongoTemplate.getConverter().convertToMongoType(user);
//remove unnecessary fields
userDBObject.removeField("_id");
userDBObject.removeField("password");
//Create setUpdate & query
Update setUpdate = Update.fromDBObject(new BasicDBObject("$set", userDBObject));
mongoTemplate.updateFirst(new Query(Criteria.where("_id").is(user.getId())), setUpdate , User.class);
//Or use native mongo
//mongoTemplate.getDb().getCollection("user").update(new BasicDBObject("_id",user.getId())
, new BasicDBObject("$set", userDBObject), false, false);
Because it uses auto converter so is is very helpful when your entity has many fields.