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())
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 three entities: A, B, C
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class A {
private long id;
private String secret;
#Builder.Default
#Valid
#OneToMany(mappedBy = "a", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#Fetch(value = FetchMode.SUBSELECT)
private List<B> bList = new LinkedList<>();
}
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class B {
#JoinColumn
private A a;
private long id;
private String secret;
#Builder.Default
#Valid
#OneToMany(mappedBy = "b", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#Fetch(value = FetchMode.SUBSELECT)
private List<C> cList = new LinkedList<>();
}
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class C {
#JoinColumn
private B b;
private long id;
private String secret;
}
I would like to recover the values from the DB EXCEPT the "secret" where the id of A belongs to a List of ids. I do not want the secret of either A, B or C to be requested from the DB. (in reality the data is much more complex, and the fields I don't want take too much time to be fetched from the database).
I've been trying to create such a query using JPQL or the Criteria API, but without success, I am unable to get even a list of B without the secrets.
For example in JPQL:
Query query = entityManager.createQuery
("SELECT a " +
"FROM A a where a.id in :aIds");
query.setParameter("aIds", aIds);
This will work, but unfortunately it also requests the secret of A, B and C.
What I want instead would be something like (this code obviously does not work):
Query query = entityManager.createQuery
("SELECT a.id, a.bList.id, a.bList.cList.id" +
"FROM A a where a.id in :aIds");
query.setParameter("aIds", aIds);
I cannot change the entities (besides adding new constructors or methods) as they are used elsewhere and in other queries.
Is that even possible?
You can create dto class with fields you need and populate it. The problem is that database query is able to return plain result only. Persistence provider can convert it to entities. As for dto you have to solve the problem yourself.
You can get List<Object[]> as query result
select a.id, b.id, c.id
from A a join a.bList b join b.cList c
where a.id in(:aIds)
Then you should convert query result to dto
My project is using JPA 2 with Hibernate and there is a single table inheritance setup for dealing with two types of customer. When I try to query data on customers by using Spring Data JPA Specification, I always get incorrect result. I think it's because I create the query wrong and still got no idea how to make it right.
Here is my test code (I am trying search customer by company name):
#Test
public void test() {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Customer> customerQuery = criteriaBuilder.createQuery(Customer.class);
Root<Customer> customerRoot = customerQuery.from(Customer.class);
Root<CompanyCustomer> companyCustomerRoot = customerQuery.from(CompanyCustomer.class);
CriteriaQuery<Customer> select = customerQuery.select(customerRoot);
select.where(criteriaBuilder.equal(companyCustomerRoot.get(CompanyCustomer_.companyName), "My Company Name"));
TypedQuery<Customer> query = entityManager.createQuery(select);
List<Customer> results = query.getResultList();
assertEquals(1, results.size()); // always got size 2
}
SQL script in the log:
Hibernate:
select
customer0_.id as id2_16_,
customer0_.type as type1_16_,
customer0_.first_name as first_na8_16_,
customer0_.last_name as last_nam9_16_,
customer0_.company_name as company13_16_
from
customers customer0_ cross
join
customers companycus1_
where
companycus1_.type='COMPANY'
and companycus1_.company_name=?
There are two records in my database:
insert into customers (id, type, company_name) values (1, 'COMPANY', 'My Company Name');
insert into customers (id, type, first_name, last_name) values (2, 'PERSONAL', 'My First Name', 'My Last Name');
My single table inheritance setup:
#Entity
#Table(name = "customers")
#DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
public abstract class Customer {
#Enumerated(EnumType.STRING)
#Column(name = "type", insertable = false, updatable = false)
private CustomerType type;
private String contactNumber;
}
#Entity
#DiscriminatorValue("PERSONAL")
public class PersonalCustomer extends Customer {
private String firstName;
private String lastName;
}
#Entity
#DiscriminatorValue("COMPANY")
public class CompanyCustomer extends Customer {
private String companyName;
}
public enum CustomerType {
COMPANY, PERSONAL;
}
I found the answer from
JPA Criteria Query over an entity hierarchy using single table inheritance
Solution:
Use CriteriaBuilder.treat() to downcast Customer entity instead of using CriteriaQuery.from()
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.
I have a problem to create query with TypedQuery interface, NamedQuery and many-to-many relationship.
Here is my Report entity:
#Entity
#Table(name = "REPORT")
#NamedQueries({
#NamedQuery(name = Report.NAMED_QUERY.FIND_USERS, query = "SELECT r.users FROM Report r WHERE r = :report")})
public class Report {
public interface NAMED_QUERY {
String FIND_USERS = "Report.findUsers";
}
#ManyToMany
#JoinTable(name = "REPORT_USER", joinColumns = #JoinColumn(name = "REPORT_ID"), inverseJoinColumns = #JoinColumn(name = "USER_ID"))
private Set<User> users;
//another fields, getters and setters
}
And User Entity. Here i have no field that maps many-to-many relation.
#Entity
#Table(name = "USER")
public class User {
//fields, getters and setters
}
I have no idea how to use this named query.
public List<User> findUsersRelatedToReport(Report report) {
TypedQuery<User> query = entityManager.createNamedQuery(Report.NAMED_QUERY.FIND_USERS, User.class)
.setParameter("report", report);
return query.getResultList();
}
In the end I have exception:
Type specified for TypedQuery [package_name.User] is incompatible with query return type [interface java.util.Set]
Any help would be appreciated.
You cannot use collection valued attributes (in JPA specification terminology: collection_valued_path_expression) in SELECT.
That's why query is bit more complex, one way is following:
SELECT DISTINCT(u)
FROM User u
WHERE EXISTS (
SELECT r
FROM Report r
WHERE r = :report AND u MEMBER OF r.users)
Try changing data type of Users in your report class to List.
private List<User> users;
instead of
private Set<User> users;
You are trying to return set of users as a column in your select that is causing the error.
I think you could try to select data from User table. Try something like that:
SELECT u FROM User u WHERE u.report = :report
It's an old question, but I've also hit this recently and came up with more elegant solution: it's true you cannot use collection_valued_path in select expression, but you definitely can do joins over this path:
SELECT u FROM Report r JOIN r.users u where r = :report