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
}
Related
#Entity
#Table(name = "customers")
public class Customer implements Serializable{
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private int custID;
private String custName;
#Id
private String email;
private int phone;
#OneToMany (mappedBy = "customer", fetch = FetchType.LAZY)
private List<Transaction> transaction;
#Entity
#Table(name = "transactions")
public class Transaction implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int transID;
private Date date;
private int amount;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "custID", nullable= false)
private Customer customer;
These are my entities, and I have a method:
#PostMapping("/record-transaction")
public Transaction recordTransaction(#RequestBody Transaction transaction) {
return transactionService.addTransaction(transaction);
}
But when I try to create JSON in postman, I get into a loop where while entering values for transaction, at the end I must enter the Customer object as well and when I am entering customer object at the end I again reach to enter the transaction's values. Its like a never ending loop. Help
I couldn't think of anything to do at all. My mind enters the loop itself.
Decouple your DB entities from your request/response by using an intermediate DTO.
Controller:
#PostMapping("/record-transaction")
public TransactionResponse recordTransaction(#RequestBody TransactionRequest body) {
return TransactionResponse.from(transactionService.addTransaction(
body.getDate();
body.getAmount();
body.getCustomerId();
));
}
TransactionRequest:
public class TransactionRequest {
//don't need ID here it'll be auto generated in entity
private Date date;
private int amount;
private int customerId;
}
TransactionResponse:
public class TransactionResponse {
private int id;
private Date date;
private int amount;
private int customerId;
public static TransactionResponse from(Transaction entity) {
return //build response from entity here
}
}
TransactionService:
//when your entity is lean may as well pass the values directly to reduce boilerplate, otherwise use a DTO
public Transaction addTransaction(Date date, int amount, int customerId) {
Customer customerRepo = customerRepo.findById(customerId).orElseThrow(
() -> new CustomerNotFoundException();
);
Transaction trans = new Transaction();
trans.setDate(date);
trans.setAmount(amount);
trans.setCustomer(customer);
return transactionRepository.save(trans);
}
If you want to embed the customer model inside TransactionResponse or TransactionRequest it'll be fairly easy to do and this solution will produce way nicer contract and swagger docs than a bunch of use case specific annotations in your entity.
In general decoupling you request/response payloads, service dtos and entities from each other results in code with more boilerplate but easier to maintain and without weird unexpected side effects and specific logic.
I have a doubt about how the modeling of my entity would be. Come on, I have a table in the database that serves to save documents from my system, this table has the columns id, fk_id (element foreign key), fk_table (entity name) and file_name (stores the name of my file) .
I did a lot of research before posting my question here, but I didn't find anything related to it, what would my entities, user, patient and doctor?
DB:
id
fk_id
fk_table
file_name
1
21
user
test1.jpg
2
32
doctor
test2.pdf
3
61
user
test10.pdf
4
100
patient
test5.jpg
Class:
public class User{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String LastName;
// What would a one-to-many relationship look like?
}
public class patient{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
// What would a one-to-many relationship look like?
}
You can use #Where. But be aware that #Where is a Hibernate annotation. It's not in the JPA standard.
For example in the User entity: (I assume that your table is mapped to an entity called Document)
#Where( clause = "fk_table = 'user'")
#JoinColumn(name = "fk_id")
#OneToMany
private List<Document> documents = new ArrayList<>( );
The following is based only on standard JPA annotations. The idea is to create an inheritance hierarchy for the documents table. The base is:
#Entity
#Table(name = "XX_DOCUMENT")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "fk_table")
public abstract class BaseDocument {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#Column(name = "file_name")
private String fileName;
}
Here we define that all entities extending this will go to the same table, with the fk_table column to discriminate. The entities extending it are defined as follows:
#Entity
#DiscriminatorValue("doctor")
public class DoctorDocument extends BaseDocument {
#ManyToOne
#JoinColumn(name = "fk_id")
private Doctor doctor;
}
#Entity
#DiscriminatorValue("patient")
public class PatientDocument extends BaseDocument {
#ManyToOne
#JoinColumn(name = "fk_id")
private Patient patient;
}
// and so on
The interesting thing is that we are reusing the column fk_id to point to the right table. From a small experiment, Hibernate seems to not have problems with it. I would suggest that you manage the DB creation another way just to be safe.
The Doctor, Patient etc need not have a common base class, e.g.:
#Entity
#Table(name = "XX_DOCTOR")
public class Doctor {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#OneToMany(mappedBy = "doctor")
private Collection<DoctorDocument> documents = new ArrayList<>();
// any doctor-specific fields
}
#Entity
#Table(name = "XX_PATIENT")
public class Patient {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#OneToMany(mappedBy = "patient")
private Collection<PatientDocument> documents = new ArrayList<>();
// any patient-specific fields
}
// and so on
You can read a (doctor, patient, ...)'s documents from the relevant collection. You can even query BaseDocument instances based on any criteria.
You can even go ahead and do more fabcy stuff with the Java code. E.g. define an interface HasDocuments:
public interface HasDocuments<D extends BaseDocument> {
Collection<D> getDocuments();
}
Doctor, Patient, ..., implements this, so they can all be treated the same way.
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 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.
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.