How to Map Result Set of Native Query to Variable in Entity - java

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;

Related

I'm receiving one object using findAllBy in springBoot

I'm trying to fetch all rows that have the same patient_id, so I'm doing findAllByPatientId. But I'm always receiving one object in the Listinstead of all the rows.
#Entity
#Getter
#Setter
public class MedicalHistory extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name = "operator_id")
private MedicalOperator medicalOperatorId;
#ManyToOne
#JoinColumn(name = "illness_id")
private Illness illnessId;
#ManyToOne
#JoinColumn(name= "patientId")
private Patient patientId;
}
public List<MedicalHistory> getPatientMedicalRecords(PatientDto patientDto) {
Optional<Patient> getPatient = patientRepository.findByNin(patientDto.getNin());
Long patientId = getPatient.get().getPatientId();
return medicalHistoryRepository.findAllByPatientId(patientId);
}
I want to receive multiple rows using the patient_id but instead, I'm always getting one !!.
I tried native query and hibernate but nothing is working.
public interface MedicalHistoryRepository extends JpaRepository<MedicalHistory, Long> {
// List<MedicalHistory> findAllByPatientId(Long id);
ArrayList<MedicalHistory> findMedicalHistoriesByPatientId(Long id);
#Query(value = "SELECT * FROM medical_history WHERE patient_id = id",nativeQuery = true)
List<MedicalHistory> findAllByPatientId(Long id);
}
Now you are requesting "give me medical_history where id = patient_id" and getting only one result row.
You need to add a colon to the query to set a parameter to fix a result
value = "SELECT * FROM medical_history WHERE patient_id = :id"
Look for JPQL, it's java persistancy query language and spring is automatically turning your #query into it. Spring is also supporting spEL you can also have a look to it here : https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query.spel-expressions where you can see than you can grab your parameters ever with ?number or with :name or putting #Param("name") into your parameters definition. As said before there is multiple ways to receive a parameter in you query but certainly not like you previously did.
That's why you don't receive anything.

hibernate #Loader annotation on #oneToOne relation

I'm trying to implement a custom #loader using a namedQuery on a OneToOne - Relation of an entity.
However the lastDatalog field remains null at all given times
I've tested the named query befor on a simple integration test using a repositry, the result was exactly what I intend to have in the lastDestinationStatus
(I need the last updated record from the logs for this data and IREF combination)
when I query the Datalog entity with the id of the data I get the correct result so the Datalog entity seems to be persisted
maybe good to know : curent hibernate version on the project is 4.2.11.Final
this is en extract from entity 1
#Entity
#Table(name = "data")
#NamedQueries({
#NamedQuery(name = "LastLogQuery", query = "select log from DataLog log where log.data.id= ?1 and " +
"log.IREF = (select max(log2.IREF) from DataLog log2 where log2.data = log.data ) " +
"and log.tsUpdate = (select max(log3.tsUpdate) from DataLog log3 where log3.data = log.data and log3.IREF = log.IREF)")})
public class Data{
....
#OneToOne(targetEntity = DataLog.class)
#Loader(namedQuery = "LastLogQuery")
private DataLog lastDataLog;
}
extract from entity 2
#Entity
#Table(name ="log")
public class DataLog{
.......
#ManyToOne(fetch = FetchType.EAGER)
#org.hibernate.annotations.Fetch(value = org.hibernate.annotations.FetchMode.SELECT)
#JoinColumn(name = "DTA_IDN", nullable = false)
private Data data;
/** IREF */
#Column(name = "DSE_LOG_UID_FIL_REF_COD")
private String IREF;
#Column(name = "LST_UPD_TMS", nullable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date tsUpdate;
}

How to rewrite native query to JPA criteria

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.

Mapstruct - Send nested entity having (one-to-many relation) in the response

