My entity classes are following
#Entity
#table
public class User {
#OneToOne
private UserProfile userProfile;
// others
}
#Entity
#Table
public class UserProfile {
#OneToOne
private Country country;
}
#Entity
#Table
public class Country {
#OneToMany
private List<Region> regions;
}
Now I want to get all the user in a particular region. I know the sql but I want to do it by spring data jpa Specification. Following code should not work, because regions is a list and I am trying to match with a single value. How to fetch regions list and compare with single object?
public static Specification<User> userFilterByRegion(String region){
return new Specification<User>() {
#Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.equal(root.get("userProfile").get("country").get("regions").get("name"), regionalEntity);
}
};
}
Edit: Thanks for the help. Actually I am looking for the equivalent criteria query for the following JPQL
SELECT u FROM User u JOIN FETCH u.userProfile.country.regions ur WHERE ur.name=:<region_name>
Try this. This should work
criteriaBuilder.isMember(regionalEntity, root.get("userProfile").get("country").get("regions"))
You can define the condition for equality by overriding Equals method(also Hashcode) in Region class
Snippet from my code
// string constants make maintenance easier if they are mentioned in several lines
private static final String CONST_CLIENT = "client";
private static final String CONST_CLIENT_TYPE = "clientType";
private static final String CONST_ID = "id";
private static final String CONST_POST_OFFICE = "postOffice";
private static final String CONST_INDEX = "index";
...
#Override
public Predicate toPredicate(Root<Claim> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> predicates = new ArrayList<Predicate>();
// we get list of clients and compare client's type
predicates.add(cb.equal(root
.<Client>get(CONST_CLIENT)
.<ClientType>get(CONST_CLIENT_TYPE)
.<Long>get(CONST_ID), clientTypeId));
// Set<String> indexes = new HashSet<>();
predicates.add(root
.<PostOffice>get(CONST_POST_OFFICE)
.<String>get(CONST_INDEX).in(indexes));
// more predicates added
return return andTogether(predicates, cb);
}
private Predicate andTogether(List<Predicate> predicates, CriteriaBuilder cb) {
return cb.and(predicates.toArray(new Predicate[0]));
}
If you are sure, that you need only one predicate, usage of List may be an overkill.
I am trying to build a specifications to predicate subquery for the below query.
Select u.* from User u where u.login in (select ur.role_id from userRoles ur where ur.role_Id = roleId).
till now this is the part I have built
public static Specification<User> userRoleId(String roleId) {
return new Specification<User>() {
#Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
//return builder.equal(root.<LocalDate> get("scheduledDisbursementDate"), scheduledDisbursementDate);
Subquery<UserRole> subquery = query.subquery(UserRole.class);
Root<UserRole> subqueryRoot = subquery.from(UserRole.class);
subquery.select(subqueryRoot);
Predicate roleIdList = builder.equal(subqueryRoot.get("roleId"), roleId);
subquery.select(subqueryRoot).where(roleIdList);
return builder.exists(subquery);
}
};
}
Can you please help me out to link the subquery with main query.
Note: There are no joins defined in the Entity classes. evrything should be done through subquerys only
Found Answer
first predicate will join UserRole userId column with and User table login column.
Second predicate will filter the condition based on roleId.
public static Specification<User> userRoleId(String roleId) {
return new Specification<User>() {
#Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
Subquery<UserRole> subquery = query.subquery(UserRole.class);
Root<UserRole> subqueryRoot = subquery.from(UserRole.class);
subquery.select(subqueryRoot);
Predicate userIdPredicate = builder.equal(subqueryRoot.get("userId"), root.<String> get("login"));
Predicate rolePredicate = builder.equal(subqueryRoot.get("roleId"), roleId);
subquery.select(subqueryRoot).where(userIdPredicate, rolePredicate);
return builder.exists(subquery);
}
};
}
I got stuck using a Spring Project with Spring Data + specification + criteria api.
I will try to simulate the situation with general entities we used write to get easy example.
The Entities:
Consider all attributes of the each entity is passed on the constructor showed below
Country(Long id, String name, String iso)
State(Long id, String name, String iso)
City(Long id, String name, String iso)
This is my repository:
public interface CityRepository extends PagingAndSortingRepository<City, Integer>, JpaSpecificationExecutor<City> {
}
As you can see, I don't need to implement anything on the repository
This is my service
#Service
#Transactional
public class CityService {
#Autowired
private CityRepository cityRepository;
#Transactional(readOnly = true)
public CityListVO findByNameLike(String name, PageRequest pageRequest) {
name = "%" + name + "%";
if (pageRequest == null) {
List<City> result = cityRepository.findAll(fillGridCriteria(name));
return new CityListVO(1, result.size(), result);
} else {
Page<City> result = cityRepository. findAll(fillGridCriteria(name), pageRequest);
return new CityListVO(result.getTotalPages(), result.getTotalElements(), result.getContent());
}
}
private static Specification<City> fillGridCriteria(String name) {
return new Specification<City>() {
#Override
public Predicate toPredicate(
Root<City> root,
CriteriaQuery<?> query,
CriteriaBuilder builder) {
/*
The current return I can do a like by name, and it works fine.
My problem is if for any reason I need to do multiple joins like the folow jpql:
select ci FROM City ci, State st, Country co where ci.st = st AND st.co = co AND co.name = 'Canada';
How to do this from here ? Inside this method.
How is gonna be the return for this method ?
*/
return builder.like(root.get("name"), name.trim());
}
};
}
}
Let's assume you want all the cities that their country's name like name and you have a relational Model in which :
Country(Long id, String name, String iso)
State(Long id,Long country, String name, String iso)
City(Long id, Long state, String name, String iso)
Predicate:
private static Specification<City> fillGridCriteria(String name) {
return new Specification<City>() {
#Override
public Predicate toPredicate(
Root<City> root,
CriteriaQuery<?> query,
CriteriaBuilder builder) {
return
builder.like(root.get("state").get("country").get("name"), name.trim());
}
};
}
I am new to Hibernate, can anybody correct the HQL for below SQL to find out the unique employees that are working to the client currently. if possible please provide me some links for tutorial.
while parsing the SQL I was able to receive some records from the database & also with Native SQL, but when I use below HQL I could not receive any records.
SQL :
select distinct u.employeeid from employee u
inner join address ad on u.employeeid = ad.employeeid
left join company cp on (ad.oldcompanyid = cp.currentcompanyid and cp.wokingornot='Y')
left join client cl on (ad.oldcompanyid = cl.currentcompanyid and cl.wokingornot='Y')
where (u.lastdate is null and ad.lastdate is null)
and (cp.wokingornot = 'Y' or cl.wokingornot = 'Y');
please correct the HQL listed below :
select distinct u from employee u ,address uo,company tp ,client mp
inner join u.address uo
left join uo.company tp with tp.wokingornot= true
left join uo.client mp with mp.wokingornot= true
where (u.lastdate is null and uo.lastdate is null)
and (tp.wokingornot= true or mp.wokingornot= true );
Below are the entities which I am looking for the HQL:
public class employee {
public Int employeeid ;
public set<address> address;
public Date lastdate;
public Date Startdate;
}
public class address{
public int addressid;
public int oldcompanyid;
public employee employee;
public Date lastdate;
public Date Startdate;
}
public class company{
public int currentcompanyid;
public string wokingornot;
public address address;
}
public class client{
public int currentcompanyid;
public string wokingornot;
public address address;
}
It's going to look more like this. You will get the whole object graph from the Employee class through Address down to Company and Client classes.
select distinct u from employee u
join u.address uo
left join uo.company tp
left join uo.client mp
where u.lastdate is null and uo.lastdate is null
and (tp.wokingornot= true or mp.wokingornot= true )
I have a Spring Data repository method with a native query
#Query(value = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", nativeQuery = true)
GroupDetails getGroupDetails(#Param("userId") Integer userId, #Param("groupId") Integer groupId);
and I'd like to map the result to Non-Entity POJO GroupDetails.
Is it possible and if so, could you please provide an example ?
I think the easiest way to do that is to use so called projection. It can map query results to interfaces. Using SqlResultSetMapping is inconvienient and makes your code ugly :).
An example right from spring data JPA source code:
public interface UserRepository extends JpaRepository<User, Integer> {
#Query(value = "SELECT firstname, lastname FROM SD_User WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);
public static interface NameOnly {
String getFirstname();
String getLastname();
}
}
You can also use this method to get a list of projections.
Check out this spring data JPA docs entry for more info about projections.
Note 1:
Remember to have your User entity defined as normal - the fields from projected interface must match fields in this entity. Otherwise field mapping might be broken (getFirstname() might return value of last name et cetera).
Note 2:
If you use SELECT table.column ... notation always define aliases matching names from entity. For example this code won't work properly (projection will return nulls for each getter):
#Query(value = "SELECT user.firstname, user.lastname FROM SD_User user WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);
But this works fine:
#Query(value = "SELECT user.firstname AS firstname, user.lastname AS lastname FROM SD_User user WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);
In case of more complex queries I'd rather use JdbcTemplate with custom repository instead.
Assuming GroupDetails as in orid's answer have you tried JPA 2.1 #ConstructorResult?
#SqlResultSetMapping(
name="groupDetailsMapping",
classes={
#ConstructorResult(
targetClass=GroupDetails.class,
columns={
#ColumnResult(name="GROUP_ID"),
#ColumnResult(name="USER_ID")
}
)
}
)
#NamedNativeQuery(name="getGroupDetails", query="SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", resultSetMapping="groupDetailsMapping")
and use following in repository interface:
GroupDetails getGroupDetails(#Param("userId") Integer userId, #Param("groupId") Integer groupId);
According to Spring Data JPA documentation, spring will first try to find named query matching your method name - so by using #NamedNativeQuery, #SqlResultSetMapping and #ConstructorResult you should be able to achieve that behaviour
I think Michal's approach is better. But, there is one more way to get the result out of the native query.
#Query(value = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", nativeQuery = true)
String[][] getGroupDetails(#Param("userId") Integer userId, #Param("groupId") Integer groupId);
Now, you can convert this 2D string array into your desired entity.
You can write your native or non-native query the way you want, and you can wrap JPQL query results with instances of custom result classes.
Create a DTO with the same names of columns returned in query and create an all argument constructor with same sequence and names as returned by the query.
Then use following way to query the database.
#Query("SELECT NEW example.CountryAndCapital(c.name, c.capital.name) FROM Country AS c")
Create DTO:
package example;
public class CountryAndCapital {
public String countryName;
public String capitalName;
public CountryAndCapital(String countryName, String capitalName) {
this.countryName = countryName;
this.capitalName = capitalName;
}
}
This is my solution for converting to Map and then to custom Object
private ObjectMapper objectMapper;
public static List<Map<String, Object>> convertTuplesToMap(List<?> tuples) {
List<Map<String, Object>> result = new ArrayList<>();
tuples.forEach(object->{
if(object instanceof Tuple single) {
Map<String, Object> tempMap = new HashMap<>();
for (TupleElement<?> key : single.getElements()) {
tempMap.put(key.getAlias(), single.get(key));
}
result.add(tempMap);
}else{
throw new RuntimeException("Query should return instance of Tuple");
}
});
return result;
}
public <T> List<T> parseResult(List<?> list, Class<T> clz){
List<T> result = new ArrayList<>();
convertTuplesToMap(list).forEach(map->{
result.add(objectMapper.convertValue(map, clz));
});
return result;
}
public static class CustomDTO{
private String param1;
private Integer param2;
private OffsetDateTime param3;
}
public List<CustomDTO> doSomeQuery(){
Query query = entityManager.createNativeQuery("SELECT param1, param2 param3 ... ", Tuple.class);
return parseResult(query.getResultList(), CustomDTO.class);
}
Use the default method in the interface and get the EntityManager to get the opportunity to set the ResultTransformer, then you can return the pure POJO, like this:
final String sql = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = ? WHERE g.group_id = ?";
default GroupDetails getGroupDetails(Integer userId, Integer groupId) {
return BaseRepository.getInstance().uniqueResult(sql, GroupDetails.class, userId, groupId);
}
And the BaseRepository.java is like this:
#PersistenceContext
public EntityManager em;
public <T> T uniqueResult(String sql, Class<T> dto, Object... params) {
Session session = em.unwrap(Session.class);
NativeQuery q = session.createSQLQuery(sql);
if(params!=null){
for(int i=0,len=params.length;i<len;i++){
Object param=params[i];
q.setParameter(i+1, param);
}
}
q.setResultTransformer(Transformers.aliasToBean(dto));
return (T) q.uniqueResult();
}
This solution does not impact any other methods in repository interface file.
USE JPA PROJECTIONS
In your case it may be desirable to retrieve data as objects of customized types. These types reflect partial views of the root class, containing only properties we care about. This is where projections come in handy.
first declare Entity as #immutable
#Entity
#Immutable
public class Address {
#Id
private Long id;
set your Repository
public interface AddressView {
String getZipCode();
}
Then use it in a repository interface:
public interface AddressRepository extends Repository<Address, Long> {
#Query("EXEC SP_GETCODE ?1")
List<AddressView> getAddressByState(String state);
}
If you are looking for running a custom SQL query in spring boot with #repository and #service structures. Please have a look.
https://stackoverflow.com/a/71501509/4735043
You can do something like
#NamedQuery(name="IssueDescriptor.findByIssueDescriptorId" ,
query=" select new com.test.live.dto.IssuesDto (idc.id, dep.department, iss.issueName,
cat.issueCategory, idc.issueDescriptor, idc.description)
from Department dep
inner join dep.issues iss
inner join iss.category cat
inner join cat.issueDescriptor idc
where idc.id in(?1)")
And there must be Constructor like
public IssuesDto(long id, String department, String issueName, String issueCategory, String issueDescriptor,
String description) {
super();
this.id = id;
this.department = department;
this.issueName = issueName;
this.issueCategory = issueCategory;
this.issueDescriptor = issueDescriptor;
this.description = description;
}