Join two tables in one java object by jpa - java

I need to join 2 tables into one object with some condition. I have the following:
#Entity
#Table(name = "polling")
public class Polling extends DomainIdObject {
#ManyToOne
#JoinColumn(name = "owner_id")
private Person owner;
#Column(name = "poll_name")
private String name;
#Column(name = "description")
private String description;
#ManyToMany(targetEntity = PollingSchedule.class, mappedBy = "polling", fetch = FetchType.EAGER)
private List<PollingSchedule> variants;
#Column(name = "start_time")
private LocalDateTime startTime;
#Column(name = "end_time")
private LocalDateTime endTime;
//getters and setters
#Entity
#Table(name = "polling_schedule")
public class PollingSchedule extends DomainIdObject {
#JoinColumn(name = "polling_id")
private Polling polling;
#Column(name = "poll_var")
private String pollingVariant;
//gettters and setters
but when I execute the following code:
Query query = getEntityManager().createNativeQuery("SELECT * FROM polling p WHERE p.id=1", Polling.class);
List list = query.getResultList();
List<PollingSchedule> variants = ((Polling) list.get(0)).getVariants();
the variants list is empty. Tables in DB looks following:
polling
|id|owner_id|poll_name|description|start_time|end_time|
polling_schedule
|id|polling_id|poll_var|
So, in result I want that Polling object contains only those PollingVariants, that have corresponding polling_id in polling_schedule table.
I've try use Filter, SecondaryTable annotations, but it`s not work for me (or I was incorrect use it).
I use hibernate4 and spring boot 1.5.1
Could anyone help me?

I think the relation between Polling and PollingSchedule is one-to-many (not many-to-many). And since you need a bidirectional relationship between those objects, you should change them like this:
Pooling.java
#Entity
#Table(name = "polling")
public class Polling extends DomainIdObject {
...
#OneToMany(mappedBy="polling")
private List<PollingSchedule> variants;
...
}
PoolingSchedule.java
#Entity
#Table(name = "polling_schedule")
public class PollingSchedule extends DomainIdObject {
#ManyToOne
#JoinColumn(name = "polling_id")
private Polling polling;
...
}

Related

Hibernate many to many with an additional column without changing HQL

In the existing project we have a many-to-many relationship, it turned out that now we need to keep the time when that connection happed. The following approach has been used
https://vladmihalcea.com/the-best-way-to-map-a-many-to-many-association-with-extra-columns-when-using-jpa-and-hibernate/
Now we have these classes (the names are just taken from the link above):
#Embeddable
public class PostTagId
implements Serializable {
#Column(name = "post_id")
private Long postId;
#Column(name = "tag_id")
private Long tagId;
}
#Entity
#Table(name = "post_tag")
public class PostTag {
#EmbeddedId
private PostTagId id;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("postId")
private Post post;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("tagId")
private Tag tag;
#Column(name = "created_on")
private Date createdOn = new Date();
}
#Entity
public class Tag {
#Id
#GeneratedValue
private Long id;
private String name;
}
#Entity
public class Post {
#Id
#GeneratedValue
private Long id;
private String title;
#OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostTag> tags = new ArrayList<>();
}
Since it is a big project and there are tons of places to change, the Post class still has methods like getTags, setTags, removeTags, etc.
List<Tag> getTags() {
return tags.stream().map(PostTag::getTag).collect(collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet))
}
void addTag(Tag tag) {
tags.add(new PostTag(this, tag, new Date())
}
It allows to hide from the outer world that collection is changed from <Tag> to <PostTag>. The problem happens with buzillion HQL queries that we have at the moment, since smth like this won't work:
...
JOIN post.tags t
WHERE t.name = <?>
Because "t" is now PostTag and not Tag, hence t.name is trying to access an unknown field
Are there any ways to mitigate that problem? Add some annotations above PostTag, so that instead of accessing tag.name hibernate will do t.tag.name, use other ways of mapping, etc.

How to audit a #JoinTable with #ManyToMany

I'm working on a Spring-Boot project with a H2 database. I have two entities Portfolio and Report, and there is a many-to-many association between the two.
I want those entities to be audited, so I followed this tutorial to audit through an AuditorAware interface with custom fields.
The two entities are well audited, the columns are created in the database. However, the join table portfolio_reports is not audited. How can I audit the join table as well ?
Portfolio.java
#Entity
#Table(name = "portfolio")
public class Portfolio extends Auditable<String> implements Serializable {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
#Unique
private String name;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinTable(name = "portfolio_report", joinColumns = #JoinColumn(name = "portfolio_id"), inverseJoinColumns = #JoinColumn(name = "report_id"))
private List<Report> reports;
// Getters and setters
}
Report.java
#Entity
#Table(name = "report")
public class Report extends Auditable<String> implements Serializable {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "axioma_id")
private Long axiomaId;
#Column(name = "name")
private String name;
#AuditJoinTable
#ManyToMany(mappedBy = "reports", cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
private List<Portfolio> portfolios;
// Getters and setters
}
Auditable.java
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public abstract class Auditable<U> {
#Version
#Column(name = "version_no")
protected Long versionNo;
#CreatedDate
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_date")
protected Date createdDate;
#LastModifiedDate
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "modified_date")
protected Date modifiedDate;
}
AuditorAwareImpl.java
public class AuditorAwareImpl implements AuditorAware<String> {
#Override
public Optional<String> getCurrentAuditor() {
return Optional.of("Admin");
}
}
PersistenceConfiguration.java
#Configuration
#EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class PersistenceConfiguration {
#Bean
public AuditorAware<String> auditorAware() {
return new AuditorAwareImpl();
}
}
Problem:
Clearly here Auditable should add some column to your intermediate table that maintains relation between Portfolio and Report and that table is created behind the scene and you don't have access to that table in your program. Only hibernate can use that table to maintain relation between your entities and do join operation.
Solution:
Here you should make Join table that maintain Many to Many relation between Portfolio and Report explicit so that you can have entity like PortfolioReport in your program that can extends from Auditable. Please read the following post to see how to do that: The best way to map a many-to-many association with extra columns when using JPA and Hibernate

