JPA: join table syntax - java

Given the following entity (some columns omitted from this long definition for brevity):
#Table(name = "Products")
public class Products implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#Column(name = "SKU")
private String sku;
#Basic(optional = false)
#Column(name = "ProductName")
private String productName;
private boolean allowPreOrder;
#ManyToMany(mappedBy = "productsCollection")
private Collection<Categories> categoriesCollection;
#JoinTable(name = "Products_CrossSell", joinColumns = {
#JoinColumn(name = "SKU", referencedColumnName = "SKU")}, inverseJoinColumns = {
#JoinColumn(name = "CrossSKU", referencedColumnName = "SKU")})
#ManyToMany
private Collection<Products> productsCollection;
#ManyToMany(mappedBy = "productsCollection")
private Collection<Products> productsCollection1;
#JoinTable(name = "Products_Related", joinColumns = {
#JoinColumn(name = "SKU", referencedColumnName = "SKU")}, inverseJoinColumns = {
#JoinColumn(name = "RelatedSKU", referencedColumnName = "SKU")})
#ManyToMany
private Collection<Products> productsCollection2;
#ManyToMany(mappedBy = "productsCollection2")
private Collection<Products> productsCollection3;
How do I get the set of related products for a given product SKU?
The products_related table looks like this:
I know how to get the answer using SQL but I'm new to JPA so I haven't quite grokked the API and query syntax yet.

It seems to me there are some unnecessary collections defined. Anyway:
#JoinTable(name = "Products_Related", joinColumns = {
#JoinColumn(name = "SKU", referencedColumnName = "SKU")}, inverseJoinColumns = {
#JoinColumn(name = "RelatedSKU", referencedColumnName = "SKU")})
#ManyToMany
private Collection<Products> productsCollection2;
This piece (it is present in your code) should give you the desired products. Just rename it to relatedProducts, and the respective setter/getter.
Update: You can get the object by:
Product p = entityManager.find(Product.class, yourProductId);
p.getRelatedProducts();
Obtaining the entity manager depends on your setup, and a better place to look for how to obtain it, is a tutorial.

Related

Multiple queries executed by Hibernate with #OneToOne and #JoinColumnsOrFormulas

