My entity class looks like this :
#Entity
#Table(name = "tbl_programstrm_projstream")
public class ProgramStm_Projstrm_Model {
#Id
#GeneratedValue
private int id;
#Lob
#Column(columnDefinition="TEXT")
private String programstrm;
private int programstrmId;
#ManyToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH })
#JoinColumn(name = "projstrmId")//
private ProjectStreamModel projstrmId;
//getter and setter
}
Based on this entity I am getting result json like this
{
"programstrm": "D-BSS Implementation",
"programstrmId": 3,
"projstrmId": {
"name": "Program Leadership"
}
}
But I want ProjectStreamModel response as a string not as an object so that final result json looks like this"
{
"programstrm": "D-BSS Implementation",
"programstrmId": 3,
"projstrmId": "Program Leadership"
}
If I undestood right, using #JsonSerialize will probably help you (for example link)
Related
I have two entities, which are in a many to many relationship.
#Entity
public class Room {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToMany(mappedBy = "rooms")
private Set<Team> teams;
}
#Entity
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToMany
#JoinTable(name = "teams_rooms",
joinColumns = #JoinColumn(name= "team_id"),
inverseJoinColumns = #JoinColumn(name = "room_id"))
private Set<Room> rooms;
}
To yield data, i have a repository for "Room" and "Team":
public interface RoomRepository extends CrudRepository<Room, Long> {
}
public interface TeamRepository extends CrudRepository<Team, Long> {
}
My goal is to request all rooms of a team, but prevent JPA from looping infinitely.
#RestController
#RequestMapping("....")
public class RoomController {
#Autowired
private RoomRepository roomRepository;
#GetMapping
public Iterable<Room> getAllRoomsOfTeam() {
final long exampleId = 1; //This is just a placeholder. The id will be passed as a parameter.
final var team = teamRepository.findById(exampleId);
return ResponseEntity.ok(team);
}
}
This is the result:
{
"id": 1,
"name": "Team1",
"rooms": [
{
"id": 1,
"name": "Room 1",
"teams": [
{
"id": 1,
"name": "Team 1",
"rooms": [
{
"id": 1,
"name": "Room 1",
"teams": [
Jackson will loop forever, until an exception occurs (Since the back reference also references the parent element, which will create a loop).
I already tried #JsonManagedReference and #JsonBackReference, but they are used for many to one relationships.
How do i stop Jackson from looping infinitely? I want to affect other repositories and queries as little as possible.
Your controller shoud not return entities ( classes with the annotation #Entity). As a best practice is to create another separate class with same attributes. This code has a little dupplication but it keeps all the layers clean. I also suggest to use #Service.
public class RoomDTO {
private String name;
private List<TeamDTO> teams = new ArrayList<>();
public RoomDTO() {
}
public RoomDTO(Room room) {
this.name = room.name;
for(Team team : room.getTeams()) {
TeamDTO teamDTO = new TeamDTO();
teamDTO.setName(team.getName);
teams.add(teamDTO);
}
}
}
public class TeamDTO {
List<RoomDTO> rooms = new ArrayList();
public TeamDTO() {
}
public TeamDTO(Team team) {
this.name = team.name;
for(Room room : team.getRooms()) {
RoomDTO roomDTO = new RoomDTO();
roomDTO.setName(team.getName);
rooms.add(roomDTO);
}
}
}
The controller should return this
#GetMapping
public Iterable<TeamDTO> getAllRoomsOfTeam() {
final long exampleId = 1;
final var team = teamRepository.findById(exampleId);
TeamDTO teamDTO = new TeamDTO(team);
return ResponseEntity.ok(teamDTO);
}
How to use DTOs in the Controller, Service and Repository pattern
Currently, there is cyclic dependency in your classes which is causing issues while converting objects to JSON. Please add #JsonIgnore annotation on rooms variable in your Team class as shown in below example:
import com.fasterxml.jackson.annotation.JsonIgnore;
#Entity
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToMany
#JoinTable(name = "teams_rooms",
joinColumns = #JoinColumn(name= "team_id"),
inverseJoinColumns = #JoinColumn(name = "room_id"))
#JsonIgnore
private Set<Room> rooms;
}
If you need a solution for bidirectional conversion then you can use JsonView annotation.
First of all you need to create JSON view profiles for Team and Room as shown in below example:
public class JsonViewProfiles
{
/**
* This profile will be used while converting Team object to JSON
*/
public static class Team {}
/**
* This profile will be used while converting Room object to JSON
*/
public static class Room {}
}
Use above created JSON view profiles in your entities as shown in below example:
public class Room {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView({ JsonViewProfiles.Team.class, JsonViewProfiles.Room.class })
private long id;
#JsonView(JsonViewProfiles.Room.class)
#ManyToMany(mappedBy = "rooms")
private Set<Team> teams;
}
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView({JsonViewProfiles.Team.class, JsonViewProfiles.Room.class})
private long id;
#ManyToMany
#JoinTable(name = "teams_rooms",
joinColumns = #JoinColumn(name= "team_id"),
inverseJoinColumns = #JoinColumn(name = "room_id"))
#JsonView(JsonViewProfiles.Team.class)
private Set<Room> rooms;
}
While converting your object to JSON please use these profiles as shown in below example:
#GetMapping
public String getAllRoomsOfTeam() {
final long exampleId = 1; //This is just a placeholder. The id will be passed as a parameter.
final Team team = teamRepository.findById(exampleId);
String result = new ObjectMapper().writerWithView(JsonViewProfiles.Team.class)
.writeValueAsString(team);
return result;
}
well I have a doubt, i have a class like this:
#Entity(name = "movie_details")
#Getter #Setter
public class MovieDetailEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private long id;
#ManyToOne
#JoinColumn(name = "movie_id", nullable = false)
#JsonIgnoreProperties(value = {"movieDetails", "handler","hibernateLazyInitializer"}, allowSetters = true)
private MovieEntity movie;
#ManyToOne
#JoinColumn(name = "character_id", nullable = false)
#JsonIgnoreProperties(value = {"movies", "handler","hibernateLazyInitializer"}, allowSetters = true)
private CharacterEntity character;
}
And I just want to show the characters from a movie, but I don't want to show the id and the MovieEntity I just want to show CharacterEntity...
#Repository
public interface MovieDetailRepository extends JpaRepository<MovieDetailEntity, Long> {
Page<MovieDetailEntity> findByMovie(Pageable pageable, MovieEntity movie);
}
And this query return a json like this
"content": [
{
"id": ...,
"movie": {
...
},
"character": {
...
}
}
],
I was reading about making an interface dto with getters but didn't work for me
It sounds like you want to turn your entity into a DTO object that you can return. You'd need to create a new object like
public class MovieDetailDto {
private CharacterDto;
}
You would then probably want to implement a setter and getter method for CharacterDto and create the CharacterDto class similarly to the MovieDetailDto class with the fields from CharacterEntity you want to return with setters and getters for them.
When you get back a MovieDetailEntity from your repository you can then just create a new CharacterDto for the CharacterEntity and set it in a new MovieDetailDto.
I have could solve it, i create a interface projection, like this...
public interface MovieCharactersDto {
CharacterEntity getCharacter();
interface CharacterEntity {
Long getId();
String getName();
String getImage();
BigDecimal getWeight();
Integer getAge();
String getStory();
}
}
And the query was as follows:
#Repository
public interface MovieDetailRepository extends JpaRepository<MovieDetailEntity, Long> {
Page<MovieCharactersDto> findByMovie(Pageable pageable, MovieEntity movie);
}
I have the following json that I would like to store it into java object:
{
"shippingDetails":{
"address":"khalda",
"country":"Jordan",
"town":"amman"
},
"product":[
{
"product":{
"id":2,
"name":"Wrap Dress",
"price":330,
"salePrice":165,
"discount":50,
"pictures":[
"assets/images/fashion/product/1.jpg",
"assets/images/fashion/product/1.jpg",
"assets/images/fashion/product/1.jpg",
"assets/images/fashion/product/1.jpg"
],
"shortDetails":"test",
"description":"test",
"stock":2,
"new":true,
"sale":false,
"category":"women",
"colors":[
"gray",
"orange"
],
"size":[
"M",
"L",
"XL"
],
"tags":[
"caprese",
"puma",
"lifestyle"
],
"variants":[
{
"color":"gray",
"images":"assets/images/fashion/product/1.jpg"
},
{
"color":"orange",
"images":"assets/images/fashion/product/1.jpg"
}
]
},
"quantity":1
}
],
"totalAmount":330
}
And I have the following java beans for it:
The main object class called Order and it has
#Data
#Entity
#Table(name = "orders")
public class Order {
#Id
#Column(name="shippingDetailsId")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToOne(cascade = {CascadeType.ALL})
private ShippingDetails shippingDetails;
//#OneToMany(cascade = {CascadeType.ALL})
//List<Product> products;
List<Object> product = new ArrayList<>();
private float totalAmount;
}
and the inner object classes are:
#Data
#Entity
#Table(name = "products")
public class Product {
#Id
#Column(name = "productId")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne
private Order order;
private Boolean isNew;
private String salePrice;
private String shortDetails;
private String discount;
private String description;
private Variants[] variants;
private String[] pictures;
private String[] colors;
private String[] tags;
private String sale;
private String[] size;
private String price;
private String name;
private String stock;
private String category;
}
and the shipping details bean:
#Data
#Entity
#Table(name = "shippingDetails")
public class ShippingDetails {
#Id
#Column(name="shippingDetailsId")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String address;
private String country;
private String town;
}
I have tried many ways but non of them worked, first I have tried to accept an Order object in my controller and try to store it but did not work, the results were:
when using List all the values are printed to the console as desired and inserted in the database but in the same table Orders, that's not what I'm looking for, I want the Products to be saved in a separate table called Products so I used List.
when used List the values of the product were null.
Then I have tried to accept a string in from my controller and use GSON library to convert it to the Order object but it did not work as well, I got many kind of exceptions trying to figure it out and tried many ways that were mentioned on stackoverflow and other websites but no luck, please not that I have validated my json string and it is a valid json.
If you see the hierarchy in JSON it does not match your Java Object.
"product":[ // Parent-Product
{
"product":{ // Child-Product
"id":2,
A direct mapping to Java would be something like
List<Map<String,Product>> products
If you have the option to change the JSON file then change to something like below and get rid of the Parent-Child Product hierarchy.
"product":[
{
"id":..
},
{
"id":..
}
..
]
You can use the converter here. It generates java objects from json.
http://www.jsonschema2pojo.org/ (among others it supports, gson and jackson)
This will ensure that you have the correct mapping of Json to Java and vice versa.
Attachement class:
#Entity
#Table(name="attachments")
#Getter
#Setter
public class AttachmentModel {
//#EmbeddedId
//private AttachmentId attachmentId;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="notice_attachment_id")
private long attachmentId;
#Column(name="notice_id")
private long noticeId;
#Column(name="attachment")
private String attachmentUrl;
#JsonIgnore
#ManyToOne(cascade = {CascadeType.PERSIST , CascadeType.MERGE,
CascadeType.DETACH , CascadeType.REFRESH},optional = false)
#JoinColumn(name="notice_id", insertable=false, updatable=false)
#MapsId("notice_id")
NoticesModel notice;
public void addNotice(NoticesModel notice) {
this.notice = notice;
}
public AttachmentModel() {
}
}
Notices class:
#Entity
#Table(name = "notices")
#Getter #Setter
public class NoticesModel {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "notice_id" ,updatable = false, nullable = false,insertable = true)
private long noticeID;
#OneToMany(fetch = FetchType.EAGER, cascade = { CascadeType.ALL } , mappedBy = "notice")
//#mappedBy(name = "notice_id")
private List<AttachmentModel> attachments;
}
Code to parse JSON and saving it
public HashMap<String,Object> saveNotices(#RequestBody List<NoticesModel> tmpNotices)
{
List<NoticesModel> notices = tmpNotices;
for (NoticesModel notice : notices) {
List<AttachmentModel> attachments = notice.getAttachments();
for (AttachmentModel attachment : attachments) {
attachment.addNotice(notice);
System.out.println(attachment.getAttachmentUrl());
}
for (AttachmentModel attachment : attachments) {
//attachment.addNotice(notice);
System.out.println(attachment.getNotice().getContent());
System.out.println(attachment.getNotice().getNoticeID());
}
}
int result = noticesServices.saveNotice(notices);
HashMap<String,Object> res = new HashMap<>();
res.put("message",result);
return res;
}
This is my JSON I am sending
[
{
"attachments": [
{
"attachmentUrl": "/abc/bcd"
}
],
"content": "string",
}
]
For this case I am trying to save save my notice and attachment.
in this particular case notice_id is getting created while saving to database.
so while trying to save attachement table it is trying to save with notice_id as 0.
so I am getting the exception.
could not execute statement; SQL [n/a]; constraint [attachments_notices_fk]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
How I can be able to solve this issue?
Is this possible to get the notice_id before saving to DB so that I can get notice_id so that I can set it in attachment so that it will not be saved with 0?
What am I doing wrong(Any alternative approach I can take) in this case(I am pretty new to JPA and springboot)?
I think you just should not need to use any notice_id. Remove notice_id and relevant things from your AttachmentModel and usenotice for mapping (NOTE: there will still be column notice_id in db after removal), so:
#ManyToOne
private NoticesModel notice;
and change also the mapping in the NoticesModel to refer to the correct field:
// ALL is just a suggestion
#OneToMany(mappedBy = "noticesModel", cascade = CascadeType.ALL)
private List<AttachmentModel> attachementModels;
Then your for loop might look like:
for (NoticesModel notice : notices) {
for (AttachmentModel am : notice.getAttachments()) {
am.setNotice(notice);
}
noticesServices.save(notice);
}
You could also add something like this in your NoticesModel to handle setting the reference always before persisting:
#PrePersist
private void prePersist() {
for (AttachmentModel am : attachments) {
am.setNotice(this);
}
}
When I return a Page<Entity> from a method inside my #RestController class, all fields of Entity both referenced via #OneToXXX and #ManyToXXX take place in the returned JSON object. But when I switched the return type to PagedResource (to be able to add links to the response), #ManyToXXX fields are not included at all.
Here is the method in question:
#GetMapping("/fetch")
public PagedResources getResults(Pageable pageable, PagedResourcesAssembler assembler) {
Page<ParentClass> page = myRepository.findAll(pageable);
PagedResources pagedResources = assembler.toResource(page, myResourceAssembler);
return pagedResources;
}
Here is the resource assembler: it's #Autowired in the MyController's body.
MyResourceAssembler
#Component
public class MyResourceAssembler extends ResourceAssemblerSupport<ParentClass, Resource> {
public MyResourceAssembler() { super(MyController.class, Resource.class); }
#Override
public Resource toResource(ParentClass obj) {
return new Resource<>(obj,
linkTo(methodOn(MyController.class).getResults(obj.getId())).withRel("edit"),
}
}
Here are the basic class definitions:
ParentClass
#Entity
#Table(name = "parent_table", catalog = "myDB")
public class ParentClass implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#ManyToOne(optional = false)
#JoinColumn(name = "other_class", referencedColumnName = "id")
private OtherClass otherClass;
#OneToOne(mappedBy = "parent")
private SampleField1 sampleField1;
#OneToMany(mappedBy = "parent")
private List<SampleField2> sampleField2;
}
SampleField1 OneToXXX
#Entity
#Table(name = "sample_table_1", catalog = "myDB")
public class SampleField1 implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#Column(name="some_field")
String someField;
#OneToOne
#JoinColumn(name = "sample_field_1", referencedColumnName = "id")
#JsonBackReference //to avoid infinite recursion
private ParentClass parent;
}
OtherClass ManyToOne
#Entity
#Table(name = "other_table", catalog = "myDB")
public class OtherClass implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#Column(name="some_other_field")
String someOtherField;
// I don't need any reference to ParentClass here.
}
To add further detail to the issue here is the logging output of changeProperties() method inside PersistentEntityJackson2Module class:
s.d.r.w.j.PersistentEntityJackson2Module : Assigning nested entity serializer for #javax.persistence.OneToOne(..) com.project.SampleField1 com.project.model.ParentClass.sampleField1
s.d.r.w.j.PersistentEntityJackson2Module : Assigning nested entity serializer for #javax.persistence.OneToMany(..) com.project.SampleField2 com.project.model.ParentClass.sampleField2
// .... omitted other lines for brevity
the resulting JSON is :
{
"_embedded":{
"parentClasses":[
{
"id":1,
// <-- There is no field for otherClass !
"sampleField1":{
"id":1,
"sampleField":"blabla"
},
"sampleField2":[ ]
}
]
},
"links":[
]
}
As it can be seen above, OneToXXX fields are being taken to be serialized but no output for the ManyToOne fields like
Assigning nested entity serializer for #javax.persistence.ManyToOne ... com.my.OtherClass ... and therefore those aren't existed in the response JSON.
According to this SO answer, #ManyToXXX referenced entities are appended as links to the JSON response. But that's not an acceptable solution for me since I have a different planning of consumption in my mind for the rest client.
Bottomline, I'd like to have my ManyToOne referenced entities in my JSON Response returned from getResults() method.
Anything I can provide just ask in the comments.
Return Entity in responses is not the best way, because usually clients dont need whole set of data. Also, if Entities has links for each other, it will cause StackoverflowException on serialization tries. Use DTO for responses. At least it will help you to determine where is the problem - serialization, or fetching from database. Anyway it is more proper way for serving data to clients.
By the way, check getter and setter for otherClass in your ParentClass :) If threre is no getter and setter, thats will be reason of your issue.
Also, take a look into OtherClass for default empty constructor. If it hasn't present in there, you should add it.