I have 2 entities CallRecords and CallRecordOperators with one-to-many relation as given below
public class CallRecords {
#Id
#Column(name = "id", unique = true)
private String id;
#Column(columnDefinition = "varchar(255) default ''")
private String callerNumber = "";
#OneToMany(mappedBy="callrecord")
private List<CallRecordOperators> callRecordOperators = new ArrayList<CallRecordOperators>();
//getter setters
}
public class CallRecordOperators {
#Id
#Column(name = "id", length = 50, unique = true, nullable = false, insertable = false, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "callRecordId")
private CallRecords callrecord;
#ManyToOne
#JoinColumn(name = "operatorId")
private Operator operator;
#Formats.DateTime(pattern = "yyyy-MM-dd HH:mm:yy")
#Column(columnDefinition = "TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP")
private Date startTime = new Date();
#Column(columnDefinition = "varchar(100) default ''")
private String dialStatus;
//getter setter
}
So if the user ask for all "CallRecords" data I also have to give "CallRecordOperators" as they are related.
Current code for Mapper and DTOs
#Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface CallRecordsMapper {
CallRecordsMapper INSTANCE = Mappers.getMapper(CallRecordsMapper.class);
#Mapping(source="callRecordOperators",target = "operators")
CallRecordsDto callRecordsToCallRecordsDto(CallRecords callRecords);
public abstract CallRecordOperatorsDto toTarget(CallRecordOperators source);
List<CallRecordsDto> callRecordsToCallRecordsDtos(List<CallRecords> callRecords);
}
public class CallRecordsDto {
private String callerNumber;
private List<CallRecordOperatorsDto> operators;
//getter setters
}
public class CallRecordOperatorsDto {
private String callRecordsId;
private String operatorId;
private String operatorName;
private String currentTime;
// getter setter
}
But for above code I am getting
{
"callerNumber": "9898989898",
"operators": [{
"callRecordsId": null,
"operatorId": null,
"operatorName": null,
"currentTime": null
}, {
"callRecordsId": null,
"operatorId": null,
"operatorName": null,
"currentTime": null
}]
}
the values of operator array are null. what could be he issue?
It seems your are lacking the mappings from CallRecordOperators to CallRecordOperatorsDto:
#Mapper
public interface CallRecordsMapper {
CallRecordsMapper INSTANCE = Mappers.getMapper(CallRecordsMapper.class);
#Mapping(source="callRecordOperators",target = "operators")
CallRecordsDto callRecordsToCallRecordsDto(CallRecords callRecords);
#Mapping(target = "callRecordsId", source = "callrecord.id")
#Mapping(target = "operatorId", source = "operator.id")
#Mapping(target = "operatorName", source = "operator.name")
#Mapping(target = "currentTime", source = "startTime")
CallRecordOperatorsDto callRecordOperatorsToDto(CallRecordOperators source);
}
When you do a Hibernate query of A elements, you can fetch the related B elements of the bs collection using different strategies. Some of them are:
If you use HQL to construct your queries, you can do a JOIN FETCH or LEFT JOIN FETCH to populate the bs collection:
String hql = "SELECT DISTINCT a FROM " + A.class.getName()
+ " a LEFT JOIN FETCH a.bs WHERE ...";
This query will load all data using a single SQL query.
Use eager fetching of the bs collection, changing the #OneToMany annotation:
#OneToMany(fetch=FetchType.EAGER)
private List<B> bs;
In this case, when you run a query of A elements, a SQL query will be launched to retrieve the A data, and for each A object in the result, a SQL query will be executed to load the corresponding bs collection.
If you use Criteria to build the query, you can change the fetch mode of the bs collection in a way similar to the HQL JOIN FETCH:
Criteria c = session.createCriteria(A.class);
c.setFetchMode("bs", FetchMode.JOIN);
c.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
How about switching to a slightly different approach that also performs better? By using Blaze-Persistence Entity Views you can define your mapping directly on the DTO classes and apply that onto a query builder to generate efficient queries that perfectly fit your DTO structure.
#EntityView(CallRecords.class)
public interface CallRecordsDto {
// The id of the CallRecords entity
#JsonIgnore
#IdMapping("id") String getId();
String getCallerNumber();
#Mapping("callRecordOperators")
List<CallRecordOperatorsDto> getOperators();
}
#EntityView(CallRecordOperators.class)
public interface CallRecordOperatorsDto {
// The id of the CallRecordOperators entity
#JsonIgnore
#IdMapping("id") Long getId();
#Mapping("callrecord.id")
String getCallRecordId();
#Mapping("operator.id")
String getOperatorId();
#Mapping("operator.name")
String getOperatorName();
#Mapping("startTime")
String getCurrentTime();
// Whatever properties you want
}
See how you can map the entity attributes right in your DTOs? And here comes the code for querying
EntityManager entityManager = // jpa entity manager
CriteriaBuilderFactory cbf = // query builder factory from Blaze-Persistence
EntityViewManager evm = // manager that can apply entity views to query builders
CriteriaBuilder<User> builder = cbf.create(entityManager, CallRecords.class)
.where("callerNumber").eq("123456789");
List<CallRecordsDto> result = evm.applySetting(
builder,
EntityViewSetting.create(CallRecordsDto.class)
).getResultList();
Note that this will roughly generate the following optimized query
SELECT
c.id,
c.callerNumber,
o.callrecord.id,
o.id,
o.startTime,
op.id,
op.name
FROM CallRecords c
LEFT JOIN c.callRecordOperators o
LEFT JOIN o.operator op
WHERE c.callerNumber = :param_1

