I am using a JPA model with two classes. The first one is mapping a table with "dynamic" data, the second one is mapping a table with read-only, reference data.
As an example, I have a Person entity mapping a Person Table, that contains a #OneToOne reference to the Civility entity, which itself maps to the Civility table (2 columns) that only has 3 records in it (Miss, Mrs and Mr).
I wanted to know the best way to write a query on the person entity based on Civility value. For example, what query would I use to get all Person's with civility = Mr?
Thanks.
one way to map reference lookup data is to use the #Enumerated annotation in jpa. You still have to create enumeration with the lookup values, but that's why it's reference data anyway.
For example, I have a rating code, and its a string/varchar value on table.
But can use a enumeration to use it:
#Enumerated(EnumType.STRING)
#Column
public RatingCode getRating() {
return rating;
}
public void setRating(RatingCode rating) {
this.rating = rating;
}
and the enumeration is:
public enum RatingCode {
Core, Star
}
Use a unit test to try all values, and you know it's a safe way to get reference data.
You can still use HQL to pull out the values, and pass the enumeration as the value:
hql = "select r from Rating as r where r.rating = :aEnum"
// and in the call to pass the parameter
qry.setParameter("aEnum", aRatingCode)
The enumeration is a field within the Rating entity:
#Entity
#Table
public class Rating {
private Integer rating_Id;
private RatingCode rating;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column
public Integer getRating_Id() {
return rating_Id;
}
public void setRating_Id(Integer rating_Id) {
this.rating_Id = rating_Id;
}
#Enumerated(EnumType.STRING)
#Column
public RatingCode getRating() {
return rating;
}
public void setRating(RatingCode rating) {
this.rating = rating;
}
}
So I have a profile, that requires a Rating, so I lookup a rating via the enumeration and add it to the profile.
Profile p = new Profile();
RatingServiceI rs = new RatingService()
Rating r = rs.getRating(RatingCode.Core);
p.setRating(r);
You didn't post your entity definitions, so you will need to interpret the code in this answer to match up with your actual models. Also, note that querying the entities themselves, in this case, has nothing to do whether the data in the underlying tables is 'read-only' or not:
final String queryStr = "SELECT p FROM Person p WHERE p.civility.value = :value";
final TypedQuery<Person> query = entityManager.createQuery(queryStr, Person.class);
query.setParameter("value", "Mr");
List<Person> results = query.getResultList();
Related
My goal is to create my Contact object directly from query and hibernate, the peculiarities are mainly three:
The contact object has another custom object inside that is the Company.
The contact object contains many more fields and I just want to retrieve a few
To retrieve the values I have to use a complex query that I cannot generate via a simple createCriteria using hibernate.
Contact
#Entity
#Table(name = "contacts")
public class Contact
{
private String firstName;
private String lastName;
private Company company;
...
}
Company
#Entity
#Table(name = "companies")
public class Contact
{
private Integer id;
private String name;
...
}
SQL Query
As I explained before the query is very complex but for convenience both in writing and in answering the question I have reduced it to its minimum terms:
SELECT a.first_name as firstName,
a.last_name as lastName,
a.company_id as companyId,
b.company_name as companyName
FROM contacts a
INNER JOIN companies b ON a.company_id = b.company_id
UserType (CompanyType)
To create the Company object I use a UserType and it is the following
public class CompanyType implements UserType
{
#Override
public int[] sqlTypes()
{
return new int[] { Types.INTEGER, Types.VARCHAR };
}
#SuppressWarnings("rawtypes")
#Override
public Class returnedClass()
{
return Company.class;
}
...
}
Java
I am currently building my object in the following way and is it working.. currently thanks to my UserType I can create a new Company object and set the id.
Type companyType = session.getTypeHelper().custom(CompanyType.class);
results = session.createSQLQuery(SQL_QUERY)
.addScalar("firstName", StandardBasicTypes.STRING)
.addScalar("lastName", StandardBasicTypes.STRING)
.addScalar("companyId", companyType)
.setResultTransformer(Transformers.aliasToBean(Contact.class))
.list();
My goal is to set the name of the Company in the same object created before (by the id).. I tried to add the following line of code but I get an error because it is trying to allocate a new object instead of setting the current one:
.addScalar("companyName", companyType)
Not sure if I completely understand the problem, but do you want to set that the fields of the nested entity(Company) inside the Contact entity object?
If that's the goal, then the simple AliasToBeanResultTransformer won't help for single-level entity transformation. For transformation involving nested entities, you will need to use an AliasToBeanNestedResultTransformer - like what samiandoni has contributed here - https://github.com/samiandoni/AliasToBeanNestedResultTransformer/blob/master/AliasToBeanNestedResultTransformer.java.
You can use it like so:-
results = session.createSQLQuery(SQL_QUERY)
.addScalar("firstName", StandardBasicTypes.STRING)
.addScalar("lastName", StandardBasicTypes.STRING)
.addScalar("companyId", companyType)
.setResultTransformer(new AliasToBeanNestedResultTransformer(Contact.class))
.list();
You can create a DTO object of selected fields and Transformed the query result to DTO.
Transformers.aliasToBean(ContactCompanyDTO.class)
public class ContactCompanyDTO {
private String firstName;
private String lastName;
private String companyName;
private Integer id;
}
If you want to use an actual Domain object then you can easily convert the DTO to a Domain object.
I have this rest point which is used to return IDs:
#GetMapping("{id}")
public ResponseEntity<?> get(#PathVariable String id) {
return contractService
.findById(Integer.parseInt(id))
.map(mapper::toNewDTO)
.map(ResponseEntity::ok)
.orElseGet(() -> notFound().build());
}
DTO:
public class ContractNewDTO {
.....
private Integer terminal_id;
....
}
How I can translate terminal_id into terminal name using second SQL query?
I need something like this:
......map(mapper::toNewDTO) -> get here terminal_id and make another SQL query to find which name relates to this terminal_id and return the terminal name NOT terminal_id.
Can you give me some advice how to do this?
So you want to retrieve terminal_name based on terminal_id.
You have a couple of options:
1) If terminal_name is in the same database table as temrminal_id, then it should be loaded inside ContractNewDTO in your mapper::toNewDTO you can implement the conversion logic which uses terminal_name instead of temrminal_id.
2) If termminal_name is in another table (e.g. terminaldetails table) and you will need all the data from that table, then you can create a mapping (e.g. OneToOne joined on the terminal_id). Here is an excellent writing on how to do OneToOne mapping, from the master himself.
3) Another option is to use #SecondaryTable. Let's say your current entity is TerminalEntity and it has column terminal_id and is mapped to table "terminal". The terminal_name is in another table "terminaldetails".
What you can do is:
#Entity
#Table(name = "terminal")
#SecondaryTable(name = "terminaldetails")
public class TerminalEntity {
#Id
#GeneratedValue
private Long id;
#Column(name = "terminal_id")
private String terminalId;
#Column(name = "terminal_name", table = "terminaldetails")
private String terminalName;
}
I have an Entity called Student
#Entity
#Table(name = "students")
public class Student implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "STUDENT_ID")
private Integer studentId;
#Column(name = "STUDENT_NAME", nullable = false, length = 100)
private String studentName;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "student", cascade = CascadeType.ALL)
private List<Note> studentNotes;
// Some other instance variables that are not relevant to this question
/* Getters and Setters */
}
and an entity called as Note
#Entity
#Table(name = "notes")
public class Note implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "NOTE_ID")
private Integer noteId;
#Column(name = "NOTE_CONTENT")
private String noteText;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "STUDENT_ID")
private Student student;
/* Getters and Setters */
}
As you can see the relationship dictates that a Student can have multiple number of notes.
For displaying some information about the student on a particular page I need only the studentName, count of notes and all the notes.
I created a StudentDTO for that and it looks something like this:
public class StudentDTO {
private Long count;
private String name;
private List<Note> notes;
/* Getters and setters */
}
And I am using the following code to map the Student and Notes returned from the DB to the StudentDTO
private static void testDTO() {
Session session = getSessionFactory().openSession();
String queryString = "SELECT count(n) as count, s.studentName as name, s.studentNotes as notes " +
"from Student s join s.studentNotes n where s.id = 3";
Query query = session.createQuery(queryString);
List<StudentDTO> list = query.setResultTransformer(Transformers.aliasToBean(StudentDTO.class)).list();
for (StudentDTO u : list) {
System.out.println(u.getName());
System.out.println(u.getCount());
System.out.println(u.getNotes().size());
}
}
The above code fails when there are notes fetched in the query but if I remove the notes and get only name and count it works fine.
When notes is included in the query, this is the error that is fired by Hibernate:
select
count(studentnot2_.NOTE_ID) as col_0_0_,
. as col_3_0_,
studentnot3_.NOTE_ID as NOTE_ID1_2_,
studentnot3_.NOTE_CONTENT as NOTE_CON2_2_,
studentnot3_.STUDENT_ID as STUDENT_3_2_
from
students studentx0_
inner join
notes studentnot2_
on studentx0_.STUDENT_ID=studentnot2_.STUDENT_ID
inner join
notes studentnot3_
on studentx0_.STUDENT_ID=studentnot3_.STUDENT_ID
where
studentx0_.STUDENT_ID=3;
And this is the error message that I get:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'as col_3_0_, studentnot3_.NOTE_ID as NOTE_ID1_2_, studentnot3_.NOTE_CONTENT as N' at line 1
Now I can see where the query is wrong but it is generated by Hibernate, not something that I have control on. Is there something that I need to change in my queryString to acheive the result that I need.
I do not want to manually map the results to my DTO, is there a way that I can directly map my studentNotes in Student.java to notes in StudentDTO.java
Looks like this query is wrong. The better way is to get just the student. You can always get collection of notes from a student.
Session session = getSessionFactory().openSession();
String queryString = from Student s where s.studentId = 3;
Query query = session.createQuery(queryString);
Student student = query.getSingleResult();
sysout(student.getNotes().size())
Also, I never retrieved collection this way in SELECT clause; so, not sure but do you really need
join s.studentNotes
in your query? Not sure if my answer is helpful.
Your query is wrong as you would need two joins to also select the count of notes, but that's not even necessary, as you could determine the count by just using the size of the notes collection.
I created Blaze-Persistence Entity Views for exactly that use case. You essentially define DTOs for JPA entities as interfaces and apply them on a query. It supports mapping nested DTOs, collection etc., essentially everything you'd expect and on top of that, it will improve your query performance as it will generate queries fetching just the data that you actually require for the DTOs.
The entity views for your example could look like this
#EntityView(Student.class)
interface StudentDTO {
#Mapping("studentName")
String getName();
#Mapping("studentNotes")
List<NoteDTO> getNotes();
default int getCount() { return getNotes().size(); }
}
#EntityView(Note.class)
interface NoteDTO {
// attributes of Note that you need
#IdMapping Integer getId();
String getNoteText();
}
Querying could look like this
StudentDTO student = entityViewManager.find(entityManager, StudentDTO.class, studentId);
I have a complex SQL query that I'm implementing as a native query in JPA. The entity manager is automatically converting each row of my result set into a Person object, resulting in List<Person>
String sql = "select name, address, date_of_birth from ...";
Query q = entityManager.createNativeQuery(sql, Student.class);
List<Student> results = q.getResultList();
return results;
The entityManager demands that the Student bean is annotated with #Entity, and this requires an #Id. The API also requires that all fields are matched. Unfortunately, my select clause pulls from various sources, so there isn't really an Id. In fact, I'd like to later have an id generated when I call StudentRepostiory.save(student).
I'm in a catch 22 situation. I need an id for the object to be generated, but I don't have an id as yet.
What should I do?
I've tried making the ID null. It makes all results null.
I've tried making the ID zero. It makes all results the same.
I've tried adding a Generation Strategy annotation. Didn't do anything.
For your reference
Student.java
#Entity // This forces me to have an Id.
class Student {
#Id Long id // I'm forced to include this, but my query doesn't have one
String name;
String dateOfBirth;
String address;
// Getters and Setters
}
You can use #SqlResultSetMapping and #ConstructorResult to map the results of your native sql query to a POJO/bean, without requiring it to have an id.
Note: #ConstructorResult is only available in JPA 2.1.
First, you need to have a constructor in your Student entity that takes 3 parameters: name, dateOfBirth and address. Since this is an entity, it requires you to have a default no-arg constructor, so you need to declare that too.
So you have 2 constructors, one no-arg and another with 3 args.
Now you declare your SqlResultSetMapping using #ConstructorResult:
#SqlResultSetMapping(name="StudentMapping",
classes={
#ConstructorResult(targetClass=Student.class,
columns={
#ColumnResult(name="name", type=String.class),
#ColumnResult(name="address", type=String.class),
#ColumnResult(name="date_of_birth", type=String.class)
})
}
)
#Entity
public class Student {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String String name;
private String address;
private String dateOfBirth;
public Student () {}
public Student(String name, String address, String dateOfBirth) {
this.name = name;
this.address = address;
this.dateOfBirth = dateOfBirth;
}
// Setters and Getters
}
Now when you call the createNativeQuery, you can pass your SQL string together with the name of the mapping you declared.
String sql = "select name, address, date_of_birth from ...";
Query q = entityManager.createNativeQuery(sql, "StudentMapping");
List results = q.getResultList();
Then, later on you can iterate the Student objects in your list and persist them. Since Student is an entity and objects in your list does not have a value for the id field yet, calling persist on these objects will work.
You'll see a sample of this implementation in my github repo.
It's possible mapping custom native/named queries to entities? I have something like this
NamedQueries({
NamedQuery(name = "StateBo.findByCountry", query = "SELECT state FROM StateBo state WHERE state.country.id = ?"),
NamedQuery(name = "StateBo.showIdfindByCountry", query = "SELECT state.id FROM StateBo state WHERE state.country.id = ?")
})
#Table(name = "STATE")
#Entity(name = "StateBo")
public class StateBo extends BaseNamedBo {
private static final long serialVersionUID = 3687061742742506831L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "STATE_ID")
private Long id;
#Column(name = "ISO_CODE")
private String isoCode;
#ManyToOne
#JoinColumn(name = "COUNTRY_ID")
private CountryBo country;
// getters and setters ...
}
I have my method to call Native/Named queries like this.
#Override
public List<E> executeQuery(String queryName, List<Object> criteria) {
TypedQuery<E> query = entityManager.createNamedQuery(queryName, entityClass);
Integer argumentPosition = 1;
if ( (criteria != null) && (criteria.size() > 0) ){
for(Object object : criteria) {
query.setParameter(argumentPosition, object);
argumentPosition++;
}
}
return (List<E>) query.getResultList();
}
When I call the StateBo.findByCountry the result is mapped to StateBo, but if I call StateBo.showIdfindByCountry the result is not mapped to StateBo because I'm only selected on the query the state.id instead of the fields on the table.
I don't want to select all the fields of the STATE table, I only want in this case the state.id, but when I customize my native query, the result is not mapped to StateBo instead of this, the result is a Long type.
My question is, Is possible map to an Entity the result of StateBo.showIdfindByCountry? I case that I have more fields like state.isoCode, is possible map to StateBo, the custom query? or only is possible if I return all the fields from the query, like the first query StateBo.findByCountry
It is possible, but as JB Nizet said - "your collegues will suffer from such a design decision".
Anyway, in order to do that you should create custom constructor in your entity class. This constructor should accept Long argument and assign it to id field of your entity class.
Then you should change your query to include NEW keyword followed by full qualified entity class name as below:
SELECT NEW your.package.StateBo(sb.id)
FROM StateBo sb
WHERE state.country.id = ?
Please note that all entities retreived from database in such a way will not be managed by persistence context.