Here is my situation: I have a PrimeFaces application that is connecting to a DB2 database, and currently I am switching over JPQL queries to native SQL queries, because I need to be able to swap a schema programmatically for the same objects. With different native queries used to fetch from the DB2 database, a different schema can be used. However, I am running into an issue where one of the objects being fetched has a member with a #JoinColumn annotation.
The fetch of the main object seems to work fine, but when it tries to fetch the #JoinColumn member it fails, saying the table cannot be found using what I assume is some default schema. Consider the below classes:
#Entity
#Table(name = "MYOBJ1")
#NamedNativeQueries({
#NamedNativeQuery(name="MyObj1.findAll", query="SELECT * FROM SCHEMA1.MYOBJ1"),
#NamedNativeQuery(name="MyObj1.findAllAlt", query="SELECT * FROM SCHEMA2.MYOBJ1")
})
public class MyObj1 implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Integer id;
#OneToOne
#JoinColumn(name="obj2id", referencedColumnName = "id", insertable = false, updatable = false)
private MyObj2 myObj2;
// getters and setters
...
}
#Entity
#Table(name = "MYOBJ2")
#NamedNativeQueries({
#NamedNativeQuery(name="MyObj2.findAll", query="SELECT * FROM SCHEMA1.MYOBJ2"),
#NamedNativeQuery(name="MyObj2.findAllAlt", query="SELECT * FROM SCHEMA2.MYOBJ2")
})
public class MyObj2 implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Integer id;
/// getters and setters
...
}
Running a native query like the below:
public MyObj1 getMyObj1(Integer id) {
Query query = em.createNativeQuery("SELECT * FROM " + ServerUtilities.getSchema() + ".MYOBJ1 WHERE " + ServerUtilities.getSchema() + ".MYOBJ1.id = '" + id + "'", MyObj1.class);
MyObj1 results = new MyObj1();
try {
results = (MyObj1) query.getSingleResult();
} catch (NoResultException e) {
results = null;
}
return results;
}
Yields this exception: java.sql.SQLException: [SQL0204] MYOBJ2 in MYAPPL type *FILE not found.
One option is to remove the #JoinColumn's, just store the foreign key and look up MyObj2 manually with separate SQL statements, but I am wondering if there is a better way to tell JPA which schema to use for #JoinColumn fetch statements at runtime.
Related
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.
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;
}
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.
I am using Spring Data JPA + Hibernate for a webapp. For a particular domain model A, we have a 1-to-many association in another domain B. Such that A will have a Set getB() and B will have A getA().
While querying for a A graph, I see hibernate is using 1+n queries. A single outer join query for fetching the A graph, but then 'n' queries for setting A in each B.
Am I missing any pattern here? Since all the childs have the same parent, is not somehow possible to avoid these 'n' queries?
#MappedSuperclass
#Data
public abstract class Batch implements Serializable {
private static final long serialVersionUID = 1L;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "batch_id", referencedColumnName = "batch_id")
protected BatchID batchId;
}
/*
//The parent class in a simplified form
*/
#Entity
#Table(name = "DRYRUN")
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class DryrunBatch extends Batch {
/**
*
*/
private static final long serialVersionUID = -1596595930859735318L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Getter#Setter
protected Long id;
public DryrunTNStatus newTNStatus()
{
final DryrunTNStatus tn = new DryrunTNStatus();
tn.setBatch(this);
getTnStatus().add(tn);
return tn;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "batch")
#Getter#Setter
private Set tnStatus = new HashSet();
}
//The child class in a simplified form
#Entity
#Table(name = "DRYRUN_TN_STATUS")
#Data
public class DryrunTNStatus implements Serializable{
/**
*
*/
private static final long serialVersionUID = -4388406636444350023L;
public DryrunTNStatus(String accountNo, String telNo) {
super();
this.accountNo = accountNo;
this.telNo = telNo;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "BATCH_ID", referencedColumnName = "BATCH_ID")
private DryrunBatch batch;
public DryrunTNStatus()
{
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
}
The code to fetch the object graph using JpaRepository. Using Spring JPA support to enforce an outer join. I preferred this over Hibernate's #Fetch annotation.
DryrunBatch drBatch = drBatchRepo.findOne(new Specification() {
#Override
public Predicate toPredicate(Root root, CriteriaQuery query,
CriteriaBuilder cb) {
query.distinct(true);
root.fetch("tnStatus", JoinType.LEFT);
return cb.equal(root.get("batchId").get("id"),
batch.getId());
}
});
And finally the hibernate queries from log. I am running a junit that fetches a parent with 10 childs from DB.
//this query can fetch data for the complete graph??
Hibernate: select distinct dryrunbatc0_.id as id1_6_0_, tnstatus1_.id as id1_9_1_[etc..] from dryrun dryrunbatc0_ left outer join dryrun_tn_status tnstatus1_ on dryrunbatc0_.batch_id=tnstatus1_.batch_id where dryrunbatc0_.batch_id=15
//and then 10 queries like
Hibernate: select dryrunbatc0_.id as id1_6_3_, [etc..] from dryrun dryrunbatc0_ left outer join batch_id batchid1_ on dryrunbatc0_.batch_id=batchid1_.batch_id inner join users user2_ on dryrunbatc0_.created_by=user2_.login_id left outer join dryrun_tn_status tnstatus3_ on dryrunbatc0_.batch_id=tnstatus3_.batch_id where dryrunbatc0_.batch_id=?
You've encountered the famous N+1 problem with lazy loading. There is no JPA standard way to tackle this, however, every JPA provider provides means to turn on "Batch fetching", which will load all lazy references at once instead loading each in a single SQL query.
Here is information on how to turn it on in hibernate.
Here is an article with explanation of how batch fetching works and examples using eclipselink.
I'm facing a problem when updating an Entity using JPA 2.0 and Hibernate (I didn't test with other providers). Here Is my entity (cutted down for brevity):
#Entity
public class CriterioDefinicaoImpFederais implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name="criterio_definicao_imp_federais", sequenceName="criterio_definicao_imp_federais")
#GeneratedValue(generator="criterio_definicao_imp_federais", strategy=GenerationType.AUTO)
private Long id;
#Column(length=100)
#NotNull
#TextoValido(min=1, max=100)
private String descricao = "";
//Other fields ommited
#NotEmpty
#OneToMany(cascade=CascadeType.ALL, orphanRemoval=true, mappedBy="criterio")
//Bidirectional association
private List<GrupoCriterioImpFederais> grupos = new ArrayList<>();
public Long getId() {
return id;
}
public void addGrupo(GrupoCriterioImpFederais grupo) {
grupo.setCriterio(this);
this.grupos.add(grupo);
}
public void removerGrupo(GrupoCriterioImpFederais grupo) {
grupos.remove(grupo);
grupo.setCriterio(null);
}
//Other methods ommited
}
Supose I try to persist one new CriterioDefinicaoImpFederais instance. Validation works well, including the #NotEmpty on field grupos.
Then I load the instance persisted, clear the grupos list (calling removerGrupo) and try to update (using the JPA 2.0 merge) the instance.
At this point, the validation for grupos (#NotEmpty) is not fired. BUT, if I change another field of CriterioDefinicaoImpFederais (like descricao for example), all validations are fired including the validations for grupos.
Is this the correct behavior? Or what am I missing? Is there a way to fire the validations?
Ps: I've tried to call flush after merge, without success.
Code to load and update objects:
To load I use the following hql:
//This is critRepo.porId
String sql = "select distinct c from CriterioDefinicaoImpFederais c "
+ " join fetch c.licenca "
+ " join fetch c.grupos g "
+ "where "
+ " c.id = :id ";
This is the code executed after the object is persisted:
em.getTransaction().begin();
CriterioDefinicaoImpFederais outro = critRepo.porId(criterio.getId());
em.getTransaction().commit();
em.clear();
outro.removerGrupo(outro.getGrupos().get(0));
outro.removerGrupo(outro.getGrupos().get(0));
em.getTransaction().begin();
//This method calls merge
critRepo.salvar(outro);
em.getTransaction().commit();
Thanks!
I haven't verified this, but it might be because you aren't accessing the value through an setter that adheres to the Java Bean Specification.
Try if you set it to a new empty collection through a normal setter.