(Aware that this is not the first time question being asked, but I tried all of the suggested solutions and it did no good.)
So, I have a Parent entity and a Child entity with a OneToOne mapping - the issue is that the join isn't (and shouldn't) being done by the primary key columns, but by some other id column (a nullable natural id).
I have 4 relevant entities here:
class EntityId implements Serializable {
int storeId;
long uniqueId;
}
#Entity
#IdClass(EntityId.class)
#Immutable
#Table(name = "parent")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(
discriminatorType = DiscriminatorType.STRING,
name = "disc_type"
)
public class Parent {
#Id
#Column(name = "store_id")
private int storeId;
#Id
#Column(name = "unique_id")
private long uniqueId;
// getters and setters omitted for brevity
}
#Entity
#DiscriminatorValue(value = "Extended")
public class ExtendedParent extends Parent {
#Column(name = "id", unique = true, nullable = false)
private Long id;
#OneToOne(mappedBy="parent")
#Fetch(FetchMode.JOIN)
private ExtendedChild child;
// getters and setters omitted for brevity
}
#Immutable
#MappedSuperclass
public abstract class AbstractChild {
#Id
#Column(name = "id", nullable = false)
private long id;
// getters and setters omitted for brevity
}
#Entity
#Table(name = "extended_child")
public class ExtendedChild extends AbstractChild {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name="id", referencedColumnName = "id", insertable = false, updatable = false, nullable = false)
private ExtendedParent parent;
}
Unfortunately, whenever I try to start the application it fails with "Failed to load Application Context" with the following error:
Caused by: java.lang.ArrayIndexOutOfBoundsException: 1
at org.hibernate.sql.ANSIJoinFragment.addJoin(ANSIJoinFragment.java:81)
at org.hibernate.loader.plan.exec.internal.LoadQueryJoinAndFetchProcessor.addJoins(LoadQueryJoinAndFetchProcessor.java:281)
at org.hibernate.loader.plan.exec.internal.LoadQueryJoinAndFetchProcessor.renderEntityJoin(LoadQueryJoinAndFetchProcessor.java:184)
The problem is that it simply ignored the referencedColumnName and attempts to join via the primary key - visible when we investigate it further (breakpoint in the exception row - this is the root cause):
The right-hand side of the join in the left join (ignore the names of the columns) includes that are defined as the #Id of the Parent, instead of the "id" column defined in the ExtendedParent entity, as defined in the #JoinColumn in the ExtendedChild entity.
NOTE: I also tried setting the id column as a natural identifier with the #NaturalId annotation (moving it into the root entity Parent + adding the annotation there) but it made no difference - same error.
Ideas?
Related
I have Product entity and ProductRating entity, each Product can have many ProductRatings. When Product is deleted I want to have associated ratings deleted too, but nothing works so far (also orphanRemoval set to true)...
Classes:
#Getter
#Setter
#Entity
#Table(name = "PRODUCT")
public class Product extends AbstractEntity<Long> {
#Column(nullable = false)
private String name;
private String description;
#Column(nullable = false)
#Min(value = 0)
private Float cost;
#OneToMany(mappedBy = "product",
orphanRemoval = true, cascade = CascadeType.PERSIST,
fetch = FetchType.EAGER)
//#OnDelete(action = OnDeleteAction.CASCADE)
#Fetch(value = FetchMode.SELECT)
private Set<ProductRating> productRatings;
}
#Getter
#Setter
#Entity
#Table(name = "PRODUCT_RATING")
public class ProductRating extends Rating {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "product_id")
#NotNull(message = "Rating must be in context of Product")
private Product product;
}
After Product deletion ratings stay with deleted Product's ID
AbstractEntity implementation:
#Getter
#Setter
#MappedSuperclass
public abstract class AbstractEntity<I> implements Serializable {
private static final long serialVersionUID = 1700166770839683115L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID", unique = true, nullable = false)
private I id;
}
In the #OneToMany relation you need to add the cascade type delete: cascade = {CascadeType.PERSIST, CascadeType.REMOVE}
Or if you don't mind having all cascade types you can just put: cascade = CascadeType.ALL
EDIT:
Also check the name of the Product primary key in the database.
It should match the defined in the #JoinColumn annotation of ProductRating
The default database field for the attribute id of the Product class would be product_id.
However you have defined the id in AbstractEntity as name = "ID" so the #JoinColumn should be something like: #JoinColumn(name = "ID")
My alternative approach to fix this problem is to:
On parent-side relation create method with #PreRemove annotation
in this method iterate over collection with #[One/Many]ToMany annotation and call delete(obj) method for corresponding repository on child
On child-side relation create method with #PreRemove annotation
In this method set parent to null
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?
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 am getting this error when I will persist() my entity. I think that the cause of the error is the relation, my idea is that FolderEntity (represents a virtual folder) can be stay inside another (only one) Then I created the reference to self (In the extended class, because all resources can be inside a folder, and folder is an resource)
org.hibernate.AnnotationException: Referenced property not a (One|Many)ToOne: com.editor.entity.FolderEntity.id in mappedBy of com.editor.entity.FolderEntity.folderId
This my main Entity:
#MappedSuperclass
public abstract class Entity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID", nullable = false)
private Integer id;
/** getter/setter **/
}
Then I extends it in my ResourceEntity Entity:
#MappedSuperclass
public class ResourceEntity extends Entity {
#Column(name = "NAME", length = Lengths.NAME40, unique = true, nullable = false)
private String name;
#Column(name = "DESCRIPTION", length = Lengths.DESCRIPTION1000, unique = false, nullable = true)
private String description;
#JoinColumn(name = "FOLDER_ID", updatable = true, nullable = false)
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "id")
private FolderEntity folderId;
/** getter/setter **/
}
Finally, I am working with this entity:
#javax.persistence.Entity
#Table(name = "EDITOR_FOLDERS")
#NamedQueries({
#NamedQuery(name = FolderEntity.ALL_FOLDERS, query = "select f from FolderEntity f"),
#NamedQuery(name = FolderEntity.FOLDER_BY_NAME, query = "select f from FolderEntity f where name = :name and resourceType = :resourceType") })
public class FolderEntity extends ResourceEntity {
public static final String ALL_FOLDERS = "findAllFolders";
public static final String FOLDER_BY_NAME = "findAllFoldersByName";
#Column(name = "RESOURCE_TYPE", length = Lengths.CODE, unique = false, nullable = false)
private Integer resourceType;
/** getter/setter **/
}
Anybodys help me to solve this? Thanks!
You should check the meaning of mappedBy: It does not reference the field that contains the ID (JPA is clever enough to find that one by itself), but it references another XToOne field that "owns" the mapping
public abstract String mappedBy
(Optional) The field that owns the relationship. This element is only specified on the inverse (non-owning) side of the association.
(from javadoc of OneToOne)
In your case you don't need the mappedBy as you are on the owning side. And you should name the attribute folder as you are referencing no ID but an entity.
Another remark: Use an enum for resourceType if you intend to define the possible values in your application as constants.
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