Hibernate/JPA JPQL to wrong SQL when querying Map<String,String> field

This is my Entity configuration
#Entity
#NamedQuery(name = "Payment.findByEmail", query = "SELECT p FROM Payment p JOIN p.additionalAuthData a " +
"WHERE KEY(a) = 'email' AND VALUE(a) = ?1 AND (p.paymentType = 4 OR p.paymentType = 10)")
public class Payment {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Column(name = "payment_type")
private Integer paymentType;
/** other properties, getters and setters */
#ElementCollection
#CollectionTable(name = "additional_auth_data")
#MapKeyJoinColumn(name = "id", referencedColumnName = "id")
#MapKeyColumn(name = "field")
#Column(name = "data_value")
private Map<String, String> additionalAuthData;
}
The NamedQuery findByEmail("test#example.com") generates the following SQL
select -- all fields ...
from payment payment0_ inner join additional_auth_data additional1_ on payment0_.id=additional1_.id
where
additional1_.field='email' and (select additional1_.data_value from additional_auth_data additional1_ where payment0_.id=additional1_.id)='test#example.com' and (payment0_.payment_type=4 or payment0_.payment_type=10)
which is wrong: it may work if you have only one row but it blows up otherwise. H2 complains Scalar subquery contains more than one row and PostgreSQL more than one row returned by a subquery used as an expression. In fact, query's where condition compares a scalar value ('test#example.com') with a subquery.
The correct SQL should be:
select -- all fields
from payment payment0_ inner join additional_auth_data additional1_ on payment0_.id=additional1_.id
where additional1_.field='payerEmail' and additional1_.data_value='test#example.com' and (payment0_.payment_type=4 or payment0_.payment_type=10)
Is the HSQL correct? Is there a way to instruct Hibernate to generates a clever, better SQL? Is this a Hibernate bug?
Note: Hibernate shipped with Spring Boot Starter 1.3.7.RELEASE
Edit:
Using an #Embeddable class
#ElementCollection
#JoinTable(name = "additional_auth_data", joinColumns = #JoinColumn(name = "id"))
#MapKeyColumn(name = "field")
#Column(name = "data_value")
private Set<AdditionalData> additionalAuthData;
#Embeddable
public static class AdditionalData {
#Column(name = "field", nullable = false)
private String field;
#Column(name = "data_value")
private String dataValue;
protected AdditionalData() {
}
public AdditionalData(String field, String dataValue) {
this.field = field;
this.dataValue = dataValue;
}
/** Getters, setters; equals and hashCode on "field" */
}
#NamedQuery(name = "Payment.findByEmail", query = "SELECT p FROM Payment p JOIN p.additionalAuthData a " +
"WHERE a.field = 'email' AND a.dataValue = ?1 AND (p.paymentType = 4 OR p.paymentType = 10)")
solves the problem, and the SQL is correct, but it looks just plain wrong, like shooting a fly with a bazooka...
It generates correct SQL without value().
Use just a=?1
But I would expect is should generate it simple also with it.

Categories

Resources