How to do a Inner-Join in JPA Criteria? - sql

I'm using Netbeans to program a webservice REST that returns a JSON response, I am also using JPA Criteria to create the querys. I have two Entities that looks like this:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Column(name = "id_user")
private Integer idUser;
#Size(max = 45)
#Column(name = "username")
private String username;
#Size(max = 45)
#Column(name = "password")
private String password;
#Size(max = 45)
#Column(name = "email")
private String email;
#OneToMany(mappedBy = "idUser")
private Collection<Comment> commentCollection;
public class Comment implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Column(name = "id_comment")
private Integer idComment;
#Column(name = "id_thesis")
private Integer idThesis;
#Size(max = 250)
#Column(name = "comment")
private String comment;
#Column(name = "cdate")
#Temporal(TemporalType.DATE)
private Date cdate;
#JoinColumn(name = "id_user", referencedColumnName = "id_user")
#ManyToOne
private User idUser;
}
Both entities with sets and gets. I want to do a query like this:
SELECT * FROM Comments c INNER JOIN User u WHERE c.id_user = u.id_user;
but in the JPA Criteria language, I've had many problems trying to make it work but i don't get it yet.
This is the code that I'm using for the join
AbstractFacade.java
public Join<User, Comment> getCommentInfo() {
CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
CriteriaQuery q = cb.createQuery();
Root<User> r = q.from(User.class);
Join<User, Comment> j = r.join("commentCollection", JoinType.INNER);
Query query = getEntityManager().createQuery(q);
return (Join<User, Comment>) query.getResultList();
}
UserFacadeREST.java
#GET
#Path("test")
#Produces({"application/json"})
public Join<User, Comment> getCommentInfoREST() {
return getCommentInfo();
}
This error is shown when I test the method:
java.util.Vector cannot be cast to javax.persistence.criteria.Join
Please help me with that, I do not know if the sentence join is wrong or how to solve the cast properly.
Edit: I add the next lines to the getCommentInfo() method to see the content of the list.
q.select(j.get("username"));
List results = query.getResultList();
Iterator iter = results.iterator();
while (iter.hasNext()){
System.out.println(iter.next());
}
Error: The attribute [username] is not present in the managed type [EntityTypeImpl#1000979996:Comment.

In the case of getResultList(), the javadocs state that it returns an java.util.List (see here: http://docs.oracle.com/javaee/5/api/javax/persistence/Query.html#getResultList%28%29 ), that Vector implements.
The result type, aka what's in the list, depends on the criteria projection or, in a JPQL Query, of the from statement.
In your case, because you don't do projection, I think it should return a List<User>.
For your information, and if you are using JPA 2.0, you can also use TypedQuery which could avoid that (ugly !) cast : http://www.javabeat.net/typedquery-jpa-2/

Related

Query Many-to-many jpa

I need to implement SELECT with a many-to-many relationship in #QUERY. Perhaps I am misinterpreting the information written in the documentation.
My query looks like this:
#Query("select massages.id from massages join string_massage on massages.id = string_massage.massage_id where string_massage.string_id = ?1")
List<MasageEntity> findMassagesIdByStringId(#Param("strings_id") long strings_id);
In my example, I use table names. The names are underlined as an error (without compilation). Maybe I should use Entities. Then how do I do it with many-to-many relationship?
I will show a part of my Entities.
I have two Entities. MasageEntity and RstringEntity.
//MasageEntity
#Entity
#Table(name = "massages")
public class MasageEntity {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "string_text")
private String string_text;
#Column(name = "string_speed")
private Long string_speed;
#Column(name = "string_color_type")
private Long string_color_type;
#Column(name = "string_color")
private String string_color;
#Column(name = "string_timing_type")
private String string_timing_type;
#Column(name = "string_timing")
private String string_timing;
#Column(name = "showed")
private Long showed;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "string_massage",
joinColumns = { #JoinColumn(name = "massage_id") },
inverseJoinColumns = { #JoinColumn(name = "string_id") })
//RstringEntity
#Entity
#Table(name = "string")
public class RstringsEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name="code")
private String code;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
}, mappedBy = "strings")
#JsonIgnore
private Set<MasageEntity> masagess = new HashSet<>();
public RstringsEntity() {}
There are multiple ways how to perform queries in Spring Boot: native SQL and JPQL.
In case of native queries we are using pure SQL language, defining query on DB level.
In case of Java Persistence Query Language (JPQL) we define query via entity objects.
Solution 1, native queries
You created the native query in repository, but to use it we need mark it like SQL nativeQuery = true. It required for framework to understang what query laguage do you use. #Query annotation use JPQL by defult, so that's the reason of your errors.
#Repository
public interface MessageRepository extends JpaRepository<MassageEntity, Long> {
//find MessageEntities by String ID via native query
#Query(value = "select massages.* from massages join string_massage on massages.id = string_massage.massage_id where string_massage.string_id = ?1", nativeQuery = true)
List<MassageEntity> findMassagesByStringIdNativeSQL(#Param("strings_id") long strings_id);
//find Message IDs by String ID via native query
#Query(value = "select massages.id from massages join string_massage on massages.id = string_massage.massage_id where string_massage.string_id = ?1", nativeQuery = true)
List<Long> findMassagesIdByStringIdNativeSQL(#Param("strings_id") long strings_id);
}
Solution 2, JPQL queries
Example how to define JPQL queries for your case. JPQL will be translated to SQL during execution.
#Repository
public interface MessageRepository extends JpaRepository<MassageEntity, Long> {
//find MessageEntities by String ID via JPQL
#Query("select message from MassageEntity message join message.strings string where string.id = :strings_id")
List<MassageEntity> findMassagesByStringIdJPQL(#Param("strings_id") long strings_id);
//find Message IDs by String ID via JPQL
#Query("select message.id from MassageEntity message join message.strings string where string.id = :strings_id")
List<Long> findMassagesIDByStringIdJPQL(#Param("strings_id") long strings_id);
}
Native query generated by Hibernate:
select
massageent0_.id as id1_3_,
massageent0_.string_text as string_t2_3_
from
massages massageent0_
inner join
string_massage strings1_
on massageent0_.id=strings1_.massage_id
inner join
string rstringsen2_
on strings1_.string_id=rstringsen2_.id
where
rstringsen2_.id=?
Solution 3, Spring auto-generated queries
Spring can auto-generate queries by repository method definition.
Example for your case:
#Repository
public interface MessageRepository extends JpaRepository<MassageEntity, Long> {
//find MessageEntities by String ID
List<MassageEntity> findByStrings_Id(#Param("id") long strings_id);
}
Entries which I used fro sulutions:
#Entity
#Table(name = "massages")
public class MassageEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "string_text")
private String string_text;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "string_massage",
joinColumns = { #JoinColumn(name = "massage_id") },
inverseJoinColumns = { #JoinColumn(name = "string_id") })
private Set<RstringsEntity> strings = new HashSet<>();
}
#Entity
#Table(name = "string")
public class RstringsEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "code")
private String code;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
}, mappedBy = "strings")
#JsonIgnore
private Set<MassageEntity> massages = new HashSet<>();
}

Spring data jpa native query. Insert object

I'm using a native query on my repository. I want to insert object data, but the query doesn't understand the object fields. How to solve this problem, I don't want to have a method with a lot of parameters.
My class:
public class StudentDTO {
private long numberzachetka;
private String fio;
private Date entrydate;
private int course;
private int numbergroup;
private long specialty;
private long faculty;
//getters, setters..}
Native query:
#Query(value ="insert into student(numberzachetka,fiostudent,entrydate,course,numbergroup,specialtykey,facultynumber)" +
" values" +
" (:student.numberzachetka,:student.fio,:student.entrydate,:student.course,:student.numbergroup,:student.specialty,:student.faculty)", nativeQuery = true)
void addNewStudent(#Param("student") StudentDTO studentDTO);
Student entity:
#Entity
#Table(name="student")
public class Student {
#Id
#Column(name="numberzachetka", nullable = false)
private long numberzachetka;
#Column(name="fiostudent", nullable = false, length = 100)
private String fio;
#Temporal(TemporalType.DATE)
#Column(name = "entrydate", nullable = false)
private Date entrydate;
#Column(name="course", nullable = false)
private int course;
#Column(name="numbergroup", nullable = false)
private int numbergroup;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "specialtykey", nullable = false)
private Specialty specialty;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "facultynumber", nullable = false)
private Faculty faculty;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "student")
private Set<Performance> performances;
#Query(value ="insert into student(numberzachetka,fiostudent,entrydate,course,numbergroup,specialtykey,facultynumber)" +
" values" +
" (:student.numberzachetka,:student.fio,:student.entrydate,:student.course,:student.numbergroup,:student.specialty,:student.faculty)", nativeQuery = true)
void addNewStudent(#Param("student") StudentDTO studentDTO);
This is not the right way to save value in db using JPA repository.
If you want to save value in DB there is very simpler way, follow below steps:
Copy all the values from the studentDTO to Student object using below code.
Student student = new Student();
BeanUtils.copyProperties(studentDto,student);
create StudentRepository interface
#Repository public interface StudentRepository extends
JpaRepository<Student, Long> { }
#Autowired above Repository in the service class
#Autowired private StudentRepository studentRepository;
Save the Student object using repository predefined save method.
studentRepository.save(student);
These are the minimal change you want to save object in db, instead of going through native SQL query.
Within the entity you could have a constructor accepting the DTO. The constructor transfers the DTO attributes to the fields of the entity. Then you can just use the JpaRepository save method. You don't need the select statement. Keep in mind to add also an empty constructor which is required by JPA.
Another Idea which I use in my projects is to use mapstruct to transfert attributes from DTOs to entities.
The way you present your idea is not possible. JPA does not know how to map a DTO to an entity.

JPQL query for left outer join over parent vs inherited object

Entities/Model:
#Inheritance(strategy=InheritanceType.JOINED)
public class UserAccount implements CommonUserAccount {
#Id
private Long id;
private String email;
#Embedded
private PersonalInfo personalInfo = new PersonalInfo(); // name/surname - regular stuff
#ElementCollection
#CollectionTable(name = "UserAccountTags", joinColumns = #JoinColumn(name = "accountId", nullable = false))
#Column(name = "tag")
//#Transient
private Set<String> tags = new HashSet<String>();
#ElementCollection
#CollectionTable(name = "UserAccountRoles", joinColumns = #JoinColumn(name = "accountId", nullable = false))
#Enumerated(EnumType.STRING)
#Column(name = "userRole")
private Set<UserAccountRole> userRoles = new HashSet<UserAccountRole>();
// regular getters/setters
}
#Entity
#Table
#PrimaryKeyJoinColumn(name = "useraccountid")
public class DemoUserAccount extends UserAccount implements CommonUserAccount {
#Column
private String passwordHash;
#Column
private Long failedLogins;
#Column
#Temporal(TemporalType.TIMESTAMP)
Date lockedAt;
// regular getters/setters
}
Question:
Is it possible to build query using JPQL (for JPA2.0) that would return DemoUserAccounts joined on parent table - UserAccounts? Doing this would assume I can filter on tags/user_roles as well. In general some records will not have DemoUserAccount specific fields filled in.
When you do a SELECT from DemoUserAccount, you already have the UserAccount fields available to do a query using them.
So, if you want to filter by email and failedLogins:
SELECT d FROM DemoUserAccount d WHERE d.email = 'you#you.com' AND d.failedLogins > 3

Best way to get aggregate function result inside the entity object

Many time I have to get the SQL aggregate query result inside the Entity Object itself. As of now I could able to achive the same by the following code
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> q = cb.createTupleQuery();
Root<Test> c = q.from(Test.class);
q.multiselect(c, cb.count(c));
q.groupBy(c.get("type"));
TypedQuery<Tuple> t = em.createQuery(q);
List<Tuple> resultList = t.getResultList();
List<Test> list = new ArrayList<>();
for(Tuple tuple : resultList){
Test te = (Test) tuple.get(0);
te.setQuantity((long)tuple.get(1));
list.add(te);
}
But I want to know what could be the best way. My Test Entity is as
#Entity
#Table(name = "test")
public class Test {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "name")
private String name;
#Column(name = "type")
private Integer type = 0;
#Transient
private long quantity;
}
If you cannot use #Formula then I'd suggest create a database view basic on your select and mapping an additional entity to that. You can then map this to your existing entity using either as a #OneToOne or by using #SecondaryTable.
This has the added advantage of being JPA compliant (i.e. not using Hibernate's propreitary #Formula) and would look something like:
#Entity
#Table(name = "test")
public class Test {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "name")
private String name;
#Column(name = "type")
private Integer type = 0;
#OneToOne//or via secondary table
private TestSummaryInfo summaryInfo;
public long getQuantity(){
return summaryInfo.getQuantity();
}
}
Summary mapped to a view:
#Entity
#Table(name = "vw_test_summary")
public class TestSummaryInfo {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "quantity")
private Long quantity;
}

