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

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;

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.

Force to do flush to get new Primary key ID to store EmbeddedId

We are using JPA and we are using EmbeddedId in one of the Entity.
Tables are as bellow :
Role:
+-----------------------------+
| roleId | name | discription |
+-----------------------------+
Rights:
+-----------------------------+
| rightId | name | discription|
+-----------------------------+
rightrole
+--------------------------------------+
| roleId | rightId | some other column |
+--------------------------------------+
Entity for role table is:
#Entity
#Table(name = "role")
public class Role {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#NotNull
#Column(nullable = false)
private Long roleID;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "role", fetch = FetchType.LAZY)
private List<RightRole> rightRoleList;
.......
}
Entity for rightrole table is:
#Entity
#Table(name = "rightrole")
public class RightRole extends BaseEntity<RightRolePK> {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected RightRolePK rightRolePK;
#JoinColumn(name = "roleID", referencedColumnName = "roleID", insertable = false, updatable = false)
#ManyToOne(fetch = FetchType.LAZY)
private Role role;
#JoinColumn(name = "rightID", referencedColumnName = "rightID", insertable = false, updatable = false)
#ManyToOne(fetch = FetchType.LAZY)
private Right right;
......
}
#Embeddable
public class RightRolePK implements Serializable {
private static final long serialVersionUID = 1L;
#Basic(optional = false)
#NotNull
#Column(nullable = false)
private long roleID;
#Basic(optional = false)
#NotNull
#Column(nullable = false)
private long rightID;
.....
}
My problem is whenever I want to create new role with rights then first I have to store(persist) role object and then I have to do flush to get newly generated id for role. then and then i can put it in rightrole entity's object.
Is there any way by that i can set rightrole list in role object and persist it in one go.
This flush casing us performance bottle neck because for bulk insert we have to persist single single object.
we are using Auto Generated primary key.
JPA 2.0 allows derived IDs, an expanded to support this better by adding #MapsId. Keeping everything else the same but using:
#Entity
#Table(name = "rightrole")
public class RightRole extends BaseEntity<RightRolePK> {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected RightRolePK rightRolePK;
#MapsId("roleID")
#JoinColumn(name = "roleID", referencedColumnName = "roleID")
#ManyToOne(fetch = FetchType.LAZY)
private Role role;
#MapsId("rightID")
#JoinColumn(name = "rightID", referencedColumnName = "rightID", insertable = false, updatable = false)
#ManyToOne(fetch = FetchType.LAZY)
private Right right;
......
}
This will tell JPA that the value within the roleID and rightID attributes in your PK class are controlled by the relationship mapping, and it will set it after synchronizing to the database with the primary key value from the reference. You just need to be sure you set the relationship before calling persist, as it doesn't have a primary key otherwise.
This works even if the referenced object is also composite. Something that needs to reference RightRole would use RightRolePK though instead of a Long value:
#Entity
#Table(name = "wrong_right_role")
public class WrongRightRole{
#EmbeddedId
WrongRightRoleId wrongRightRoleId;
#MapsId("rightRoleID")
#JoinColumns({
#JoinColumn(name = "roleID", referencedColumnName = "roleID"),
#JoinColumn(name = "rightID", referencedColumnName = "rightID")
})
RightRole rightRole;
}
#Embeddable
public class WrongRightRolePK implements Serializable {
private static final long serialVersionUID = 1L;
private RightRoleID rightRoleID;
.....
}
JPA also allows marking the right and role mappings with the #ID annotation, allowing you to remove the rightRolePK embeddedId from within the object, and use it as a primary key class instead.
Using a #ManyToMany annotation you can define your entity like this:
Role:
#Entity
#Table(name = "role")
public class Role {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#NotNull
#Column(name = "ROLE_ID", nullable = false)
private Long roleID;
#JoinTable(name = "rightrole",
joinColumns = {
#JoinColumn(name = "roleID", referencedColumnName = "ROLE_ID")},
inverseJoinColumns = {
#JoinColumn(name = "rightID", referencedColumnName = "RIGHT_ID")})
#ManyToMany
private List<Right> rightList;
}
You have to write #ManyToMany(cascade = {CascadeType.PERSIST}) in inverseJoinColumns otherwise your parent data will get deleted if child get deleted.
Right:
#Entity
#Table(name = "right")
public class Right {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#NotNull
#Column(name = "RIGHT_ID",nullable = false)
private Long rightId;
}
you can then simply:
Role role = new Role();
List<Right> rightList = new ArrayList<>();
Right right1 = new Right();
Right right2 = new Right();
rightList.add(right1);
rightList.add(right2);
role.setRightList(rightList);
and persist
the #JoinTable annotation will take care of inserting in the rightrole table even without an entity (as long as that table have only the id columns of role and right) so you'll get something like this in db
Role :
id name etc
1 roleName etc
Right:
id name etc
1 rightName1 etc
2 rightName2 etc
rightrole:
roleId rightID
1 1
1 2

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 Mapping of Association table where one of the entities has composite key

I have the following model that I need to annotate using JPA:
Merchant(merchant_id, ...).
MerchantType(id1, id2, ...)
MerchantMerchantTypeAssociationTable(merchant_id, id1, id2)
I cannot figure out how to map the association table. Mapping Merchant is straitghtforward, so I will leave it outside of the mappings. The other mappings are as follows:
MerchantType:
#Entity
class MerchantType {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "e1_id", column=#Column(name="e1_id")),
#AttributeOverride(name = "another_id", column=#Column(name="another_id"))
})
MerchantTypePk id;
#ManyToOne
#JoinColumn(name = "e1_id", referencedColumnName = "e1_id", insertable = false, nullable = false)
#MapsId("e1_id")
AnotherEntity1 e1;
#Column(name = "another_id", referencedColumnName = "another_id", insertable = false, nullable = false)
Long anotherId;
//Two other local fields irrelevant to the discussion here
public MerchantType(){
this.id = new MerchantTypePk();
}
//Getters and setters here.
}
//MerchantTypePk is a simple Embeddable class here below with two Long fields:
//e1_id and another_id
MerchantMerchantTypeAssociation:
#Entity
class MerchantMerchantTypeAssociation {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "e1_id", column = #Column(name = "e1_id")),
#AttributeOverride(name = "another_id", column = #Column(name = "another_id"))
#AttributeOverride(name = "offer_id", column = #Column(name = "merchant_id"))
})
private MerchantMerchantTypeAssociationPk id;
//******** HERE IS THE QUESTION
//******** HERE IS THE QUESTION
//******** HERE IS THE QUESTION
#ManyToOne
#JoinColumns({
#JoinColumn(name = "e1_id", referencedColumnName = "e1_id", insertable = false, updatable = false),
#JoinColumn(name = "another_id", referencedColumnName = "another_id", insertable = false, updatable = false)
})
#MapsId("e1_id")
#MapsId("another_id")
private MerchantType merchantType;
//Similar mapping to the one above, but with only one Join Column
private Merchant merchant;
//One more local field that is irrelevant to the mapping
//but is the one that is forcing me to map a many - to - many relationship
//in this way.
}
//MerchantMerchantTypeAssociationPk as a simple embeddable
Question: How can I make a mapping for this kind of entities when the annotation '#MapsId' cannot be repeated and it does not accept more than one value?
You did not include the code for MerchantMerchantTypeAssociationPk, but I'm guessing it looks like this:
#Embeddable
public class MerchantMerchantTypeAssociationPk {
public MerchantPk merchantPK;
public MerchantTypePk merchantTypePK;
}
#MapsId is used to specify the attribute within the composite key to which the relationship attribute corresponds, not the columns. So MerchantMerchantTypeAssociation should look like this:
#Entity class MerchantMerchantTypeAssociation {
#EmbeddedId
private MerchantMerchantTypeAssociationPk id;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "e1_id", referencedColumnName = "e1_id",...),
#JoinColumn(name = "e2_id", referencedColumnName = "e2_id",...)
})
#MapsId("merchantTypePK") // <<< *attribute* in Embeddable
private MerchantType merchantType;
#ManyToOne
#JoinColumn(name = "m_id", referencedColumnName = "merchant_id",...)
#MapsId("merchantPK") // <<< *attribute* in Embeddable
private Merchant merchant;
}
Derived identities are discussed in the JPA 2.1 spec, section 2.4.1.

JPA: join table syntax

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.

Categories

Resources