I have a table that contains personal data. This can be referenced by different tables.
PersonalData.java
#Entity
#Table(name = "personal_information")
#Getter
#Setter
public class PersonalInformation implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "personal_information_no")
#GeneratedValue(strategy = GenerationType.AUTO, generator = "personal_information_seq")
#SequenceGenerator(name = "personal_information_seq", sequenceName = "personal_information_seq", allocationSize = 1, initialValue = 1)
private int personalInformationNo;
#Column(name = "ref_object_type")
private String refObjectType;
#Column(name = "ref_object_no")
private int refObjectNo;
#Column(name = "type")
private String type;
}
Staff.java
#Entity
#Table(name = "staff")
public class Staff {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "staff_no", unique = true, nullable = false)
private int staffNo;
#OneToOne(optional = false, fetch = FetchType.LAZY)
#LazyToOne(LazyToOneOption.NO_PROXY)
#JoinColumnsOrFormulas({
#JoinColumnOrFormula(column = #JoinColumn( name = "staff_no", referencedColumnName = "ref_object_no", insertable=false, updatable=false)),
#JoinColumnOrFormula(formula = #JoinFormula( value = "(SELECT 'staff')", referencedColumnName = "ref_object_type")),
#JoinColumnOrFormula(formula = #JoinFormula( value = "(SELECT 'work')", referencedColumnName = "type"))
})
private PersonalInformation workPersonalInformation;
#OneToOne(optional = false, fetch = FetchType.LAZY)
#LazyToOne(LazyToOneOption.NO_PROXY)
#JoinColumnsOrFormulas({
#JoinColumnOrFormula(column = #JoinColumn( name = "staff_no", referencedColumnName = "ref_object_no", insertable=false, updatable=false)),
#JoinColumnOrFormula(formula = #JoinFormula( value = "(SELECT 'staff')", referencedColumnName = "ref_object_type")),
#JoinColumnOrFormula(formula = #JoinFormula( value = "(SELECT 'private')", referencedColumnName = "type"))
})
private PersonalInformation privatePersonalInformation;
}
User.java
#Entity
#Table(name = "user")
public class BusinessProviderUser {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "user_no", unique = true, nullable = false)
private int userNo;
#Column(name = "staff_no")
private Integer staffNo;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas({
#JoinColumnOrFormula(column = #JoinColumn( name = "user_no", referencedColumnName = "ref_object_no", insertable=false, updatable=false)),
#JoinColumnOrFormula(formula = #JoinFormula( value = "(SELECT 'user')", referencedColumnName = "ref_object_type"))
})
private PersonalInformation personalInformation;
}
As you can see, the reference is a bit more complex. This problem was solved with the annotation #JoinColumnsOrFormulas. The join to the person data is annotated #OneToOne in the referencing table.
The problem now is that each time the referencing table is called, additional queries are always executed.
How can I prevent this so that everything is executed in one query?
It is known that Hibernate does not support lazy loading with #OneToOne. One approach here was to implement lazy loading using bytecode enhencment. Unfortunately without success.
#OneToOne(fetch = FetchType.LAZY, optional = false)
#LazyToOne(LazyToOneOption.NO_PROXY)
#JoinColumnsOrFormulas({
#JoinColumnOrFormula(column = #JoinColumn( name = "user_no", referencedColumnName = "ref_object_no", insertable=false, updatable=false)),
#JoinColumnOrFormula(formula = #JoinFormula( value = "(SELECT 'user')", referencedColumnName = "ref_object_type"))
})
private PersonalInformation personalInformation;
Another approach is to load everything in one single query by adding #Fetch(FetchMode.Join).
#OneToOne(optional = false)
#JoinColumnsOrFormulas({
#JoinColumnOrFormula(column = #JoinColumn( name = "user_no", referencedColumnName = "ref_object_no", insertable=false, updatable=false)),
#JoinColumnOrFormula(formula = #JoinFormula( value = "(SELECT 'user')", referencedColumnName = "ref_object_type"))
})
#Fetch(FetchMode.JOIN)
private PersonalInformation personalInformation;
In the end, it is not crucial for the project whether the data is loaded lazy (preferred) or immediately.
It is only important that multiple queries are not sent per user or staff.
Are there any tips or solutions for the problem?
Maybe there is also a nicer solution regarding the database structure? Changes could be made here as well.
Unfortunately, Hibernate version 3.6.10-final must still be used.

Double self-reference within the same entity using an extra join table

