Java Hibernate weird marshaling bug? - java

I am having an issue with Spring Data JPA when it returns data from the database. I'm giving this one more shot on here. Below is the setup.
Spring V 1.5.15.RELEASE
PGV 9.5.4
SQL:
CREATE TABLE dmg.gps_user_to_data_set (
group_id int8 NOT NULL,
data_set int4 NOT NULL,
agency_id int8 NOT NULL,
FOREIGN KEY (agency_id) REFERENCES funding_agency(agency_id),
FOREIGN KEY (group_id, data_set) REFERENCES data_set(group_id, data_set)
)
WITH (
OIDS=FALSE
) ;
Repo:
public interface GpsUserToDataSetTestRepository extends JpaRepository<GpsUserToDataSetTest, DataSetPK> {
#Query(value = "select group_id,data_set,agency_id from gps_user_to_data_set where group_id=?1 and data_set=?2", nativeQuery = true)
List<GpsUserToDataSetTest> test(Long groupId, Long dataSet);
#Query(value = "select group_id,data_set,agency_id from gps_user_to_data_set where group_id=?1 and data_set=?2", nativeQuery = true)
List<Object> test2(Long groupId, Long dataSet);
}
Entity:
#Entity
#Data
#Table(name = "GPS_USER_TO_DATA_SET")
public class GpsUserToDataSetTest implements Serializable {
#EmbeddedId
private DataSetPK primaryKey;
#Column(name = "AGENCY_ID")
private Long agencyId;
}
Test:
System.out.println("ID: "+dataSetPK);
System.out.println("YYY : ");
gpsUserToDataSetTestRepo.test(dataSetPK.getGroupId(), dataSetPK.getDataSetId()).stream().forEach(e -> {
try {
System.out.println(new ObjectMapper().writeValueAsString(e));
} catch(JsonProcessingException jpe) {
}
});
System.out.println("ZZZ: ");
gpsUserToDataSetTestRepo.test2(dataSetPK.getGroupId(), dataSetPK.getDataSetId()).stream().forEach(e -> {
try {
System.out.println(new ObjectMapper().writeValueAsString(e));
} catch(JsonProcessingException jpe) {
}
});
So I run this and I get two different results, I would expect when the interface GpsUSerToDataSetTest the encapsulated information should be exacly the same as when I run the same query with java.lang.Object, but you can see the ids are not the same, below is the data in the DB & the stdOut dump.
I dont even know what to say here, this just seems very odd to me, and potentially a bug?
DB:
group_id |data_set |agency_id |
---------|---------|----------|
1356 |1 |2 |
1356 |1 |2 |
1356 |1 |19 |
Dump:
ID: DataSetPK(dataSetId=1, groupId=1356)
YYY :
{"primaryKey":{"dataSetId":1,"groupId":1356},"agencyId":2}
{"primaryKey":{"dataSetId":1,"groupId":1356},"agencyId":2}
{"primaryKey":{"dataSetId":1,"groupId":1356},"agencyId":2}
ZZZ:
[1356,1,2]
[1356,1,2]
[1356,1,19]
as you can see the EXACT same query returns two different results on the agencyId as soon as I add the GpsUserToDataSetTest to the result set for marshaling. Any feedback would be good, I am lost at this point.
EDIT:
Updated Entity:
public class GpsUserToDataSetTest implements Serializable {
// #EmbeddedId
// private DataSetPK primaryKey;
#Column(name = "DATA_SET")
#Id
private Long dataSetId;
#Column(name = "GROUP_ID")
#Id
private Long groupId;
#Column(name = "AGENCY_ID")
private Long agencyId;
}
Edit based on Comments:
#Entity
#Data
#Table(name = "GPS_USER_TO_DATA_SET")
public class GpsUserToDataSetTest implements Serializable {
// #EmbeddedId
// private DataSetPK primaryKey;
#Column(name = "DATA_SET")
private Long dataSetId;
#Column(name = "GROUP_ID")
private Long groupId;
#Column(name = "AGENCY_ID")
#Id
private Long agencyId;
}
ID: DataSetPK(dataSetId=1, groupId=1356)
YYY :
{"dataSetId":1,"groupId":1356,"agencyId":2}
{"dataSetId":1,"groupId":1356,"agencyId":2}
{"dataSetId":1,"groupId":1356,"agencyId":19}
ZZZ:
[1356,1,2]
[1356,1,2]
[1356,1,19]