JPA COUNT with composite primary key query not working

In my db, I have a table (Defaults), and when I generate an entity from table, I get these two classes:
#Entity
public class Defaults implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected DefaultsPK DefaultsPK;
#Column(name = "ERTEK")
private String ertek;
getter/setter...
}
#Embeddable
public class DefaultsPK implements Serializable {
#Basic(optional = false)
#Column(name = "VALUE_1")
private String value1;
#Basic(optional = false)
#Column(name = "TYPE")
private String type;
#Basic(optional = false)
#Column(name = "VALID_FROM")
#Temporal(TemporalType.TIMESTAMP)
private Date validFrom;
#Basic(optional = false)
#Column(name = "VALID_TO")
#Temporal(TemporalType.TIMESTAMP)
private Date validTo;
getter/setter...
}
That is why becaues the primary key is including the values.
I want to count all the rows in the table, so I use this code:
String sql = "SELECT COUNT(d) FROM Defaults d";
Query q = em.createQuery(sql);
long count = (long)q.getSingleResult();
But I am getting this error:
org.hibernate.exception.SQLGrammarException: could not execute query
...
java.sql.SQLSyntaxErrorException: ORA-00907: The right expression is missing from the arithmetic expression
What is the problem? The other count queries with other entities are working.
I am using hibernate.
Use count(d.ertek) or count(d.id) instead of count(d). This can be happen when you have composite primary key at your entity.