I want to create a double self-reference entity using an extra join table. I tried thus the following :
#Entity
#Table(name = "entity_a", schema="schema_a")
public class EntityA{
#Id
#Column(name = "id", unique = true, nullable = false)
private UUID id = UUID.randomUUID();
//skipped source code...
#OneToOne(fetch = FetchType.LAZY)
#JoinTable(name = "origin_child",
joinColumns =
{ #JoinColumn(name = "origin_id", referencedColumnName = "id", nullable = false)},
inverseJoinColumns =
{ #JoinColumn(name = "child_id", referencedColumnName = "id", nullable = false)})
private EntityA child;
#OneToOne(fetch = FetchType.LAZY)
#JoinTable(name = "origin_child",
joinColumns =
{ #JoinColumn(name = "child_id", referencedColumnName = "id", nullable = false)},
inverseJoinColumns =
{ #JoinColumn(name = "origin_id", referencedColumnName = "id", nullable = false)})
private EntityA origin;
//skipped source code...
}
When running my code I get the following error : org.hibernate.boot.spi.InFlightMetadataCollector$DuplicateSecondaryTableException: Table with that name [origin_child] already associated with entity
I tried thus to remove the origin field from EntityA and it worked. Now I am trying to figure out a way to join my EntityA.id and EntityA.origin_id over the already existing origin_child.child_id and origin_child.origin_id. Any idea how may I achieve that. Any alternative or better solution is more than welcome.
UPDATE :
I have tried the #JoinColumn alternative as following :
#OneToOne(fetch = FetchType.LAZY, mappedBy = "child")
private EntityA origin;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "child_id", referencedColumnName = "id", nullable = true)
private EntityA child;
Now when trying to update both origin and child instances :
child.setOrigin(origin);
entityARepository.save(child);
origin.setChild(child);
entityARepository.save(origin);
I get java.lang.StackOverflowError due to infinite recursion.
Any work around please?

hibernate table with 3 foreign keys

I'm trying to map the following table to an entity.
The mapping for the event table looks like this:
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer _id;
#Column(name = "title")
private String _title;
#Column(name = "description")
private String _description;
#Column(name = "location")
private String _place;
#Column(name = "start")
private Date _start;
#Column(name = "end")
private Date _stop;
#Column(name = "points")
private int _points;
#Enumerated(EnumType.STRING)
#Column(name = "type")
private EventType _eventtype;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "event2work2instrumentation", joinColumns = {
#JoinColumn(name = "event")},
inverseJoinColumns = {#JoinColumn(name = "work")})
private Set<WorkMapper> _works = new HashSet<>(0);
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "event2work2instrumentation", joinColumns = {
#JoinColumn(name = "event")},
inverseJoinColumns = {#JoinColumn(name = "instrumentation")})
private Set<InstrumentationMapper> _instrumentations = new HashSet<>(0);
It works fine and i get all the data from the database, but when I try to store a new event which has a work and instrumentation assigned to it I get errors.
When the fields in the event2work2instrumentation table are configured to not be null, i get: field does not have a default value.
If I give the field default values I get:
SQLIntegrityConstraintViolationException: Cannot add or update a child row:
a foreign key constraint fails (`schema`.`event2work2instrumentation`, CONSTRAINT `e2w2i_instrumentation_fk` FOREIGN KEY (`instrumentation`) REFERENCES `instrumentation` (`id`))
You have any ideas? May it depend on the Mappings of instrumentation and/or Work? These classes do not have a reference to event2work2instrumentation in their mapping.
Try truncating both tables and then store the new FK. It happened to me when I had a object_related_id which wasn't in object table as an id
The solution is to use a map and the MapKeyJoinColumn annotation:
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "event2work2instrumentation", joinColumns = {
#JoinColumn(name = "event")},
inverseJoinColumns = {#JoinColumn(name = "instrumentation")})
#MapKeyJoinColumn(name = "work")
private Map<WorkMapper, InstrumentationMapper> _eventToWorkAndInstrumentationMappers = new HashMap<>();

The abstract schema type 'User_Book' is unknown

I have a database with several entities, in particular Book and User. Between them there exists a ManyToMany relationship like this:
Book:
#Entity
#Table(name = "Books")
public class Book implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
#Column(name = "bookId", nullable = false, unique = true)
private Long id;
#Column(name = "title", nullable = false)
private String title;
#Column(name = "price", nullable = false)
private int price;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "User_Book",
joinColumns = #JoinColumn(name = "bookId"),
inverseJoinColumns = #JoinColumn(name = "userId"))
private Set<UserAccount> users;
User:
#Entity
#Table(name = "UserAccounts")
public class UserAccount implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
#Column(name = "userId", nullable = false, unique = true)
private Long id;
#Column(name = "username", nullable = false, unique = true)
private String username;
#Column(name = "password", nullable = false)
private String password;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "User_Book",
joinColumns = #JoinColumn(name = "userId"),
inverseJoinColumns = #JoinColumn(name = "bookId"))
Set<Book> purchasedBooks;
Everything works fine, the table User_Book is indeed created in the database. The problem seems to be related to the access of this Table.
For example,
Query query = entityManager.createQuery("SELECT u FROM User_Book u");
keeps telling me the following:
The abstract schema type 'User_Book' is unknown
So, shall I create from scratch the User_Book entity? Will it get automtically populated like now, that is, whenever a user buys a book, will this purchase be recorded in the table?
User_Book is not an entity. Therefore you cannot use createQuery, BUT you can use createNativeQuery to execute a SQL query:
Query query = entityManager.createNativeQuery("SELECT * FROM User_Book");
The result will be List<Object[]>

JPA Entity Mapping which is related based on two other entity mappings

Not sure if this is possible but trying to map WorkflowInstancePlayer player which is related based on two other entity mappings, WorkActionClass and WorkflowInstance in the entity below.
public class Action implements Serializable {
#Id
private Long action_id;
#ManyToOne
#JoinColumn(name = "work_action_class_id", referencedColumnName = "work_action_class_id")
private WorkActionClass workActionClass;
#ManyToOne
#JoinColumn(name = "workflow_instance_id", referencedColumnName = "workflow_instance_id")
private WorkflowInstance workflowInstance;
UPDATE: How can I map to a WorkflowInstancePlayer player?????
#ManyToOne
#JoinColumns( {
#JoinColumn(name = "workflow_instance_id", referencedColumnName = "workflow_instance_id", insertable = false, updatable = false),
#JoinColumn(name = "workActionClass.role_class_id", referencedColumnName = "role_class_id", insertable = false, updatable = false)
})
private WorkflowInstancePlayer player;
The workflowInstancePlayer is derived based on workflow_instance_id and role_class_id but role_class_id is really an attibute of workActionClass mapped above (workActionClass.role_class_id)
public class WorkflowInstancePlayer implements Serializable {
#Id
private WorkflowInstance workflowInstance;
#Id
private RoleClass roleClass;
#ManyToOne
#JoinColumn(name = "badge", referencedColumnName = "badge")
private Employee employee;
public class WorkActionClass implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "trx_seq")
private Long work_action_class_id;
#ManyToOne
#JoinColumn(name = "role_class_id")
private RoleClass roleClass;
Example data would be:
Action
------
Id = 10
work_action_class_id = 7
workflow_instance_id = 2
WorkActionClass
---------------
Id = 7
role_name = reviewer
role_class_id = 3
WorkflowInstancePlayer
----------------------
workflow_instance_id = 2
role_class_id = 3
badge = 111222
So in the Action Entity, I'll know the Workflow Instance Player is Employee with Id 111222 without actually storing the badge in the Action table.
UPDATE
Based on Vlad's post I tweaked it to be
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumnsOrFormulas({
#JoinColumnOrFormula(formula=#JoinFormula(value="(SELECT a.role_class_id FROM (Use Table Name not Entity Name here) a WHERE a.work_action_class_id = work_action_class_id)", referencedColumnName="role_class_id")),
#JoinColumnOrFormula(column = #JoinColumn(name="workflow_instance_id", referencedColumnName="workflow_instance_id"))
})
Try with this mapping:
#ManyToOne
#JoinColumn(
name = "workflow_instance_id",
referencedColumnName = "workflow_instance_id",
insertable = false,
updatable = false
)
private WorkflowInstance workflowInstance;
#ManyToOne
#JoinColumnOrFormula(
formula = #JoinFormula(
value="(SELECT a.work_action_class_id FROM WorkActionClass a WHERE a.role_class_id = role_class_id)",
referencedColumnName = "work_action_class_id"
)
)
private WorkActionClass workActionClass;
#ManyToOne
#JoinColumns( {
#JoinColumn(
name = "workflow_instance_id",
referencedColumnName = "workflow_instance_id"),
#JoinColumn(
name = "role_class_id",
referencedColumnName = "role_class_id")
})
private WorkflowInstancePlayer player;

Categories

Resources