Is it possible to use JpaRepository without entity? - java

Is it possible to use JpaRepository without entity? In this case, replacing it with a DTO.
as follows the example
#Repository
public interface BffRepository extends JpaRepository<BffDTO, String> {
#Query(nativeQuery = true, value = "select\n"
+ "ent.name as enterprise_name, dep.name as department_name,\n"
+ "sq.name as squad_name, acc.firstname as job_owner_name,\n"
+ "tpt.name as test_template_name, job.name, job.job_blocked, job.job_removed,\n"
+ "job.bot_scm_branch, job.bot_scm_url, job.schedule_startdate,\n"
+ "job.expiration_date, job.timestamp,job.uuid,job.schedule_starttime,\n"
+ "tpt.job_execution_timeout\n"
+ "from portal.jobs job\n"
+ "left join portal.enterprises ent on (ent.uuid = job.enterprise_id)\n"
+ "left join portal.departments dep on (dep.uuid = job.department_id)\n"
+ "left join portal.squads sq on (sq.uuid = job.squad_id)\n"
+ "left join portal.accounts acc on (acc.uuid = job.job_owner)\n"
+ "left join portal.test_plan_templates tpt on (tpt.uuid = job.template_id) where\n"
+ "job.job_owner = ?1 and job.job_removed = false order by timestamp desc;")
List<BffDTO>buscarPorJobOwner(String jobOwner);
Are there alternatives for this case?
NOTE: the DTO is already mapped, but I would not like to create a view to transform this DTO into an Entity.
I already validated this topic, but without major advances
Use JpaRepository interaction style without entity
i'm trying this
Interface -
public interface BffDTOInterface2 {
String uuid();
String enterprise_name();
String department_name();
String squad_name();
String job_owner_name();
String test_template_name();
String name();
Boolean job_blocked();
Boolean job_removed();
String bot_scm_branch();
String bot_scm_url();
String schedule_startdate();
String expiration_date();
String timestamp();
String schedule_starttime();
Integer job_execution_timeout();
#Transient
String status();
}
I'm having this error
Caused by: java.lang.IllegalArgumentException: Not a managed type: interface br.com.cloud.api.domain.dto.BffDTOInterface2

You can use Projections based on interfaces.
e.g
Create your native-query givin it column a alias. select name as fullName, age as age from person.
Create a Interface that represents your DTO with get-methods to every alias of your native query.
interface MyDTO {
String getFullName();
Integer getAge();
}
The return type of your query now can be this MyDTO
#Query(value = "select name as fullName, age as age from person", nativeQuery=true)
List<MyDTO> findMyDTO();

