I have a Many-to-Many relationship between Thread and Participant through a ThreadParticipant entity (because the association has an additional field). I have the following mapping.
Thread entity
#Entity
#Table(name = "thread")
public class Thread {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "thread", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private Collection<ThreadParticipant> threadParticipants = new HashSet<>();
// Getters and setters
}
Participant entity
#Entity
#Table(name = "participant")
public class Participant {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#ManyToOne(fetch = FetchType.LAZY, optional = true, targetEntity = Account.class, cascade = { CascadeType.PERSIST })
#JoinColumn(name = "account_id")
private Account account;
#ManyToOne(fetch = FetchType.LAZY, optional = true, targetEntity = Company.class)
#JoinColumn(name = "company_id")
private Company company;
// Getters and setters
}
ThreadParticipant entity
#Entity
#Table(name = "thread_participant")
#IdClass(ThreadParticipantPK.class)
public class ThreadParticipant implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Participant.class, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "participant_id")
private Participant participant;
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Thread.class)
#JoinColumn(name = "thread_id")
private Thread thread;
#Column(name = "last_viewed", nullable = true)
private Date lastViewed;
// Getters and setters
}
ThreadParticipantPK
public class ThreadParticipantPK implements Serializable {
private Thread thread;
private Participant participant;
public ThreadParticipantPK() { }
public ThreadParticipantPK(Thread thread, Participant participant) {
this.thread = thread;
this.participant = participant;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ThreadParticipantPK)) return false;
ThreadParticipantPK that = (ThreadParticipantPK) o;
if (!participant.equals(that.participant)) return false;
if (!thread.equals(that.thread)) return false;
return true;
}
#Override
public int hashCode() {
int result = thread.hashCode();
result = 31 * result + participant.hashCode();
return result;
}
// Getters and setters
}
Now, I am trying to fetch threads with the following query (using Spring Data JPA) and Hibernate as my JPA provider.
#Repository
public interface ThreadRepository extends JpaRepository<Thread, Integer> {
#Query("select distinct t from Thread t inner join fetch t.threadParticipants tp inner join fetch tp.participant p left join fetch p.account a left join fetch p.company c")
public List<Thread> test();
}
The problem is that when the fetch type for the associations within ThreadParticipants are set to FetchType.LAZY, the Thread.threadParticipants collection is empty. Consequently, if I set the associations to FetchType.EAGER, Thread.threadParticipants contains two elements (as it should). In this case, however, Hibernate goes nuts and executes four SQL queries for fetching a single thread.
Hibernate: select thread0_.id as id1_18_0_, threadpart1_.participant_id as particip2_19_1_, threadpart1_.thread_id as thread_i3_19_1_, participan2_.id as id1_12_2_, account3_.id as id1_0_3_, company4_.id as id1_6_4_, thread0_.created as created2_18_0_, thread0_.last_activity as last_act3_18_0_, thread0_.subject as subject4_18_0_, threadpart1_.last_viewed as last_vie1_19_1_, threadpart1_.thread_id as thread_i3_18_0__, threadpart1_.participant_id as particip2_19_0__, threadpart1_.thread_id as thread_i3_19_0__, participan2_.account_id as account_2_12_2_, participan2_.company_id as company_3_12_2_, account3_.email as email2_0_3_, account3_.facebook_profile_id as facebook3_0_3_, account3_.first_name as first_na4_0_3_, account3_.last_name as last_nam5_0_3_, account3_.middle_name as middle_n6_0_3_, company4_.additional_address_text as addition2_6_4_, company4_.banner_name as banner_n3_6_4_, company4_.ci_number as ci_numbe4_6_4_, company4_.city_id as city_id22_6_4_, company4_.co_name as co_name5_6_4_, company4_.company_type_code as company_6_6_4_, company4_.created as created7_6_4_, company4_.description as descript8_6_4_, company4_.email as email9_6_4_, company4_.last_modified as last_mo10_6_4_, company4_.logo_name as logo_na11_6_4_, company4_.name as name12_6_4_, company4_.number_of_reviews as number_13_6_4_, company4_.phone_number as phone_n14_6_4_, company4_.postal_box as postal_15_6_4_, company4_.rating as rating16_6_4_, company4_.second_phone_number as second_17_6_4_, company4_.street_name as street_18_6_4_, company4_.street_number as street_19_6_4_, company4_.teaser as teaser20_6_4_, company4_.website as website21_6_4_ from thread thread0_ inner join thread_participant threadpart1_ on thread0_.id=threadpart1_.thread_id inner join participant participan2_ on threadpart1_.participant_id=participan2_.id left outer join account account3_ on participan2_.account_id=account3_.id left outer join company company4_ on participan2_.company_id=company4_.id
Hibernate: select participan0_.id as id1_12_0_, participan0_.account_id as account_2_12_0_, participan0_.company_id as company_3_12_0_ from participant participan0_ where participan0_.id=?
Hibernate: select thread0_.id as id1_18_0_, thread0_.created as created2_18_0_, thread0_.last_activity as last_act3_18_0_, thread0_.subject as subject4_18_0_ from thread thread0_ where thread0_.id=?
Hibernate: select participan0_.id as id1_12_0_, participan0_.account_id as account_2_12_0_, participan0_.company_id as company_3_12_0_ from participant participan0_ where participan0_.id=?
Apparently it's executing a query for each participant, and two queries for each thread. So, without FetchType.EAGER, my code is simply not working, but with it, my database will get killed. I tried adding a #OneToMany association between Participant and ThreadParticipant (similar to the one from Thread to ThreadParticipant), but with the same results. I also tried to add all of the aliases to my query's field list, but to no avail.
Why is this happening? Is my mapping or query wrong? Thank you in advance!
Try changing the the one-to-many collection to a Set:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "thread", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private Set<ThreadParticipant> threadParticipants = new HashSet<>();
The additional queries should NEVER be generated if you only have LAZY associations. That may be because the many-to-one and one-to-one relationships are EAGER by default. Try setting those to LAZY instead.
Related
#Entity
public class DocumentConsolidated {
#Id private UUID id;
#OneToOne(fetch = FetchType.EAGER, optional = false, cascade = CascadeType.ALL)
#JoinColumn(name = "metadata_id")
private DocumentMetadata documentMetadata;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "documentConsolidated")
private DocumentConfiguration documentConfiguration;
}
#Entity
public class DocumentConfiguration {
#Id private UUID id;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private DocumentConsolidated documentConsolidated;
}
// Service code:
QDocumentConsolidated qDoc = QDocumentConsolidated.documentConsolidated;
(JPQLQueryFactory) queryFactory
.select(Projections.fields(qDoc, /*qDoc.id, */qDoc.documentConfiguration, qDoc.documentMetadata))
.from(qDoc)
.innerJoin(qDoc.documentConfiguration)
.fetch();
This is only going 2 ways:
with qDoc.id: id is present, documentConfiguration is null
without qDoc.id: id is null, documentConfiguration is present
Why?
What I already checked: Hibernate query is always bringing documentConfiguration fields when I run it in a Postgres client. Bot documentMetadata is present in both cases.
Problem is not solved, but I worked around it by removing Projections from the mix:
// Service code:
QDocumentConsolidated qDoc = QDocumentConsolidated.documentConsolidated;
QDocumentConfiguration qCfg = qDoc.documentConfiguration;
QDocumentMetadata qMeta = qDoc.documentMetadata;
return queryFactory
.select(qDoc, qCfg, qMeta) // <-- get rid of Projections
.from(qDoc)
.innerJoin(qCfg) // <-- manual join on Lazy entity (qMeta is auto-joined)
.fetch().stream()
.map(tuple -> { // <-- entity creation, similar with Projections.bean()
DocumentConsolidated documentConsolidated = Objects.requireNonNull(tuple.get(qDoc));
documentConsolidated.setDocumentMetadata(tuple.get(qMeta));
documentConsolidated.setDocumentConfiguration(tuple.get(qCfg));
return documentConsolidated;
})
.collect(Collectors.toList());
Lately my project encountered a really odd issue with JPA findOne(id) returning a proxy object instead of a full object.
Here is the scenario. Consider the entities and their connections shown below.
#Table(name = "HOUSE")
#Entity
#EqualsAndHashCode
#Setter
#ReadPermission(expression = "user has rights for template snapshots AND has filter")
public class HouseEntity extends VersionedEntity {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "house", fetch = FetchType.LAZY, orphanRemoval = true)
public List<RoomEntity> getRooms() {
return rooms;
}
#OneToMany(cascade = CascadeType.ALL, mappedBy = "template", fetch = FetchType.LAZY, orphanRemoval = true)
public List<TableEntity> getTables() {
return tables;
}
}
#Entity
#Table(name = "ROOMS")
public class Room {
#ManyToOne(fetch = FetchType.LAZY)
public HouseEntity getHouse() {
return house;
}
#OneToMany(mappedBy = "room", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
public List<TableEntity> getTables() {
return tables;
}
}
#Entity
#Table(name = "TABLES")
public class TableEntity{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "HOUSE_ID")
public HouseEntity getHouse() {
return template;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ROOM_ID")
public RoomEntity geRoom() {
return room;
}
As you can see, House has Tables and Rooms, Rooms also has Tables, and each child entity has a connection to its parents.
Add a table to the HouseEntity
Remove the table from the HouseEntity immediately after.
For 1, the houseRepository.findById gets my HouseEntity wrapped in proxy, as if it is lazy loaded.
The weird part is that if I do:
Add a table to a RoomEntity, which is a child of HouseEntity.
Remove the table from RoomEntity.
Then houseRepository.findById returns the HouseEntity without the proxy.
My question here is, why would this happen? Why would the findById method return a proxyed entity in this case? I need to have access to the normal entity without the proxy directly, even if the proxy has the entity populated in the target.
I have Many To Many with additional column. Here realization(getters/setters generated by lombok, removed for clarity):
public class User extends BaseEntity {
#OneToMany(mappedBy = "user",
orphanRemoval = true,
fetch = FetchType.LAZY
cascade = CascadeType.ALL,)
private List<UserEvent> attendeeEvents = new ArrayList<>();
}
#Table(
name = "UC_USER_EVENT",
uniqueConstraints = {#UniqueConstraint(columnNames = {"user_id", "event_id"})}
)
public class UserEvent extends BaseEntity {
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "event_id")
private Event event;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_response_id")
private UserResponse userResponse;
}
public class Event extends BaseEntity {
#OneToMany(mappedBy = "event",
orphanRemoval = true,
fetch = FetchType.EAGER,
cascade = CascadeType.ALL)
private List<UserEvent> userEvents = new ArrayList<>();
}
I want this - when i delete Event, All "UserEvents" connected to it should be removed. And if I delete User, all "UserEvents" should be removed too.
I delete my event(eventRepository is Spring Jpa interface):
eventRepository.delete(event);
Then retrieving UserEvents from db:
List<UserEvent> userEvents = userEventsId.stream()
.map(id -> entityManager.find(UserEvent.class, id)).collect(Collectors.toList());
And there is collection with 2 items(this is count of UserEvents), but they all "null".
I can't understand what happening and how to do it right.
I need them deleted and when I check collection there should be 0, instead of 2.
The delete says marked for deletion, please try calling flush after deletion, and then find.
I guess find goes to the database, find the two rows, but when trying to instantiate them, find the entities marked for deletion and then you have this strange behaviour.
Recomendation: try to abstract more from the database and use less annotations. Learn the conventions of names for columns and tables (if you need to) and let JPA do its job.
I didn't know how to describe my question in the title but I hope it will do.
So here is my situation.
I use hibernate to map my entities to db tables.
I got one entity like this:
#Entity
#Table(name = "EX.EXAMPLE")
public abstract class Entity
{
private CustomEntity customEntity;
public static final String CUSTOM_ENTITY = "customEntity";
#OneToOne(cascade = CascadeType.ALL, mappedBy = CustomEntity.ENTITY, fetch = FetchType.LAZY)
#Fetch(FetchMode.SELECT)
public CustomEntity getCustomEntity()
{
return this.customEntity;
}
}
And my CustomEntity
#Entity
#Table(name = "EX.EXAMPLE2")
public class CustomEntity
{
private Entity entity;
public static final String ENTITY = "entity";
#OneToOne
#JoinColumn(name = "ID_ENTITY", nullable = true)
public Entity getEntity()
{
return this.ntity;
}
}
So here is my question: Is it possible to add another CustomEntity relation to Entity? And how do I map it?
Example what I mean:
#Entity
#Table(name = "EX.EXAMPLE")
public abstract class Entity
{
private CustomEntity customEntity;
public static final String CUSTOM_ENTITY = "customEntity";
private CustomEntity customEntity2;
public static final String CUSTOM_ENTITY2 = "customEntity2";
#OneToOne(cascade = CascadeType.ALL, mappedBy = CustomEntity.ENTITY, fetch = FetchType.LAZY)
#Fetch(FetchMode.SELECT)
public CustomEntity getCustomEntity()
{
return this.customEntity;
}
#OneToOne(cascade = CascadeType.ALL, mappedBy = CustomEntity.ENTITY, fetch = FetchType.LAZY)
#Fetch(FetchMode.SELECT)
public CustomEntity getCustomEntity2()
{
return this.customEntity2;
}
}
I only managed it by changing customEntity to a list in Entity.
Greetings
Yes, that is perfectly normal situation. You just need two fields with different mappedBy`, one for each relation
#OneToOne(cascade = CascadeType.ALL, mappedBy = CustomEntity.ENTITY1, fetch = FetchType.LAZY)
#Fetch(FetchMode.SELECT)
public CustomEntity getCustomEntity()
{
return this.customEntity;
}
#OneToOne(cascade = CascadeType.ALL, mappedBy = CustomEntity.ENTITY2, fetch = FetchType.LAZY)
#Fetch(FetchMode.SELECT)
#JoinColumn(name = "entity_2_id")
public CustomEntity getCustomEntity2()
{
return this.customEntity2;
}
And two fields in CustomEntity, one for each mapping
#OneToOne
#JoinColumn(name = "ID_ENTITY_1", nullable = true)
public Entity getEntity1()
{
return this.entity1;
}
#OneToOne
#JoinColumn(name = "ID_ENTITY_2", nullable = true)
public Entity getEntity2()
{
return this.entity2;
}
I use the single table strategy (with discriminator) to use inheritance
UML schema => http://yuml.me/67acf6a6
I would like to fetch all the orders of a customer with all the associations related (cars, books and tvs). Do you know how to achieve this without breaking the model classes.
#Entity
#Table(name = "customers")
public class Customer{
private Date birthDate;
private String name;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "customer", cascade = CascadeType.ALL, orphanRemoval = true)
#OrderBy("order")
private List<? extends Order> orders;
}
#Entity
#Table(name = "orders")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
public abstract class Order{
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#NotNull
#JoinColumn(name = "CustomerID")
private Customer customer;
}
#Entity
#DiscriminatorValue("book")
public class BookOrder extends Order {
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name="book_id")
private Set<Book> books;
}
#Entity
#DiscriminatorValue("car")
public class CarOrder extends Order {
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name="car_id")
private Set<Car> cars;
}
#Entity
#DiscriminatorValue("tv")
public class TvOrder extends Order {
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name="tv_id")
private Set<Tv> tvs;
}
If I'm doing in HQL
select cutomer from customer customer
inner join fetch customer.orders order
left join fetch order.cars
left join fetch order.books
left join fetch order.tvs
I'm getting the error
org.hibernate.QueryException: could not resolve property: cars,
It makes sense in the abstract class Order this field doesnt exist.
Do you know how i can achieve this ? what is the recommendation of hibernate in this case ?
My goal is to simple a simple query and to fetch everything.
If you do the following query
select c from customer c
join fetch c.orders
then you retrieve all the customers with any types of the orders (cars | books | tvs). Those orders are polymorphic, if do instanceof for each Customer.orders element you can determine what type of order element is.