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.
Related
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[]>
I following have hibernate entities:
#Entity
#Table(name = "News")
public final class News implements Serializable, IEntity {
private static final long serialVersionUID = 3773281197317274020L;
#Id
#SequenceGenerator(name = "NEWS_SEQ_GEN", sequenceName = "NEWS_SEQ")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "NEWS_SEQ_GEN")
#Column(name = "NEWS_ID", precision = 0)
private Long newsId; // Primary key
#Column(name = "TITLE")
private String title;
#Column(name = "SHORT_TEXT")
private String shortText;
#Column(name = "FULL_TEXT")
private String fullText;
#Temporal(TemporalType.DATE)
#Column(name = "CREATION_DATE")
private Date creationDate;
#Temporal(TemporalType.DATE)
#Column(name = "MODIFICATION_DATE")
private Date modificationDate;
#OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true)
#JoinColumn(name = "NEWS_ID", updatable = false, referencedColumnName = "NEWS_ID")
#OrderBy("creationDate ASC")
private List<Comment> commentsList;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "NEWS_TAG", joinColumns = { #JoinColumn(name = "NEWS_ID") }, inverseJoinColumns = { #JoinColumn(name = "TAG_ID") })
private Set<Tag> tagSet;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "NEWS_AUTHOR", joinColumns = { #JoinColumn(name = "NEWS_ID") }, inverseJoinColumns = { #JoinColumn(name = "AUTHOR_ID") })
private Set<Author> author;
And the second:
#SequenceGenerator(name = "COMMENTS_SEQ", sequenceName = "COMMENTS_SEQ")
#Entity
#Table(name = "Comments")
public class Comment implements Serializable, IEntity {
private static final long serialVersionUID = 3431305873409011465L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "COMMENTS_SEQ")
#Column(name = "COMMENT_ID", precision = 0)
private Long commentId; // Primary key
#Column(name = "NEWS_ID")
private Long newsId;
#NotEmpty
#NotNull
#Column(name = "COMMENT_TEXT")
private String commentText;
#Temporal(TemporalType.DATE)
#Column(name = "CREATION_DATE")
private Date creationDate;
When I'm trying to remove entity News, I get the exception ORA-02292: integrity constraint (ROOT.SYS_C007062) violated - child record found. So, if I remove the property "updatable = false" it tries to set nullable fields into property Comment. What is my mistake? Please, help.
Thanks.
Because your news records have a one to one or one to many relation with comments. You most likely did not specifcy a CACASDE ON DELETE clause while defining your table. in order to delete entity NEWS you have to make sure that all of its related comments records are deleted or are referencing another NEWS record.
basicaly the definition of the ORA 02292 exception.
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;
Brand
public class Brand implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "BrandID", nullable = false)
private Integer brandID;
#Basic(optional = false)
#Column(name = "BrandName", nullable = false, length = 100)
private String brandName;
#Basic(optional = false)
#Column(name = "Description", nullable = false, length = 1000)
private String description;
#Column(name = "Is_Visible")
private Boolean isVisible;
#JoinTable(name = "brandcategory", joinColumns = {
#JoinColumn(name = "BrandID", referencedColumnName = "BrandID")}, inverseJoinColumns = {
#JoinColumn(name = "CategoryID", referencedColumnName = "CategoryID")})
#ManyToMany(fetch = FetchType.EAGER)
private Collection<Category> categoryCollection;
#OneToMany(mappedBy = "brand", fetch = FetchType.EAGER)
private Collection<Product> productCollection;
I want to retrive the Brand IDs from table brandcategory whoes categoryID = :categoryID
how can i createnamed query for it in entity brand?
this does not work:
#NamedQuery(name = "Brand.getBrandListByCategory",
query = "SELECT b FROM Brand b WHERE b.brandID =
(SELECT bc.brandID
FROM b.brandctegory bc
WHERE bc.category.categoryID = :categoryID)")
If I understand correctly, you want all the brands belonging to a category. Why don't you simply make the association bidirectional. You could then just do:
Category category = em.find(Category.class, categoryId);
return category.getBrands();
If it's unidirectional, then you'll need a query, but it's much simpler that the one you tried:
select b from Brand b inner join b.categoryCollection category
where category.id = :categoryId;
Your query doesn't make sense: it uses a non-existing association (b.brandcategory). Remember that JPQL uses entities, their persistent fields and associations to other entities. And nothing else. Tables don't exist in JPQL.
AFAIK, you cant go out of a entity boundary, when creating queries in entity class.
Instead use .createNativeQuery() method of the entity manager, to create complex and mixed queries.
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.