How to rewrite the query on the criteria
#Query(value = "select count(*), hicn.name \n" +
"from \n" +
" table1 cid, \n" +
" table2 hicn \n" +
"where TRUNC(cid.CREATED_WHEN) = TRUNC(?) \n" +
" and hicn.ID = cid.ID\n" +
"group by hicn.name", nativeQuery = true)
and put result in the DTO?
public class DataDto {
private String name;
private Long count;
public DataDto(String name, Long count) {
this.name = name;
this.count = count;
}
}
Entity for example.Entity large, reduced for convenience. Can you show the solution with 'join' and 'and'. The main problem is that I don't understand how to access two tables and get data from them using criteria.
For table 1
#Data
#Entity
#Table(name = "TABLE_ONE")
public class TableOneModel {
#Id
#Column(name = "TAB_ONE_ID")
private Long tabOneId;
#Column(name = "CREATED_WHEN")
private Date createdWhen;
}
For table 2
#Data
#Entity
#Table(name = "TABLE_TWO")
public class TableTwoModel {
#Id
#Column(name = "TAB_TWO_ID")
private Long tabTwoId;
#Column(name = "NAME")
private String name;
}
Criteria criteria = session.createCriteria(TableOneModel.class);
criteria.setFetchMode("TableTwoModel", FetchMode.JOIN).add(Restrictions.eq("trunc(cid.CREATED_WHEN)", "checkpoint"))
.setProjection(Projections.projectionList().add(Projections.property("name"), "name")
.add(Projections.rowCount(),"Count")
.add(Projections.groupProperty("name"))).uniqueResult();
List list = criteria.list();
Your target must be looking something near to this. I didnt test out code though. I was not sure about what to put on your sql trunc method, so I added checkpoint there. If thats a dynamic variable, please research yourself how you can add it to criteria. In fact that may depend more on your code as well.
Related
I would like to be able to include an #Entity from another table using a foreign key. I'm following guides but I'm still confused and can't seem to get this working. The end goal would be something like this:
#Entity
#Table(name = "Labor", schema = "dbo", catalog = "database")
public class LaborEntity {
private int laborId;
private Timestamp laborDate;
private Integer jobNumber;
private Integer customerId;
//mapping of customer to labor
private CustomerEntity customer;
#Id
#Column(name = "LaborID", nullable = false)
public int getLaborId() {
return laborId;
}
public void setLaborId(int laborId) {
this.laborId = laborId;
}
#Basic
#Column(name = "LaborDate", nullable = true)
public Timestamp getLaborDate() {
return laborDate;
}
public void setLaborDate(Timestamp laborDate) {
this.laborDate = laborDate;
}
#Basic
#Column(name = "JobNumber", nullable = true)
public Integer getJobNumber() {
return jobNumber;
}
public void setJobNumber(Integer jobNumber) {
this.jobNumber = jobNumber;
}
#Basic
#Column(name = "CustomerID", nullable = true)
public Integer getCustomerId() {
return customerId;
}
public void setCustomerId(Integer customerId) {
this.customerId = customerId;
}
#ManyToOne(targetEntity = CustomerEntity.class)
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumn(name = "CustomerID", //in this table
referencedColumnName = "CustomerID", //from CustomerEntity
insertable = false, updatable = false,
foreignKey = #javax.persistence.ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
public CustomerEntity getCustomer() {
return this.customer;
}
public void setCustomer(CustomerEntity customer) {
this.customer = customer;
}
Anyway, the end goal is to get the Customer data from the Customer table as part of the Labor entity so it can be accessed directly with something like getCustomerEntity(). I supposed I would have to accomplish it by querying first using a JOIN like so:
TypedQuery<LaborEntity> query = entityManager.createQuery(
"SELECT l FROM LaborEntity l " +
"INNER JOIN CustomerEntity c " +
"ON l.customerId = c.customerId " +
"WHERE l.laborDate = '" + date + "'", LaborEntity.class);
List<LaborEntity> resultList = query.getResultList();
And then I can simply access the Customer that's associated like so:
resultList.get(0).getCustomer().getCustomerName();
Am I dreaming or is this actually possible?
Yes, this is completely possible.
(I'm not sure about what was the question though - but assuming you only want get it working)
You query needs to be a JPQL, not an SQL. And the Join is different on JPQL:
"SELECT l FROM LaborEntity l " +
"JOIN l.customer c " +
"WHERE ... "
The join starts from the root entity and then you use the field name (not column).
You can also use JOIN FETCH, then the associated entity (customer) will be loaded in the same query. (that is, Fetch EAGER)
Other recommendations:
Don't concat the parameters like that date. Instead use setParameter.
You don't need those #Basic
You don't need that targetEntity = CustomerEntity.class. It'll be detected automatically.
I have a requirement of paging and sorting an entity.
#Entity
#Table(name = "CATEGORY", catalog = "")
public class CategoryEntity {
private CategoryEntity categoryByParentCategoryId;
private Set<CategoryEntity> categoriesByCategoryId;
#ManyToOne(fetch = FetchType.LAZY,optional = false, cascade = CascadeType.PERSIST)
#JoinColumn(name = "PARENT_CATEGORY_ID", referencedColumnName = "CATEGORY_ID")
public CategoryEntity getCategoryByParentCategoryId() {
return categoryByParentCategoryId;
}
public void setCategoryByParentCategoryId(CategoryEntity categoryByParentCategoryId) {
this.categoryByParentCategoryId = categoryByParentCategoryId;
}
#OneToMany(mappedBy = "categoryByParentCategoryId", cascade = CascadeType.PERSIST)
public Set<CategoryEntity> getCategoriesByCategoryId() {
return categoriesByCategoryId;
}
public void setCategoriesByCategoryId(Set<CategoryEntity> categoriesByCategoryId) {
this.categoriesByCategoryId = categoriesByCategoryId;
}
From this link and other stack overflow answers, I found out that I can use sorting and paging using Paging Request like
Pageable size = new PageRequest(page, paginationDTO.getSize(),Sort.Direction.ASC, "id");
My problem is I have a self join parent and child relationship as shown above in the model and I need to sort the parent based on the count of child as shown below.
Here the Number of SubCategories is the size of categoriesByCategoryId. What do I need to pass in the PageRequest in the place of id to sort on basis of size of list of child.
PS. The model has more fields but for the question to be short I posted only the relevant fields
After going through this answer I was able to achieve the requirement by using a custom query, the method in JPARepository looked like
#Query(
value = "select c from CategoryEntity c " +
" WHERE LOWER(c.categoryNameEn) LIKE LOWER(CONCAT('%',?2, '%')) AND activeInd = ?1 " +
"AND c.categoryByParentCategoryId is null" +
" Order By c.categoriesByCategoryId.size desc",
countQuery = "select count(c) from CategoryEntity c " +
" WHERE LOWER(c.categoryNameEn) LIKE LOWER(CONCAT('%',?2, '%')) AND activeInd = ?1" +
" AND c.categoryByParentCategoryId is null"
)
Page<CategoryEntity> findAllActiveCategoriesByCategoriesByCategoryIdCountDesc(String activeInd, String categoryNameEn, Pageable pageable);
The Count query is needed for pagination details.
I have an entity like the following were I use #Formula to populate clientId from other tables.
#Entity
public class Failure {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public int id;
public String name;
#ManyToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH} )
public PVPlant pvPlant;
#Formula("(SELECT cl.id from failure f " +
"INNER JOIN pvplant p ON f.pv_plant_id = p.id " +
"INNER JOIN company co ON p.company_id = co.id "+
"INNER JOIN client cl ON co.client_id = cl.id "+
"WHERE f.id = id) ")
public Integer clientId;
}
while CrudRepository<Failure,Integer> JPA method getByClientId works fine I am trying to make something more dynamic for filtering using a Map of keys and values with Specification and CriteriaBuilder.
public MySpecifications {
public static Specification<Failure> equalToEachColumn(HashMap<String,Object> map) {
return new Specification<Failure>() {
#Override
public Predicate toPredicate(Root<Failure> root, CriteriaQuery<?> cq, CriteriaBuilder builder) {
return builder.and(root.getModel().getAttributes().stream().map(a ->
{
if (map.containsKey(a.getName())) {
Object val = map.get(a.getName());
return builder.equal(root.<Integer>get(a.getName()), Integer.parseInt(val.toString()));
}
return builder.disjunction();
}
).toArray(Predicate[]::new)
);
}
};
}
}
When I am passing id in the HashMap it works fine but when I have clientId it doesn't send anything back. It is interesting that getAttributes() actually returns clientId but it seems that builder.equal(root.<Integer>get(a.getName()), Integer.parseInt(val.toString())) is false and not true
This is how I am using the Specification:
failureRepository.findAll(Specifications.where(MySpecifications.equalToEachColumn(map)));
Am I missing something?
Thanks in advance!
I wouldn't expect this to work however you could make it work by using a database view as an alternative to #Formula and mapping the entity across the table and view using #SecondaryTable.
//failures_client_vw is a 2 column db view: failure_id, client_id
#Table(name = "failures")
#SecondaryTable(name = "failures_client_vw",
pkJoinColumns = #PrimaryKeyJoinColumn(name = "failure_id"))
#Entity
public class Failure {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public int id;
public String name;
#ManyToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH} )
public PVPlant pvPlant;
#Column(name = "client_id", table = "failures_client_vw")
public Integer clientId;
}
You can then query clientId as you would any other property.
https://en.wikibooks.org/wiki/Java_Persistence/Tables#Multiple_tables
The actual problem was that I was using
builder.disjunction()
in the and() which creates 0=1 false predicates.
When I replaced it with
builder.conjunction() (which creates 1=1 true predicates)
in my code it worked fine. So #Formula properties behave as native ones to the table and it seems there is no need for SecondaryTable and a new View. Apparently in my earlier tests I used an entity that had just an id in its class and when I added clientId it misled me to believe that #Formula properties don't work, while it was the disjunction from the id that broke clientId
I use Spring Data JPA and have two entities:
#Entity
#Data
#Table(name="vehicle_entity")
public class Vehicle {
#Id
#GeneratedValue
private Integer id;
private String type;
private String vehicleRegNumber;
#OneToMany(targetEntity = com.transport.model.BookedTime.class, fetch=FetchType.EAGER)
#JoinColumn(name="bookingTime", referencedColumnName="id")
Set<BookingTime> bookingTime;
}
and
#Entity
#Data
#Table(name="booked_time")
public class BookedTime {
#Id
#GeneratedValue
Integer id;
Long startPeriod;
Long finishPeriod;
}
And repository:
public interface VehicleRepository extends JpaRepository<Vehicle, Integer> {
#Query("correct query")
List<Vehicle> findAllByPeriod(#Param("startPeriod") int startPeriod, #Param("endPeriod") int endPeriod);
}
I need to find available vehicles, which not booked by time. How can I do this?
I would go for something like:
#Query(nativeQuery=true, value = "select * from vehicle_entity v join booked_time b on b.vehicle = v.id where not (b.startPeriod > :startPeriod and b.endPeriod < :endPeriod)"
BTW I think that you might wanna try to change FetchType from EAGER to LAZY for bookingTime and then use join fetch in select query.
So I found several solutions of my issue. The first one is offered by Enigo and the second one:
#Query(nativeQuery = false, value = "select v from Vehicle v right join fetch v.bookedTime b \n" +
"Where b.vehicle = v.id " +
"AND (b.startPeriod < :startPeriod OR b.startPeriod > :finishPeriod) " +
"AND (b.finishPeriod < :startPeriod OR b.finishPeriod > :finishPeriod)")
Set<Vehicle> findAllByPeriod(#Param("startPeriod") Long startPeriod, #Param("finishPeriod") Long finishPeriod);
I have two entities, Users and Annotations, and I want to add a set of results from a query on the Annotations table to a transient variable in the Users entity.
Entities:
Users
#Entity
public class Users {
#Id
#GeneratedValue
#Column(name="user_id")
private Long userId;
#Column(name="username", unique=true, nullable=false)
private String username;
#Transient
private Set<Annotation> annotations;
.....
Annotations
#Entity
#Table(name="Annotation")
public class Annotation {
#Id
#GeneratedValue
#Column(name="anno_id")
private Long annoId;
#Column(name="user_id", nullable=false)
private Long userId;
#Enumerated(EnumType.STRING)
#Column(name="access_control", nullable=false)
private Access accessControl;
#Column(name="group_id", nullable=true)
private Long groupId;
.....
So I want the Set<Annotation> annotations variable to hold results from a query on the Annotations table. This can't simply be a one-to-many mapping, because I have to limit the results in a specific way. In fact, the query is this:
SELECT anno_id, a.user_id, timestamp, is_redacted, access_control, a.group_id, vocabulary_id, key_, value, target_type, target_id, root_type, root_id FROM Annotation AS a
LEFT JOIN group_membership g ON g.user_id = ?#{ principal?.getId() }
WHERE a.user_id = :id
AND (a.access_control='PUBLIC'
OR (a.access_control='GROUP' AND a.group_id = g.group_id
OR (a.access_control='PRIVATE' AND g.user_id = a.user_id))
GROUP BY a.anno_id
I think this is possible through SQLResultSetMapping, however, it seems as though the results are always mapped to another, distinct entity. Is it possible to extract the set as collection and store it in the way I want?
You cannot use SQLResultSetMapping in this scenario as the results will only be mapped to be distinct entity. What you can do is execute as native query and then get the result as a list of object array. You can then construct the desired object that you need.
Query query = entityManager
.createNativeQuery("SELECT anno_id, a.user_id FROM Annotation AS a"
+ " LEFT JOIN group_membership g ON g.user_id = ?"
+ " WHERE a.user_id = ?"
+ " AND (a.access_control='PUBLIC'"
+ " OR (a.access_control='GROUP' AND a.group_id = g.group_id)"
+ " OR (a.access_control='PRIVATE' AND g.user_id = a.user_id))"
+ " GROUP BY a.anno_id");
query.setParameter(1, new Long(1));
query.setParameter(2, new Long(1));
List<Object[]> list = query.getResultList();
return list;