Hibernate Fetch is not longer sub-type of Join

I'm using Hibernate 5.2.10 with dynamic criteria and find out that Fetch is not longer assignable to Join.
In the next example, I need to fetch group data in the same query, but I also need to use the group's field for sorting or restriction.
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<GroupAssignment> query = cb.createQuery(GroupAssignment.class);
Root<GroupAssignment> root = query.from(GroupAssignment.class);
SingularAttributeJoin<GroupAssignment, Group> groupFetch = (SingularAttributeJoin<GroupAssignment, Group>) root.fetch(GroupAssignment_.group, JoinType.LEFT);
query.orderBy(cb.asc(groupFetch.get(Group_.title)));
I have manually cast Fetch to the SingularAttributeJoin and after that, I can use get method for the ordering purpose, but I'm looking for the right way how I can do that without casting manually.
GroupAssignment class:
#Entity()
#Table(name = "group_assignment")
public class GroupAssignment {
#Id
#Column(name = "group_assignment_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne
#JoinColumn(name = "group_id", nullable = false)
private Group group;
//other fields, getters and setters
}
GroupAssignment_ class:
#Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
#StaticMetamodel(GroupAssignment.class)
public abstract class GroupAssignment_ {
public static volatile SingularAttribute<GroupAssignment, Integer> id;
public static volatile SingularAttribute<GroupAssignment, Group> group;
}
Group class:
#Entity()
#Table(name = "navigation_group")
public class Group {
#Id
#Column(name = "group_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "title")
private String title;
//other fields, getters and setters
}
Group_ class:
#Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
#StaticMetamodel(Group.class)
public abstract class Group_ {
public static volatile SingularAttribute<Group, Integer> id;
public static volatile SingularAttribute<Group, String> title;
}
I think that javax.persistence.criteria.FetchParent may solve your problem:
FetchParent<GroupAssignment, Group> groupFetch = root.fetch(GroupAssignment_.group, JoinType.LEFT);
It appears that correct way would be next:
query.orderBy(cb.asc(root.get(GroupAssignment_.group).get(Group_.title)));
So using multiple call to .get method you can go as deep as needed.

Hibernate - multi-language tables