In case the query result is captured into List<GpsUserToDataSetTest> in test1() the GpsUserToDataSetTest ORM objects are populated and added to the list. Hence, the JSON converted string is the exact representation of the ORM.
On the other hand, when the query result is captured into List<Object> in test2() these are populated as mere values array and then added to the list. Hence, the JSON representation has only values array.
Write a new method test3() in the repository and capture the results into List<Map<String, String>> and observe the output. You'll notice plain individual key values (no field like primaryKey etc.) in the JSON string.
Edit
Also, all the rows returned from test1() is duplicate because of the primary key (#Id or #EmbeddedId) fields in the rows returned are the same. Hibernate cache is the cause here. This is not a bug in Hibernate rather the data is the database should be fixed or the #Id annotation should be used correctly.

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.

TypeMismatchException on fetching the table data by Hibernate Session.get() method

In a application, having a database table CUSTOMERS defined as:
create table CUSTOMERS (
ID varchar(10),
CODE varchar(10),
CID varchar(10),
SID varchar(10),
FNAME varchar(50),
LNAME varchar(50),
constraint PK_CUSTOMERS primary key (ID, CODE, CID, SID)
);
and the Entity classes are created to populate the data as
#Embeddable
public class CustKey implements Serializable , Cloneable{
#Transient
private static final long serialVersionUID = 1L;
#Column(name = "ID", nullable = false)
private String id;
#Column(name = "CODE", nullable = false)
private String code;
#Column(name = "CID", nullable = false)
private String cid;
#Column(name = "SID", nullable = false)
private String sid;
public boolean equals(Object o){
return id.equals(o.getId()) && ...;
}
public int hashcode(){
return id.hashcode() & ...;
}
}
#Entity
#Table(name = "CUSTOMERS")
public class CustProfileWrapper implements Serializable,Cloneable {
#Transient
private static final long serialVersionUID = 1L;
#EmbeddedId
private CustKey custKey;
#Column(name = "FNAME")
private String fname;
#Column(name = "LNAME")
private String lname;
}
The records are populated without an issue.
But the Entity classes are move to other project (but keeping the same package name as before) due to some rewrite of the code/project. but on fetching the data by Hibernate Session as
Object object = session.get(CustProfileWrapper.class, custProfileWrapper.getCustKey(), LockMode.NONE);
getting the error
org.hibernate.TypeMismatchException: Provided id of the wrong type for class CustProfileWrapper. Expected: class com.db.CustProfileWrapper, got class com.db.CustProfileWrapper
However, able to get the record when using the parametrized query as
SQLQuery query = session.createSQLQuery("SELECT * FROM CUSTOMERS WHERE ID = ? "
+ " AND CODE = ? AND CID = ? AND SID = ? ");
query.addEntity(CustProfileWrapper.class);
query.setParameter(0, "101");
...
object = query.list();
But it's a low level code when using the query, and we should use the
better way like get() method.
Any help/hint will be appreciated!!
Full stack trace of the error:
After so much investigation, found the culprit spring-boot-devtools dependency, as explained here:
I was getting this problem after adding a dependency to
spring-boot-devtools in my Springboot project. I removed the
dependency and the problem went away. My best guess at this point is
that spring-boot-devtools brings in a new classloader and that causes
the issue of class casting problems between different classloaders in
certain cases where the new classloader is not being used by some
threads.
Reference: A dozer map exception related to Spring boot devtools
Refs: ClassCastException when casting to the same class

#OneToOne Relationship Spring Data JPA/Hibernate Entity Relationship

I'm working on adding a feature to an already developed spring boot web application. The primary entity that has child entities is a Record. It has a few columns/variables that I want to now be in its own, separate entity (CustomerOrder) and exist in a one-to-one relationship with the Record. To summarize:
Record {
thing 1
thing 2
thing 3
}
is now becoming:
CustomerOrder {
thing 1
thing 2
thing 3
}
Record {
CustomerOrder
}
I'm having some issues with what I've produced. Here is the CustomerOrder model's relevant relationship data:
#Entity
#Table(name="customer_orders")
public class CustomerOrder {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
... other columns
#OneToOne(orphanRemoval = true, cascade = CascadeType.ALL, mappedBy="customerOrder", fetch = FetchType.EAGER)
private Record record;
}
And then here is the Record model's relevant data:
#Entity
#Table(name="records")
public class Record extends Auditable<String> implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
... other columns
#OneToOne
#JoinColumn(name="customer_order_id", nullable = false, unique = true)
private CustomerOrder customerOrder;
}
My issue exists when I try to POST a record, when a user tries creating one in the ui. Here is the POST method for a record:
#PostMapping
public ResponseEntity<?> saveRecord(#RequestBody Record recordBody, BindingResult result) {
if(!result.hasErrors()) {
if(recordBody.getHardwareItems().isEmpty()) {
record = recordsService.save(recordBody);
} else {
// Save the record first, recordId is required on hardwareItems
// TODO: investigate Spring Hibernate/JPA rules - is there a way to save parent before children to avoid a null recordId
CustomerOrder customerOrder = recordBody.getCustomerOrder();
recordBody.setCustomerOrder(new CustomerOrder());
customerOrder.setRecord(record);
customerOrder = customerOrdersService.save(customerOrder);
record = recordsService.save(recordBody);
}
} else {
return new ResponseEntity<>(result.getAllErrors(), HttpStatus.BAD_REQUEST);
}
// Return the location of the created resource
uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{recordId}").buildAndExpand(record.getId()).toUri();
return new ResponseEntity<>(uri, HttpStatus.CREATED);
}
The error I receive is the following:
2021-02-19 00:46:28.398 WARN 29990 --- [io-8080-exec-10] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1364, SQLState: HY000
2021-02-19 00:46:28.398 ERROR 29990 --- [io-8080-exec-10] o.h.engine.jdbc.spi.SqlExceptionHelper : Field 'record_id' doesn't have a default value
This makes sense to me at least, since I'm trying to save the CustomerOrder object that depends on a Record object, which has yet to have been persisted. So, how do I go about changing up the order and/or creating and persisting a Record object so that I can then save the CustomerOrder object to it?
You need to mark your column record_id as AI(AUTO_INCREMENT) in your table definition.
ALTER TABLE records CHANGE record_id INT(6) NOT NULL AUTO_INCREMENT;
Your primary key is record_id, add #Column(name = "record_id", nullable = false)
#Entity
#Table(name="records")
public class Record extends Auditable<String> implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "record_id", nullable = false)
private Long id;
... other columns
#OneToOne
#JoinColumn(name="customer_order_id", nullable = false, unique = true)
private CustomerOrder customerOrder;
}

