Generic bi-dir one-to-many relationship - java

I would like to know if there exists some way to use generics in JPA 2.0?
Consider this scenario:
#Entity
public class GenericPhoto<T> implements Serializable {
#Id
#GeneratedValue
private long id;
#NotNull
private byte[] file;
#ManyToOne(cascade = { CascadeType.DETACH })
#JoinColumn(name = "PARENTID", nullable = false)
#NotNull
private T parent;
//...
}
#Entity
public Car {
#OneToMany(mappedBy = "parent")
private Set<GenericPhoto<Car>> photos;
//...
}
#Entity
public Truck {
#OneToMany(mappedBy = "parent")
private Set<GenericPhoto<Truck>> photos;
//...
}
I hope the code explains it all. I simply want to make a generic class for photo, which I think makes it easier to implement services etcetera.
Best regards

When you use a generic, it is similar to not typing the field (i.e. Object parent), so you need to tell JPA how to map the relationship. For this you can use targetEntity in JPA.
For this to work, you will need a common superclass to Car and Truck i.e. Auto, and set the targetEntity in the #ManyToOne to Auto (you may also consider moving photos up to Auto).
If you can't use inheritance for some reason, (it is best to use inheritance). Then if you use EclipseLink you could use a #VariableOneToOne relationship.

Related

Jackson #JsonView for long chain of associations on JPA/Hibernate entity graph

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.

JPA Entity relationship?

