So I have three entities. A FormCollection contains multiple Form. The Form is created from a template and thus has also a many-to-one relation to FormTemplate.
#Table(name = "form_collection", schema = "public")
public class FormCollectionDO extends BaseAuditableDO {
#OneToMany(mappedBy = "formCollection")
#OrderBy("formTemplate.templateId") //throws error
private List<FormDO> forms = new ArrayList<>();
}
#Table(name = "form", schema = "public")
public class FormDO extends BaseAuditableDO {
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "form_template_id")
private FormTemplateDO formTemplate;
}
#Table(name = "form_template", schema = "public")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class FormTemplateDO extends BaseDO {
#Column(name = "template_id", nullable = false)
#NotNull
private Long templateId;
}
#OrderBy("formTemplate.templateId") throws an error:
o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: missing FROM-clause entry for table "formtemplate"
#OrderBy("formTemplate.id") works fine. The id comes from the abstract class BaseDO. Why does it not work with any of the fields from the FormTemplateDO class?
Although I am not sure about this solution, What I am suspecting is this issue happens because the formTemplate.templateId isnt part of your query indeed.
I see you are using #OneToMany for defining the relationship, but in hibernate, the default FetchMode is SELECT which means your order by parameter isnt part of your query. To make this parameter part of your query, you will have to make a Join query.
Try this out -
#Fetch(value = FetchMode.JOIN)
#OneToMany(mappedBy = "formCollection")
#OrderBy("formTemplate.templateId") //throws error
private List<FormDO> forms = new ArrayList<>();
And propogate this join to further levels. It might solve your problem.
When you want to order by a collection of entities by a nested attribute, you can not use #OrderBy, because the nested attribute is not part of your query. You can only use #OrderBy for of first level attribute OR a nested attribute IF it is a collection of #Embeddable.
So for this case, you have to use #SortNatural or #SortComparator.
Similar issue : Hibernate - How to sort internal query lists (or List in List)?
More about #OrderBy vs #SortNatural: Sort vs OrderBy - performance impact
Related
I'm trying to get all Posts which don't contain certain category using QueryDsl
My models are defined as:
Post
#QueryEntity
#Table(name = "posts")
public class PostEntity implements {
#Id
#Column(name = "id")
private String id;
#OneToMany
#JoinTable(
name = "post_categories",
joinColumns = #JoinColumn(name = "post_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "category_id", referencedColumnName = "id")
)
private List<CategoryEntity> categories;
}
Category
#QueryEntity
#Table(name = "categories")
public class CategoryEntity {
#Id
#Column
private String id;
}
(some Lombok annotations omitted for brevity)
The two are related through post_categories join table to tag posts with categories.
I've tried using the query similar to this one, to exclude posts categorised as news:
var query = QPostEntity
.postEntity
.categories.any().id.notIn("news");
However that still returns posts in that category - only way I got it to work properly is to include all post categories in notIn statement.
Question: How do I query for Posts which don't contain specific category?
Update #1
Seems the query above generates subquery similar to
where exists(
select 1 from post_categories where category_id not in ('news')
)
which also includes all the posts with other categories. I found the following query does produce correct results (not moved before exists statement):
where not exists(
select 1 from post_categories where category_id in ('news')
)
Which can be done by rewriting querydsl as:
.categories.any().id.in("news").not();
However that seems to be very confusing. Any better way of doing it?
I would try to solve this with subqueries. Can you try the following?
SubQueryExpression<String> subquery = JPAExpressions.select(QCategoryEntity.categoryEntity.id)
.from(QCategoryEntity.categoryEntity)
.where(CategoryEntity.categoryEntity.eq("news"));
return new JPAQueryFactory(em)
.select(QPostEntity.postEntity)
.from(QPostEntity.postEntity)
.innerJoin(QPostEntity.postEntity.categories)
.where(QCategoryEntity.categoryEntity.id.notIn(subquery));
Probably you are not using the JPAQueryFactory... if not, could you share how you are actually performing the query?
The Problem
I have a 1:n relation, but the n side shouldnt rely on constraints. So i actually wanna insert a EntityPojo via its future id, when its not saved yet ( Lets ignore that its a bad practice ). This looks kinda like this.
var relation = new RelationshipPojo();
.
.
.
relation.targets.add(session.getReference(futureID, EntityPojo.class));
session.save(relation);
// A few frames later
session.save(theEntityPojoWithTheSpecificId);
Cascading is not possible here, i only have its future ID, not a reference to the object i wanna save. Only its id it will have in the future.
#Entity
#Table(name = "relationship")
#Access(AccessType.FIELD)
public class RelationshipPojo {
.
.
.
#ManyToMany(cascade = {}, fetch = FetchType.EAGER)
public Set<EntityPojo> targets = new LinkedHashSet<>();
}
Question
How do we tell hibernate that it should ignore the constraints for this 1:n "target" relation ? It should just insert the given ID into the database, ignoring if that EntityPojo really exists yet.
Glad for any help on this topic, thanks !
For a much simpler solution, see the EDIT below
If the goal is to insert rows into the join table, without affecting the ENTITY_POJO table, you could model the many-to-many association as an entity itself:
#Entity
#Table(name = "relationship")
#Access(AccessType.FIELD)
public class RelationshipPojo {
#OneToMany(cascade = PERSIST, fetch = EAGER, mappedBy = "relationship")
public Set<RelationShipEntityPojo> targets = new LinkedHashSet<>();
}
#Entity
public class RelationShipEntityPojo {
#Column(name = "entity_id")
private Long entityId;
#ManyToOne
private RelationshipPojo relationship;
#ManyToOne
#NotFound(action = IGNORE)
#JoinColumn(insertable = false, updatable = false)
private EntityPojo entity;
}
This way, you'll be able to set a value to the entityId property to a non-existent id, and if an EntityPojo by that id is later inserted, Hibernate will know how to populate relationship properly. The caveat is a more complicated domain model, and the fact that you will need to control the association between RelationshipEntityPojo and EntityPojo using the entityId property, not entity.
EDIT Actually, disregard the above answer, it's overly complicated. Turing85 is right in that you should simply remove the constraint. You can prevent Hibernate from generating it in the first place using:
#ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
#JoinTable(inverseJoinColumns = #JoinColumn(name = "target_id", foreignKey = #ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT)))
public Set<EntityPojo> targets = new LinkedHashSet<>();
The only caveat is that when you try to load RelationshipPojo.targets before inserting the missing EntityPojo, Hibernate will complain about the missing entity, as apparently #NotFound is ignored for #ManyToMany.
When I run the spring boot project I get the following error
" Invocation of init method failed; nested exception is org.hibernate.AnnotationException: No identifier specified for entity:"
I have a few other classes with multiple primary keys and foreign keys but they didn't run to an error.
import javax.persistence.*;
#Entity
#Table(name="roles_has_features")
public class RoleFeatures {
#Column(name = "role_id_fk")
private Long roleIdFk;
#Column(name = "feature_id_fk")
private Long featureIdFk;
public Long getRoleIdFk() { return roleIdFk; }
public void setRoleIdFk(Long roleIdFk) { this.roleIdFk = roleIdFk; }
public Long getFeatureIdFk() { return featureIdFk; }
public void setFeatureIdFk(Long featureIdFk) { this.featureIdFk = featureIdFk; }
}
This actually has nothing to do with Spring. This is an error thrown by Hibernate, because JPA specification requires an Identity for each entity. As for your case, I would not suggest to create a separate entity, because as far as I understand from your column names, it's just a mapping for a relation between role and feature tables. I'd suggest to JPA Many-To-Many relationship. Take a look at #ManyToMany and #JoinTable annotations.
Also this looks as a really good tutorial for me
Hibernate – Many-to-Many example
The error message describes the issue pretty well:
No identifier specified for entity
You do not have an #Id annotated column in your RoleFeatures entity. Thus, hibernate is unable to identify an entity in the database and refuses to start.
Your so-called entity looks more like an Many-To-Many relationship. Maybe it's better to go this way.
Something like this:
#Entity
public class Role {
#Id
#Column(name = "role_id")
private Long id;
#ManyToMany
#JoinTable(name = "roles_has_features",
joinColumns = #JoinColumn(name = "feature_id_fk", referencedColumnName = "feature_id"),
inverseJoinColumns = #JoinColumn(name = "role_id_fk", referencedColumnName = "role_id"))
private List<Feature> features;
...
}
See also: https://www.baeldung.com/jpa-many-to-many
happy new year:)
I have a Spring MVC project using Hibernate and DataJPA.
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id", nullable = false)
private User user;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "restaurant_id", nullable = false)
#NotNull
private Restaurant restaurant;
As you can see, here is two fields with eager fetch. I want to make both a lazy. I need to user #NamedEntityGraph annotation asI made here:
#NamedQueries({
#NamedQuery(name = Restaurant.GET_BY_ID, query = "SELECT r FROM Restaurant r WHERE r.id=?1"),
})
#Entity
#NamedEntityGraph(name = Restaurant.GRAPH_WITH_MENU_HISTORY, attributeNodes = {#NamedAttributeNode("menuHistory")})
#Table(name = "restaurants")
public class Restaurant extends NamedEntity {
#OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, mappedBy = "restaurant")
#OrderBy(value = "date DESC")
private List<Dish> menuHistory;
public static final String GRAPH_WITH_MENU_HISTORY = "Restaurant.withMenuHistory";
I want to know, if I'll write
#NamedEntityGraph(name = "G_NAME", attributeNodes = {#NamedAttributeNode("user", "restaurant")})
and if I'll request one of them, will the second load anyway or it will load only by request to him? May be, I need to user two graphs?
According to JPA 2.1 Spec 3.7.4:
The persistence provider is permitted to fetch additional entity state
beyond that specified by a fetch graph or load graph. It is required,
however, that the persistence provider fetch all state specified by
the fetch or load graph.
So actually the #NamedEntityGraph just guarantees what fields should be eagerly loaded, but not what fields should not be loaded.
So, if you make #NamedEntityGraph with user, your persistence provider (Hibernate for example) can load only user field or both user and restaurant fields eagerly. This is dependent on implementation and not guaranteed.
See this hibernate's issue.
But as far as I know, the Hibernate loads only simple fields in addition to specified in #NamedEntityGraph, and not loads lazy associations.
So if you use hibernate, it should work.
But of course you need two separate #NamedEntityGraphs for user and restaurant fields.
Or you can use ad-hoc spring-data-jpa's feature:
#Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
#EntityGraph(attributePaths = { "members" })
GroupInfo getByGroupName(String name);
}
With this code you don't need explicitly declare #NamedEntityGraph anymore. You can just specify fields inline.
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);