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;
.....
}
Related
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 want to create a simple "You may also like" feature for a blog.
There are posts and each of them has one or more tags. Also a tag can contain many posts. I want to implement the feature where you open a post and the posts, which have similar tags, are recommended to you.
So i created 3 entities:
Post.java
#Entity
#Table
public class Post {
#Id
#Column
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String text;
#Column
private String author;
#OneToMany(mappedBy = "post")
Set<PostTags> postTags;
public Post(){}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Set<PostTags> getPostTags() {
return postTags;
}
public void setPostTags(Set<PostTags> postTags) {
this.postTags = postTags;
}
}
Tags.java
#Entity
#Table
public class Tags {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private Long id;
#Column
private String name;
#OneToMany(mappedBy = "tag")
Set<PostTags> postTags;
public Set<PostTags> getPostTags() {
return postTags;
}
public void setPostTags(Set<PostTags> postTags) {
this.postTags = postTags;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
PostTags.java
#Entity
public class PostTags {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
#Column
private Long id;
#ManyToOne
#JoinColumn(name = "post_id")
private Post post;
#ManyToOne
#JoinColumn(name = "tag_id")
private Tags tag;
public PostTags(){}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Post getPost() {
return post;
}
public void setPost(Post post) {
this.post = post;
}
public Tags getTag() {
return tag;
}
public void setTag(Tags tag) {
this.tag = tag;
}
}
And repositories:
#Repository
public interface PostTagsRepository extends JpaRepository<PostTags, Long> {
#Query("select p.post from PostTags p where p.tag.id IN :tagIds")
Set<Post> findPostsbyTagIds (List<Long> tagIds);
}
#Repository
public interface PostRepository extends JpaRepository<Post, Long> {
#Query("select p from Post p where p.author = :author")
Set<Post> findPostsByAuthor(String author);
}
I managed to create this feature in an amateurish way, but better than nothing. I piled up everything in one method just to test it:
#GetMapping("/posts")
public Set<Post> showRecommendedPosts(){
//Imitate post id
long postId = 1;
Post postFound = postRepository.findById(postId).get();
Set<PostTags> postTags = postFound.getPostTags();
List<Long> listTagIds = new ArrayList<>();
//extract ids of the tags from the post
for(PostTags tag : postTags){
listTagIds.add(tag.getTag().getId());
}
//find posts by Author
Set<Post> postsByAuthor = postRepository.findPostsByAuthor(postFound.getAuthor());
//find posts by Tags
Set<Post> postsByTagIds = postTagsRepository.findPostsbyTagIds(listTagIds);
//We combine both sets
Set<Post> recommendedPosts = new HashSet<>(postsByAuthor);
recommendedPosts.addAll(postsByTagIds);
recommendedPosts.remove(postFound);
return recommendedPosts;
}
But this works only if i manually add data to "post_tags" table in the db like this:
Here is my question, i don't know how to add multiple tags to a post in Spring. Because it would be something like this:
PostTags newPostTag1 = new PostTags();
newPostTag.setPost(post1);
newPostTag.setTag(tag1);
PostTags newPostTag2 = new PostTags();
newPostTag2.setPost(post1);
newPostTag2.setTag(tag2);
PostTags newPostTag3 = new PostTags();
newPostTag3.setPost(post1);
newPostTag3.setTag(tag3);
And so on...
Therefore, it's not an option. So how can i save tags correctly? Or have my entities been created incorrectly? What is my mistake? Thank you!
I am not sure to understand the idea behind Tag being an Entity.
How I see it is you use the postTags and change it to tags. This tags would be a Set of an enum if you want to restrict the user or a Set of String other way. After that, I would add an endpoint that return post based on a tag or a list of tags for your You may also like feature. This endpoint just make a request to the database (find posts where tags contains givenTag max 10). At the end, you only have one entity :
#Entity
#Table
public class Post {
#Id
#Column
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String text;
#Column
private String author;
#Column
#Convert(converter = StringListConverter.class)
Set<String> tags;
// ...
}
Converter implementation here
I am using the debugger in IntelliJ and right before the point of returning the result, the array is perfectly fine, as you can see here
But for some reason, the response in the browser looks like this
I don't understand why the fields are invisible.
This is what my 2 models look like:
Municipality:
#Entity
public class Municipality {
#Id
#JsonIgnore
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
}
Prediction
#Entity
public class Prediction {
#Id
#JsonIgnore
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
LocalDateTime tsPredictionMade;
LocalDateTime tsPredictionFor;
float pm10;
float pm25;
#ManyToOne
#OnDelete(action = OnDeleteAction.CASCADE)
Municipality municipality;
}
And this is my controller:
#RestController
#RequestMapping("/predict")
public class PredictionController {
private MunicipalityService municipalityService;
private PredictionService predictionService;
#Autowired
public PredictionController(MunicipalityService municipalityService, PredictionService predictionService) {
this.municipalityService = municipalityService;
this.predictionService = predictionService;
}
#GetMapping
public List<Municipality> getPredictions(){
List<Municipality> result = municipalityService.getPredictions();
return result;
}
#GetMapping("/{municipality}")
public List<Prediction> getPredictionsForMunicipality(#PathVariable("municipality") String name){
List<Prediction> result = predictionService.getPredictions(name);
return result;
}
}
The rest of the app (service and persistence layer) is pretty standard.
What is the reason for this?
You will need the getters and setters for your models. The Jackson library needs it for accessing its fields when converting the models into JSON, differently from JPA when converting the resultSet into models. Here is the code:
Prediction
#Entity
public class Municipality {
#Id
#JsonIgnore
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public LocalDateTime getTsPredictionMade() {
return tsPredictionMade;
}
public void setTsPredictionMade(LocalDateTime tsPredictionMade) {
this.tsPredictionMade = tsPredictionMade;
}
public LocalDateTime getTsPredictionFor() {
return tsPredictionFor;
}
public void setTsPredictionFor(LocalDateTime tsPredictionFor) {
this.tsPredictionFor = tsPredictionFor;
}
public float getPm10() {
return pm10;
}
public void setPm10(float pm10) {
this.pm10 = pm10;
}
public float getPm25() {
return pm25;
}
public void setPm25(float pm25) {
this.pm25 = pm25;
}
public Municipality getMunicipality() {
return municipality;
}
public void setMunicipality(Municipality municipality) {
this.municipality = municipality;
}
}
Municipality
#Entity
public class Municipality {
#Id
#JsonIgnore
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
You need getters and setter for each field that you want to expose.
You can use #Data from lombok project to avoid boilerplate code.
https://projectlombok.org/
i am try to create a one to many bidirectional mapping using spring boot and spring data jpa please look the below entity
Employer Entity
#Entity
public class Employer
{
private Long id;
private String employerName;
private List<Employee> employees;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public String getEmployerName()
{
return employerName;
}
public void setEmployerName(String employerName)
{
this.employerName = employerName;
}
#OneToMany(cascade=CascadeType.ALL, mappedBy="employer")
public List<Employee> getEmployees()
{
return employees;
}
public void setEmployees(List<Employee> employees)
{
this.employees = employees;
}
}
Employee Entity
#Entity
public class Employee
{
private Long id;
private String employeeName;
private Employer employer;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public String getEmployeeName()
{
return employeeName;
}
public void setEmployeeName(String employeeName)
{
this.employeeName = employeeName;
}
#ManyToOne(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
public Employer getEmployer()
{
return employer;
}
public void setEmployer(Employer employer)
{
this.employer = employer;
}
}
Employer Repo
public interface EmployerServices extends JpaRepository<Employer, Long> {
}
Employee Repo
public interface EmployeeServices extends JpaRepository<Employee, Long> {
}
REST Controller is
#RestController
public class Controller {
#Autowired EmployeeServices employeeServices;
#Autowired EmployerServices employerServices;
#GetMapping("/getempr")
public Object getempr(){
return employerServices.findOne(1L);
}
}
now the problem begin start see my out put
its look like a infighting loop and my server throwing error getOutputStream() has already been called for this response.
I used #JsonBackReference & #JsonManagedReference
annotation but the problem is its working like one to many
{
"id":1,
"employerName":"employer",
"employees":[
{"id":1,"employeeName":"emp1"},
{"id":2,"employeeName":"emp2"}
]
}
if I am trying to get in the concern of many to one like all employee with employer. the output is
[
{
"id":1,
"employeeName":"emp1"
},
{
"id":2,
"employeeName":"emp2"}
]
its not showing me the employer details.
please suggets me guys what i am doing wrong. thanks in advance!!
Instead of using #JsonBackReferenceand #JsonManagedReference try to use annotation #JsonIgnoreProperties:
#JsonIgnoreProperties("employer")
private List<Employee> employees;
#JsonIgnoreProperties("employees")
private Employer employer;
It prevents Jackson from rendering a specified properties of associated objects.
with the JSON its a problem with bi-directional mapping. Use the below properties.
#JsonIgnoreProperties("employer")
#JsonIgnoreProperties("employees")
please keep fetching type as eager.
hope this will work.
You can solve your issue with two modification with annotations.
Employer.class
#Entity
public class Employer {
private Long id;
private String employerName;
#OneToMany(cascade = CascadeType.ALL,
mappedBy = "employer",
orphanRemoval = true)
private List<Employee> employees;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmployerName() {
return employerName;
}
public void setEmployerName(String employerName) {
this.employerName = employerName;
}
public List<Employee> getEmployees() {
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
}
Employee.class
#Entity
public class Employee {
private Long id;
private String employeeName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "employer_id")
private Employer employer;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public Employer getEmployer() {
return employer;
}
public void setEmployer(Employer employer) {
this.employer = employer;
}
}
For more information please visit this link.
Change your getEmployer Method like this:
#ManyToOne(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
public Employer getEmployer()
{
return employer;
}
use
#JsonProperty(access = Access.WRITE_ONLY)
private List<Employee> employees;
So that it will ignore employees while printing to JSON in the response (and thus prevents the looping), but will still consider the JSON data (employee list) you pass in the request body so that it is available for persistence.
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)
...