I'm trying to display a table listing the Country codes (iso3166) in a postgresql db onto an html page using Spring Boot and Angular, the parameter name in the http response lists "number" when instead I want it to list "nbr".
The SQL table has 4 columns
name (varchar) unique
alpha2 (varchar) PK unique
alpha3 (varchar) unique
nbr (int4)
My Spring Boot Models is the following:
#Entity
#Table(name = "iso3166")
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private String alpha2;
#Column(name = "name")
private String name;
#Column(name = "alpha3")
private String alpha3;
#Column(name = "nbr")
private int nbr;
public Country()
{
}
public Country(String name, String alpha2, String alpha3, int nbr)
{
this.name = name;
this.alpha2 = alpha2;
this.alpha3 = alpha3;
this.nbr = nbr;
}
/*
getters and settings + toString()
*/
The repository uses JPARepository
public interface ICountryRepository extends JpaRepository<Country, String> {
}
And the Controller has only the findAll() method
#RestController
#RequestMapping({"/api"})
public class CountryController {
#Autowired
ICountryRepository countryRepository;
#GetMapping
public List<Country> findAll(){
List<Country> country = countryRepository.findAll();
return country;
}
}
Running spring-boot and opening localhost in chrome, the table shows up just fine.
However, looking at the Response tab under Network, it shows up like this
Shows the same thing if I go to http://localhost:8080/api
[{"alpha2":"AX","name":"AALAND ISLANDS","alpha3":"ALA","number":248},{"alpha2":"AF","name":"AFGHANISTAN","alpha3":"AFG","number":4},{"alpha2":"AL","name":"ALBANIA","alpha3":"ALB","number":8},{"alpha2":"DZ","name":"ALGERIA","alpha3":"DZA","number":12},{"alpha2":"AS","name":"AMERICAN SAMOA","alpha3":"ASM","number":16},{"alpha2":"AD","name":"ANDORRA","alpha3":"AND","number":20},{"alpha2":"AO","name":"ANGOLA","alpha3":"AGO","number":24},
Why does the Http Response return the "nbr" field as "number" instead? And how can I change it to show up as "nbr" in the Http response? Does something happen in the background in Spring Boot when formulating the http response that I can't control?
It is a number because you defined it as a number here, in Country entity:
#Column(name = "nbr")
private int nbr;
The best solution is to create another object which is used for HTTP communication. For example: CountryDTO.
In CountryDTO you can define nbr field as String.
Then you just have to create a mapping between Country and CountryDTO objects.
Why you should always do like this:
You should never send Entities directly to the client.
It keeps your code clean and separated: One object is responsible for holding the database model, and another is responsible for communicating with the client. It is now the same, and it is just pure luck.
Found out what happened, although I don't know the specifics.
spring uses Jackson to serialize, and Jackson uses by default public getters to serialize and name data.
Since I named the nbr getter/setters as getNumber() and setNumber, changing it to getNbr() and setNbr() respectively solved the issue.
Related
I am trying to understand how spring data converts my POJO to the API params/body and return type. My goal is to have a save endpoint that takes in non generated fields only, but returns all fields. As an example, consider the following class:
#Node
public class Person {
#Id #GeneratedValue(generatorClass = UUIDStringGenerator.class) private String id;
private String firstname, lastname;
private String zip;
}
with acommpanying repository
public interface PersonRepository extends CrudRepository<Person, String> {}
I don't need the POST /persons endpoint to accept id or zip, only firstName and lastName. However I would like it to return all fields on a successful save.
The first thing I noticed is that if I:
Add getters, but not setters for id and zip and
Add getters and setters for firstName and lastName
The generated open api docs request body has this format
{
"id": "string",
"firstname": "string",
"lastname": "string",
"zip": "string"
}
and if i set id or zip in the request, it will somehow set the properties in the DB, despite both fields being private and having no setters. How is this possible?
I came up with this solution, which works, but it is reliant on id and zip being included in the constructor args but intentionally being ignored in the method body.
#Node
#Setter
#Getter
public class Person {
#Id #GeneratedValue(generatorClass = UUIDStringGenerator.class) private String id;
private String firstname, lastname;
private String zip;
Person(String id, String firstname, String lastname, String zip) {
// If I uncomment the below line, it will override the id with the one passed in.
// this.id = id;
this.firstname = firstname;
this.lastname = lastname;
}
}
This solution works because of the object population hierarchy, it will default to using my all args constructor. But it is not ideal because:
I could easily make a mistake and allow end users to set a custom id.
The API docs request body still asks for id and zip, but setting them now just does nothing
The lack of a no args constructor means the find method fails to cast the result back into a Person object
What is the recommended way to have my api only accept a subset of fields on create, ignoring those that I do not supply when writing to the data store, and then return a different subset of fields as the response. Are projections the recommended way to handle custom responses on default CrudRepository methods?
NB: I am aware that I need to use config.exposeIdsFor(Person.class); and am doing so. I am also awa
I have an entity 'Product' and I want the primary key in ES to be used as a combination of 'id' and 'name' attributes. How can we do that using spring data elastic search.
public class Product {
#Id
private String id;
#Id
private String name;
#Field(type = FieldType.Keyword)
private Category category;
#Field(type = FieldType.Long)
private double price;
#Field(type = FieldType.Object)
private List<ValidAge> age;
public enum Category {
CLOTHES,
ELECTRONICS,
GAMES;
}
}
One way to achieve this would be the following:
first rename your id property, I changed it to documentId here. This is necessary, because in Spring Data
Elasticsearch an id-property can be either annotated with #Id or it can be namend id. As there can only be one
id-property we need to get this out of the way. It can have the name id in Elasticsearch, set by the #Field
annotation, but the Java property must be changed.
second, add a method annotated with #Id and #AccessType(AccessType.Type.PROPERTY) which returns the value you
want to use in Elasticsearch.
third, you need to provide noop-setter for this property. This is necessary because Spring Data Elasticsearchsoe
not check the id property to be read only when populating an entity after save or when reading from the index.
This is a bug in Spring Data Elasticsearch, I'll create an issue for that
So that comes up with an entity like this:
#Document(indexName = "composite-entity")
public class CompositeEntity {
#Field(name="id", type = FieldType.Keyword)
private String documentId;
#Field(type = FieldType.Keyword)
private String name;
#Field(type = FieldType.Text)
private String text;
#Id
#AccessType(AccessType.Type.PROPERTY)
public String getElasticsearchId() {
return documentId + '-' + name;
}
public void setElasticsearchId(String ignored) {
}
// other getter and setter
}
The repository definition would be straight forward:
public interface CompositeRepository extends ElasticsearchRepository<CompositeEntity,
String> {
}
Remember that for every method that needs an Elasticsearch Id, you'll need to create like it's done in the entity
class.
I am not sure about spring data elasticsearch but spring jpa provides the facility of defining composite primary key by using #IdClass where we can define a separate class(let us say class A) in which we can define all the fields which we want to be a part of composite key Then we can use #IdClass(A.class) in entity class and use #Id annotation on all the fields which should be the part of the composite key
you can refer to this article, although I am not sure whether the same concept will be applicable for spring data es - https://www.baeldung.com/jpa-composite-primary-keys
I'm currently creating a microservice with spring boot and mysql to manage information about auctions. I have created a Bid-object and an Offer-object. Next to some properties of bid and offers, the most important thing here is the OneToMany-Relationship between Offer and Bid, since obviously every offer can have multiple related Bids.
I use the default JpaRepository-Interface for my database interactions, and tested my database structure by entering data and testing if I would get the correct output. This all worked fine, but when I tried to test the endpoints of my service that entered the data, I got some curious behaviour. First of all, here's my structure, so you can keep up with what I'm talking about. These are (shortened) versions of my Bid and Offer-Objects:
#Entity
#Data
public class Bid {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#NotNull
private String bidderUUID;
#NotNull
#JsonBackReference
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "offerId")
private Offer offer;
#NotNull
private Integer amount;
private Boolean hasWon;
}
#Entity
#Data
public class Offer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
private String creatorUUID;
#JsonManagedReference
#OneToMany(mappedBy = "offer")
List<Bid> bids;
}
This is my very simple repsitory and controller:
public interface BidRepository extends JpaRepository<Bid, Long> {
}
#RestController
#RequestMapping("/bid")
public class BidController {
#Autowired
private BidRepository bidRepository;
#GetMapping("/bids")
public List<Bid> getAllBids() {
return bidRepository.findAll();
}
#PostMapping("/add")
public void createBid(#RequestBody Bid request) {
bidRepository.saveAndFlush(request);
}
}
With and offer with the id 27 in the database I proceeded to send a bid to the service.
I'm using postman to test my requests, and this is what I put in my request body, when adressing the endpoint localhost:8080/bid/add:
{
"amount": 2,
"bidderUUID": "eine uuid",
"offerId": 27
}
I received a 200 OK response, and thought, seems fine, but the data in the database is wrong, since it looks like this:
The offer is missing, even though the ID 27 definitely exists. Also, when I'm entering 27 manually and pushing it to the database, the data is correctly recognized.
I think this problem has something to do with the fact, that I expect an offer-object when posting the new bid, but only give him the ID, but when I enter the entire object, I get an org.hibernate.PersistentObjectException: detached entity passed to persist.
How can I make spring accept the id of the offer, when transmitting a new bid object?
This happed to me also, so as the solution I added the mapping column as Entity variable. In your case if I say, your Bid entity is missing the offer_id column mapping, although it mentioned the relationship. Add below entry in your Bid table as below:
#Column(name = "offer_id")
private Long offerId;
// generate setter-getter
add referencedColumn on the #JoinColumn annotation.
#JoinColumn(name = "offerId", referencedColumn = "id")
private Offer offer;
I'm building a rest API using Spring Boot rest services.
I have a Java class:
class Person{
int id;
#notNull
String name;
#notNull
String password;
}
And I want to make an API to create a Person object. I will recieve a POST request with json body like:
{
"name":"Ahmad",
"password":"myPass",
"shouldSendEmail":1
}
As you can see there are an extra field "shouldSendEmail" that I have to use it to know if should I send an email or not after I create the Person Object.
I am using the following API:
#RequestMapping(value = "/AddPerson", method = RequestMethod.POST)
public String savePerson(
#Valid #RequestBody Person person) {
personRepository.insert(person);
// Here I want to know if I should send an email or Not
return "success";
}
Is there a method to access the value of "shouldSendEmail" while I using the autoMapping in this way?
There's many options for you solve. Since you don't want to persist the shouldSendEmail flag and it's ok to add into you domain class, you can use the #Transient annotation to tell JPA to skip the persistence.
#Entity
public class Person {
#Id
private Integer id;
#NotNull
private String name;
#NotNull
private String password;
#Transient
private Boolean shouldSendEmail;
}
If you want more flexible entity personalizations, I recommend using DTO`s.
MapStruct is a good library to handle DTO`s
You will need an intermediary DTO, or you will otherwise have to modify person to include a field for shouldSendEmail. If that is not possible, the only other alternative is to use JsonNode and manually select the properties from the tree.
For example,
#Getter
public class PersonDTO {
private final String name;
private final String password;
private final Integer shouldSendEmail;
#JsonCreator
public PersonDTO(
#JsonProperty("name") final String name,
#JsonProperty("password") final String password,
#JsonProperty("shouldSendEmail") final Integer shouldSendEmail
) {
this.name = name;
this.password = password;
this.shouldSendEmail = shouldSendEmail;
}
}
You can use #RequestBody and #RequestParam together as following
.../addPerson?sendEmail=true
So send the “sendEmail” value as request param and person as request body
Spring MVC - Why not able to use #RequestBody and #RequestParam together
You have mutli solutions
1 - You can put #Column(insertable=false, updatable=false) above this property
2 - send it as request param #RequestParam
#RequestMapping(value = "/AddPerson", method = RequestMethod.POST)
public String savePerson(
#Valid #RequestBody Person person, #RequestParam boolean sendMail) {}
3- use DTO lets say PersonModel and map it to Person before save
I am making a simple CRUD application using Spring boot and MongoDB, the problem that I am facing is that I don't know how to define the model classes.
My application should be like this:
A site has some characteristics such as an ID, region, city, ... and contains 4 parts (cellulars) that each has its own characteristics. Any help would be appreciated.
This is what I have so far:
public class Site {
#Id
String siteId;
String projectPhase;
String region;
String city;
String siteName;
String newSiteName;
String clusterName ;
String longitude ;
String lattitude ;
#OneToMany(mappedBy = "siteId")
List L;
What I want to know is how do I associate another class inside this one.
Annotations like #OneToMany are typically used within JPA-context, and are unnecessary when using Spring Data MongoDB. This is also mentioned by the documentation:
There’s no need to use something like #OneToMany because the mapping framework sees that you want a one-to-many relationship because there is a List of objects.
You have a few options when you want to define one-to-many relations when using MongoDB. The first of them is to define them as embedded objects within the same document:
#Document
public class Site {
#Id
private String id;
private String city;
private String region;
private List<Part> cellulars;
}
public class Part {
private String characteristic1;
private String characteristic2;
}
This means that the parts do not exist on their own, so they don't need their own ID either.
Another possibility is to reference to another document:
#Document
public class Site {
#Id
private String id;
private String city;
private String region;
#DBRef
private List<Part> cellulars;
}
#Document
public class Part {
#Id
private String id;
private String characteristic1;
private String characteristic2;
}
In this case, parts are also separate documents, and a site simply contains a reference to the part.