Using a view as a join table with Hibernate - java

I've got two entities which I want to join via a common String.
I've created a view which I want to use as the join table. This all works fine except for when I try to delete an entity. Hibernate then tries to delete from that view which of course fails. The database used is MySQL.
So I've got
#Entity
public class Event {
...
String productId;
Date eventDatetime;
...
}
#Entity
public class Stock {
...
String productId;
...
}
I've created a view in MySQL
DROP VIEW IF EXISTS EVENT_STOCK_VIEW;
create view EVENT_STOCK_VIEW AS
SELECT EVENT.EVENT_ID, STOCK.STOCK_ID
FROM EVENT, STOCK
where STOCK.PRODUCT_ID = EVENT.PRODUCT_ID;
in Event I've added:
#ManyToOne(fetch=FetchType.LAZY)
#JoinTable(name="EVENT_STOCK_VIEW",
joinColumns=#JoinColumn(name="EVENT_ID"),
inverseJoinColumns=#JoinColumn(name="STOCK_ID",updatable=false,insertable=false))
public Stock getStock(){
return this.stock;
}
and in Stock:
#OneToMany(fetch=FetchType.LAZY)
#JoinTable(name="EVENT_STOCK_VIEW",
joinColumns=#JoinColumn(name="STOCK_ID",updatable=false,insertable=false), inverseJoinColumns=#JoinColumn(name="EVENT_ID",updatable=false,insertable=false))
#OrderBy("eventDatetime DESC")
public List<Event> getEvents(){
return events;
}
I've googled a bit and found this site.
But the solution isn't really that nice (you have to use entity in between stock and event).
Are there any other solutions?
I could use a Hibernate Interceptor and override onPrepareStatement(String sql) and check whether the SQL string contains delete from EVENT_STOCK_VIEW and return an dummy command. Clearly a hack which I try to avoid.

Can't you do it without join table at all? As far as I understand, your relationship is effectively read-only, so that the following approach should work fine:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "productId",
referencedColumnName = "productId", insertable = false, updateable = false)
public Stock getStock(){
return this.stock;
}
...
#OneToMany(fetch=FetchType.LAZY, mappedBy = "stock")
#OrderBy("eventDatetime DESC")
public List<Event> getEvents(){
return events;
}

Related

How to map one-to-many connection between two entities using a 3rd table, in Hibernate?

I have two entities, User and Event. Each event can have multiple users associated with it, so its a one to many between Event and User.
The way its being stored in the database, is that I have 3 tables, user, event, and event_user. event_user contains 3 fields, id, eventId, userId. So I can do a query like select userId from event_user where eventId = ? to get all the users which are associated with the event.
My question is, how can I map this relationship between the events and users in Hibernate, to get it to auto save/load the users associated with the event? I want to have the following field in the Event class:
Set<User> users = new HashSet<>();
and have hibernate auto load / save the users to this set.
How can I map this (using annotations)?
Use the #ManyToMany annotation.
class Event{
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name = "EVENT_USER",
joinColumns = { #JoinColumn(name = "EVENT_ID") },
inverseJoinColumns = { #JoinColumn(name = "USER_ID") })
private Set<Users> users = new HashSet<Users>();
}
For more information on many to many associations in JPA check out this video tutorial at my blog.
Hibernate doc on the Bidirectional mapping using annotations should help
Basically you need to do something like this
#Entity
public class User implements Serializable {
#ManyToMany(
targetEntity=org.hibernate.test.metadata.manytomany.Event.class,
cascade={CascadeType.ALL}
)
#JoinTable(
name="USER_EVENT",
joinColumns=#JoinColumn(name="USER_ID"),
inverseJoinColumns=#JoinColumn(name="EVENT_ID")
)
public Set<Event> getEvents() {
return events;
}
...
}
#Entity
public class Event implements Serializable {
#ManyToMany(
cascade = {CascadeType.ALL},
mappedBy = "events",
targetEntity = User.class
)
public Set<User> getUsers() {
return users;
}
}

fetch data in ManyToOne relation using Restriction

