I m using spring and hibernate for my java object.
I have an entity like this :
#Entity
#Table
public class Function implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
/** Code value. */
private String code;
}
And another entity that reference the first one like
#Entity
#Table(name = "role", uniqueConstraints = { #UniqueConstraint(columnNames = { "id" }), #UniqueConstraint(columnNames = { "code" }) })
public class RoleDef extends CodeLabelEntity{
...some other primitive type...
/** List of functions */
#OneToMany(fetch = FetchType.EAGER)
#JoinTable(name = "role_function",
joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "function_code", referencedColumnName = "code"))
private Set<Function> functions;
}
I m using repository like this :
public interface RoleDefRepository extends JpaRepository<RoleDef, Long>, QuerydslPredicateExecutor<RoleDef> {
}
when i m doing in my roleDefService :
roleDefRepository.findAll();
I can iterate over RoleDef and functions beacause EAGER do the job
But when i'm trying to do:
RoleDef roleDef = roleDefRepository.findById(id).orElse(null);
My functions Set inside the roledef is empty. findById is not overided, it's the default method like findAll.
If you see something that i ommit to delcare...
I m using last version of spring/hibernate
Thanks
I resolved my problem by doing this in my repository :
#Query("select r from RoleDef r join fetch r.functions where r.id = ?1")
Optional<RoleDef> findByIdWithFunctionTree(Long id);
But if someone can find the real problem because i think EAGER on the relation or accessing by a get when transaction still open will do the same as the fetch...
Related
I am trying to understand hibernate filters, i thought that the filter is applied even if the query is not started from the filtered entity and can be applied if i just join to it.
My entities:
#Table(name = "SPM_SECTION", schema = "TEST")
#GenericGenerator(name = "MODSEC_ID.GEN", strategy = "uuid2")
public class ModuleSection implements Serializable {
private AcademicClass academicClass;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CLASS_ID")
public AcademicClass getAcademicClass() {
return academicClass;
}
public void setAcademicClass(AcademicClass academicClass) {
this.academicClass = academicClass;
}
}
#Entity
#GenericGenerator(name = "AC_CLASS_ID.GEN", strategy = "uuid2")
#Where(clause="1=1")
#FilterDef(name= Resources.SECURITY_FILTER_NAME, parameters={#ParamDef(name=Resources.SECURITY_FILTER_PARAMETER, type="string")})
#Filter(name=Resources.SECURITY_FILTER_NAME, condition = "DISCRIMINATOR_ID in (:"+Resources.SECURITY_FILTER_PARAMETER+")")
#Table(name = "ACADEMIC_CLASS", schema = "TEST", uniqueConstraints = #UniqueConstraint(columnNames = {"OUS_ID", "YEAR_ID",
"PERIOD_ID", "SHIFT_ID", "SEMESTER", "CODE" }))
public class AcademicClass implements java.io.Serializable {
//I tried by having the association here, i also tried without it.
private Set<ModuleSection> sections = new HashSet<>(0);
#OneToMany(fetch = FetchType.LAZY, mappedBy = "academicClass")
public Set<ModuleSection> getSections() {
return this.sections;
}
public void setSections(Set<ModuleSection> sections) {
this.sections = sections;
}
}
The filter is enabled through an interceptor and the parameter list is fetched from the database for security.
When i execute a query like this:
em.createQuery("select acc from AcademicClass acc ...........", AcademicClass.class)
.getResultList();
the filter is applied. But i also want the filter to be applied when my query starts from ModuleSection:
em.createQuery("select ms from ModuleSection ms join ms.academicClass acc", AcademicClass.class)
.getResultList();
In above query the filter is not applied.
The academicClass in ModuleSection entity is nullable but i also have other entities not null where the above case does not work.
I also tried to apply the #Filter or #FilterJoinTable in module section property with no luck:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CLASS_ID")
#Filter(name=Resources.SECURITY_FILTER_NAME, condition = "DISCRIMINATOR_ID in (:"+Resources.SECURITY_FILTER_PARAMETER+")")
#FilterJoinTable(name=Resources.SECURITY_FILTER_NAME, condition = "DISCRIMINATOR_ID in (:"+Resources.SECURITY_FILTER_PARAMETER+")")
public AcademicClass getAcademicClass() {
return academicClass;
}
My questions:
Are filters meant to filter only the entity in the from clause? does the filter apply in join entities?
If I want to implement the above should I also add a DISCRIMINATOR_ID in ModuleSection and add the filter to that entity starting the query from there?
There is a silence about it in the hibernate documentation, but it looks like this is true that #Filter is applied only to the from clause.
Assuming that we have the following mapping:
#Entity
#Table(name = "ACADEMIC_CLASS")
#FilterDef(
name="isAccessible",
parameters = #ParamDef(
name="sec",
type="string"
)
)
#Filter(
name="isAccessible",
condition="{acClass}.discriminator_id in (:sec)",
aliases = {
#SqlFragmentAlias(alias = "acClass", table= "TEST_SCHEMA.ACADEMIC_CLASS")
}
)
public class AcademicClass
{
#Id
#Column(name = "class_id")
private Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "academicClass")
private Set<ModuleSection> sections;
// getters/setters
}
#Entity
#Table(name = "SPM_SECTION")
public class ModuleSection
{
#Id
#Column(name = "sec_id")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "sec_class_id")
private AcademicClass academicClass;
// getters/setters
}
When we run the following query:
session
.enableFilter("isAccessible")
.setParameter("sec", "A1");
List<AcademicClass> classes = session.createQuery(
"select ac from ModuleSection ms join ms.academicClass ac",
AcademicClass.class
).getResultList();
The filter is not applied. It should happen in the JoinSequence.toJoinFragment. The filterCondition is empty in this case.
But for the rewritten in the following way query:
List<AcademicClass> classes = session.createQuery(
"select ac from ModuleSection ms, AcademicClass ac where ms.academicClass = ac",
AcademicClass.class
).getResultList();
We will have:
and as result the following query will be generated:
Hibernate:
/* select
ac
from
ModuleSection ms,
AcademicClass ac
where
ms.academicClass = ac
*/
select
academiccl1_.class_id as class_id1_0_
from TEST_SCHEMA.SPM_SECTION modulesect0_ cross
join TEST_SCHEMA.ACADEMIC_CLASS academiccl1_
where academiccl1_.discriminator_id in (?)
and modulesect0_.sec_class_id=academiccl1_.class_id
So, as a workaround you can rewrite your query in this way.
The #FilterJoinTable annotation can be used only if you have a link table between the parent entity and the child table.
I have the following many to many relationship:
#Entity
public class Foo implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonIgnore
#ManyToMany
#JoinTable(name = "foo_bar",
joinColumns = {#JoinColumn(name = "foo_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "bar_name", referencedColumnName = "name")})
private Set<Bar> bars = new HashSet<>();
}
#Entity
public class Bar implements Serializable {
#Id
private String name;
}
Now I want to query the FooRepository for all Foo's which DO NOT contain a Bar with the name "example". I tried to use the following Spring data method in FooRepository:
findByBars_NameNot(String barName);
But this returns one of every entry of the pivot table foo_bar which doesn't have "example" in its bar_name column. This means it can return duplicate Foo objects as well as Foo object which do actually contain a Bar with name "example" i.e. it's equivalent to the following SQL:
SELECT * FROM myschema.foo_bar WHERE bar_name != "example";
Is there any nice way in Spring data to write a repository method to do what I am trying?
I have found the following native query which does what I need but I am hesitant to use a native query as I feel there is a cleaner way of doing this:
SELECT * FROM myschema.foo WHERE id NOT IN (SELECT foo_id FROM myschema.foo_bar WHERE bar_name = "example")
From Spring Data JPA docs
public interface FooRepository extends JpaRepository<Foo, Long> {
#Query("SELECT f FROM Foo f WHERE NOT EXISTS (SELECT b FROM f.bars b WHERE b.name = ?1)")
Foo findByNotHavingBarName(String name);
}
Unfortunately, there's no support for EXISTS queries in the query creation from method names
Suppose I have the following two entities:
#Entity
#Table(name="manifest")
public class Manifest extends DbTable implements Serializable {
public Manifest() { }
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
public Long id;
#ManyToMany(cascade=CascadeType.ALL, mappedBy="manifests",fetch= FetchType.LAZY)
public List<Thingy> thingys;
}
and
#Entity
#Table(name="thingy")
public class Thingy extends DbTable implements Serializable {
public Thingy(){}
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
public Long id;
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(name = "manifest_thingy",
joinColumns = #JoinColumn(name = "thingy_id", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name = "manifest_id", referencedColumnName="id"))
public List<Manifest> manifests;
}
How can I query my thingies that belong to a given manifest? I have tried queries like
"SELECT DISTINCT d
FROM Thingy d
WHERE :manifest MEMBER OF d.manifests"
or
"SELECT DISTINCT d
FROM Thingy d
JOIN d.manifests m
WHERE m = :manifest"
or
"SELECT DISTINCT d
FROM Thingy d
JOIN d.manifests m
WHERE m.id = :manifestId"
the latter of those three being basically the only suggestion I could find searching around for this, but to no avail. For all 3 of those I think what I'm getting is an empty list (rather than an error). The query is being fed through something like this (parameters set as appropriate):
myEntityManager
.createQuery(giantQueryStringGoesHere, Thingy.class)
.setParameter("manifest", myManifestObject)
.getResultList();
If you know the specific manifest ID couldn't you just retrieve that manifest and get the list of thingys from it?
Manifest m = em.find(Manifest.class, manifestId);
List<Thingy> thingys = m.thingys;
My app has 2 java pojo classes linked via ManyToMany relationship User & Season:
#Entity
#Table(name = "user")
public class User implements Serializable {
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "user_season", joinColumns = { #JoinColumn(name = "user_id") }, inverseJoinColumns = { #JoinColumn(name = "season_id") })
private Set<Season> followingSeason;
Set<Season> getSeasonsWhichTheUserFollows(){
return this.followingSeason;
}
}
Season class
#Entity
#Table(name = "season")
public class Season implements Serializable{
#ManyToMany(mappedBy = "followingSeason", fetch = FetchType.EAGER)
private Set<User> user;
}
When a user unfollows a season unfollowedSeason object I remove it from the set of season which the user follows.
Set<Season> seasonSet = user.getSeasonsWhichTheUserFollows();
seasonSet.remove(unfollowedSeason);
user.setFollowingSeason(seasonSet );
this.userService.update(user);
well this removes the entry from the user_season bridge table, everything is fine. But at the same time I also want to update some fields of the Season entity in the db for an instance decrementing the count of users following by 1. Is there a way I can do that within the same call? Or do I have to run a separate query to update the season entity?
Not sure if i got that right, but why can't you just put something in there like unfollowedSeason.setCount(unfollowedSeason.getCount() +1 ) and then just update the season?
EDIT AFTER DISCUSSION IN COMMENTS:
What you want to do is not possible because
you can't do a update and a remove in the same SQL Statement(as over9k stated)
I have the following entities:
#Entity
#Table(name = "place_revision")
public class PoiRevision {
#OneToMany(mappedBy = "pk.revision", cascade = {CascadeType.ALL})
private Collection<PoiRevisionCategory> categoryMapping;
// ...
}
#Entity
#Table(name = "place_revision__category")
#AssociationOverrides({
#AssociationOverride(name = "pk.revision",
joinColumns = #JoinColumn(name = "place_revision_id")),
#AssociationOverride(name = "pk.category",
joinColumns = #JoinColumn(name = "category_id"))
})
public class PoiRevisionCategory {
#EmbeddedId
private PoiRevisionCategoryId pk = new PoiRevisionCategoryId();
// ...
}
#Embeddable
public class PoiRevisionCategoryId implements Serializable {
#ManyToOne
private PoiRevision revision;
#ManyToOne
private Category category;
// ...
}
#Entity
#Table(name = "category")
public class Category {
#ManyToMany(targetEntity = Section.class, cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
#JoinTable(
name = "category__section",
joinColumns = #JoinColumn(name = "category_id"),
inverseJoinColumns = #JoinColumn(name = "section_id")
)
private Collection<Section> sections;
// ...
}
And want to select PoiRevisions that have Categories that have some Sections.
I'm using Spring-data Specification to query the database for these entities.
My intent is to write something like:
Specification<PoiRevision> spec = new Specification<PoiRevision>() {
#Override
public Predicate toPredicate(Root<PoiRevision> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> conditions = new ArrayList<>(CONDITION_COUNT);
CollectionJoin<PoiRevision, PoiRevisionCategory> mapping = root.join(PoiRevision_.categoryMapping);
// BROKEN here as we cannot use nested path for joins
Join<PoiRevisionCategory, Category> categories = mapping.join("pk.category");
conditions.add(categories.get("sections").in(sections));
// ...
return cb.and(conditions.toArray(new Predicate[] {}));
}
};
But we cannot use nested path for such joins as JPA provider (Hibernate, in my case) looks only for direct properties of PoiRevisionCategory class. And we cannot "join" embedded Id to our result set because it's not a manageable entity.
I'm really stuck with this issue which seems to be far from complicated when translated into SQL yet it has some complexity on the ORM-side.
Any idea is much appreciated.
After switching completely to metamodel API it became clearer and I was actually able to join embedded entity just like I tried and failed with string api.
So the correct way is just to join like one would normally do
Join<PoiRevisionCategory, PoiRevisionCategoryId> pk = mapping.join(PoiRevisionCategory_.pk);
Join<PoiRevisionCategoryId, Category> cats = pk.join(PoiRevisionCategoryId_.category);
CollectionJoin<Category, Section> sec = cats.join(Category_.sections);
conditions.add(sec.get(Section_.id).in(sections));
And it does the thing just fine!
What a relief.