Is it possible to use JpaRepository without entity?
No, it is not, and it would completely defeat the purpose of JPA, by definition.
JPA is the persistence specification that enables ORM - Object Relational Mapping - that is, you map Java objects to database tables' entries/rows, and Java types to database tables, respectively.
DTO (Data Transfer Object) has nothing to do with ORM, and it serves different purpose (I recommend you to read this article for DTO vs. Entity matter) - transferring data through Java objects - and it usually serves the middle layer, for converting persistent objects(#Entitys) into objects to be used in the web layer (DTOs), and vice versa.
If you really want to avoid persistence layer models (#Entitys), you may go for JDBC abstractions (e.g. Spring Data JDBC), native queries, JPQL, HQL, or a bare JDBC API (which I wouldn't recommend).

but you can try this.
What you can do is you can create your own custom repository class. First, you would have some service class that calls repository class. also notice that we have custom models for the result set of SQL queries.
#Service
public class CustomService {
#Autowired
private CustomRepository repository;
public List<CustomResponse> getAllResult(String id) {
List<Object[]> items = repository.getAllResult(id);
List<CustomResponse> customResponseList = new ArrayList();
for (Object[] item: items) {
CustomResponse c = new CustomResponse();
c.setTestValue1(String.valueOf(item[0]));
c.setTestValue2(String.valueOf(item[1]));
customResponseList.add(c);
}
return customResponseList;
}
}
and your repository class will be look like this.
#Repository
public class CustomRepository {
#Autowired
private EntityManager entityManager;
public List<Object[]> getAllResult(String id) {
Query q = (Query) entityManager.createNativeQuery("SELECT\n" +
" users.user_id as user_id,\n" +
" users.email as user_email\n" +
" FROM Users\n" +
" WHERE users.parent_id = :parent_id;");
q.setParameter("parent_id", id);
List<Object[]> results = q.getResultList();
return results;
}
}
Also you might want to have your own model for that. (like entities)
public class CustomResponse {
private String testValue1;
private String testValue2;
public String getTestValue1() {
return testValue1;
}
public void setTestValue1(String testValue1) {
this.testValue1 = testValue1;
}
public String getTestValue2() {
return testValue2;
}
public void setTestValue2(String testValue2) {
this.testValue2 = testValue2;
}
}

This is possible.
Define base entity and have one column. If you dont want this to exist in database, turn off ddl-auto in application.propeties.
spring.jpa.hibernate.ddl-auto=none
#Entity
#Data
public class BaseEntity {
#Id
private Long id;
}
and use any custom query with any other dao extending jpa repository with BaseEntity.
public interface EmployeeDao extends JpaRepository<BaseEntity, Long> {
#Query(value = "select name from employee where employee_number = ?", nativeQuery = true)
Optional<Employee> get(String employeeNumber);
}
public interface Employee{
String getName();
}

Related

How to convert Optional<Entity> to Optional<EntityDTO> in Spring JPA?

I am new in Spring and although I can convert domain entities as List<Entity>, I cannot convert them properly for the the Optional<Entity>. I have the following methods in repository and service:
EmployeeRepository:
#Query(value = "SELECT ...")
Optional<Employee> findByUuid(#Param(value = "uuid") final UUID uuid);
EmployeeService:
#Override
#LogExecution
#Transactional(readOnly = true)
public Optional<EmployeeDTO> findByUuid(UUID uuid) {
Optional<Employee> employee = employeeRepository.findByUuid(uuid);
return employee
.stream()
.map(EmployeeDTO::new)
// .orElse(null);
//.findFirst(); /// ???
}
My questions:
1. How should I convert Optional<Employee> to Optional<EmployeeDTO> properly?
2. Does Spring JPA collect the fields in the SELECT clause and map them in the service method to the corresponding DTO by matching their names? If so, does it maintain the naming e.g. employee_name to employeeName in database table and domain model class?
The mapping that happens between the output of employeeRepository#findByUuid that is Optional<Employee> and the method output type Optional<EmployeeDTO> is 1:1, so no Stream (calling stream()) here is involved.
All you need is to map properly the fields of Employee into EmployeeDTO. Handling the case the Optional returned from the employeeRepository#findByUuid is actually empty could be left on the subsequent chains of the optional. There is no need for orElse or findFirst calls.
Assuming the following classes both with all-args constructor and getters:
class Employee {
private final long id;
private final String firstName;
private final String lastName;
}
class EmployeeDTO {
private final long id;
private final String name;
private final String surname;
}
... you can perform this. Nothing else than finding a way to create EmployeeDTO from Employee's fields is needed. If the Optional returned from the employeeRepository is returned, no mapping happens and an empty Optional is returned.
#Override
#LogExecution
#Transactional(readOnly = true)
public Optional<EmployeeDTO> findByUuid(UUID uuid) {
return employeeRepository
.findByUuid(uuid) // Optional<Employee>
.map(emp -> new EmployeeDTO( // Optional<EmployeeDTO>
emp.getId(), // .. id -> id
emp.getFirstName(), // .. firstName -> name
emp.getLastName())); // .. lastName -> surname
}
Note: For Employee -> EmployeeDTO mapping I recommend picking one of these:
Create a constructor accepting Employee in EmployeeDTO allowing to map with .map(EmployeeDTO::new) (drawback: creates a dependency).
Just map with getters/setters.
Use a mapping framework such as MapStruct or any other.
There are multiple options to map your entity to a DTO.
Using projections: Your repository can directly return a DTO by using projections. This might be the best option if you don't need the entity at all. You can find everything about projections here https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
Using a library like mapstruct or modelmapper to generate your mapping code
Add a constructor or static factory method to your DTO. Something like
class EmployeeDTO {
// fields here ...
public static EmployeeDTO ofEntity(Employee entity) {
var dto = new EmployeeDTO();
// set fields
return dto;
}
}
And call employee.map(EmployeeDTO::ofEntity) in your service.

Getting a list of results by using CRUD Repository

I am new to using CRUD Repository.
I have a database table with three columns:
course_id, name, course
I want to get a list of course_id give name, example,
SELECT id FROM table WHERE name='charmaine';
However, I do not want to do it with query but using crud repository.
There is an error shown in my controller.
May I know there is this error?
My controller
#GetMapping(value = "getting/{name}")
//#ResponseBody
public String getting(#PathVariable("name") String name) {
List<CourseMaster> getIds = CourseService.findIdByName(Name); —> error icon here
return getIds; —> error icon here
}
Service
public List<CourseMaster> findIdByName(String Name) {
return CourseMasterRepo.findByName(Name);
}
Repository
public interface CourseMasterRepo extends CrudRepository<CourseMaster, Long> {
List<CourseMaster> findByName(String Name);
}
You have to autowired service class in your controller like.
#Autowired
CourseService courseService;
#GetMapping(value = "getting/{name}")
public String getting(#PathVariable("name") String name) {
List<CourseMaster> getIds = courseService.findIdByName(Name);
return getIds;
}
if your code is done in java spring you must use hql language in hibernate, that is a interface of sql query.
hql query that use lambda expression is very simple and useful.
For example,
String hql = "FROM Employee E WHERE E.id = 10";
Query query = session.createQuery(hql);
List results = query.list();

Hibernate Filter is not applied for FindOne CRUD operation

I do have this hibernate filter in my repository:
#Entity
#Audited
#DiscriminatorValue(value = "GROUP")
#FilterDef(name = "groupACL", parameters = #ParamDef(name = "userId", type = "long"))
#Filters({
#Filter(name = "groupACL", condition = "app_group_id IN (SELECT DISTINCT APP_GROUP_ID FROM APP_GROUP START WITH APP_GROUP_ID IN (SELECT UG.APP_GROUP_ID FROM USER_GROUP UG JOIN APP_USER AU ON AU.APP_USER_ID = UG.APP_USER_ID WHERE USER_ID=:userId) CONNECT BY PARENT_APP_GROUP_ID = PRIOR APP_GROUP_ID)", deduceAliasInjectionPoints = false) })
public class Group extends AbstractGroup {
It is triggered using Spring AOP with the following class:
#Component
#Aspect
public class ACLFilterAspect {
private static final String GROUP_ACL = "groupACL";
#Autowired
private EntityManager em;
#Before("execution(* com.trendshift.kyn.pug.data.GroupRepository.*(..))")
public void forceFilter() {
Session hibernateSession = em.unwrap(Session.class);
....
hibernateSession.enableFilter(GROUP_ACL).setParameter("userId", userId);
}
}
}
I finally have the following service:
#Service
#Transactional(propagation = Propagation.REQUIRED)
public class GroupServiceImpl implements GroupService {
#Autowired
GroupRepository groupRepository;
#Override
public Group findGroupById(long id) {
Group group = groupRepository.findById(id);
return group;
}
}
and these repositories:
#RepositoryRestResource(exported = false)
public interface AbstractGroupRepository<T extends AbstractGroup>
extends JpaRepository<T, Long>, QueryDslPredicateExecutor<T> {
List<T> findByNameIgnoreCase(String name);
List<T> findByNameAndTypeOrderByNameAsc(String name, String type);
List<T> findByIdOrderByNameAsc(Long id);
AbstractGroup findById(Long id);
}
public interface GroupRepository
extends AbstractGroupRepository<Group>, GroupRepositoryExtras {
List<Group> findByNameAndCustomerId(String name, Long customerId);
Iterable<Group> findByCustomerIdAndTypeIn(Long id, List<String> types);
Group findById(long id);
}
The issue is that when I use groupRepository.findById(id) the filter is correctly applied.
If I use a CRUD core query groupRepository.findOne(id) the filter is not applied even after processing the Aspect hibernateSession.enableFilter(GROUP_ACL).setParameter("userId", userId);
Even if Java enables the filter, the log file doesn't show any trace of the filter in the hibernate query.
The problem seem to be only with the .findOne. findAll is working fine.
Is there something in the Hibernate doc that says that you cannot applied a filter to findOne methods?
I used filters to restrict user access to some information based on entity attributes. This was why I wanted even the findOne to respect the filters.
This was the prettiest way that I found to solve this "problem".
ClassPool pool = ClassPool.getDefault();
try {
CtClass cl = pool.get("org.hibernate.loader.plan.exec.internal.EntityLoadQueryDetails");
CtMethod me = cl.getDeclaredMethod("applyRootReturnFilterRestrictions");
String s = "{final org.hibernate.persister.entity.Queryable rootQueryable = (org.hibernate.persister.entity.Queryable) getRootEntityReturn().getEntityPersister();" +
"$1.appendRestrictions(" +
"rootQueryable.filterFragment(" +
"entityReferenceAliases.getTableAlias()," +
"getQueryBuildingParameters().getQueryInfluencers().getEnabledFilters()" +
"));}";
me.setBody(s);
cl.toClass();
} catch (Exception e) {}
To answer the actual Question:
Is there something in the Hibernate doc that says that you cannot applied a filter to findOne methods?
Yes, there is. From the Hibernate docs
Filters apply to entity queries, but not to direct fetching.
Therefore, in the following example, the filter is not taken into consideration when fetching an entity from the Persistence Context.
entityManager
.unwrap( Session.class )
.enableFilter( "activeAccount" )
.setParameter( "active", true);
Account account = entityManager.find( Account.class, 2L );
assertFalse( account.isActive() );
The implementation of for e.g SimpleJpaRepository.java in Spring uses em.find under the hood. Therefore the request is not filtered.
BUT if you override the implementation somehow (e.g. by using a projection or by writing a custom query), so that a query is generated, the request will be filtered.
This behaviour can be pretty confusing.
I ended up listening for any access to the CRUDRepository class. Not sure if that's the best way but that solves my issue.
#Component
#Aspect
public class ACLFilterAspect {
private static final String GROUP_ACL = "groupACL";
#Autowired
private EntityManager em;
#Before("||execution(* *(..)) && this(com.trendshift.kyn.pug.data.GroupRepository)"
+ "||execution(* *(..)) && this(org.springframework.data.repository.CrudRepository)")
Just override findById and use getById instead
#Repository
public interface CustomerRepository extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer>, SupportedRepositoryOperation<Customer> {
default Optional<Customer> findById(Long aLong) {
throw new OperationFindByIdNotAllowedException();
}
Optional<Customer> getById(Long id);
}

#NamedNativeQuery - How can I bind it to a repository method?

I am using Spring + Hibernate and I have a particular case where I need to obtain (a list of) non-Entity objects as a result of the query.
I decided to use #ConstructorResult in #SqlResultSetMapping and refer to this mapping in #NamedNativeQuery, as mentioned here and here.
However, in all examples using named native queries, they obtain EntityManager instance via #PersistenceContext and call createNativeQuery on it, providing the name of #NamedNativeQuery as parameter to that call, as seen in this answer.
How can I map a method declared in a repository interface to a particular #NamedNativeQuery? My attempt was to use EntityName.MethodNameInRepository or MethodNameInRepository as the name of #NamedNativeQuery, but no luck.
Here is my simplified code:
#Entity(name = "AdDailyData")
#SqlResultSetMapping(
name="RevenueByAppAndDayMapping",
classes=#ConstructorResult(
targetClass=RevenueByAppAndDay.class,
columns={#ColumnResult(name="country_code"),
#ColumnResult(name="revenue", type=Double.class),
#ColumnResult(name="currency")}))
#NamedNativeQuery(
name="AdDailyData.aggregateRevenue",
query="SELECT country_code, sum(earnings) as revenue, currency "
+ "FROM ad_daily_data, pseudo_app, app "
+ "WHERE ad_daily_data.pseudo_app_id=pseudo_app.id AND pseudo_app.app_id=app.id AND app.id=:appId and ad_daily_data.day = :day "
+ "GROUP BY country_code, currency "
+ "ORDER BY country_code ASC",
resultSetMapping="RevenueByAppAndDayMapping")
public class AdDailyDataEntity {
// fields, getters, setters etc.
public static interface Repository extends JpaRepository<AdDailyDataEntity, Long> {
public List<RevenueByAppAndDay> aggregateRevenue(#Param("appId") long appId, #Param("day") LocalDate day);
}
}
Here is my non-Entity class.
public class RevenueByAppAndDay {
private String countryCode;
private Double earnings;
private String currency;
public RevenueByAppAndDay(String countryCode, Double earnings, String currency) {
this.countryCode = countryCode;
this.earnings = earnings;
this.currency = currency;
}
public String getCountryCode() {
return countryCode;
}
public Double getEarnings() {
return earnings;
}
public String getCurrency() {
return currency;
}
}
Any kind of help is highly appreciated.
EDIT:
The end of the stack trace is as follows:
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property aggregateRevenue found for type AdDailyDataEntity!
The name value on the #NamedNativeQuery needs to be set to "AdDailyDataEntity.aggregateRevenue". The first part (before the dot) needs to match the entity class name.
Although the answer is correct, for someone to arrived here for similar cuestions this works for me without used #NamedNativeQuery:
public class Example {
private Long someProperty;
// getters and setters
}
To call a query or stored procedure and populate my object:
#Repository
public interface ExampleRepository extends
JpaRepository<Example,Long> {
/**
* Search Example by someProperty
*
* #param property
* #return
*/
#Query( nativeQuery = true, value = "SELECT * FROM public.my_stored_procedure(:property)")
List<Example> findByProperty(#Param("property") Long property);
}
I know this is another approach and the code is decoupled, but we get the same result.
I hope that can be useful for someone.
Regards.

JPA enum query ERROR on playframework

I am working on project using java playframework 1.2.4 and I have a #Entity class. It is look like
#Entity
public class EmployeeType extends Model {
public static enum TYPE { HOURLY, DAILY, MONTHLY };
public static enum NATIONALITY { LOCAL, FOREIGN };
#Required
#Enumerated(EnumType.STRING)
public TYPE type;
#Required
#Enumerated(EnumType.STRING)
public NATIONALITY nationality;
}
And in my controller class I want to get list of EmployeeTypes using my 2 enum attributes.
Query looks like
Query query = JPA.em().createQuery("SELECT e FROM EmployeeType e where " +
"e.nationality = :nationality " +
"and e.type = :type");
query.setParameter("nationality", NATIONALITY.LOCAL);
query.setParameter("type", TYPE.HOURLY);
List<models.EmployeeType> employeeType = query.getResultList()
Gives this error: IllegalArgumentException occured : Parameter value [LOCAL] was not matching type [models.EmployeeType$NATIONALITY]
What should i do?
The error is possibly because of the fact that your enums are nested in your entity. You need to access it on entity name.
You can change your setParameter code to: -
query.setParameter("nationality", EmployeeType.NATIONALITY.LOCAL);
query.setParameter("type", EmployeeType.TYPE.HOURLY);

Categories

Resources