I have an abstract and generic class:
#MappedSuperclass
public abstract class Field<T> {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "field_id")
private int id;
#ManyToMany
#JoinTable(name = "FIELD_SECTION")
private List<Section> sections;
}
with a couple of classes extending it, such as:
#Entity
#Inheritance
public class StringField extends Field<String> {
}
or
#Entity
#Inheritance
public class DateField extends Field<Date> {
}
I also have a Section class, which contains an abstract collection of Field
#Entity
public class Section {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "section_id")
private int id;
#ManyToMany(mappedBy = "sections")
private List<Field<?>> fields;
}
However, the JPA cannot properly map the List<Field<?>> fields collection which results in the following error:
Caused by: org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class: com.model.Section.fields[com.model.field.Field]
Basicly, it should be doable, since the FIELD_SECTION table would hold a pair of (section_id, field_id). Each type of field would have a separate table, but all of the field_id's would be unique in all of the field tables, allowing the JPA to determine the type of each field.
Is there a way to achieve the above?
Related
I have a parent entity 'contracts' that has a one-to-one relation with another entity 'child-contract'. the interesting thing is that the mapping field ('contract_number')id not a primary key-foreign key but is rather a unique field in both the tables. Also it is possible for a contracts to not have any child contract altogether. With this configuration I have observed hibernate to generate 1 additional query every time a contracts does not have a child-contract. I filed this behavior very strange. Is there a way to stop these unnecessary query generation or have I got something wrong.
below is a piece of my code configuration.
#Data
#Entity
#Table(name = "contracts")
public class Contracts implements Serializable {
#Id
#JsonIgnore
#Column(name = "id")
private String id;
#JsonProperty("contract_number")
#Column(name = "contract_number")
private String contractNumber;
#OneToOne(fetch=FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#JsonProperty("crm_contracts")
#JoinColumn(name = "contract_number", referencedColumnName = "contract_number")
private ChildContract childContract ;
}
#Data
#NoArgsConstructor
#Entity
#Table(name = "child_contract")
#BatchSize(size=1000)
public class ChildContract implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#JsonProperty("id")
#Column(name = "id")
private String id;
#JsonProperty("contract_number")
#Column(name = "contract_number")
private String contractNumber;
}
Please help.
Thank-you
You can use NamedEntityGraph to solve multiple query problem.
#NamedEntityGraph(name = "graph.Contracts.CRMContracts", attributeNodes = {
#NamedAttributeNode(value = "crmContract") })
Use this on your repository method as
#EntityGraph(value = "graph.Contracts.CRMContracts", type = EntityGraphType.FETCH)
// Your repo method in repository
Lets assume we have a complex JPA relation, a fraction of which looks like this:
#MappedSuperclass
public class DiffEntity {
private String diffId;
public DiffEntity() {
this.diffId = UUID.randomUUID().toString();
}
//...
}
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class ParentEntity extends DiffEntity {
#Id
#GeneratedValue
private long id;
#Column
private String name;
//...
}
#Entity
public class Construct extends ParentEntity {
#Column
private String variable;
#OneToMany(mappedBy = "construct", cascade = CascadeType.ALL)
private List<Partconstruct> partconstructs;
//...
}
#Entity
public class Partconstruct extends ParentEntity {
#OneToMany(mappedBy = "partconstruct", cascade = CascadeType.ALL)
private List<Field> fields;
#OneToMany(mappedBy = "partconstruct", cascade = CascadeType.ALL)
private List<Hardparameter> hardparameters;
#ManyToOne
#JoinColumn(name = "construct_id")
private Construct construct;
//...
}
#Entity
public class Field extends ParentEntity {
#Column
private int fieldSize;
#ManyToOne
#JoinColumn(name = "partconstruct_id")
private Partconstruct partconstruct;
//...
}
#Entity
public class Hardparameter extends ParentEntity {
#Column
private String value;
#ManyToOne
#JoinColumn(name = "partConstruct_Id")
private Partconstruct partConstruct;
//...
}
We are concerned with Construct type objects. Construct is deeply cloned and persisted, having all its nested objects on the object graph being cloned too and getting a new Id (primary key). On every clone the diffId (from DiffEntity entity) stays the same (it serves the purpose of correlating objects for a diffing feature).
How would it be possible to search and get a reference for a specific DiffEntity given we have the below:
a reference to the Construnct instance
type of the nested object
diffId we are after.
I have tried different versions of object graph traversers with reflection, which will work for a small in size Construct object, but once it becomes too big performance is very slow.
Is there any magic on the entity manager itself to achieve that ?
There are 2 entities (lets say Rule and Label) with many-to-many relationship using linked entity
as per hibernate reference documentation
Rule enity:
#Entity
#Table(name = "rule")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "name")
public class Rule implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NaturalId
#NotBlank
#Column(unique = true)
private String name;
#Lob
#Column(columnDefinition = "TEXT")
private String content;
#OneToMany(mappedBy = "rule", cascade = {CascadeType.PERSIST,
CascadeType.MERGE})
private List<RuleLabel> labels = new ArrayList<>();
...
Label entity:
#Entity
#Table(name = "label")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Label implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
private String name;
#OneToMany(mappedBy = "label", cascade = {CascadeType.PERSIST,
CascadeType.MERGE})
private List<RuleLabel> rules = new ArrayList<>();
...
Link entity:
#Entity
public class RuleLabel implements Serializable {
#Id
#ManyToOne
private Rule rule;
#Id
#ManyToOne
private Label label;
...
Repositories:
#Repository
public interface LabelRepository extends JpaRepository<Label, Long>
...
#Repository
public interface RuleRepository extends JpaRepository<Rule, Long>
...
Creating new entity via RuleRepository.save(Rule) works fine, but when I'm trying to update existing entity (the same method RuleRepository.save(Rule), but entity to be saved contains id field) it leads to infinite loop of Hibernate: select... queries:
Hibernate: select rule0_.id as id1_7_1_, rule0_.is_active as is_activ2_7_1_, rule0_.content as content3_7_1_, rule0_.is_deleted as is_delet4_7_1_, rule0_.import_section as import_s5_7_1_, rule0_.name as name6_7_1_, rule0_.rule_configuration as rule_con7_7_1_, labels1_.rule_id as rule_id1_8_3_, labels1_.label_id as label_id2_8_3_, labels1_.rule_id as rule_id1_8_0_, labels1_.label_id as label_id2_8_0_ from rule rule0_ left outer join rule_label labels1_ on rule0_.id=labels1_.rule_id where rule0_.id=?
and StackOverflowError as a result
java.lang.StackOverflowError: null
at com.mysql.jdbc.ServerPreparedStatement.getInstance(ServerPreparedStatement.java:332)
...
(LabelRepository acts in the same manner)
How it can be fixed?
Update:
After changing fetch strategy to Lazy
#Id
#ManyToOne(fetch = FetchType.LAZY)
private Rule rule;
#Id
#ManyToOne(fetch = FetchType.LAZY)
private Label label;
infinite loop problem has gone, but new one has appeared - related entities are not being populated and when Hibernate is trying to insert values into link table
Hibernate: insert into rule_label (rule_id, label_id) values (?, ?)
we get
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
...
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'rule_id' cannot be null
Okay, well I've always used an EmbeddableId for link entities with JPA. I haven't tried the hibernate example you refer to in terms of using cascade to do the work for me. It could be interesting but there are some differences between pure JPA and Spring Data Repositories. By using an EmbeddableId you can create a separate spring repository for the link entity. Then you manage the relationships yourself. If you don't want to do that then you should use a ManyToMany annotation, but the link entity allows you to create link entity attributes, not shown here. This code will work for you and get you to point B and you can experiment from there:
#Entity
public class Label {
#Id #GeneratedValue private Long id;
#OneToMany(mappedBy = "ruleLabelId.labelId")
private List<RuleLabel> rules = new ArrayList<>();
#Entity
public class Rule {
#Id #GeneratedValue private Long id;
#OneToMany(mappedBy = "ruleLabelId.ruleId")
private List<RuleLabel> labels = new ArrayList<>();
#Entity
public class RuleLabel {
#EmbeddedId
private RuleLabelId ruleLabelId;
#SuppressWarnings("serial")
#Embeddable
public class RuleLabelId implements Serializable {
private Long ruleId;
private Long labelId;
public interface RuleRepository extends JpaRepository<Rule, Long> {
#Query("from Rule r left join fetch r.labels where r.id = :id")
public Rule getWithLabels(#Param("id") Long id);
}
public interface RuleLabelRepository extends JpaRepository<RuleLabel, RuleLabelId> {}
and to use it:
Rule rule = new Rule();
Label label = new Label();
ruleRepo.save(rule);
labelRepo.save(label);
RuleLabel ruleLabel = new RuleLabel();
RuleLabelId ruleLabelId = new RuleLabelId();
ruleLabelId.setRuleId(rule.getId());
ruleLabelId.setLabelId(label.getId());
ruleLabel.setRuleLabelId(ruleLabelId);
ruleLabelRepo.save(ruleLabel);
rule = ruleRepo.getWithLabels(1L);
System.out.println(rule + Arrays.toString(rule.getLabels().toArray()));
Yes because its what you are telling hibernate to do.
By default, all #ManyToOne and #OneToOne associations are EAGER loaded, so when it querying Rule then its also querying RuleLabel and then inside there is Rule again which is causing infinite select queries. It's better to have them LAZY loaded.
You can do field lazy load like this #ManyToOne(fetch=FetchType.LAZY)
This is what JPA 2.0 spec say about defaults:
OneToMany: LAZY
ManyToOne: EAGER
ManyToMany: LAZY
OneToOne: EAGER
A good read on Lazy and Eager loading
I want to use mixed #Inheritance strategy, but Hibernate doesn't support it.
Is there any way to implement JOINED inheritance without actual class inheritance.
For example:
#Entity
#Table(name="A")
#Inheritance(strategy=InheritanceType.JOINED)
public class A {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID_SEQ")
private Long id;
//getters
//setters
}
#Entity
#Table(name="B")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public class B {
#Id
private Long id;
//getters
//setters
}
So, basically in B I just want to refer to #Id generated in A without extending from A.
I found the solution. JPA doesn't allow you to combine #Id and #OneToOne. However, #MapsId annotation does the trick:
#Entity
public class A {
#Id
private Long id;
//getters
//setters
}
#Entity
public class B {
#Id
private Long id;
#MapsId
#OneToOne(optional=false, fetch=FetchType.EAGER)
#JoinColumn(nullable=false, name="id")
private A a;
//getters
//setters
}
I think you can accomplish this by making a #OneToOne relationship or a #OneToMany and point the table name like this
#Id #OneToOne
#JoinColumn(name = "id")
private A a;
I want to model a couple of object relations and I'm currently not sure about a smart way to do this. Let's assume a Record has a OneToMany relationship to different RecordSources. The common attributes of the RecordSources are long id and boolean preferred. Other attributes are individual and the number of record sources may increase in the future.
One possibility is for Record
#OneToMany(cascade = CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="record")
private List<GenericSimpleRecordSource> recordSources;
where GenericSimpleRecordSource would look like this:
#Entity
public class GenericSimpleRecordSource implements Serializable
{
public static enum Type {a,b}
#ManyToOne private Record record;
#NotNull
private Type sourceType;
#OneToOne(cascade = CascadeType.ALL,mappedBy="source")
private SimpleRecordSourceA sourceA;
#OneToOne(cascade = CascadeType.ALL,mappedBy="source")
private SimpleRecordSourceB sourceB;
}
SimpleRecordSourceA and SimpleRecordSourceA are individual #Entity classes. I don't feel comfortable with this approach, using inheritance might be better:
#Entity
#Inheritance(strategy= InheritanceType.TABLE_PER_CLASS)
#DiscriminatorColumn(name="type")
#DiscriminatorValue("generic")
public class GenericRecordSource
{
#Id #GeneratedValue(strategy=GenerationType.AUTO) private long id;
#ManyToOne private Record record;
private boolean preferred;
}
#Entity
#DiscriminatorValue("A")
public class RecordSourceA extends GenericRecordSource
{
...
}
#Entity
#DiscriminatorValue("B")
public class RecordSourceB extends GenericRecordSource
{
...
}
This seems to be smarter, but are there any shortcomings using Inheritance? I appreciate any comments on both approaches or even another alternative.
Is it possible to model the #OneToMany relationship from Record like this?
#OneToMany(cascade = CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="record")
private List<GenericRecordSource> recordSources;