I need to fetch 6 columns by joining 3 different tables. I have declared them as NamedNativequery on top of the entity class and I have used create named query method form JPA. When I try fo fetch the result set i get the list of array objects instead of the List of objects of POJO type. is there any external mapping should I be defining in order to map the result set to an external POJO?
You certainly can. This should help:
#NamedNativeQuery(query = "SELECT t1.col1, t2.col2 FROM t1 JOIN t2 ON ...", name = "MyNamedQuery", resultSetMapping = "MyPojoMapper")
#SqlResultSetMapping(name = "MyPojoMapper", classes = #ConstructorResult(
targetClass = MyPojo.class,
columns = {
#ColumnResult(name = "col1", type = String.class),
#ColumnResult(name = "cols", type = String.class)
}))
Then use it as such:
NativeQuery query = session.getNamedNativeQuery("MyNamedQuery");
MyPojo result = (MyPojo) query.getSingleResult();
You can use projection to specify what properties you want to get
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
or directly get with JPQL:
Repository.java
#Repository
public class CustomRepositoryImpl {
#Autowired
private EntityManager entityManager;
public List<Dto> find() {
var query = "SELECT new Dto(
x.Field1,
y.Field2,
z.Field3,
...)
FROM XxxEntity x
LEFT JOIN YyyEntity y
LEFT JOIN ZzzEntity z"
var jpqlQuery = entityManager.createQuery(query);
return jpqlQuery.getResultList();
}
}
Dto.java
public class Dto {
// Must have parameterized constructor with all fields what used in Repository
public Dto(int field1, String field2, String field3, ...) {
}
}
Related
I have two tables:
A : x_id, emp_id, name, age
B: emp_id, company_id, location
I want to fetch data which contain columns 'x_id', 'emp_id', 'company_id', 'name' joining tables 'A' and 'B' using emp_id..What is the best way to get it?
Is it possible to fetch data without creating beans mapping A and B like
Can I create a bean 'Result' containing variables 'x_id', 'emp_id', 'company_id', 'name' and populate it and get list of 'Result' beans as my output?
Yes, first you have to create a model class which includes the required details as the attributes.
#SqlResultSetMapping(
name = "ResultMap",
classes = #ConstructorResult(
targetClass = A.class,
columns = {
#ColumnResult(name = "x_id", type = Long.class),
#ColumnResult(name = "emp_id", type = Long.class),
#ColumnResult(name = "company_id", type = Long.class),
#ColumnResult(name = "name", type = String.class)
}
)
)
public class ResultMap {
private BigInteger x_id;
private BigInteger emp_id;
private BigInteger company_id;
private String name;
public ResultMap(BigInteger x_id, BigInteger emp_id, BigInteger company_id, String name) {
this.x_id = x_id;
this.emp_id = emp_id;
this.company_id = company_id;
this.name = name;
}
}
Then, write a custom query in repository class to get required data. Return type will be List of Tuple.
#Query(
value = "SELECT a.x_id, a.emp_id, b.company_id, a.name \n" +
"FROM A as a, B as b \n" +
"WHERE a.emp_id = b.emp_id",
nativeQuery = true)
List<Tuple> findResultMaps();
Finally Map this List of Tuple to List of ResultMap where ever it used in.
List<Tuple> resultsMapTuples = resultMapDao.findResultMaps();
List<ResultMap> resultMaps = resultsMapTuples.stream()
.map(t -> new ResultMap(
t.get("x_id", BigInteger.class),
t.get("emp_id", BigInteger.class),
t.get("company_id", BigInteger.class)
t.get("name", String.class)
)).collect(Collectors.toList());
resultMapDao is the repository class that findResultMaps() method written in.
I am doing a join with a native query. The returned object is a new object that contains all the attributes in the query. But it throws an error saying it can't bind object[] to CombinedObj. What do I do?
public interface SettingsRepository extends JpaRepository<Setting, Long> {
#Query(value = "select s.some_value, o.other_value from settings s inner join other_table o on o.id = s.other_table_id where user_id = :user_id", nativeQuery = true)
List<CombinedObj> find(#Param("user_id") int userId);
}
You can't simply project your columns and expect that jpa infer this columns to result object.
It's possible to solve this using a Named Native Query. Like this:
#Entity
#Table(name = "settings")
#SqlResultSetMapping(
name = "yourResultSetMapping",
classes = {
#ConstructorResult(
targetClass = CombinedObj.class,
columns = {
#ColumnResult(name = "some_value"), #ColumnResult(name = "other_value")
}
)
}
)
#NamedNativeQuery(
name = "Settings.getNativeQuery",
query = "select s.some_value, o.other_value from settings s inner join other_table o on o.id = s.other_table_id where user_id = :user_id",
resultSetMapping = "yourResultSetMapping"
)
public class Settings{...}
I'm wondering what's the benefit of using #SqlResultSetMapping and #ConstructorResult? Is it better, and if, why, from using JPQL SELECT NEW SomeConstructor ?
For example, is that better:
#Table(name = "person")
#Entity
#SqlResultSetMappings(
#SqlResultSetMapping(name = "contactLess",
classes = #ConstructorResult(columns = {
#ColumnResult(name = "id", type = Long.class),
#ColumnResult(name = "full_name", type=String.class)
}, targetClass = Person.class)
)
)
#NamedNativeQueries({
#NamedNativeQuery(name = "Person.findWithoutContacts",
query = "SELECT c.id, c.full_name FROM person c ",
resultSetMapping = "contactLess", resultClass = Person.class)
})
than this ( spring data in this case):
#Query("SELECT NEW Person(c.id, c.fullName) FROM Person c")
public List<Person> findAll();
If you have several JPQLs (and/or query builders) refering to one entity, than using annotations will make your code easier to refactor. JPQL also has no compile safety.
I am using a JPA query to get a result set, then within the same class, I would like to conditionally get more data. Here's what it looks like:
public SchoolUser getCandidatesAsJson(#PathParam("applicationId") String applicationId, #PathParam("userPassword") String userPassword ) {
EntityManager em = createEM();
Query query = em.createQuery("SELECT su FROM SchoolUser su WHERE su.applicationId LIKE :applicationId and su.userPassword LIKE :userPassword", SchoolUser.class);
query.setParameter("applicationId", applicationId);
query.setParameter("userPassword", userPassword);
List <SchoolUser> schoolUser = query.getResultList();
if(!schoolUser.isEmpty()) {
SchoolUser loginRecord = schoolUser.get(0);
int teacherId = loginRecord.getTeacherId();
int studentId = loginRecord.getStundentId();
if(teacherId!=0){
TypedQuery<Classroom> query2 = em.createQuery("SELECT c FROM Classroom c where c.teacherId = :teacherId ORDER BY c.period", Classroom.class);
query2.setParameter("teacherId", teacherId);
List <Classroom> teacherClassList = query2.getResultList();
if(!teacherClassList.isEmpty()){
//put 2nd results set in SchoolUser object - line is commented because it causes an erro
//loginRecord.setClassRooms(teacherClassList);
}
} else if(studentId!=0){
TypedQuery<ClassroomStudent> query3 = em.createQuery("SELECT cs FROM ClassroomStudent cs where cs.statusId = 1 AND cs.studentId = :studentId", ClassroomStudent.class);
query3.setParameter("studentId", studentId);
//put results in SchoolUser object
}
return loginRecord;
} else {
SchoolUser emptyRecord = new SchoolUser();
return emptyRecord;
}
}
The error comes from putting the Classroom JPA object into the SchoolUser object - since these two objects don't have a direct relationship.
Any way that I can accomplish this with JPA?
If you do not want to persist the classroom (or any other attribute for that matter) then the #Transient annotation allows you to ignore a particular field so that JPA won't try to map it.
This annotation specifies that the property or field is not
persistent. It is used to annotate a property or field of an entity
class, mapped superclass, or embeddable class.
Example:
#Entity
public class Employee {
#Id int id;
#Transient User currentUser;
...
}
I try to convert a sql query to Criteria API without success so far. I can create two separate queries which return the values I need, but I don't know how to combine them in a single query.
Here is the sql statement which works:
select company.*, ticketcount.counter from company
join
(select company, COUNT(*) as counter from ticket where state<16 group by company) ticketcount
on company.compid = ticketcount.company;
This Criteria query returns the inner query results:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<intCompany> qTicket = cb.createQuery(intCompany.class);
Root<Ticket> from = qTicket.from(Ticket.class);
Path groupBy = from.get("company");
Predicate state = cb.notEqual(from.<State>get("state"), getStateById(16));
qTicket.select(cb.construct(
intCompany.class, cb.count(from),from.<Company>get("company")))
.where(state).groupBy(groupBy);
em.createQuery(qTicket).getResultList();
In the application I defined a small wrapper/helper class:
public class intCompany{
public Company comp;
public Long opentickets;
public intCompany(Long opentickets,Company comp){
this.comp = comp;
this.opentickets = opentickets;
}
public intCompany(){
}
}
So does anyone has an idea how to get this working?
Update
Thank you. I changed my criteria query as you suggested. I just had to add a loop at the end to get the information I wanted.
List<intCompany> result = em.createQuery(cq).getResultList();
List<Company> cresult = new ArrayList();
for(intCompany ic: result){
ic.comp.setOpentickets(ic.opentickets.intValue());
cresult.add(ic.comp);
}
return cresult;
Maybe it is just not possible to convert the original sql to Criteria API.
Another update
I figured out I had to change the original sql expression to
select company.*, ticketcount.counter from company
left join
(select company, COUNT(*) as counter from ticket where state<16 group by company) ticketcount
on company.compid = ticketcount.company;
Otherwise I do not get companies with no entries in the ticket table.
So are there any other suggestions?
You have almost everything done.
//---//
CriteriaBuilder cb = em.getCriteriaBuilder();
//Your Wrapper class constructor must match with multiselect arguments
CriteriaQuery<IntCompany> cq = cb.createQuery(IntCompany.class);
//Main table
final Root<Ticket> fromTicket= cq.from(Ticket.class);
//Join defined in Ticket Entity
final Path company = fromTicket.get("company");
//Data to select
cq.multiselect(cb.count(from), company);
//Grouping
cq.groupBy(company);
//Restrictions (I don't really understand what you're querying)
Predicate p = cb.lessThan(fromTicket.get("state"), 16);
//You can add more restrictions
// p = cb.and/or(p, ...);
cq.where(p);
List<IntCompany> results = entityManager.createQuery(cq).getResultList();
This should work as expected.
I had similar problem. My solution was to use left outer joins.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Entity> query = cb.createQuery(Entity.class);
Root<Entity> root = query.from(Entity.class);
Join<Entity,ChildEntity> join = root.join(Entity_.children, JoinType.LEFT);
query.groupBy(root.get( Entity_.id ));
query.select(
cb.construct(
EntityDTO.class,
root.get( Entity_.id ),
root.get( Entity_.name ),
cb.count(join)
));
This JoinType.LEFT guarantees that you will get Entity records (companies) even if it doesn't have any child entities (tickets).
Entity class:
#Entity
public class Entity {
...
#OneToMany(targetEntity = ChildEntity.class, mappedBy = "parent", fetch = FetchType.LAZY, orphanRemoval = false)
private Set<ChildEntity> objects;
...
}
Static model:
#StaticMetamodel( Entity.class )
public class Entity_ {
public static volatile SingularAttribute<Entity, Long> id;
public static volatile SingularAttribute<Entity, String> name;
...
public static volatile SetAttribute<Entity, ChildEntity> objects;
}