I'm working on a JPA based project and I have two entities say for example Student and School. Each student has one single School.
The Student School attribute fetch type is lazy, yet I need to be able to fetch eagerly only the school name attribute.
is there a way to do this ?
Thank's for respoding
If you are using JPA 2.1, you could try an Entity Graph, indicating the attributes you want to load:
#Entity
#NamedQueries({
#NamedQuery(name = "Student.findAll", query = "SELECT s FROM Student s")
})
#NamedEntityGraphs({
#NamedEntityGraph(
name = "studentGraph",
attributeNodes = {
#NamedAttributeNode(value = "id"),
#NamedAttributeNode(value = "name"),
#NamedAttributeNode(value = "school", subgraph = "schoolGraph")
},
subgraphs = {
#NamedSubgraph(
name = "schoolGraph",
attributeNodes = {
#NamedAttributeNode("name")
}
)
}
)
})
public class Student {
#Id
private Long id;
private String name;
#ManyToOne
private School school;
}
And use like the following:
List<Student> students = entityManager.createNamedQuery("Student.findAll")
.setHint("javax.persistence.fetchgraph", entityManager.getEntityGraph("studentGraph"))
.getResultList();
Entity Graphs can also be create dynamically.
Related
I'm trying to implement a custom #loader using a namedQuery on a OneToOne - Relation of an entity.
However the lastDatalog field remains null at all given times
I've tested the named query befor on a simple integration test using a repositry, the result was exactly what I intend to have in the lastDestinationStatus
(I need the last updated record from the logs for this data and IREF combination)
when I query the Datalog entity with the id of the data I get the correct result so the Datalog entity seems to be persisted
maybe good to know : curent hibernate version on the project is 4.2.11.Final
this is en extract from entity 1
#Entity
#Table(name = "data")
#NamedQueries({
#NamedQuery(name = "LastLogQuery", query = "select log from DataLog log where log.data.id= ?1 and " +
"log.IREF = (select max(log2.IREF) from DataLog log2 where log2.data = log.data ) " +
"and log.tsUpdate = (select max(log3.tsUpdate) from DataLog log3 where log3.data = log.data and log3.IREF = log.IREF)")})
public class Data{
....
#OneToOne(targetEntity = DataLog.class)
#Loader(namedQuery = "LastLogQuery")
private DataLog lastDataLog;
}
extract from entity 2
#Entity
#Table(name ="log")
public class DataLog{
.......
#ManyToOne(fetch = FetchType.EAGER)
#org.hibernate.annotations.Fetch(value = org.hibernate.annotations.FetchMode.SELECT)
#JoinColumn(name = "DTA_IDN", nullable = false)
private Data data;
/** IREF */
#Column(name = "DSE_LOG_UID_FIL_REF_COD")
private String IREF;
#Column(name = "LST_UPD_TMS", nullable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date tsUpdate;
}
I have two entities, Movie and Genre, with a many to many relationship from genre to movie. Genre being the "parent" of the relationship.
This generates three tables: genre, movie and genre_movie
#Entity
public class Genre {
#Id
#GeneratedValue(strategy = AUTO)
private Long id;
#ManyToMany(fetch = LAZY)
#JoinTable( name = "genre_movie",
joinColumns = {#JoinColumn(name = "genre_id")},
inverseJoinColumns = {#JoinColumn(name = "movie_id")}
)
private Set<Movie> movies = new HashSet<>();
...
}
#Entity
public class Movie {
#Id
#GeneratedValue(strategy = AUTO)
private Long id;
...
}
I it possible perform this query using the criteria query api? Filter movies based on their genre id.
select *
from movie
join genre_movie on movie.id = genre_movie.movie_id
where genre_id = 19;
It is not possible because the Movie entity does not have a reference to Genre as it is not bidirectional.
But you can restructure the query to get the same results:
Select * from genre
inner join genre_movie on genre.id = genre_movie.genre_id
inner join movie on genre_movie.movie_id = movie.id
where genre.genre_id = 19;
The query would be implemented as follows:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Movie> cq = cb.createQuery(Movie.class);
Root<Genre> rootGenre = cq.from(Genre.class);
Join<Genre,Movie> joinMovie = rootGenre.join("movies");
//Join<Genre,Movie> joinMovie = rootGenre.join(Genre_.movies);
cq.select(joinMovie);
cq.where(cb.equals(rootGenre.get("id"), 19));
//cq.where(cb.equals(rootGenre.get(Genre_.id), 19));
return this.em.createQuery(cq).getResultList();
I particularly recommend using metamodels (commented lines) since when accessing the properties by name in String format, errors can occur that with Metamodels not in more complex queries.
I also think it would be interesting to define the relationship as bidirectional to be able to set the Movie class as Root
I am trying to write simple Pet-project, I use Hibernate in DAO layer. I have entity Group with field Students.
#Data
#AllArgsConstructor
#RequiredArgsConstructor
#EqualsAndHashCode
#ToString
#Builder
#Entity
#Table(name = "groups")
#NamedQueries({
#NamedQuery(name = "get all groups", query = "from Group"),
#NamedQuery(name = "find group by id", query = "select g from Group g join fetch g.students where g.id=:groupId"),
#NamedQuery(name = "find group by name", query = "from Group g where g.name like :groupName"),
#NamedQuery(name = "find number of groups", query = "select count(*) from Group"),
#NamedQuery(name = "find group and sort by name", query = "from Group order by name, id"),
#NamedQuery(name = "find group and sort by id", query = "from Group order by id")
})
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#Column
private String name;
#OneToMany(cascade={CascadeType.PERSIST, CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH})
#JoinTable(
name="studentsGroups",
joinColumns=#JoinColumn(name="groupId"),
inverseJoinColumns=#JoinColumn(name="studentId")
)
#LazyCollection(LazyCollectionOption.FALSE)
private List<Student> students;
}
in DAO layer for exaple I have method
#Transactional
public Optional<Group> findById(int groupId) {
Session session = sessionFactory.getCurrentSession();
return Optional.ofNullable(session.get(Group.class, groupId));
}
I want to check my DAO and I am writing junit test. I use assertThat to compare entity from database and entity from test data. In test data, field students have type ArrayList but from Hibernate get type PersistentBag and I can't compare these fields although all data are same.
#Test
public void givenFindGroupById_whenFindGroupById_thenGroupWasFound() {
Optional<Group> actual = groupDao.findById(2);
assertThat(actual).isPresent().get().isEqualTo(expectedGroups.get(1));
}
List<Group> expectedGroups = Arrays.asList(
Group.builder().id(1).name("a2a2").students(expectedStudentsForGroupA2A2Name).build(),
Group.builder().id(2).name("b2b2").students(expectedStudentsForGroupB2B2Name).build(),
Group.builder().id(3).name("c2c2").students(expectedStudentsForGroupC2C2Name).build(),
Group.builder().id(4).name("d2d2").students(expectedStudentsForGroupD2D2Name).build());
Is there some way to convert PersistentBag to ArrayList, or I am doing something wrong?
PersistentBag uses Object's equals method for equals() and hashCode() Bug.
FIX:
Exclude students field for equals check by adding #EqualsAndHashCode.Exclude over it.
Now assert the Students values: assertThat(actual.get().getStudents()).containsOnlyElementsOf(expectedGroups.get(1).getStudents())
I am working on setting up some entities for a project that I am working on. My issue is that I am getting an invalid identifier error on my hibernate filters. Below is a simplified example:
#Data
#Entity
#Table(name = "###")
public class C1 {
#OneToMany(mappedBy = "###")
#Filter(name = "C2.set1Filter")
private Set<C2> set1;
#OneToMany(mappedBy = "###")
#Filter(name = "C2.set2Filter")
private Set<C2> set2;
#OneToMany(mappedBy = "###")
#Filter(name = "C2.set3Filter")
private Set<C2> set3;
}
#Data
#Entity
#Table(name = "###")
#FilterDefs({
#FilterDef(
name = "set1Filter"
defaultCondition = "c3.value = 'One'"
),
#FilterDef(
name = "set2Filter"
defaultCondition = "c3.value = 'Two'"
),
#FilterDef(
name = "set3Filter"
defaultCondition = "c3.value = 'Three'"
)
})
public class C2 {
#ManyToOne
#JoinColumn(name = "ID")
C3 c3;
}
#Data
#Entity
#Table(name = "###")
public class C3 {
#Id
#Column(name = "ID")
private Long id;
#Column(name = "VALUE")
private String value;
}
When the filters are off, they don't run, and therefore I don't get an error. However, when they are on, I am getting an error saying that c3.value is an invalid identifier.
I am obviously doing something wrong here. Is it possible to get the above to work?
As it stated in the documentation:
The #Filter condition uses a SQL condition and not a JPQL filtering predicate.
So, you should use actual tables and columns names.
#Filter(
name="setFilter",
condition="{c3}.VALUE = :val",
aliases = {
#SqlFragmentAlias( alias = "c3", table= "C3_TABLE_NAME")
}
)
As it stated here:
When using the #Filter annotation and working with entities that are mapped onto multiple database tables, you will need to use the #SqlFragmentAlias annotation if the #Filter defines a condition that uses predicates across multiple tables.
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.