There are two tables with #OneToMany and #ManyToOne bidirectional relation, like this:
#Entity
public class Asset {
private int id;
private int count;
#OneToMany
private Set<Dealing> dealings;
...
}
#Entity
public class Dealing {
private int id;
...
#ManyToOne
#JoinColumn(name = "customer_id", nullable = false, updatable = false)
private Customer customer;
#ManyToOne
#JoinColumn(name = "product_id", nullable = false, updatable = false)
private Product product;
#ManyToOne(cascade = CascadeType.ALL)
private Asset asset;
}
all things sound OK, but when I want to search data using Restriction like this,
session.createCriteria(Asset.class).add(Restrictions.eq("dealings.customer.id", customerId)).add(Restrictions.eq("dealing.product.id", productId)).list();
In this level I get this error,
could not resolve property: dealings.customer of: com.project.foo.model.Asset
one of the solutions are to change my strategy but i wasted time to find this,btw I don't have any idea about it, do you ?
First of all, you don't have a bidirectional OneToMany association, but two unrelated unidirectional associations. In a bidirectional OneToMany association the One side must be marked as the inverse of the Many side using the mappedBy attribute:
#OneToMany(mappedBy = "asset")
private Set<Dealing> dealings;
Second, using the criteria API for such static queries is overkill, and leads to code that is harder to read than necessary.I would simply use HQL which is much easier to read. Criteria should be used for dynamic queries, IMHO, but not for static ones:
select asset from Asset asset
inner join asset.dealings dealing
where dealing.customer.id = :customerId
and dealing.product.id = :productId
Whether you use HQL or Criteria, you can't use asset.dealings.customer, since asset.dealings is a collection. A collection doesn't have a customer attribute. To be able to reference properties from the Dealing entity, you need a join, as shown in the above HQL query. And it's the same for Criteria:
Criteria criteria = session.createCriteria(Asset.class, "asset");
criteria.createAlias("asset.dealings", "dealing"); // that's an inner join
criteria.add(Restrictions.eq("dealing.customer.id", customerId);
criteria.add(Restrictions.eq("dealing.product.id", productId);

Hibernate Annotations with a collection

I am trying to implement my model using hibernate annotations. I have 3 classes, image, person, and tags. Tags is a a table consisting of 4 fields, an id, personId, imageId, and a createdDate. Person has the fields name, id, birthdate, etc. My image class is defined as follows:
#Entity
#Table(name="Image")
public class Image {
private Integer imageId;
private Set<Person> persons = new HashSet<Person>();
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
public Integer getImageId() {
return imageId;
}
public void setImageId(Integer imageId) {
this.imageId = imageId;
}
#ManyToMany
#JoinTable(name="Tags",
joinColumns = {#JoinColumn(name="imageId", nullable=false)},
inverseJoinColumns = {#JoinColumn(name="personId", nullable=false)})
public Set<Person> getPersons() {
return persons;
}
public void setPersons(Set<Person> persons) {
this.persons = persons;
}
If I remove the annotations on the getPersons() method I can use the classes and add and remove records. I want to fetch all the tags with the image and I am trying to use a set. I keep getting the following error:
org.hibernate.LazyInitializationException - failed to lazily initialize a collection of role: com.exmaple.persons, no session or session was closed
Can someone please help me and let me know what I am doing wrong?
Thank you
This error message - which actually has nothing to do with your association mapping strategy or annotations - means that you have attempted to access a lazy-loaded collection on one of your domain objects after the Session was closed.
The solution is to either disable lazy-loading for this collection, explicitly load the collection before the Session is closed (for example, by calling foo.getBars().size()), or making sure that the Session stays open until it is no longer needed.
If you are not sure what lazy-loading is, here is the section in the Hibernate manual.
Thanks for the response matt. I am confused now. My query to retrieve the image looks like this:
public Image findByImageId(Integer imageId) {
#SuppressWarnings("unchecked")
List<Image> images = hibernateTemplate.find(
"from Image where imageId=?", imageId);
return (Image)images.get(0);
}
I thought that I can call the single hql query and if my mappings are correct it will bring back the associated data.
I was looking at this example at this link hibernate mappings:
2.2.5.3.1.3. Unidirectional with join table
A unidirectional one to many with join table is much preferred. This association is described through an #JoinTable.
#Entity
public class Trainer {
#OneToMany
#JoinTable(
name="TrainedMonkeys",
joinColumns = #JoinColumn( name="trainer_id"),
inverseJoinColumns = #JoinColumn( name="monkey_id")
)
public Set<Monkey> getTrainedMonkeys() {
...
}
#Entity
public class Monkey {
... //no bidir
} Trainer describes a unidirectional relationship with Monkey using the join table TrainedMonkeys, with a foreign key trainer_id to Trainer (joinColumns) and a foreign key monkey_id to Monkey (inversejoinColumns).

Ternary (and n-ary) relationships in Hibernate

Q 1) How can we model a ternary relationship using Hibernate? For example, how can we model the ternary relationship presented here using Hibernate (or JPA)?
NOTE: I know that JPA 2 has added some constructs for building ternary relationships using maps. However, this question assumes JPA 1 or Hibernate 3.3.x and I don't like to use maps to model this.
(source: grussell.org)
(source: grussell.org)
Ideally I prefer my model to be like this:
class SaleAssistant {
Long id;
//...
}
class Customer {
Long id;
//...
}
class Product {
Long id;
//...
}
class Sale {
SalesAssistant soldBy;
Customer buyer;
Product product;
//...
}
Q 1.1)
How can we model this variation, in which each Sale item might have many Products?
class SaleAssistant {
Long id;
//...
}
class Customer {
Long id;
//...
}
class Product {
Long id;
//...
}
class Sale {
SalesAssistant soldBy;
Customer buyer;
Set<Product> products;
//...
}
Q 2) In general, how can we model n-ary, n >= 3 relationships with Hibernate?
Thanks in advance.
Q1. How can we model a ternary relationship using Hibernate? For example, how can we model the ternary relationship presented here using Hibernate (or JPA)? (...)
I would remodel the association with an intermediate entity class (and that's the recommended way with Hibernate). Applied to your example:
#Entity
public class Sale {
#Embeddable
public static class Pk implements Serializable {
#Column(nullable = false, updatable = false)
private Long soldById;
#Column(nullable = false, updatable = false)
private Long buyerId;
#Column(nullable = false, updatable = false)
private Long productId;
public Pk() {}
public Pk(Long soldById, Long buyerId, Long productId) { ... }
// getters, setters, equals, hashCode
}
#EmbeddedId
private Pk pk;
#ManyToOne
#JoinColumn(name = "SOLDBYID", insertable = false, updatable = false)
private SaleAssistant soldBy;
#ManyToOne
#JoinColumn(name = "BUYERID", insertable = false, updatable = false)
private Customer buyer;
#ManyToOne
#JoinColumn(name = "PRODUCTID", insertable = false, updatable = false)
private Product product;
// getters, setters, equals, hashCode
}
Q1.1. How can we model this variation, in which each Sale item might have many Products?
I wouldn't use a composite primary key here and introduce a PK for the Sale entity.
Q2. In general, how can we model n-ary, n >= 3 relationships with Hibernate?
I think that my answer to Q1. covers this. If it doesn't, please clarify.
Update: Answering comments from the OP
(...) the pk's fields are not getting populated and as a result I cannot save Sale items in the DB. Should I use setters like this for the Sale class? public void setBuyer(Customer cust) { this.buyer = cust; this.pk.buyerId = cust.getId(); }
You need to create a new Pk (I removed the constructors from my original answer for conciseness) and to set it on the Sale item. I would do something like this:
Sale sale = new Sale();
Pk pk = new Pk(saleAssistant.getId(), customer.getId(), product.getId());
sale.setPk(pk);
sale.setSoldBy(saleAssistant);
sale.setBuyer(customer);
sale.setProduct(product);
...
And then persist the sale.
Also, in the JoinColumn annotations, what column are "name" fields referring to? The target relations' pks or the sale table's own column names?
To the columns for the attributes of the composite Pk (i.e. the sale table's own column names), we want them to get PK and FK constraints.
Are you using database generated primary keys for Customer, Product and SalesAssistant? That might cause an issue since it looks like you're trying to use the actual DB identities rather than letting Hibernate resolve the object references during actual persistence.
The embedded PK above looks odd to me personally but I've not had a chance to try it out. It seems like the columns are overlapping and clobbering each other.
I would think it sufficient to just have the ManyToOne references.
Also, turn on SQL statement debugging and see what's being sent to the DB.

Wrong SQL for view object using Hibernate Annotations

I'm working on a hibernate entity mapping for a database view; when I do a criteria query against it, hibernate is generating bad SQL. Any help figuring out what the problem is with my mapping would be greatly appreciated!
I have two mapped entities which I am trying to grab from a database view; the view has no other columns, just the FK of each entity. One of these FK's can be treated as a primary key, since the view has a row for each primary entity. So my DB schema for the view looks like:
primary(primary_id, some_other_fields)
history(history_id, primary_id, some_other_fields)
view_latest_status_history(primary_id, history_id)
Note the view is used because I want to pick out only the latest history for each primary, not all mapped history records. Here is the object I am using for the view, with entity annotations:
#Entity
#org.hibernate.annotations.Entity(dynamicUpdate = true)
#Table(name = "view_latest_status_history")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class ViewLatestStatusHistoryRow implements Serializable {
private Primary primary;
private History history;
/**
* #return Returns the history.
*/
#ManyToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE }, fetch = FetchType.LAZY)
#JoinColumn(name = "history_id", nullable = true)
#AccessType("field")
public History getHistory() {
return history;
}
//equals() and hashCode() implementations are omitted
/**
* #return Returns the primary.
*/
#Id
#ManyToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE }, fetch = FetchType.LAZY)
#JoinColumn(name = "primary_id", nullable = false)
#AccessType("field")
public Primary getPrimary() {
return primary;
}
}
Both the Primary and History objects have complete, working entity annotations.
My criteria setup:
criteria.add(Restrictions.in("primary", [collection of primary objects]));
criteria.setFetchMode("primary", FetchMode.JOIN);
criteria.setFetchMode("history", FetchMode.JOIN);
And the (wrong) generated SQL:
select this_.primary as primary78_1_, this_.primary_id as prim2_78_1_, primary2_.history_id as unique1_56_0_, ...history fields
from DB_CATALOG.dbo.view_latest_status_history this_
left outer join DB_CATALOG.dbo.history primary2_ on this_.primary_id=primary2_.primary_id
where this_.specChange in (?, ?...)
I might've mucked up a few things when editing out the specifics of our project's DB schema, but the point is the first field in the 'select' clause is wrong:
this_.primary (view_latest_status_history.primary) is not a field; the field should be called primary_id. I think this may have something to do with the #Id annotation on the primary field? Any ideas how to fix this? If I remove the #Id, I get an error telling me that the entity has no primary key.
Update:
I no longer map the view as a field using a join table notation (as suggested below). The annotations have been revised, as follows. This solution works correctly in HQL, and generates the expected schema when hbm2ddl is enabled, but I have not re-tested it using the criteria query.
#Entity
#Table(name = "view_latest_status_history")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class ViewLatestStatusHistoryRow implements Serializable {
private String id;
private Primary primary;
private History history;
/**
* #return Returns the history.
*/
#OneToOne(optional = true)
#JoinColumn(name = "history_id", nullable = true)
#AccessType("field")
public History getHistory() {
return history;
}
//equals() and hashCode() implementations are omitted
#Id
#Column(name = "primary_id", nullable = false)
#Override
#AccessType(value = "field")
public String getId() {
return id;
}
/**
* #return Returns the primary.
*/
#PrimaryKeyJoinColumn(name = "primary_id", referencedColumnName = "unique_id")
#OneToOne(optional = false)
#AccessType("field")
public Primary getPrimary() {
return primary;
}
}
It most certainly is due to #Id annotation - primary_id is NOT a primary key in this case. Nor can you realistically have #Id and #ManyToOne on the same property.
Let me ask you this - why are you mapping ViewLatestStatusHistoryRow as an entity to begin with? It's not like you ever going to persist it. Consider mapping your latest history entry directly (as read-only) on primary (as many-to-one) and using your view as join table.

Categories

Resources