I'm trying to build a multi-language database, so I've used this database design as a approach for mine.
Now I've two problems/questions:
I want to retrieve all LocalizedEvent for a given language and given categoryId. How can I make a inner join over the LocalizedCategory table with Hibernate Criteria API?
With SQL I would make this statement to get all LocalizedEvent + LocalizedCategory:
SELECT * FROM event e
INNER JOIN
localized_event le ON (le.event_id = e.event_id)
INNER JOIN
localized_category lc ON (lc.category_id = e.category_id)
WHERE
le.locale = 'de' AND lc.locale = 'de'
My current approach looks like this without getting the LocalizedCategory (with Criteria API):
Criteria c = session.createCriteria(LocalizedEvent.class, "localizedEvent");
c.createAlias("localizedEvent.event", "event");
c.createAlias("event.category", "category");
c.add(Restrictions.eq("category.categoryId", categoryId));
c.add(Restrictions.eq("localizedEvent.locale", language));
I think my mapping is not 100% correct. The entity LocalizedEvent should have a property localizedCategory, but I don't want to save the ID of this localizedCategory (therefore I'm using the #Transient annotation) in the LocalizedEvent table, e.g. using a ManyToOne relation (joining LOC_CATEGORY_ID). But I think it's not possible to do this, isn't it? I would have to map this transient field to LocalizedEvent "manually", because Hibernate is not supporting this mapping (if I'm right).
(Using JDBC this property/mapping would cause no problems, because I can easily make my inner joins and assign the property localizedCategory to the LocalizedEvent in a RowMapper or so).
My entities looks like this:
Event
#Entity
#Table(name = "EVENT")
public class Event {
#Id
#GeneratedValue
#Column(name = "EVENT_ID", unique = true)
private Long eventId;
#Column(name = "DATE")
private Date date;
#OneToMany(mappedBy = "event")
private Set<LocalizedEvent> localizedEvents;
#ManyToOne
#JoinColumn(name = "CATEGORY_ID")
private Category category;
}
LocalizedEvent
#Entity
#Table(name = "LOCALIZED_EVENT")
public class LocalizedEvent {
#Id
#GeneratedValue
#Column(name = "LOC_EVENT_ID")
private Long locEventId;
#ManyToOne
#JoinColumn(name = "EVENT_ID")
private Event event;
#Transient
private LocalizedCategory localizedCategory;
#Column(name = "DESCRIPTION")
private String description;
#Column(name = "LOCALE")
private String locale;
}
Category
#Entity
#Table(name = "CATEGORY")
public class Category {
#Id
#GeneratedValue
#Column(name = "CATEGORY_ID", unique = true)
private Long categoryId;
#OneToMany(mappedBy = "category")
private Set<LocalizedCategory> localizedCategories;
#OneToMany(mappedBy = "category")
private Set<Event> events;
}
LocalizedCategory
#Entity
#Table(name = "LOCALIZED_CATEGORY")
public class LocalizedCategory {
#Id
#GeneratedValue
#Column(name = "LOC_CATEGORY_ID")
private Long locCategoryId;
#ManyToOne
#JoinColumn(name = "CATEGORY_ID")
private Category category;
#Column(name = "NAME")
private String name;
#Column(name = "LOCALE")
private String locale;
}

TopLink JPA children fetch results in many SQL's calls for children

I am having an issue with generated SQL's from a model we built using JPA (TopLink)
We have the following
#Entity
#Table(name = "T_TEST_A")
#Access(AccessType.FIELD)
public class TTestA implements Serializable {
#Id
#Column(name = "A_ID")
private String id;
#OneToOne
#PrimaryKeyJoinColumn
private D detail;
#OneToMany()
#JoinTable(name = "T_TEST_JOIN", joinColumns = #JoinColumn(name = "TABLE_FK"), inverseJoinColumns = #JoinColumn(name = "B_ID"))
private List<B> childrens;
...
}
#Entity
#Table(name = "T_TEST_B")
#Access(AccessType.FIELD)
public class TTestB implements Serializable {
#Id
#Column(name = "B_ID")
private String id;
#OneToOne
#PrimaryKeyJoinColumn
private D detail;
...
}
#Entity
#Table(name = "T_TEST_D")
#Access(AccessType.FIELD)
public class D implements Serializable {
#Id
#Column(name = "D_ID")
private String id;
#OneToOne
#PrimaryKeyJoinColumn
private M moreDetail;
...
}
It's basically an one to many using a 3 tables relation. Using the Criteria API, I am able to get the first level and all the One-To-One relations (cascading) and children from A, ie A=>D=>M, in a single SQL using fetch's, but I can't get the children B=>D=>M to act the same.
I end up with a SQL query which gets A, D, M and B, but then multiple queries to get B=>D=>M.
Here is what I do:
final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<A> c = cb.createQuery(A.class);
final Root<A> a = c.from(A.class);
a.fetch(A_.details).fetch(D_.modeDetails);
a.fetch(A_.childrens);
...
Is it possible to "compound" the calls for the children also?
After much fiddling, I noticed I had another issue also which was that the query would return duplicates.
So I ended up using a.join(A_.childrens, JoinType.LEFT).fetch(B_.details)
And using the query hint
query.setHint(QueryHints.FETCH, "t1.childrens")
I managed to eliminate the duplicates and fetch the deeper level.

Categories

Resources