JPA OneToMany on composited column

First of all, this is a legacy DB and the schema cannot be changed. Now imagine I've the following tables:
----------- -----------
| table A | | table B |
----------- -----------
| id_A |----->| id_B |
| col_A | | col_B |
----------- -----------
Table A is the master and Table B is the detail.
Both id_A and id_B are strings BUT id_B = id_A + 4 characters.
For instance, if id_A = "0000000123" then there are multiple id_B like the following ones "00000001230001","00000001230002", "00000001230003", ... yes, I know, that should have been another column. As I said this is a legacy DB and I found it that way.
I'm using Spring Data JPA, JPA2 and Hibernate. And what I need is to define the entities:
#Entity
#Table(name="A")
public class A {
#Column(name = "id_A", length = 10, unique = true, nullable = false)
private String idA;
#Column(name = "col_A")
private String colA;
#OneToMany <-- WHAT MORE GOES HERE TO REFERENCE JUST THE SUBSTRING OF THE DETAIL id_B?
private List<B> detail;
}
#Entity
#Table(name="B")
public class B {
#Column(name = "id_B", length = 14, unique = true, nullable = false)
private String idB;
#Column(name = "col_B")
private String colB;
}
I don't know how to reference that what I need is that substr(id_b, 1, 10) = id_A
How can I do that?
The only solution I can see for this is to create a view at the database level. MySQL supports updateable views if certain criteria are met and we should be okay with both reading and writing.
http://dev.mysql.com/doc/refman/5.7/en/view-updatability.html
(Recent versions of MySQL also support Generated columns which may be an alternative to a view (http://mysqlserverteam.com/generated-columns-in-mysql-5-7-5/)
Create a view:
create view VW_TABLE_B
as
select *,
substring(..) as a_id
from B
Entity B is mapped to the view:
#Entity
#Table(name="VW_TABLE_B")
public class B {
#Id
#Column(name = "id_B", length = 14)
private String id;
#Column(name = "col_B")
private String colB;
protected void setId(String id){
this.id = id;
}
}
From a JPA perspective it doesn't matter if we are using a view, concrete table or whatever. The relationship from Entity A to Entity B can then become a standard mapping with join column pointing to the derived value in the view.
#Entity
#Table(name="A")
public class A {
#Id
#Column(name = "id_A", length = 10)
private String id;
#Column(name = "col_A")
private String colA;
#OneToMany
#JoinColumn(name = "a_id")
private List<B> detail;
public void addB(B b){
b.setId(this.id + new DecimalFormmat("0000").format(detail.size() + 1);
detail.add(b);
)
}

JPA Join using arbitrary field (not primary key)

I've got two entities that I want to join together using a field they have in common, called shared_id. The field is not the primary key of either entity. The shared_id is unique - each Hipster will have a unique shared_id.
The tables look like:
Hipster Fixie
========= ========
id id
shared_id shared_id
There is a OneToMany relationship between Hipsters and their Fixies. I've tried something like this:
#Entity
public class Hipster {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "shared_id")
private Integer sharedId;
#OneToMany(mappedBy = "hipster")
private List<Fixie> fixies;
}
#Entity
public class Fixie {
#Id
#Column(name = "id")
private Integer id;
#ManyToOne
#JoinColumn(name = "shared_id", referencedColumnName = "shared_id")
private Hipster hipster;
}
#Repository
public class HipsterDAO {
#PersistenceContext
private EntityManager entityManager;
public Hipster getHipsterBySharedId(Integer sharedId) {
String queryString = "SELECT h FROM Hipster h WHERE h.sharedId = :sharedId";
TypedQuery<Hipster> query = entityManager.createQuery(queryString, Hipster.class);
query.setParameter("sharedId", sharedId);
try {
return query.getSingleResult();
} catch (PersistenceException e) {
return null;
}
}
}
Now, my DAO gives me this error:
java.lang.IllegalArgumentException: Can not set java.lang.Integer field Hipster.sharedId to java.lang.Integer
I think it's upset because the sharedId field is used in a relation, rather than just being a basic field. I haven't included the sharedId field in the Fixie entity, but I get the same result if I do. How do I persuade it to run this query for me? Do I need to change the query or the entities?

Categories

Resources