I have a problem using JPA and RelationsShips One to Many with Jackson and Spring Rest ... I try to find multiples solutions but anything is working for me , and I don't kno where is the problem.
For example I have a table Team that has One to Many/Many To One relationship
I have two repository one for Team and another for Player
Team >>> has Many >> Player
Player >>> many to one >> Team
My entity Team has the following content
#Entity
#Table(name = "teams")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Team {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private long teamId;
private String abbreviation;
private String team;
private String simpleName;
private String logo;
#OneToMany(cascade = {CascadeType.ALL,CascadeType.PERSIST,CascadeType.MERGE}, mappedBy = "team")
#Column(nullable = false)
private List<Player> players;
Theirs getters/setter , hashcodes and string similars.
On the other hand the entity Player
#Entity
#Table(name = "player")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Player {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "teams_id", nullable=true)
private Team team;
private String name;
So , I have the typical get call in a controller in a repository.
#RestController
#RequestMapping("/api/public/team")
public class TeamController {
#Autowired
private TeamRepository teamRepository;
#Autowired
private GenericMethods genericMethods;
#GetMapping(value = "/{id}")
public Team getPersona(#PathVariable("id") int id) {
return teamRepository.findOne(genericMethods.toLong(id));
}
And repository
#Repository
public interface TeamRepository extends JpaRepository<Team, Long> {
}
Now , when I call this endpoint I receive the following answer and I think that is incorrect , I only need a List With Players
{
"id":2,
"teamId":0,
"abbreviation":null,
"team":null,
"simpleName":"Betis",
"logo":null,
"players":[
{
"id":1,
"team":2,
"category":{
"id":1,
"nombre":"juvenil a",
"language":null,
"description":null,
"league":[
],
"players":[
1,
{
"id":2,
"team":2,
"category":1,
"name":"hulio"
}
]
},
"name":"pepe"
},
2
]
}
I need to acces at information with Player and Team so I can't use #JsonIgnoreProperties
Could anyone help to solve this problem ?
Depending on what you really want to achieve you may try different options. I'm not sure if you're using (or intending to use) spring-data-rest or not.
1. Dedicated repository
Spring data rest will embed the related entities if they don't have their own repository. Try creating public interface PlayersRepository extends JpaRepository...
2. Lazy loading
Why are you using FetchType.EAGER ? Try without it.
3. Projections
Projections are only applicable to lists, not to individual entities (i.e. not explicitly what you're asking for). You can hide players from the Teams collection even if it was returned by default like so:
#Projection(name = "noPlayers", types = { Team.class })
public interface TeamWithoutPlayers {
Long getId();
long getTeamId();
String getAbbreviation();
String getTeam();
String getSimpleName();
String getLogo();
}
More info - Spring Data Rest Projections
4. Ignore during serialization in Team Entity using #JsonIgnore
#JsonIgnore
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "teams_id", nullable=true)
private Team team;
Final thought
With spring-data-rest you can extend a CrudRepository instead of JpaRepository and access the item directly through the repository. That way you don't need to write a controller.
Related
I'm working on a database for adding bands, musicians, instruments, etc.
I have a table 'band' and a table 'musician'. They have a ManyToMany relationship (one band can have many musicians, a musician can be in many bands), with an extra table BandMusician that has an embeddedId BandMusicianId. I did it like this because I want the relationship between bands and musicians to have also other information, like the year the musician joined the band.
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Band {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String genre;
private int year;
#OneToOne(mappedBy = "band")
private Website website;
#OneToMany(mappedBy = "band")
private List<Album> albuns;
#OneToMany(mappedBy = "band")
private List<BandMusician> musicians;
}
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#JsonDeserialize(using = MusicianJsonDeserializer.class)
public class Musician {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#JsonFormat(pattern = "dd-MM-yyyy")
#JsonProperty("DoB")
#Column(name = "date_of_birth")
private LocalDate DoB;
#ManyToMany
#JoinTable(
name = "musician_instruments",
joinColumns = #JoinColumn(name = "musician_id"),
inverseJoinColumns = #JoinColumn(name = "instrument_id")
)
private List<Instrument> instruments = new ArrayList<>();
#OneToMany(mappedBy = "musician")
private List<BandMusician> bands;
public void addInstrument(Instrument instrument) {
this.instruments.add(instrument);
}
}
#Embeddable
#Data
#AllArgsConstructor
#NoArgsConstructor
public class BandMusiciansId implements Serializable{
private static final long serialVersionUID = 1L;
#Column(name = "band_id")
private Long bandId;
#Column(name = "musician_id")
private Long musicianId;
}
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class BandMusician {
#EmbeddedId
private BandMusiciansId id = new BandMusiciansId();
#ManyToOne
#MapsId("bandId")
#JoinColumn(name = "band_id")
private Band band;
#ManyToOne
#MapsId("musicianId")
#JoinColumn(name = "musician_id")
private Musician musician;
private String role;
private int joined;
}
When I receive a POST request to "/musician" I can save a musician. I'm using Jackson to deserialize a request like this:
{
"name": "John the Ripper",
"DoB": "03-12-1965",
"instruments": "voice, guitar",
"bands": "Band1, Band2"
}
With Jackson I can get each band, search with the BandRepository and create a BandMusician.
THE PROBLEM: When I receive the request, in order to create a BandMusician I have to create a BandMusiciansId, and to do that I need the bandId and the MusicianId. But I'm creating the musician right now, so I don't have the musicianId. It is created automatically when I save the musician.
MusicianJsonDeserializer class
public class MusicianJsonDeserializer extends JsonDeserializer<Musician>{
private final InstrumentRepository instrumentRepository;
private final BandRepository bandRepository;
#Autowired
public MusicianJsonDeserializer(
InstrumentRepository instrumentRepository,
BandRepository bandRepository
) {
this.instrumentRepository = instrumentRepository;
this.bandRepository = bandRepository;
}
#Override
public Musician deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JacksonException {
ObjectCodec codec = p.getCodec();
JsonNode root = codec.readTree(p);
Musician musician = new Musician();
musician.setName(root.get("name").asText());
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
musician.setDoB(LocalDate.parse(root.get("DoB").asText(), formatter));
if (root.get("instruments") != null) {
String instrumentList = root.get("instruments").asText();
String[] instrumentArray = instrumentList.split(", ");
List<Instrument> musicianInstrumentList = new ArrayList<>();
for (String instrument : instrumentArray) {
Instrument instrumentFound =
instrumentRepository.findByName(instrument)
.orElseThrow(RuntimeException::new);
// TODO custom exception
musicianInstrumentList.add(instrumentFound);
}
musician.setInstruments(musicianInstrumentList);
}
if (root.get("bands") != null) {
// TODO Stuck here!
What I thought of doing: In my MusicianService, after saving the musician, I can create the BandMusician and the relationship. I think doing this in the Service layer would be a bad choice though.
EDIT: To make it easier to understand, I created a project only with the relevant parts of this one and pushed to github (https://github.com/ricardorosa-dev/gettinghelp).
Again, what I want is to be able to send a POST to "/musician", that will be caught by the MusicianJsonDeserializer, and somehow create a BandMusicianId and BandMusician for each band sent in the request body.
I have the entities Band and Musician and a ManyToMany relationship between them with an association table BandMusician.
What I wanted was to create the entity Musician and the relationship (BandMusician) in the same request.
As far as I can gather it is not possible, because in order to create a record in the association table (BandMusician), I would have to have the musician (I'm creating in this request) already created.
I tried everything just to see if it was POSSIBLE and wasn't able to do it. But even if it was possible, it would be a very bad practice, since it would make the class too tightly coupled.
The clear solution was to create only the Musician with this request, and then send another request to create the connection between Band and Musician.
I also tried to create many entries in the BandMusician table with one request, which was also impossible, because the JsonDeserializer table doesn't seem to accept List<> as a return type. I was trying to avoid making a lot of requests to create the relationship entries (for a musician that is in five bands, for example), but it seems it is better to keep things clear and simple.
I now save one musician-band relationship per request:
{
"musician": "Awesome musician",
"band": "Awesome band",
"role": "guitar",
"joined": 2003
}
I am stuck on given scenario:
There are three entity
a) Bill (many to one relationship with vendor) [bi-directional]
b) Vendor (one to many relationship with both vendor and vendorbank)
c) VendorBank (many to one relationship with vendor)[uni-directional]
Bills : Showing limited fields
#Entity
#Getter
#Setter
#ToString
#NoArgsConstructor
#Table(name="bill_details")
public class Bills {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="bill_id")
private int bill_id;
#Column(name="bill_no")
private String billno;
#ManyToOne(cascade = {CascadeType.MERGE})
#JoinColumn(name="b_vendor_id")
private Vendors vendors;
/* Args contructor code here */
#JsonManagedReference
public Vendors getVendors() { return vendors; }
VendorBank:Showing limited fields
public class VendorBank {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="v_bank_id")
private int vendorBankId;
#Column(name="v_acc_no")
private String accountNumber;
#Column(name="v_vendor_id")
private int vendor_id;
/*Both constructor code here*/
}
Vendor Class:Showing imp fields only
/*Lombok code here*/
public class Vendors {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="vendor_id")
private int vendor_id;
#Column(name="vendor_name")
private String vendor_name;
#OneToMany(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#JoinColumn(name="v_vendor_id")
private List<VendorBank> vendorBank;
#OneToMany(mappedBy = "vendors",cascade = {CascadeType.DETACH,CascadeType.MERGE,
CascadeType.PERSIST,CascadeType.REFRESH})
private List<Bills> bills;
#JsonBackReference
public List<Bills> getBills() {
return bills;
}
//#JsonManagedReference()
// #JsonIgnore
public List<VendorBank> getVendorBank() {
return vendorBank;
}
Output:
{
"bill_id": 102,
"billno": "B-858",
"vendors": {
"vendor_id": 3,
"vendor_name": "ABC Company",
"vendorBank": [
{
"vendorBankId": 14,
"accountNumber": "502998745002",
"vendor_id": 3
}
]
}
}
1.When I call vendor endpoint I get data from vendor + vendor bank as desired.
2.But When I call the Bill endpoints then I get data from Bill + vendor + vendorbank as above. I don't want vendor bank to come.[If I use JsonIgnore on vendorbank then i get the correct output but then above point 1 goes wrong]
OK, so what you want to achieve is to include some data from the entity in one context and not include it in another context.
I don't think you can do this by using purely annotations you put on entities, since they don't have the calling context.
So, what can we do here ?
We can use Jackson Mixins. For example:
class YourClass {
public int ignoreThis() { return 0; }
}
With this Mixin
abstract class MixIn {
#JsonIgnore abstract int ignoreThis(); // we don't need it!
}
With this:
objectMapper.getSerializationConfig().addMixInAnnotations(YourClass.class, MixIn.class)
And you can then use this to serialize the object into json in your controller (where you get it to the service) and then add it as the response body and send to the user.
It would be ideal if you could somehow configure the ObjectMapper, which is used by Spring behind the scenes to do this, however, this is not possible ( at least easily ), because you need to tie this objectMapper to your specific controller only and not all controllers.
I create new Product using Spring Boot / Hibernate / JPA and get Column 'brand_id' cannot be null error. I don't know why this error happen. Does anyone can explain where I was wrong?
Product:
#Entity
public class Product {
#javax.persistence.Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id;
#NotBlank(message = "Product name is required")
private String name;
private String image;
private String description;
private double price;
private int countInStock;
private double rating;
private int numReviews;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "brand_id", updatable = false, nullable = false)
#JsonIgnore
private Brand brand;
#ManyToMany
#JoinTable(name = "product_category", joinColumns = #JoinColumn(name = "product_id"), inverseJoinColumns = #JoinColumn(name = "category_id"))
#JsonIgnore
private List<Category> categories;
Brand:
#Entity
public class Brand {
#javax.persistence.Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id;
#NotBlank(message = "Brand name is required")
private String name;
#OneToMany(cascade = CascadeType.REFRESH, fetch = FetchType.LAZY, mappedBy = "brand", orphanRemoval = true)
private List<Product> products;
Category:
#Entity
public class Category {
#javax.persistence.Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id;
#NotBlank(message = "Category name is required")
private String name;
#ManyToMany(mappedBy = "categories")
private List<Product> products;
And my Product Object that used to input create new Product:
{
"name": "test",
"image": "/images/test.jpg",
"description": "test",
"brand_id": 4,
"category_id": 1,
"price": 99.99,
"countInStock": 100,
"rating": 4.5,
"numReviews": 120
}
Wherever you are creating this Product object, you must provide it with a Brand object.
Currently, you are only providing it with an integer as a Brand.
What you need to do is fetch the brand object you are referring to from your DB and include it in the instantiation of your new Product.
Probably the best and simplest approach to this would be using the EntityManager to get a reference to that Brand.
Getting the EntityManager is very simple when using spring.
#PersistenceContext
private EntityManager entityManager;
Now, simply use it to get the reference to the target Brand.
Brand brand = entityManager.getReference(Brand.class , brand_id);
Use this brand to instantiate your new Product and insert it to the DB without any exceptions.
How to automate this logic into the unmarshalling process
If you are always going to want to use this logic when creating a Product, you can use this logic in the constructor. If you only want to use this method when unmarshalling, here is an example that is based of something I wrote recently. I am using an XmlAdapter, but there are also JSONAdapater classes you can look into and should work about the same way.
Create you adapter class. This class is going to be used to parse JSON to java object.
//Once again, I am using an XmlAdapter, but the idea should be similar with JSONAdapters
#Service
public class BrandIdXmlAdapter extends XmlAdapter<String, Brand> {
#PersistenceContext
private EntityManager entityManager;
//v is the String that is going to be unmarshalled.
//In our case, its going to be the brand_id String.
#Override
public Brand unmarshal(String v) {
Brand brand = entityManager.getReference(Brand.class, v);
return brand;
}
There is also a possibility to override the marshal() method for parsing from a POJO to XML/JSON.
The only problem here is that to be able to use use the PersistenceContext annotation, this class has to be an EJB.
We are going to workaround that by telling Spring this is a necessary service.
First step is to give the adapter class the Service annotation(Done in example above).
The next step is to go to where you would want to unmarshall the input into a POJO (either the controller if you receive it as a request or the service if you are going to request it from another service) and autowire the adapter
#Autowired
private BrandIdXmlAdapter xmlAdapter;
Next step is to create the unmarshaller that will use this adapter.
JAXBContext context = JAXBContext.newInstance(Product.class);
brandIdUnmarshaller = context.createUnmarshaller();
brandIdUnmarshaller.setAdapter(xmlAdapter);
Now when receiving the data, use the brandIdUnmarshaller.unmarhsall() method.
Last step is to annotate your Brand variable in Product to tell it to use the adapter when parsing this specific variable.
public class Product {
.
.
.
//again, find the right annotation according to your JSONAdapter
#XmlJavaTypeAdapter(BrandIdXmlAdapter.class)
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "brand_id", updatable = false, nullable = false)
#JsonIgnore
private Brand brand;
}
Now everytime you parse from JSON to Product, you will automatically get a Product that contains a valid Brand object.
There is a given database structure and graphql schema.
Fortunately they have a lot in common but unfortunately there are some difference.
Let's say there are entities in java to match the following database structure.
SQL:
TABLE ANIMAL
+ID NUMBER(19)
+NR_OF_LEGS NUMBER(19)
TABLE SHEEP
+ID NUMBER
+LAST_TIME_SHEARED DATETIME
+ANIMAL_ID NUMBER(19)
TABLE COW
+MILK_IN_L NUMBER(3)
+ANIMAL_ID NUMER(19)
Java:
#Entity
#Table(name = "ANIMAL")
public class Animal
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name="nrOfLegs", nullable=false)
private long nrOfLegs;
}
#Entity
#Table(name = "SHEEP")
public class SheepE
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name="lastTimeSheared", nullable=false)
private Datetime lastTimeSheared;
#ManyToOne(targetEntity = AnimalE.class, cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "animalId", nullable = false, insertable = false, updatable = false)
private Animal animal;
}
#Entity
#Table(name = "COW")
public class CowE
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name="milkInL", nullable=false)
private int milkInL;
#ManyToOne(targetEntity = AnimalE.class, cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "animalId", nullable = false, insertable = false, updatable = false)
private Animal animal;
}
The existing GraphQl schema is considered to be like this:
type Sheep{
id: int!
lastTimeSheard: String!
nrOfLegs: int!
}
type Cow {
id: int!
milkInL: int!
nrOfLegs: int
}
The project uses graphql-java in version 11.0 (guess we should update soon)
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>11.0</version>
</dependency>
The graphql works fine and isimplemented like this:
#Component
public class GraphQLProvider {
#Autowired
GraphQLDataFetchers graphQLDataFetchers;
private GraphQL graphQL;
#PostConstruct
public void init() {this.graphQL = /*init;*/null;}
private RuntimeWiring buildWiring() {
RuntimeWiring.Builder b = RuntimeWiring.newRuntimeWiring()
.type(TypeRuntimeWiring.newTypeWiring("Query")
.dataFetcher("freightCarrier", graphQLDataFetchers.getCow()))
.type(TypeRuntimeWiring.newTypeWiring("Query")
.dataFetcher("personCarrier", graphQLDataFetchers.getSheep())));
return b.build();
}
}
#Component
public class GraphQLDataFetchers {
#AutoWired
private CowRepository cowRepo;
#AutoWired
private sheepRepository sheepRepo;
public DataFetcher getCow() {
DataFetcher dataFetcher = (DataFetchingEnvironment dfe) -> {
int id = dfe.getArgument("id");
return getGraphQlCowFromCowEntity(cowRepo.getById(id));//dirty!
};
return dataFetcher;
}
public DataFetcher getCow() {
DataFetcher dataFetcher = (DataFetchingEnvironment dfe) -> {
int id = dfe.getArgument("id");
return getGraphQlSheepFromSheepEntity(cowRepo.getById(id));//dirty!
};
return dataFetcher;
}
private Cow getGraphQlCowFromCowEntity(CowE ce){//dirty!
return new Cow(ce.getId(), ce.getMilkInL(),ce.getLegs());
}
private Sheep getGraphQlSheepFromSheepEntity(SheepE se){//dirty!
return new Sheep(se.getId(), se.getLastTime(),se.getLegs());
}
public class Sheep
private long id;
private Datetime lastTimeSheared;
private int nrOfLegs;
public Sheep(long id, DateTime lasttimeSheared, int nrOfLegs){
//u know what happens here
}
}
public class Cow
private long id;
private int milkInL;
private int nrOfLegs;
public Sheep(long id, int milkInL, int nrOfLegs){
//u know what happens here
}
}
So how to get rid of getGraphQlCowFromCowEntity and getGraphQlSheepFromSheepEntity. It double ups the code and also is in direct conflict to what graphql is suppose to be abstraction of the data. With this design here each time all fields are loaded through jpa and not only requested fields.
Imagine this is a way more complex environment with more fields.
The graphql schema can't be changed as it's not my responsibility, changing the entire back-end to match schema is also not what I want to archive.
Kind regards
You should use DTO. Retrieving and sending entity object is bad practice as you do not want your grahql api to change every time you refactor you database model, or in your case. Your Sheep and Cow objects are DTO, but you will need some way to convert your entity to DTO (getGraphQlCowFromCowEntity is fine, but you could use polymorphism - CowEntity.toDTO() - or have a service layer do the conversion, there are plenty of way to do this).
To answer your concerns about loading only the requested data, you want your DTO object to only be populated with the requested fields. One way to do this is, instead of populating all fields, have the DTO own a reference to the entity object and retrieve the data from the entity object only when requested.
public class Sheep {
private SheepE entity;
public Sheep(SheepE entity){
this.entity=entity;
}
public getId() {
return entity.getId();
}
public getLastTimeSheared() {
return entity.getLastTimeSheared();
}
...
}
Please see this answer I wrote to a similar question: Graphql Tools: Map entity type to graphql type
I have the following code:
public interface JSONInvoiceView {
public interface JSONInvoiceBasicView {
}
public interface JSONInvoiceWithLinesView extends JSONInvoiceBasicView {
}
}
#PersistenceUnit(unitName="ERP_PU")
#Entity
#Table(name="INVOICE")
public class Invoice extends FrameworkEntity {
#Id
#SequenceGenerator(name = "PK_INVOICE_GEN", sequenceName = "PK_INVOICE_GEN", allocationSize=1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "PK_INVOICE_GEN")
#Column(name = "ID")
#JsonView(JSONInvoiceView.JSONInvoiceBasicView.class)
private Long id;
#OneToMany(mappedBy="invoiceLine", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonView(JSONInvoiceView.JSONInvoiceWithLinesView.class)
#JsonManagedReference
private List<InvoiceLine> lines = new ArrayList<InvoiceLine>();
#Temporal(TemporalType.DATE)
#Column(name = "DATE")
#JsonView(JSONInvoiceView.JSONInvoiceBasicView.class)
private Date startDate;
//...
}
#PersistenceUnit(unitName="ERP_PU")
#Entity
#Table(name="INVOICE_LINE")
public class InvoiceLine extends FrameworkEntity {
#Id
#Column(name = "ID")
#JsonView(JSONInvoiceView.JSONInvoiceWithLinesView.class)
private Long id;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinColumn(name="FK_INVOICE")
#JsonBackReference
private Invoice invoice;
#Column(name = "AMOUNT")
#JsonView(JSONInvoiceView.JSONInvoiceWithLinesView.class)
private BigDecimal amount;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinColumn(name="FK_GOOD")
private Good good;
//...
}
#PersistenceUnit(unitName="ERP_PU")
#Entity
#Table(name="GOOD")
public class Good extends FrameworkEntity {
#Id
#Column(name = "ID")
private Long id;
#Column(name = "DESCRIPTION", length=200)
private String description;
//...
}
So – one Invoice can have multiple InvoiceLines and each line has reference to Good. I need to get two JSON views: Inovice-only view and Invoice+InvoiceLine-only view. My domain is far richer than these 3 classes – the whole entity graph involves tens of classes and I need careful control how much of this graph I am loading in my entities. But I need to control also how much of loaded graph the JSON serialization facility should try to serialize. And I have the problem with this second control.
entityList is list of Invoices which has loaded InvoiceLines (with touch, e.g. invoiceLines.size();) but InvoiceLines have not further loaded Goods (invoiceLine.good is not touched during lazy load). So, entityList if Invoice+InvoiceLines.
I use the following code for Invoice-only view and this code works:
jsonString = objectMapper.writerWithView(JSONInvoiceView.JSONInvoiceBasicView.class).writeValueAsString(entityList);
Code for retrieving JSON view with Invoice+InvoiceLine-only data:
jsonString = objectMapper.writerWithView(JSONInvoiceView.JSONInvoiceWithLinesView.class).writeValueAsString(entityList);
And this code does not work, it raises error message:
org.codehaus.jackson.map.JsonMappingException: could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->mycom.entities.Invoice["invoiceLines"]->org.hibernate.collection.internal.PersistentBag[0]-> mycom.entities.Good["good"]-> mycom.entities.Good_$$_jvst4f9_c["id"])
at org.codehaus.jackson.map.JsonMappingException.wrapWithPath(JsonMappingException.java:218)
at org.codehaus.jackson.map.JsonMappingException.wrapWithPath(JsonMappingException.java:183)
at org.codehaus.jackson.map.ser.std.SerializerBase.wrapAndThrow(SerializerBase.java:140)
at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:158)
at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:112)
Caused by: org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:165)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:286)
So, the question is – what Jackson views/annotations should I apply to serialized Invoice+InvoiceLine only parts of entity graph which has loaded only Invoice+InvoiceLine data? How should I indicate that Jackson should not try to go further along association chain and Jackson should not try to serialize 3rd, 4th and so order associations, Jackson should not try to serialize good entities?
p.s. Ignore annotations (or any similar global annotation on entities) is not applicable in my case, because there will be cases when I need only Invoice data and then there will be cases when I will need Invoice+InvoiceLine+Good data and further I will need data Invoice+InvoiceLine+Good+GoodSupplier, etc.
I have found solution - Jackson perceives fields without #JsonView annotation as the fields belonging to every view. Therefor I should introduce additional view:
public interface JSONInvoiceView {
public interface JSONInvoiceBasicView {
}
public interface JSONInvoiceWithLinesView extends JSONInvoiceBasicView {
}
public interface JSONInvoiceWithLinesViewExt extends JSONInvoiceWithLinesView {
}
}
And apply new interace to the Good field:
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinColumn(name="FK_GOOD")
#JsonView(JSONInvoiceView.JSONInvoiceWithLinesExtView.class)
private Good good;
So - I should define new JSON view interfeice for each level of associations for my entities. After appling #JsonView all works like a charm.