I want to join only one column from another table.
I have 2 entities now:
#Entity
public class Message {
....
#ManyToOne
#JoinColumn(name = "ATTRIBUTE_ID")
private Attribute attribute;
}
#Entity
#Table(name = "ATTRIBUTE_TABLE")
public class Attribute {
#Id
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
}
And I want to simplify code and don't use entity for only one column:
#Entity
#SecondaryTable(name = "ATTRIBUTE_TABLE", pkJoinColumns =
#PrimaryKeyJoinColumn(name = "ID", referencedColumnName = "ATTRIBUTE_ID")),
public class Message {
....
#Column(table = "ATTRIBUTE_TABLE", name = "NAME")
private String attribute;
}
But #SecondaryTable JoinColumn cannot reference a non-primary key.
How to add a column from another table without using additional entity for it?
Related
I have a table that holds events where the data is stored as a JSON object.
What i'am trying to do is have different entities mapped to the same table, because some of these events need to join with other tables to be useful.
What i tried doing:
EntityA is the "default" entity for the table, all events are received in this entity an them stored.
#Entity(name = "entity_a")
#Table(name = "table_a")
public class EntityA {
#Id
private UUID id;
#Column(name = "fielda")
private String fieldA;
#Column(name = "fieldb")
private Integer fieldB;
#Column(name = "json_field")
private Object jsonField;
}
EntityAB is a map of the same table with a new field that is a join with another table.
#Entity(name = "entity_ab")
#Table(name = "table_a")
public class EntityAB {
#Id
private UUID id;
#Column(name = "fielda")
private String fieldA;
#Column(name = "fieldb")
private Integer fieldB;
#Column(name = "json_field")
private Object jsonField;
#ManyToOne
#ManyToOne
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula = #JoinFormula(value = "JSON_VALUE(jsonField, '$.field_id_a')", referencedColumnName = "field_id_a")),
#JoinColumnOrFormula(formula = #JoinFormula(value = "JSON_VALUE(jsonField, '$.field_id_b')", referencedColumnName = "field_id_b")),
#JoinColumnOrFormula(formula = #JoinFormula(value = "JSON_VALUE(jsonField, '$.fieldc')", referencedColumnName = "fieldc")),
})
private EntityB entityB;
}
EntityB is used in the EntityAB.
public class EntityB implements Serializable {
#EmbeddedId
private EntityBId id;
#Column(name = "fieldc")
private String fieldC;
}
#Embeddable
public class EntityBId implements Serializable {
#Column(name = "field_id_a")
private String fieldIdA;
#Column(name = "field_id_b")
private Integer fieldIdB;
#Column(name = "field_id_c")
private Integer fieldIdC;
}
When starting the application, it gives me this error:
referencedColumnNames(field_id_a, field_id_b, fieldc) of EntityAB.entityB referencing EntityB not mapped to a single property
If i dont use the fields "field_id_a" and "field_id_b" (EmbeddedId) in the join, it works, but it's not the right join. From what i have searched it looks like i can't have a parcial PK join when using "EmbeddedId", is that correct?
#Entity
#Table(name = "A")
public class A {
#Id
#Column(name = "id")
private Long id;
#Id
#Column(name = "name")
private String name;
#JoinColumn(name = "test_id")
private List<Test> testId;
}
#Entity
#Table(name = "Test")
public class Test {
#Id
#Column(name = "test_id")
private Long testId;
}
Error Result is
" JPA trouble with OneToOne relationship: A Foreign key refering has the wrong number of column. should be 2 "
How to specific primary key for join Test table ?
Table A : column id
map with
Table B : column test_id
Since your table A has a composite key, you should separate the columns out into another key class and then join on the individual part of the key of the table.
For instance, create AKey
#Embeddable
public class AKey {
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
//getters and setters
}
Then replace the ids in class A
#Entity
#Table(name = "A")
public class A {
#EmbeddedId
private AKey key;
#JoinColumn(name = "test_id")
private List<Test> testId;
}
Then you can do a join on Test.testId = A.key.id
I have an entity BlocRecord having a composite code BlocRecordId, and in its composite code there is an #Embedded (relation code ManyToOne) pointing to another entiy Record and want to Audit the entity BlocRecord.
The entity BlocRecord
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Table(name = "blocRecord")
#Access(value = AccessType.FIELD)
#Audited
public class BlocRecord {
#EmbeddedId
private BlocRecordId blocRecordId = new BlocRecordId();
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumns({
#JoinColumn(name = "record_identifier_", referencedColumnName = "identifier_", unique = false, nullable = false),
#JoinColumn(name = "record_recordType_", referencedColumnName = "recordType_", unique = false, nullable = false)})
#MapsId("record")
private Record record;
...
}
The id class BlocRecordID
#Embeddable
public class BlocRecordId implements Serializable {
#Embedded
private RecordId record;
#Column(name = "source_")
String source ;
#Column(name = "messageType_")
String messageType ;
The entity Record
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Table(name = "records")
#Access(value = AccessType.FIELD)
#Audited
public class Record {
#EmbeddedId
private RecordId recordId = new RecordId();
#OneToMany(targetEntity = BlocRecord.class, fetch = FetchType.LAZY, mappedBy = "record")
private Set<BlocRecord> blocRecord = new java.util.HashSet<>();
...
}
The idClass of the entity Record
#Embeddable
public class RecordId implements Serializable{
#Column(name = "identifier_")
String identifier ;
#Column(name = "recordType_")
String recordType ;
}
Hibernate-envers fails when trying to generate the metadata of the field record in the embeddable BlocRecordId, the The flowing exception is thrown
org.hibernate.MappingException: Type not supported: org.hibernate.type.ComponentType
at org.hibernate.envers.configuration.internal.metadata.IdMetadataGenerator.addIdProperties(IdMetadataGenerator.java:121)
at org.hibernate.envers.configuration.internal.metadata.IdMetadataGenerator.addId(IdMetadataGenerator.java:230)
at org.hibernate.envers.configuration.internal.metadata.AuditMetadataGenerator.generateFirstPass(AuditMetadataGenerator.java:642)
at org.hibernate.envers.configuration.internal.EntitiesConfigurator.configure(EntitiesConfigurator.java:95)
at org.hibernate.envers.boot.internal.EnversServiceImpl.doInitialize(EnversServiceImpl.java:154)
at org.hibernate.envers.boot.internal.EnversServiceImpl.initialize(EnversServiceImpl.java:118)
at org.hibernate.envers.boot.internal.AdditionalJaxbMappingProducerImpl.produceAdditionalMappings(AdditionalJaxbMappingProducerImpl.java:99)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:288)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.build(MetadataBuildingProcess.java:83)
at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:417)
at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:86)
at org.hibernate.boot.MetadataSources.buildMetadata(MetadataSources.java:179)
Do you have any idea how to resolve the issue ?
Thanks
At the moment, Envers does not support the idea of nesting an embeddable inside an embeddable when we map the identifier columns like your example illustrates. The only valid mappings that Envers presently does support is if the attribute in the embeddable is a #ManyToOne or a #Basic type.
You can work around this problem but it involves being a bit more explicit and not using RecordId. What I mean is rewrite BlocRecordId to be the following:
#Embeddable
public class BlocRecordId implements Serializable {
#Column(name = "identifier_")
String identifier;
#Column(name = "recordType_")
String recordType;
#Column(name = "source_")
String source;
#Column(name = "messageType_")
String messageType;
#Transient
private RecordId recordId;
/** Helper method to assign the values from an existing RecordId */
public void setRecordId(RecordId recordId) {
this.identifier = recordId.getIdentifier();
this.recordType = recordId.getRecordType();
}
/** Helper method to get the RecordId, caching it to avoid multiple allocations */
public RecordId getRecordId() {
if ( recordId == null ) {
this.recordId = new RecordId( identifier, recordType );
}
return this.recordId;
}
}
I agree this is less than ideal but it does at least work around the current limitation of the code. I have gone added and added HHH-13361 as an open issue to support this. You're welcomed to contribute if you wish or I'll work in getting this supported for Envers 6.0.
I have two entity classes.
Order.java
#Entity
#Table(name = "order_table")
public class Order implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "order_id", referencedColumnName = "id", nullable = false, insertable=false, updatable=false)
private Set<Item> items;
// getters & setters & toString
Item.java
#Entity
#Table(name = "item")
public class Item implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "order_id", nullable = false)
private Long orderId;
// getters & setters && toString
I created a test class like this:
#Test
public void createOrderWithItems() {
Item item = new Item();
item.setName("Iron Man");
Order order = new Order();
order.setName("Toy");
order.getItems().add(item);
Order created = service.createOrder(order);
Order orderById = service.getOrderById(order.getId());
System.out.println("Created Order: " + orderById);
Item itemById = service.getItemById(item.getId());
System.out.println("Created item: " + itemById);
Assert.notNull(created.getId(), "Order ID is Null");
}
Test is green but if you check output, you'll see that orderId field in the Item class is null.
Created Order: Order{id=1, name='Toy', items=[Item{id=2, name='Iron Man', orderId=null}]}
Created item: Item{id=2, name='Iron Man', orderId=null}
Does JPA not update this column in the db automatically? Is this column is redundant? If so, how can I retrieve this information from test code?
You need to set orderId explicitly.
item.setOrderId(order.getId());
order.getItems().add(item);
You can create a method addItem(Item item) in your Order class and hide this logic within it.
Cascading will create an entry in db but it won't initialize field. JPA annotations just indicate to JPA provider how to perform mapping between entity and table.
Moreover, check your annotations. #JoinColumn should be used in the entity which owns the relationship (the corresponding table has column as a foreign key). Check the top answer for this question for detailed explanations: What's the difference between #JoinColumn and mappedBy when using a JPA #OneToMany association
I can't propper map DB tables with JPA annotation.
Tables Subject and Place is ManyToMany through JoinTable.
Subject.java
#Entity
#Table(name = "SUBJECT")
public class Subject implements Serializable {
#Id
#Column(name = "SID")
private Integer sid;
#Column(name = "NAME")
private String name;
// getters and setters
}
SubjectPlace.java
#Entity
#Table(name = "SUBJECT_PLACE")
public class SubjectPlace implements Serializable {
#Id
#Column(name = "SPID")
private Integer spid;
#ManyToOne
#JoinColumn(name = "SUB_KEY") //Subject FK
private Subject subject;
#ManyToOne
#JoinColumn(name = "PLC_KEY") //Place FK
private Place place;
// getters and setters
}
Place.java
#Entity
#Table(name = "PLACE")
public class Place implements Serializable {
#Id
#Column(name = "PID")
private Integer pid;
#Column(name = "NAME")
private String name;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinTable(name = "SUBJECT_PLACE",
joinColumns = { #JoinColumn(name = "PLC_KEY", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "SUB_KEY", nullable = false, updatable = false) })
private Set<Subject> subjects;
// getters and setters
}
But than I need to link Person with Subject in selected Places. I mean that each Place has its own collection of Subject. And a Person have link to Subject whitch resides in particular Place.
like This:
Subject (M) -- (M) Place through JoinTable Subject (1) -- (M) Subject_Place (M) -- (1) Place
Person (M) -- (M) Subject_Place through JoinTable Person (1) -- (M) Person_Subject_Place (M) -- (1) Subject_Place
Person.java
#Entity
#Table(name = "PERSON")
public class Person implements Serializable {
#Id
#Column(name = "PRSID")
private Integer prsid;
#Column(name = "NAME")
private String name;
// How to annotate this code?
// I experience problem in this part of code
#OneToMany
#JoinColumn(name="SPID_KEY")
private List<SubjectPlace> subjectPlaces;
// getters and setters
}
PersonSubjectPlace.java
#Entity
#Table(name = "PERSON_SUBJECT_PLACE")
public class PersonSubjectPlace implements Serializable {
#Id
#Column(name = "PSPID") // Person_Subject_Place ID
private Integer pspid;
#ManyToOne
#JoinColumn(name = "PER_KEY") //Person FK
private Person person;
// How to annotate this code?
// I experience problem in this part of code
#ManyToOne
#JoinColumn(name = "SPID_KEY") //Subject_Place FK
private SubjectPlace subjectPlace;
// getters and setters
}
And when I try so get Persons and its Subjects, I get this error:
Caused by: org.hibernate.MappingException: Foreign key (FK2C3B79384AABC975:PERSON_SUBJECT_PLACE [SPID_KEY])) must have same number of columns as the referenced primary key (SUBJECT_PLACE [PLC_KEY,SUB_KEY])
What, How shoul I map?
In your OneToMany mapping you don't need to specify the foreign key, you just need to use mappedBy property to refer your mapping object, you can learn more about it in OneToMany Mapping Documentation, and here's what you need to map Person and PersonSubjectPlace entities:
In your Person class:
#OneToMany(mappedBy="person")
private List<PersonSubjectPlace> personsubjectPlaces;
In your PersonSubjectPlace class:
#ManyToOne
#JoinColumn(name="PRSID") //Specify the primary key of Person
private Person person;
For further information about the difference between JoinColumn and mappedBy you can take a look at this answer.
EDIT:
For the mapping between SubjectPlace and PersonSubjectPlace:
In your SubjectPlace class:
#OneToMany(mappedBy="subjectPlace")
private List<PersonSubjectPlace> personsubjectPlaces;
In your PersonSubjectPlace class:
#ManyToOne
#JoinColumn(name="SPID") //Specify the primary key of SubjectPerson
private SubjectPlace subjectPlace;
Note:
The best approach to map those classes is to use #JoinTable between Person and SubjectPlace, take a look at this #JoinTable example, because PersonSubjectPlace is pratically an asociation-entity between Person and SubjectPlace.
You should remove #Joincolumn annotation and add mappedBy variable to #OneToMany annotation.
#OneToMany(mappedBy = "spid")
You should have a variable in SubjectPlace that has a Person where you should put #JoinColumn annotation