I have entity called Shop and different types of shops (sport, clothes, tech...). That list of types will probably be predefined. One shop can have multiple types. What is the best way to represent that?
I created two entities Shop and Type.
#Entity
#Table(name = "store")
public class Store {
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
private Long id;
...
}
#Entity
#Table(name = "type")
public class Type {
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
private Long id;
private String name; //sport, clothes, tech...
}
What type of relationship between these two entities should I use?
Given that you said Type is probably predefined, it seems more reasonable to model it as enum, and making use of ElementCollection
(I have to admit that I haven't tried to use them both in combination, I believe it should work though :P )
public enum Type {
SPORT, CLOTHES, TECH
}
public class Shop {
#Id
private Long id;
#ElementCollection
#CollectionTable(
name="SHOP_TYPE",
joinColumns=#JoinColumn(name="SHOP_ID")
)
#Column(name="TYPE")
// mapping for enum by usertype or other way, depending on JPA version you are using
private List<Type> types;
}
Of course, you can model SHOP_TYPE as an entity (e.g. ShopType) if you want more complicated operations on it, but what described above looks to me a more reasonable domain model.
Even you do not want the Type to be predefined (i.e. you can create whatever type in your application), it is still more reasonable to model it as a ManyToMany relationship:
public class Type {
#Id
#Column(name="TYPE_ID")
private Long id
#Column(name="TYPE_NAME")
private String name;
}
public class Shop {
#Id
#Column(name="SHOP_ID")
private Long id;
#ManyToMany
#JoinTable(
name="SHOP_TYPE",
joinColumns=#JoinColumn(name="SHOP_ID"),
inverseJoinColumns=#JoinColumn(name="TYPE_ID"))
private List<Type> types;
}
Just one thing to note: It does not look right to have a Type entity which contains a String as type name, and refer to Shop (as some of the answer suggested). Type should be an entity of itself, and, for example, different shops having CLOTHES type should refer to same CLOTHES entity (unless you view types as some kind of arbitrary tag)
The Store and Type many to many relationship is linked with a third / join table named STORE_TYPE_MAPS.
Store Entity:
#Entity
#Table(name = "store")
public class Store {
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
private Long id;
#ManyToMany(fetch = FetchType.LAZY, targetEntity=Type.class)
#JoinTable(name="STORE_TYPE_MAPS",
joinColumns=#JoinColumn(name="STORE_ID"),
inverseJoinColumns=#JoinColumn(name="TYPE_ID")
private Set<Type> types;
//... getter-setter
}
If Type is an Entity then make it ManyToMany
#ManyToMany
#JoinTable(name="Store_Type")
private List<Type> types;
also it can be an enum
I rather prefer to create a new entity called ShopType, the ManyToMany relationship will be created as explained below.
This new Entity allows you to have extra columns in the join table, "ShopType", (which can't be done with a simple #ManyToMany). For example, you can add this information: "the number of articles of each type in each shop".
The code is as follows:
public class Shop {
#Id
#Column(name="SHOP_ID")
private Long id;
#OneToMany(mappedBy = "shop", cascade = CascadeType.ALL)
private List<JoinAchatType> joinShopType = new ArrayList();
}
public class ShopType {
#ManyToOne
#JoinColumn(name = "SHOP_ID")
private Shop shop;
#ManyToOne
#JoinColumn(name = "TYPE_ID")
private Type type;
private int numberArticle;
}
public class Type {
#Id
#Column(name="TYPE_ID")
private Long id;
#OneToMany(mappedBy = "type", cascade = CascadeType.ALL)
private List<JoinAchatType> joinShopType = new ArrayList();
}
For more information check these links:
Mapping many-to-many association table with extra column(s)
.
The best way to use the #ManyToMany annotation with JPA and Hibernate.

Parent child relation without cascade

Are there case with is usefull to not use cascade in a parent - child relation?
Actually I use alway cascade and i would like to know if that could be interesting to avoid to use them.
#Entity
public class Lodger{
#OneToMany
private List<Bail> bailList;
...
}
#Entity
public class Bail {
#OneToMany
private List<Rent> rents;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "lodger_id")
private Lodger lodger;
#OneToOne
private Room room;
}
#Entity
private class Rent{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "bail_id")
private Bail bail;
#OneToMany
private List<RoomPayment> roomPayment;
}
#Entity
private class RoomPayment{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "rent_id")
private Rent rent;
}
So all relation seem to be composition. If i create a new RoomPayment, is it better to do:
roomPayment.setRent(rent);
roomPaymentDao.save(roomPayment);
rent.getRoomPayment().add(roomPayment);
rentDao.save(rent);
I think you are mixing up compositions and parent - child relationships.
Composition
From the wikipedia definition:
It is ... called composite, if the objects it refers to are really its parts, i.e. have no independent existence
Composite relationships are always modeled with a CascadeType.ALL or [CascadeType.PERSIST, CascadeType.REMOVE] (the later for huge objects that you would like to update only if necessary). If you don't use a cascade here, you don't have a composition.
Parent - child relationships
There are different relationships that could be called parent - child relation. I guess you are talking about something like a directory association:
#Entity
public Directory {
#ManyToOne
private Directory parent;
#OneToMany(mappedBy = "parent")
private Collection<Directory> children;
}
In that case you will often need to manage the association manually, because you don't want to update the whole tree if you change an attribute of the root directory.
You could add CascadeType.REMOVE to the children association - but it is better to delete subdirectories explicitly to prevent harmful mistakes (like the explicit -r for rm).

Initializing many-to-many association in Hibernate with join table

