Hibernate mapping with group by clause? - java

I have #OneToMany mapping in a pojo, and I need to group the resulting list by a column. I have a mapping like the following;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "X", referencedColumnName = "X", insertable = false, updatable = false)
#NotFound(action = NotFoundAction.IGNORE)
#Fetch(FetchMode.SELECT)
public Set<MyType> getSiblings() {
return siblings;
}
I need to add GROUP BY clause to this mapping, is this possible?

I think what you are looking for is support for mapping into a multimap, which is a map with one key and the value is a list of values all associated to the same key.
This is not supported by Hibernate, this is a thread in their forum with an attempt to implement it.
An alternative is to still map to a Set at the property level, and then provide a getter that does the grouping on the fly. This would be transparent for consumers and only become a problem for very large datasets:
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "X", referencedColumnName = "X", insertable = false, updatable = false)
#NotFound(action = NotFoundAction.IGNORE)
#Fetch(FetchMode.SELECT)
private Set<MyType> siblings;
public Map<String,List<MyType>> getSiblings() {
... do the grouping manually in Java ...
}

Related

Preventing circular JSON conversions on ManyToMany entities

I am converting JPA entities to JSON for a REST API. Several of the entities have ManyToMany relationships which can cause recursion in the conversion process. I understand how to use #JsonIgnoreProperties to prevent recursion on the ManyToOne relationship, but not ManyToMany. Any guidance appreciated. Here is an example:
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST,CascadeType.DETACH})
#JoinTable(name = "insurance_companycodes", joinColumns = {
#JoinColumn(name = "insurance_id", referencedColumnName = "id", nullable = false, updatable = false) }, inverseJoinColumns = {
#JoinColumn(name = "insuranceCompanyCode_id", referencedColumnName = "id", nullable = false, updatable = false) })
private Set<InsuranceCompanyCode> insuranceCompanyCodes = new HashSet<>();

"MultipleBagFetchException: cannot simultaneously fetch multiple bags" when joining three depth table

org.springframework.dao.InvalidDataAccessApiUsageException:
org.hibernate.loader.MultipleBagFetchException: cannot simultaneously
fetch multiple bags: [Order.items, OrderItem.options];
Above is an exception i faced when i join three tables like below.
OrderItemOption.java
#Entity
public class OrderItemOption {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "item_option_id")
private Long id;
#Column(name = "item_id", nullable = false)
private Long itemId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "item_id",
referencedColumnName = "item_id",
insertable = false,
updatable = false
)
private OrderItem orderItem;
}
OrderItem.java
#Entity
public class OrderItem {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "item_id")
private Long id;
#Column(name = "order_id", nullable = false)
private Long orderId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "order_id",
referencedColumnName = "order_id",
insertable = false,
updatable = false,
nullable = false
)
private Order order;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "orderItem")
#OrderBy("item_option_id ASC")
private List<OrderItemOption> options;
}
Order.java
#Entity
public class Order {
#Id
#Column(name = "order_id", nullable = false)
private Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "order")
#OrderBy("item_id ASC")
private List<OrderItem> items;
}
And here's my QueryDSL code to join them at one time.
final QOrder order = QOrder.order;
final QOrderItem item = QOrderItem.orderItem;
final QOrderItemOption option = QOrderItemOption.orderItemOption;
from(order)
.leftJoin(order.items, item).fetchJoin()
.leftJoin(item.options, option).fetchJoin()
.where(
order.id.eq(orderId)
.and(item.id.in(itemIds))
.and(option.id.in(optionIds))
)
.fetchOne())
What i'm trying to do is to get Order object which contains filtered relationship, so that i can access filtered children via order object.
and the type of relationship should be a List, not a Set.
for example, order.getItems().get(0).getOptions.get(0)
How can i achieve that goal?
To avoid above exception there are two possibilities:
Change List to Set
or
Use List but do not fetch two bags. This means don't use fetchJoin() on both collections.
Filtering:
Using where conditions collections will be not filtered. Collections will contain all associated objects. Joining in JPA is for creating conditions on root object - Order. It is not the same as in SQL.
It is possible to filter associated collections using JPA 2.1 JOIN ON feature. This allows additional conditions in ON clause
see for example QueryDSL Left Join with additional conditions in ON
If you really can not use Set instead of List:
Parent.class
#OneToMany(
mappedBy = "parent",
orphanRemoval = true,
cascade = { CascadeType.PERSIST, CascadeType.MERGE }
)
#OrderColumn(name = "position")
private List<Child> childs = new ArrayList<>();
Child.class
#ManyToOne(fetch = FetchType.LAZY)
private Parent parent;
And create a column in the Child's table named e.g "position"
ALTER TABLE child ADD COLUMN position integer NOT NULL default 0
And if you can not use other column in table, them you need to query the lists in sequence. Or use the id of the child and a custom getter.
#OrderColumn(name = "id_child", updatable = false, insertable = false)
public List<Child> getChilds() {
childs.removeAll(Collections.singleton(null));
return childs;
}

