I'm trying to create a JPQL for one to many relationship, namely user and device as 1 user can have one to many devices. I want to find the whole object of the device if it is owned by the user and the device name is correct.
If it is a SQL query then I can just do the query only for the device as follow:
select * from DEVICE where USER_ID = 2 and DEVICENAME = "mypc";
where USER_ID is the foreign key in DEVICE table. But how can I do the JPQL query for the user and device table? Here are some info for User and Device class
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
public int id;
public String username;
public String firstname;
public String lastname;
public String hashPassword;
#OneToMany(mappedBy = "user")
public List<Device> devices = new ArrayList<Device>();
}
#Entity
public class Device {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
public int id;
public String deviceName;
#ManyToOne
public User user;
String deviceOS;
String deviceType;
String attributes;
boolean standard;
}
Example JPA Query:Documention
Query query = entityManager.createQuery("SELECT di FROM Device di JOIN di.user u WHERE u.id=:userId and di.deviceName=:deviceName");
query.setParameter("userId",userId );
query.setParameter("deviceName",deviceName);
List<Device> result = query.getResultList();
You can use JPQL path expressions in your query to navigate from one entity to another, or to refer to attributes of an entity.
More specifically, path expressions are useful to navigate along #ManyToOne or #OneToOnerelationships, e.g.:
SELECT d FROM Device d WHERE d.user.id = :userId AND d.deviceName = :deviceName
Here, d.user.id is a path expression.
For a general overview of JPQL, have a look at the great Wikibooks article.
Related
I am developping a chat app. I have two Entities
#Entity(tableName = "messages")
public class MessageItem {
#PrimaryKey
private Integer msgId;
#ColumnInfo(name = "from_id")
private String contact_id;
}
And
#Entity(tableName = "contact")
public class Contact{
#PrimaryKey
private Integer id;
#ColumnInfo(name = "contact_phone")
private String phone;
}
In MessageDao I want to get Contact phone correponding to the contact_id in MessageItem
You have three ways you can do this.
1) You can use a POJO with an #Embedded and an #Relation in which case you return MessageItem's with the Contact e.g. :-
public class MessageItemWithContact {
#Embedded
MessageItem messageItem;
#Relation(entity = Contact.class, parentColumn = "from_id", entityColumn = "id")
Contact contact;
}
along with an #Query such as :-
#Transaction
#Query("SELECT * FROM messages WHERE msgId=:msgId")
MessageItemWithContact getMessageByIdWithContact(int msgId);
2) Or you can use a POJO with an #Embedded and an additional variable for the phone using a JOIN e.g. :-
public class MessageItemWithPhone {
#Embedded
MessageItem messageItem;
String phone;
}
Along with an #Query like :-
#Query("SELECT msgId, contact_phone, from_id FROM messages JOIN contact On from_id = id ")
List<MessageItemWithPhone> getMessItemWithContactPhone();
doesn't need #Transaction as the query is a single transaction (whilst the previous method Room obtains the MessageItem and then builds a query to get the related object(s)).
this query gets all MessageItems (as no WHERE clause has been included)
3) Just get the phone using the from_Id for the relevant MessageItem without the need for a POJO by having an #Query like:-
#Query("SELECT contact_phone FROM contact WHERE id=:from_Id")
String getPhoneById(String from_Id);
Have a "full Entity" class:
#Entity(name = "vacancy_dec_to_words")
public class VacancyDescriptionToWords {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#JoinColumn(name = "vacancy_description_id")
#ManyToOne(cascade = CascadeType.ALL)
private VacancyDescription vacancyDescription;
#JoinColumn(name = "words_id")
#ManyToOne
private Words words;
#Column(name = "qty")
private int qty;
#Column(name = "create_date")
private Date date;
//...getters and setters
In some methods I need use only 2 column from this database: word_id and qty
I try the follow way:
Projections
https://docs.spring.io/spring-data/jpa/docs/2.1.2.RELEASE/reference/html/#projections
public interface QtyWords {
Long getWords();
Integer getQty();
}
JpaReposytory:
*Query, that I use tested and it workable, I use him in JpaRepository:
#Repository
public interface SmallVDTWRepository extends JpaRepository<VacancyDescriptionToWords, Long> {
#Query(nativeQuery = true,
value = "SELECT sum(qty), words_id FROM vacancy_desc_to_words WHERE vacancy_description_id IN (" +
"SELECT id FROM vacancy_description WHERE vacancy_id IN (" +
"SELECT id FROM vacancy WHERE explorer_id = :exp))" +
"GROUP BY words_id")
List<QtyWords> getDistinctWordsByExplorer(#Param("exp") long exp);
}
But I get some interesting result when I get list of entities:
List<QtyWords> list = vdtwService.getByExplorerId(72);
I am not get any exceptions, but I have the list with are unknowns objects. This objects contains my data, which I need(qty and words_id), but I cannot get them from him.
Can I use this method (Projection) to implement this task and, in general, how to correctly implement the 'Light Entity' in this case?
Spring provides two mechanisms that can be used to limit data to be fetched.
Projections
Projections can help you to reduce data, retrieved from database, by setting what exactly attributes you want to fetch.
Example:
#Entity
class Person {
#Id UUID id;
String firstname, lastname;
#OneToOne
Address address;
}
#Entity
static class Address {
#Id UUID id;
String zipCode, city, street;
}
interface NamesOnly {
String getFirstname();
String getLastname();
}
#Repository
interface PersonRepository extends Repository<Person, UUID> {
Collection<NamesOnly> findByLastname(String lastname);
}
Entity graph
Annotation EntityGraph can help you to reduce amount of queries to database, by setting what exactly related entities you need to fetch.
Example:
#Entity
#NamedEntityGraph(name = "GroupInfo.detail", attributeNodes = #NamedAttributeNode("members"))
public class GroupInfo {
#Id UUID id;
#ManyToMany //default fetch mode is lazy.
List<GroupMember> members = new ArrayList<GroupMember>();
}
#Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
#EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD)
GroupInfo getByGroupName(String name); //Despite of GroupInfo.members has FetchType = LAZY, it will be fetched because of using EntityGraph
}
There are two types of EntityGraph:
EntityGraphType.LOAD - is used to specify an entity graph, attributes that are specified by attribute nodes of the entity graph are treated as FetchType.EAGER and attributes that are not specified are treated according to their specified or default FetchType.
EntityGraphType.FETCH - is used to specify an entity graph, attributes that are specified by attribute nodes of the entity graph are treated as FetchType.EAGER and attributes that are not specified are treated as FetchType.LAZY.
PS: Also remember that you can set lazy fetch type: #ManyToOne(fetch = FetchType.LAZY) and JPA will not fetching child entities when parent is being fetched.
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 an web application with hibernate which manages data in multiple languages. Currently basically every request generates a shower of select statements on the languagetranslations. The models are roughly as following:
Data <1-1> Placeholder <1-many> languageTranslation <many-1> language
If I query for all/many Dataobjects, I see lots of single selects which select one languageTranslation for the placeholder. The SQL I optimally would want to generate:
SELECT * FROM data join placeholder join languagetranslation
WHERE data.placeholder_id = placeholder.id
AND languagetranslation.placeholder_id = placeholder.id
AND languagetranslation.language_id = ?
so that I get every data with placeholder with translation in one single call. The languagetranslations have an composite primary key of language_id and placeholder_id.
I have no HBM file, everything is managed with annotations. Modelcode (only relevant sections are shown):
#Entity
public class Data {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, optional = false)
#Fetch(FetchMode.JOIN)
private Placeholder content;
}
public class Placeholder {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "primaryKey.placeholder", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#Fetch(FetchMode.JOIN)
private Set<LanguageTranslation> languageTranslations = new HashSet<>();
}
public class LanguageTranslation {
#EmbeddedId
private LanguageTranslationPK primaryKey = new LanguageTranslationPK();
#Type(type = "org.hibernate.type.StringClobType")
private String text;
}
#Embeddable
public class LanguageTranslationPK {
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
private TextPlaceholder textPlaceholder;
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
private Language language;
}
public class Language {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
I experimented with FetchType and FetchMode but couldn't generate the behavior I want, it always single selects for single languageTranslations.
I also tried multiple ways to query, criteria based, HQL, and raw SQL. My current raw SQL query is the following:
String sql_query = "select data.*, lt.* from Data as data join languagetranslation as lt on data.content_id = lt.textplaceholder_id";
Query q = getSession().createSQLQuery(sql_query).addEntity("data", Data.class).addJoin("data.content_id", "data.title").addJoin("lt", "data.content.languageTranslations").setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
return q.list();
Am I doing something generally wrong here? How can I convince hibernate to get all entities in one single database call? Or is there some other methods to improve performance in my case (e.g. batch selecting)?
You may create proxy pojo which have your all entity variables with getter setter and constructor. then initialize this constructor in hibernate query so that you just get all needed data from database.
import com.proxy;
class userProxy{
private string name;
private string password;
private string address;
private int pincode;
private byte[] profilePic;
private int age;
public userProxy(string name,string password){
this.name = name;
this.password = password;
}
//Getter and setter of all variable...
}
Then use this constructor to Hibernate query like
select new com.proxy.userProxy(user.name,user.password) from usertable
Am I doing something generally wrong here?
No, you are not. That is how Hibernate works.
How can I convince hibernate to get all entities in one single database call
You have to use HQL or SQL query to do that. You do not need to have HBM file. It can be done through #NamedQueries / #NamedQuery annotation with list method.
There are many samples on Internet as example simple one:
http://www.mkyong.com/hibernate/hibernate-named-query-examples/
I have followed this blog-tutorial and successfully got it to work:
http://jugojava.blogspot.com/2011/02/jdbc-security-realm-with-glassfish-and.html
I have named my two entities Group and User. The have a bi-directional many-to-many relationship.
Now the reason I have done it as in the blog is because I am making an administrator page where I want to be able to add new users. I also let users have the oppertunity to register them self, and they will have the role user.
#Entity
public class Group implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String groupName;
#ManyToMany
#JoinTable(joinColumns = {#JoinColumn(name="group_id")}, inverseJoinColumns = {#JoinColumn(name="user_id")})
private List<User> users;
....
}
and
#Entity
#Table(name = "app_user")
public class User implements Serializable {
public static final String ALL = "User.all";
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private Integer age;
private String username;
private String email;
private String password;
#ManyToMany(mappedBy = "users")
private List<Group> groups;
....
}
My question is how I would assign the group user to a user when it register without me picking the groups from a list in the view?
This is what I have done in the application code but it binds the code the the id of the group in the database, are there better ways?
Method from EJB
public void persistAsUser(User user) {
Group group = new Group(2L, "user");
user.addGroup(group);
userRepository.persist(user);
}
You may want to define a UNIQUE index on the field groupName. Then, create a Data Access Object for the Group table, which provides a method for getting a Group from a groupName (code not tested):
public class GroupDAO implements Serializable {
#PersistenceContext private EntityManager em;
public Group findByGroupName(String groupName) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Group> cq = cb.createQuery(Group.class);
Root<Group> group = cq.from(Group.class);
cq.where(cb.equal(group.get(Group_.groupName), groupName));
TypedQuery<Group> q = em.createQuery(cq);
return q.getSingleResult();
}
}
If you don't like Criteria Builder, you can use a Named Query. Add this annotation to your Group Entity Class:
#NamedQuery(name = "Group.findByGroupname", query = "SELECT f FROM group f WHERE f.groupname = :groupname")
and build a Named Query as follows:
return em.createNamedQuery("Group.findByGroupname").setParameter("groupname", groupName).getResultList();