I'm trying to integrate sorting with Pageable on joined fields with the use of #Query annotation from Spring Data.
1st interface's method (without #Query but with the Pageable) works like a charm. Same like when I'm fetching only one Employee with the #Query but instead of Pageable I'm using Optional<Employee> there (3rd method). But the fun begins when I try to put these two all together in one - it won't work anymore.
When I try to sort the data by name field it screams with this error:
Caused by: org.hibernate.QueryException: could not resolve property: name of: (....).model.employee.Employee
So the question is: how to tell spring to look for name in joined fields? How to do this with Spring Data?
I've already tried several things but they didn't work or I still don't know how to use them properly:
someone suggested to add countQuery to the #Query parameters so this corresponds somehow with the pagination (spring data jpa #query and pageable)
I've followed Baeldung's tutorial but this doesn't cover joins
Spring-Data FETCH JOIN with Paging is not working also suggested using countQuery but I'd prefer to stick to Page<Employee> rather than List<Employee>.
I'll leave some samples of the code below. Feel free to ask for update if I omitted something important.
// Employee
#Entity
#Table(name = "employee", schema = "emp")
#Data
#NoArgsConstructor
public class Employee {
private static final String SEQUENCE = "EMPLOYEE_SEQUENCE";
#Id
#SequenceGenerator(sequenceName = SEQUENCE, name = SEQUENCE, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
private Long id;
#Column(name = "employee_number")
private String employeeNumber;
#Column
#Enumerated(EnumType.STRING)
private EmployeeStatus status;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#JoinColumn(name = "id_details")
private Details details;
// some other fields ...
}
// Details
#Entity
#Table(name = "details", schema = "emp")
#Data
#NoArgsConstructor
public class Details {
private static final String SEQUENCE = "DETAILS_SEQUENCE";
#Id
#SequenceGenerator(sequenceName = SEQUENCE, name = SEQUENCE, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
private Long id;
private String name;
private String surname;
// some other fields ...
}
// EmployeeDTO
#NoArgsConstructor
#AllArgsConstructor
#Data
#Builder(toBuilder = true)
public class EmployeeDTO {
private Long id;
private String employeeNumber;
private String status;
private String name;
private String surname;
// some other fields ...
}
// EmployeeRepository
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
// 1st method
Page<Employee> findByStatus(EmployeeStatus status, Pageable pageable);
// 2nd method
#Query(value = "select e from Employee e join e.details where e.status = :status",
countQuery = "select count(*) from Employee e join e.details where e.status = :status")
Page<Employee> getEmployeeDetails(#Param("status") EmployeeStatus status, Pageable pageable);
// 3rd method
#Query("select e from Employee e join fetch e.details where e.id = :id")
Optional<Employee> findByIdWithDetails(Long id);
// ...
}
// EmployeeService
#Service
public class EmployeeService {
private final EmployeeRepository employeeRepository;
private final EntityDtoConverter entityDtoConverter;
#Autowired
public EmployeeService(EmployeeRepository employeeRepository, EntityDtoConverter entityDtoConverter) {
this.employeeRepository = employeeRepository;
this.entityDtoConverter = entityDtoConverter;
}
public EmployeeResponse getEmployeesByStatus(EmployeeStatus status, int pageSize, int pageIndex, Sort.Direction sortDirection, String sortColumn) {
Page<EmployeeDTO> employeePage = employeeRepository.findByStatus(status, PageRequest.of(pageIndex, pageSize, Sort.by(sortDirection, sortColumn)))
.map(entityDtoConverter::convertEmployeeBaseToDto);
return new EmployeeResponse(employeePage);
}
public EmployeeResponse getEmployeeDetails(EmployeeStatus status, int pageSize, int pageIndex, Sort.Direction sortDirection, String sortColumn) {
Page<EmployeeDTO> employeePage = employeeRepository.getEmployeeDetails(status, PageRequest.of(pageIndex, pageSize, Sort.by(sortDirection, sortColumn)))
.map(entityDtoConverter::convertToEmployeeWithDetailsDto);
return new EmployeeResponse(employeePage);
}
// ...
}
// EntityDtoConverter
#Component
public class EntityDtoConverter {
public EmployeeDTO convertEmployeeBaseToDto(Employee entity) {
return EmployeeDTO.builder()
.id(entity.getId())
.employeeNumber(entity.getEmployeeNumber())
.status(entity.getStatus())
.build();
}
public EmployeeDTO convertToEmployeeWithDetailsDto(Employee entity) {
return convertEmployeeBaseToDto(entity).toBuilder()
.name(entity.getDetails().getName())
.surname(entity.getDetails().getSurname())
.build();
}
// ...
}
EDIT:
This is one of the methods of my rest controller:
#GetMapping
public ResponseEntity<EmployeeResponse> getEmployeesByStatus(EmployeeStatus status, int pageSize, int pageIndex, String sortDirection, String sortColumn) {
try {
EmployeeResponse employeeResponse = employeeService.getEmployeesByStatus(status, pageSize, pageIndex, Sort.Direction.fromString(sortDirection), sortColumn);
return employeeResponse.getTotalElements().equals(0L) ? ResponseEntity.noContent().build() : ResponseEntity.ok(employeeResponse);
} catch (Exception e) {
log.error(ERROR_MESSAGE, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
Try below code.
Specification<Employee> joins = (employee, query, cb) -> {
Join<Employee, Detail> details = employee.join("details");
return cb.and(
employee.equal(employee.get("name", name)),
details.equal(details.get("name", detailName))
);
};
PageRequest pageRequest = new PageRequest(0, 2, new Sort(Sort.Direction.DESC, "name"));
Page<Employee> customerPage = employeeRepository.findAll(joins, pageRequest);
Here we are trying to inform JPA that this name is foreign key for Employee table.
Related
I am having trouble to converting the following postgresql query (with a join and a group by) to JPA criteria API for a Spring Boot, JPA, Hibernate application:
select u.id, u.full_name, count(*) project_applications_count from users u
join project_applications pa on pa.created_by = u.id
group by u.id, u.full_name
having count(*) >= 1 and count(*) <= 5
The tables look like this:
create table project_applications (
id serial primary key,
...
city_id integer not null references cities (id),
created_by integer not null references users (id)
);
create table users (
id serial primary key,
...
full_name varchar(100) not null
);
And the entities look like this:
#Entity
#Table(name = "project_applications")
public class ProjectApplication {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "created_by")
private User createdBy;
...
}
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "full_name")
private String fullName;
...
}
I tried searching online for a solution but every exemple I found was using either a join or group by, but not both.
Using #akortex's idea with projections, I think something like this should work:
public class UserSummary {
private Long id;
private String fullName;
private Long count;
public UserSummary() {
}
public UserSummary(Long id, String fullName, Long count) {
this.id = id;
this.fullName = fullName;
this.count = count;
}
... (getters and setters)
}
public List<UserSummary> getSummaries(Integer minProjectAppsCount, Integer maxProjectAppsCount) {
CriteriaBuilder cb = _entityManager.getCriteriaBuilder();
CriteriaQuery<UserSummary> query = cb.createQuery(UserSummary.class);
Root<ProjectApplication> projectApp = query.from(ProjectApplication.class);
Join<ProjectApplication, User> userJoin = projectApp.join("createdBy", JoinType.INNER);
query.multiselect(userJoin.get("id"), userJoin.get("fullName"), cb.count(projectApp))
.groupBy(userJoin.get("id"), userJoin.get("fullName"));
List<Predicate> predicates = new ArrayList<>();
if (minProjectAppsCount != null ) {
Predicate p = cb.ge(cb.count(projectApp), minProjectAppsCount);
predicates.add(p);
}
if (maxProjectAppsCount != null ) {
Predicate p = cb.le(cb.count(projectApp), maxProjectAppsCount);
predicates.add(p);
}
query.having(predicates.toArray(new Predicate[0]));
return _entityManager.createQuery(query).getResultList();
}
You could potentially look into projections in order to achieve what you want.
For example consider the following projection and repository:
#Data
#AllArgsConstructor
public class ProjectApplicationSummary {
private Long id;
private String fullName;
private Long count;
}
And:
#Repository
public interface ProjectApplicationRepository extends JpaRepository<ProjectApplication, Long> {
#Query(
"""
SELECT new com.example.springdemo.entities.ProjectApplicationSummary(u.id, u.fullName, count(pa))
FROM User u, ProjectApplication pa
GROUP BY u.id, u.fullName
"""
)
List<ProjectApplicationSummary> getSummaries();
}
You will most likely need to tweak the query a bit (which revolves experimenting with JPQL) but other than that, the basic idea is there.
I'm not sure in my solution, but it should be similar. I took an idea from here. Maybe it helps you to resolve your problem.
public static Specification<User> getUsers() {
return Specification.where((root, query, criteriaBuilder) -> {
CriteriaQuery<User> criteriaQuery = criteriaBuilder.createQuery(User.class);
Subquery<Long> subQuery = criteriaQuery.subquery(Long.class);
Root<ProjectApplication> subRoot = subQuery.from(ProjectApplication.class);
subQuery
.select(criteriaBuilder.count(subRoot))
.where(criteriaBuilder.equal(root.get("id"), subRoot.get("createdBy").get("id")));
query
.multiselect(criteriaBuilder.construct(root.get("id"), root.get("fullName")))
.groupBy(root.get("id"), root.get("fullName"))
.having(criteriaBuilder.and(
criteriaBuilder.greaterThanOrEqualTo(subQuery.getSelection(), 1L),
criteriaBuilder.lessThanOrEqualTo(subQuery.getSelection(), 5L)));
return query.getRestriction();
});
}
I am using PostgreSQL and java for building the backend of an application.
In PostgreSQL, I have used an enum here.
and in java I have used enum for layer variable also.
#Entity
#Getter
#Setter
#Table(name = "usecase_details")
#JsonIgnoreProperties
public class UsecaseDetails {
#Id
#Column(name="id", nullable = false)
#SequenceGenerator(name= "usecase_details_sequence", sequenceName
="usecase_details_id_sequence")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"usecase_details_sequence" )
private Long id;
#Column(columnDefinition = "layer_t", nullable = false)
#Enumerated(EnumType.STRING)
#Type(type = "com.apple.exampleportal.portal.utility.EnumTypePostgreSql")
private Layer layer;
private Long check_id;
private int usecase_id;
private String description;
private String fix_type;
private String radars;
private boolean show_usecase = true;
public UsecaseDetails(Long id, Layer layer, Long check_id, int usecase_id, String description, String fix_type, String radars, boolean show_usecase) {
this.id = id;
this.layer = layer;
this.check_id = check_id;
this.usecase_id = usecase_id;
this.description = description;
this.fix_type = fix_type;
this.radars = radars;
this.show_usecase = show_usecase;
}
// skipping getter setter for brevity
EnumTypePostgreSql. class is
public class EnumTypePostgreSql extends EnumType {
#Override
public void nullSafeSet(
PreparedStatement st,
Object value,
int index,
SharedSessionContractImplementor session)
throws HibernateException, SQLException {
st.setObject(
index,
value != null ?
((Enum) value).name() :
null,
Types.OTHER
);
}
}
and my custom queries are as follows:
public interface UsecaseRepository extends JpaRepository<UsecaseDetails, Long> {
#Query(value = "SELECT description, fix_type, usecase_id, layer FROM public.usecase_details WHERE layer = :layer", nativeQuery = true)
List<UsecaseDetails> findUsecaseByLayer(Layer layer);
#Query(value = "INSERT INTO public.usecase_details(description,fix_type, usecase_id, layer)\n"+
"\tVALUES (?,?,?,?)", nativeQuery = true)
UsecaseDetails insertDetails(UsecaseDetails usecaseDetails);
The insert query is working properly but for findUsecaseByLayer , I am getting the following error.
I am unable to detect my mistake. Please help!
There's probably something wrong with how you declare the type in EnumTypePostgreSql.
I also noticed this in your custom query: WHERE layer: = ?layer is the : wanted before your equal?
The solution I use:
Remove the #Type annotation from your entity column
#Column(columnDefinition = "layer_t", nullable = false)
#Enumerated(EnumType.STRING)
private Layer layer;
Then add to your JDBC URI:
?stringtype=unspecified
This will automatically cast the string to its enum type.
I have the following 3 Hibernate Entities within my Java Project:
CompanyStatus
#Entity(name = "company_status")
#Table(name = "company_status")
public class CompanyStatus implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#JsonProperty
#Column(name = "company_status_id")
private Integer companyStatusId;
#JsonProperty
#Column(name = "company_status_label")
private String companyStatusLabel;
}
Employee Status
#Entity(name = "employee_status")
#Table(name = "employee_status")
public class EmployeeStatus implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonProperty
#Column(name = "employee_status_id")
private Integer employeeStatusId;
#JsonProperty
#Column(name = "employee_status_name")
private String employeeStatusName;
// many other fields
}
CompanyStatusEmployeeStatus (Entity linking the 2 entities- one to one relationship)
#Entity(name = "company_status_employee_status")
#Table(name = "company_status_employee_status")
public class CompanyStatusEmployeeStatus implements Serializable {
// int(20)
#Id
#JsonProperty
#Column(name = "company_status_id")
private Integer companyStatusId;
// int(20)
#JsonProperty
#Column(name = "employee_status_id")
private Integer employeeStatusId;
}
I only want to return the necessary fields in my JSON response to the front end , so In order to do so I have created a smaller CompanyStatusDTO object that also has an EmployeeStatusDTO list nested
CompanyStatusDTO
public class CompanyStatusDTO {
#JsonProperty
private Integer companyStatusId;
#JsonProperty
private String companyStatusLabel;
#JsonProperty
private List <EmployeeStatusDTO> employeeStatusDTOs;
}
EmployeeStatusDTO
public class EmployeeStatusDTO {
#JsonProperty
private Integer employeeStatusId;
#JsonProperty
private String employeeStatusName;
}
However, I am relatively new to using Hibernate - is there a way that I can create a query that will map results directly from my MySQL DB to my CompanyStatusDTOobject?
If so, how can do I this?
you can directly map query result to you desired DTO using NativeQuery (datatype must match)
String q = "select ... from table"; // your sql query
Query query = getEntityManager().createNativeQuery(q, "EmployeeStatusDTO");
EmployeeStatusDTO data = (EmployeeStatusDTO) query.getSingleResult();
return data;
This is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
If you adapt the CompanyStatus and CompanyStatusEmployeeStatus entities a bit and add the following:
public class CompanyStatus implements Serializable {
//...
#OneToMany(mappedBy = "companyStatus")
private Set<CompanyStatusEmployeeStatus> employeeStatuses;
}
public class CompanyStatusEmployeeStatus implements Serializable {
//...
#JsonProperty
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "company_status_id", insertable = false, updatable = false)
private CompanyStatus companyStatus;
#JsonProperty
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "employee_status_id", insertable = false, updatable = false)
private EmployeeStatus employeeStatus;
}
Your model could look like the following:
#EntityView(CompanyStatus.class)
public interface CompanyStatusDTO {
#IdMapping
Integer getCompanyStatusId();
String getCompanyStatusLabel();
#Mapping("employeeStatuses.employeeStatus")
List<EmployeeStatusDTO> getEmployeeStatusDTOs();
}
#EntityView(EmployeeStatus.class)
public interface EmployeeStatusDTO {
#IdMapping
Integer getEmployeeStatusId();
String getEmployeeStatusName();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
CompanyStatusDTO c = entityViewManager.find(entityManager, CompanyStatusDTO.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
You can try this:
public class Dao{
private SessionFactory sessionFactory;
public Dao(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public <T> T save(final T o){
return (T) sessionFactory.getCurrentSession().save(o);
}
public void delete(final Object object){
sessionFactory.getCurrentSession().delete(object);
}
public <T> T get(final Class<T> type, final Long id){
return (T) sessionFactory.getCurrentSession().get(type, id);
}
public <T> List<T> getAll(final Class<T> type) {
final Session session = sessionFactory.getCurrentSession();
final Criteria crit = session.createCriteria(type);
return crit.list();
}
// and so on, you should get the idea
and you can then access like so in the service layer:
private Dao dao;
#Transactional(readOnly = true)
public List<MyEntity> getAll() {
return dao.getAll(MyEntity.class);
}
I am new to hibernate and trying to implement join annotations of hibernate, but facing this weird issue. As I have attached pojos when I run my controller no output is seen but a new row got populated in user_song_rel table as a foreign key to the song table.
In addition, value of that entry is null(in MySQL screenshot) despite there are entries in the song table corresponding to song_ids.
Please find attached schema of both tables.
Thanks in advance :)
Schema Structure
User Song Mapping
#NamedQueries({
#NamedQuery(
name="findUserSongByUserID",
query="from UserSongRel usr where usr.user_id = :user_id"
)
})
#Entity
#Table(name="user_song_rel")
public class UserSongRel {
public UserSongRel() {
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int usr_id;
private String user_id;
private String song_id;
private String song_src;
private int song_state;
#Temporal(TemporalType.TIMESTAMP)
private Date add_date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(referencedColumnName = "song_id")
private SongInfo songInfo;
Song
#Entity (name = "song_info")
public class SongInfo {
#Id
private String song_id;
private String s_name;
private String artist;
private String album;
private int duration;
private Date rel_date;
private int popularity;
#OneToMany(mappedBy = "songInfo")
private List<UserSongRel> userSongRelList=new ArrayList<UserSongRel>();
Controller
#RequestMapping(value = "/getUserSongs/{uid}", method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<?> getUserSongs(#PathVariable String uid,
HttpServletResponse response, HttpServletRequest request){
SongInfo usr= songService.getUserSongs(uid);
return ResponseEntity.ok().body(usr);
}
DAO
public SongInfo getUserSongs(String uid) {
Session s = sf.getCurrentSession();
UserSongRel songs=s.get(UserSongRel.class, 1);
List<UserSongRel> songList =new ArrayList<UserSongRel>();
songList.add(songs);
return songList.get(0).getSongInfo();
}
Select query after running controller.
(column and variable name changed after posting question)i am writing join query using entityManager.createNativeQuery(somequery) in jpa custom method when i run code i get following error :
com.ibm.db2.jcc.am.SqlException: [jcc][10150][10300][4.12.56] Invalid >parameter: Unknown column name exc_seq_nbr. ERRORCODE=-4460, SQLSTATE=null
i am using IBM DB2 server and spring boot
exceptionTenderPK (object in entity class) is not being mapped correctly thats why getting invalid column can someone please tell me how to map exceptionTenderPK object class
Note: i cant use #OneToMany in this case because tables are unrelated
#Entity
#Table(name = "Table_name")
#Data
public class MainPojoclass {
#EmbeddedId
#JsonProperty(value = "mainPojoclassPK")
private MainPojoclassPK mainPojoclassPK;
#Column(name = "amt")
#JsonProperty(value = "amt")
private BigDecimal amt;
#Column(name = "tndid")
#JsonProperty(value = "tndid")
private String tndid;
#Column(name = "cde")
#JsonProperty(value = "cde")
private String cde;
#Column(name = "ind")
#JsonProperty(value = "ind")
private String ind;
#Column(name = "user")
#JsonProperty(value = "user")
private String user;
#Column(name = "updatedtime")
#JsonProperty(value = "updatedtime")
private Date updatedtime;
#Column(name = "src")
#JsonProperty(value = "src")
private String src;
#Column(name = "stat")
#JsonProperty(value = "stat")
private String stat;
}
#Transactional
public interface JoinQueryRepository extends JpaRepository<MainPojoclass, Long>, JoinQueryRepositoryCustom{
}
public interface JoinQueryRepositoryCustom {
List<MainPojoclass> getGRDetails(MainPojoclass et,Date reportDate);
}
public class JoinQueryRepositoryImpl implements JoinQueryRepositoryCustom {
#PersistenceContext
EntityManager entityManager;
#SuppressWarnings("all")
#Override
public List<MainPojoclass> getGRDetails(MainPojoclass et,Date rdate) {
String queryStr = "select et.Salss_DTE from table et"
+ " join dte etr on et.Salss_DTE = etr.Salss_DTE where et.nbr =? ";
List<MainPojoclass> datalist = null;
Query query = entityManager.
createNativeQuery(queryStr,"mapping")
.setParameter(1, 222);
datalist = query.getResultList();
return datalist;
}
}
The error says that there is no column exc_seq_nbr and you used that in your EntityResult mapping.
In your query you only return et.SLS_DTE you have to return all columns that are in the result set mapping.
Hi all since i am not getting any solutions i am going with below solution it works for me and removing #SqlResultSetMapping below code is working without sql result set mapping
Query q = em.createNativeQuery(queryStr);
List<Object[]> resultList = q.getResultList();
for (Object[] result : resultList) {
entityObj.setReason(result[0].toString);
//rest attribute will convert from result[1].toString to corresponding
// data type and set to entity object
}