I have a Company entity that I fetch with a JPQL query with Hibernate. The entity has a many-to-many association with a Keyword entity. Since the join table has an additional column is_active, this table has been mapped to a CompanyKeyword entity. So the association is like this:
Company <-- CompanyKeyword --> Keyword
Now, the association from the Company entity is lazy, and it is not initialized by my JPQL query, as I want to avoid creating a cartesian product performance problem. That is why I want to initialize the association after running the JPQL query, e.g. like this:
#Service
class CompanyServiceImpl implements CompanyService {
#Autowired
private CompanyRepository companyRepository;
#Transactional
public Company findOne(int companyId) {
Company company = this.companyRepository.findOneWithSomeCustomQuery(companyId);
Hibernate.initialize(company.companyKeywords());
return company;
}
}
For a "normal" many-to-many association, this would work great, as all of the associated entities would be fetched in a single query. However, since I have an entity between Company and Keyword, Hibernate will only initialize the first part of the association, i.e. from Company to CompanyKeyword, and not from CompanyKeyword to Keyword. I hope that makes sense. I am looking for a way to initialize this association all the way without having to do something like this:
Company company = this.companyRepository.findOneWithSomeCustomQuery(companyId);
Hibernate.initialize(company.getCompanyKeywords());
for (CompanyKeyword ck : company.getCompanyKeywords()) {
Hibernate.initialize(ck.getKeyword());
}
The above code is neither clean, nor good in terms of performance. If possible, I would like to stick to my current approach of using a JPQL query to fetch my Company entity and then initializing certain associations afterwards; it would take quite a bit of refactoring to change this in my project. Should I just "manually" fetch the association with a second JPQL query, or is there a better way of doing it that I haven't thought of?
Below are my mappings. Thanks in advance!
Company
#Entity
#Table(name = "company")
public class Company implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#Size(max = 20)
#OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
private Set<CompanyKeyword> companyKeywords = new HashSet<>();
// Getters and setters
}
CompanyKeyword
#Entity
#Table(name = "company_service")
#IdClass(CompanyServicePK.class)
public class CompanyKeyword implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Company.class)
#JoinColumn(name = "company_id")
private Company company;
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Keyword.class)
#JoinColumn(name = "keyword_id")
private Keyword keyword;
#Column(nullable = true)
private boolean isActive;
// Getters and setters
}
CompanyKeywordPK
public class CompanyServicePK implements Serializable {
private Company company;
private Service service;
public CompanyServicePK() { }
public CompanyServicePK(Company company, Service service) {
this.company = company;
this.service = service;
}
// Getters and setters
// hashCode()
// equals()
}
Keyword
#Entity
#Table(name = "keyword")
public class Keyword {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
// Fields and getters/setters
}
You'll indeed need to execute an additional JPQL query, fetching the company with its companyKeyWords and with the keyword of each CompanyKeyWord.
You could also doing it by simply looping and initializing every entity, and still avoid executing too many queries, by enabling batch fetching.

Set JPA Annotations at the member variable or at get methods of an entity?

i haven´t some big experiences with the topic JPA and Hibernate. For me it is not clear, when it is necessary to write the JPA annotation at the member variable of my entity class ans when i can use the get methods for my annotations. Is it right, that annotations should not being set at the set method of an entity?. Here is a small example:
public class MessageEntity implements Persistable{
#ManyToOne
StatusEntity state;
#Column(nullable = false)
private Boolean freitext = false;
private Collection<Variables> variables;
#OneToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST }, targetEntity = Variables.class)
public Collection<Variables> getVariables() {
return this.variables;
}
}
Also it is not clear for me, when i must use the attribute targetEntity. Can somebody explain that for me?
Maik
The annotations can go either on the properties or the getters, but not on the setters.
In a one to many relationship, if the Set(collection) is specified without the generics, the targetEntity is required. If the Set<Generic> is used then the targetEntity is not required.
Ref: Java api
I have not used any JPA annotations on getter methods, I'd assume its the same as annotating the variable at the top. I'd keep all annotations on the variables at the top, just to be cleaner.
For that "targetEntity" attribute on your #OneToMany I have not used, however I do use mappedBy when using #OneToMany. I think targetEntity is not needed in your case.
#Entity
#Table(name="message_entity")
public class MessageEntity implements Persistable{
#ManyToOne
StatusEntity state;
#Column(nullable = false)
private Boolean freitext = false;
#OneToMany(mappedBy="message_entity"), cascade = { CascadeType.MERGE, CascadeType.PERSIS
private Collection<Variables> variables;
public Collection<Variables> getVariables() {
return this.variables;
}
}

Categories

Resources