JPA ManyToMany bi-directional insert

I have problem and i could not find solution. I have bi-directional many to many anotation. I have this tables in DB (mariaDB):
item
section
item_section
Item part:
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "item_section",
joinColumns = {
#JoinColumn(name = "item", nullable = false, updatable = false)},
inverseJoinColumns = {
#JoinColumn(name = "section", nullable = false, updatable = false)})
private Set<Section> sections;
Section part:
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "sections")
private Set<Item> items;
PROBLEM:
I create some Sections first. Than i want to create Item "within" this section. So i create instance of Item and add some existing Sections into set (Sections i get from DAO). If i persist the Item, records in item and item_section tables are created. But if i get some of affected section from DAO and iterate thru items set database changes are not here, Sections instances are in same state as before insert.
What did i wrong?
PS: i use eclipselink but i do not think it is interesting
SOLUTION
As #Chris said i called persist utimately but one side of direction already exist. No cascades was defined so during persist sections was not persisted nor merge. So best solution for my staless and JTA solution was use merge instead of persist on item and add MERGE cascade to sections collection...
Thanks everybody who came to help but especially to #Chris
It seems you are missing the attribute cascade=CascadeType.ALL. You should try to change
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "item_section",
joinColumns = {
#JoinColumn(name = "item", nullable = false, updatable = false)},
inverseJoinColumns = {
#JoinColumn(name = "section", nullable = false, updatable = false)})
private Set<Section> sections;
to
#ManyToMany(cascade=CascadeType.ALL,fetch = FetchType.LAZY)
#JoinTable(name = "item_section",
joinColumns = {
#JoinColumn(name = "item", nullable = false, updatable = false)},
inverseJoinColumns = {
#JoinColumn(name = "section", nullable = false, updatable = false)})
private Set<Section> sections;
then it works normally.
I found this mistake via the tutorial Hibernate Many-To-Many Bidirectional Mapping Example
Hope this help
Problem may be in incorrect using of eclipselink's shared cache which is enabled by default. Try to disable it by adding in persistence.xml following:
<shared-cache-mode>NONE</shared-cache-mode>
When you persist or merge Section provider flushes it to database and add Section instance to shared cache so it can be retrieved from memory on next requests. When you persist Item you update only one side of the relationship, but the other side does not get updated, and becomes out of sync. In JPA, as in Java in general, it is the responsibility of the application or the object model to maintain relationships. If your application adds to one side of a relationship, then it must add to the other side. Read more about caching in bi-direction mapping in this article.
I can offer following solutions of your problem:
Disable cache for whole application in persistence.xml
Manually refresh Section in DAO before retrieving it to user
Use cache correctly by updating both sides of relationship:

JPA entity with composite primary and foreign keys

I have an entity with a composite primary key consisting of two fields, one of which is also part of a composite foreign key.
Background: I have entities Person,Area, and Session.
Person has many-to-many relationships with Area and Session, using join entities called PersonArea and PersonSession.
So, I have PersonSession, with primary key of (personId, sessionId).
PersonId and SessionId are themselves foreign keys to Person and Session.
PersonSession also has a field areaId.
I want (personId, areaId) to be a composite foreign key to PersonArea.
My code for PersonSession:
#Entity
#Table(name="person_session")
#IdClass(PersonSession.ID.class)
public class PersonSession {
#Id
private int personId ;
#Id
private int sessionId;
#ManyToOne
#JoinColumn(name = "personId", updatable = false, insertable = false, referencedColumnName = "id")
private Person person;
#ManyToOne
#JoinColumn(name = "sessionId", updatable = false, insertable = false, referencedColumnName = "id")
private Session session;
#ManyToOne//(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "personId", updatable = false, insertable = false),
#JoinColumn(name = "areaId", updatable = false, insertable = false),
})
private PersonArea personArea;
}
Code for PersonSession.Id
public static class ID implements Serializable {
private int personId;
private int sessionId;
}
This seems OK, it creates all the correct relationships in the database. The problem comes when I try to insert PersonSession objects - the areaId column is always null, I think that is because it's defined a updatable=false, insertable=false.
However, if I try and make it updatable and insertable, I get an exception complaining the personId is a repeated column:
Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: foo.bar.PersonSession column: personId (should be mapped with insert="false" update="false")
How can I have the required relationships AND have areaId updatable and insertable?
I should be able to do what I want with this:
#ManyToOne//(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "personId"),
#JoinColumn(name = "areaId", updatable = false, insertable = false),
})
private PersonArea personArea;
But Hibernate does not support mixing updatable and non updatable Join Columns. The accepted answer to this question indicates that it might be supported at some time, but it seems the developers aren't very worried about that shortcoming.
I have how ditched Hibernate in favour of Eclipselink and it works!
I know I am late but I faced the same problem and I used #JoinColumnsOrFormulas to resolve it. Here is what you could do:
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(column = #JoinColumn(name="personId", referencedColumnName = "personId")),
#JoinColumnOrFormula(formula = #JoinFormula(value="areaId", referencedColumnName = "areaId"))})
private PersonArea personArea;

Hibernate relationship issues on no bidirectional entities

Hi there I have an entity
OfferItem with this two relationship manyToOne
#ManyToOne
#JoinColumn(name = "offer_id", nullable = false, insertable = false, updatable = false)
#XmlTransient
#Getter
#Setter
private Offer offer;
#ManyToOne
#JoinColumn(name = "item_id", nullable = false, insertable = false, updatable = false)
#XmlTransient
#Getter
#Setter
private Item item;
And the foreign keys configuration on the database
<addForeignKeyConstraint baseTableName="offer_item"
baseColumnNames="item_id"
constraintName="item_offer_item_fk"
referencedTableName="item"
onDelete="CASCADE" onUpdate="CASCADE"
referencedColumnNames="id"/>
<addForeignKeyConstraint baseTableName="offer_item"
baseColumnNames="offer_id"
constraintName="offers_offer_item_fk"
onDelete="CASCADE" onUpdate="CASCADE"
referencedColumnNames="id"
referencedTableName="offer"/>
the relationship with offer is bidireccional, so on the Offer entity I have this relationship with OfferItem
#OneToMany(orphanRemoval = true, cascade = {CascadeType.ALL})
#JoinColumn(name = "offer_id", nullable = false)
#XmlElementWrapper
#Getter
private List<OfferItem> offerItems = new ArrayList<>();
On Item I dont have the bidireccional relationship since is not necessary.
Now the problem is, that when I made the set of Item on OfferItem, and I add the offerItem on the OfferItems list of offer, and I save Offer. I can see how the offerItem id is generated because is persisted on cascade. But the link between the offerItem and item is lost.
I just try to set the item on OfferItem and save the entity with his own service, but nothing. I cannot persist this link between them.
Any idea or suggestion.
Regards.
Remove insertable = false, updatable = false from join columns. Also, the mapping for offerItems is wrong. It should be
#OneToMany(orphanRemoval = true, cascade = {CascadeType.ALL}, mappedBy = "offer")
#XmlElementWrapper
#Getter
private List<OfferItem> offerItems = new ArrayList<>();

Categories

Resources