Hibernate returning lazy loaded collections with null session - spring

(SORT OF SOLVED, SEE BELOW)
Hibernate 4.1/Spring/JPA project. I'm using Spring & JPA annotations for transactions support, entity manager injection etc.
What I'm seeing that within the same transaction my #OneToMany lazy-loaded collections dont have session property set, and of course cannot be loaded. If I do 'left join fetch' to force loading I get multiple records that point to the same PersistentBag - obviously this throws 'shared collection' exception.
Here is my setup:
Transaction entity ('transaction' is meant as in financial transaction)
Code:
#Entity
#Table(name = "transactionData")
#Access(AccessType.PROPERTY)
public class TransactionData extends AbstractTransaction implements java.io.Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
#OneToMany(cascade=CascadeType.ALL,targetEntity=Location.class,fetch=FetchType.LAZY)
#JoinColumns(
{
#JoinColumn(name="routecode",referencedColumnName="v_OPERSTAT"),
#JoinColumn(name="cogrp", referencedColumnName="v_COUNTRY")
})
#Transactional
public Collection<Location> getLocations() {
return super.getLocations();
}
public void setLocations(Collection<Location> l) {
super.setLocations(l);
}
}
Base class for Transaction:
Code:
public abstract class AbstractTransaction {
private List<Location> _locations;
#Override
#Transactional
public List<Location> getLocations() {
return _locations;
}
#Override
public void setLocations(List<Location> value) {
_locations = value;
}
}
Transaction entity is linked to Location entity using 2 integer type columns, these cols are not PK on either Transaction or Location entity.
Location entity:
Code:
#Entity
#Table(name = "locations", uniqueConstraints = #UniqueConstraint(columnNames = {
"cogrp", "cugrp", "bogrp", "status", "id" }))
public class Location implements java.io.Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
#Column(name = "cogrp", nullable = false)
public int getCogrp() {
return this.cogrp;
}
public void setCogrp(int cogrp) {
this.cogrp = cogrp;
}
#Column(name = "routecode")
public Integer getRoutecode() {
return this.routecode;
}
public void setRoutecode(Integer routecode) {
this.routecode = routecode;
}
}
Transaction entity has 1-to-many relationship to locations, and most Transaction entities will point to the same list of locations.
Now, if I do the following query:
Code:
select distinct t from " + Transaction.class.getName() + " t left join fetch t.locations where " + filterSQL
I get results back, but almost every Transaction entity points to the same PersistentBag of locations - needless to say this causes shared references error.
If I omit left join fetch all entities come back with lazily loaded collections, but none have session property set.
If I do eager loading I only get 2 entities eagerly loaded, the rest are still lazy (I know this is a built-in restriction, any way to override that?)
EDIT:
I have come to believe this is a bug in Hibernate. If your query returns multiple records pointing to the same collection (perfectly valid scenario I might add!) then Hibernate will reuse the same collection when eagerly fetching, and set session to null when lazy loading. It will always result in either shared reference to same collection, or 'no session' errors.
I have switched to EclipseLink 2.4, and after fixing my JPSQL it seems to work fine in the above described case.

Related

Spring + Hibernate without lazy = LazyInitializationException

I want to load all objects from a table without a lazy objects/children and list them on the page (Thymeleaf template), but I get a LazyInitializationException every time. I tried to convert Hibernate entity objects into a POJO that doesnt contains a lazy/unwanted object but with the same result. I also tried open-in-view parameter set to false...
Simple example:
Parent:
#Entity
public class DocumentDbe implements Serializable {
public DocumentDbe(){
}
#Id
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
private DocumentFileDbe documentFile;
....
}
Child:
#Entity
public class DocumentFileDbe implements Serializable {
public DocumentFileDbe(){}
#Id
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#Column
#Lob
private byte[] documentData;
...
}
POJO:
public class DocumentDto implements Serializable {
public DocumentDto(){
}
public DocumentDto(DocumentDbe doc){
this.id = doc.getId();
}
....
}
Controller:
#GetMapping("/list")
String getList(Model model) {
List<DocumentDbe> docs;
List<DocumentDto> data = new ArrayList<>();
try (Session ses = sessionFactory.openSession()) {
docs = ses.createQuery("FROM DocumentDbe").list();
docs.forEach(doc -> {
data.add(new DocumentDto(doc));
});
}
model.addAttribute(MODEL_LIST_DATA, data);
return "list";
}
EDIT: Thrown exception:
org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/list.html]")] with root cause
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
EDIT2:
In DocumentDbe is relation with another object (EAGER this time so I was not paying attention to it) , which has reference to DocumentDbe again.. chained relationship and LazyInitializationException is created...
EDIT3:
Although
This is modified and working controller, without POJO:
#GetMapping("/list")
String getList(Model model) {
List<DocumentDbe> docs;
try (Session ses = sessionFactory.openSession()) {
docs = ses.createQuery("FROM DocumentDbe ORDER BY id DESC").list();
docs.forEach(doc -> {
doc.setDocumentFile(null);
doc.getHistory().forEach(log ->{
log.setDocument(null);
});
});
}
model.addAttribute(MODEL_ADMIN_DATA, docs);
return "list";
}
In class DocumentDbe you have mark relation as Lazy. In default relation #ManyToOne and #OneToOne is as EAGER, so if you don't want Lazy, you have to change
#OneToOne(cascade = CascadeType.PERSIST)
If you want have #lob also as eager:
#Lob
#Basic( fetch = FetchType.EAGER )

A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance - Spring and Lombok

I am getting this A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance error with my oneToMany relationship when trying to update my child element (report). Although I see this question asked a few times here, I haven't been able to make my code to work with them and I now feel it may be an issue with me using Lombok perhaps, since most of the answers here mention about changes on the hashcode and equals methods, which are abstracted away by Lombok? I tried to remove Lombok to try without it but then I got a bit confused on what to do next. If I could get some guidance on how to fix this issue within my original Lombok implementation please.
#Entity
#Table(name = "category")
#AllArgsConstructor
#NoArgsConstructor
#Data
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(name = "category_title", nullable = false)
private String title;
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
private Collection<Report> report;
public Category(UUID id, String title) {
this.id = id;
this.title = title;
}
}
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "report")
#Data
public class Report {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(name = "report_title", nullable = false)
private String reportTitle;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
#JoinColumn(name = "category_id", nullable = false)
private Category category;
public Report(UUID id) {
this.id = id;
}
}
#Override
public ReportUpdateDto updateReport(UUID id, ReportUpdateDto reportUpdateDto) {
if (reportRepository.findById(id).isPresent()) {
Report existingReport = reportRepository.findById(id).get();
existingReport.setReportTitle(reportUpdateDto.getTitle());
Category existingCategory = categoryRepository.findById(reportUpdateDto.getCategory().getId()).get();
Category category = new Category(existingCategory.getId(), existingCategory.getTitle());
existingReport.setCategory(category); // This is needed to remove hibernate interceptor to be set together with the other category properties
Report updatedReport = reportRepository.save(existingReport);
updatedReport.setCategory(category); // This is needed to remove hibernate interceptor to be set together with the other category properties
ReportUpdateDto newReportUpdateDto = new ReportUpdateDto(updatedReport.getId(),
updatedReport.getReportTitle(), updatedReport.getCategory());
return newReportUpdateDto;
} else {
return null;
}
}
Thank you very much.
Fast solution (but not recommended)
The error of collection [...] no longer referenced arrises in your code beacuse the synchronization between both sides of the bidiretional mapping category-report was just partially done.
It's important to note that binding the category to the report and vice-versa is not done by Hibernate. We must do this ouserselves, in the code, in order to sync both sides of the relationship, otherwise we may break the Domain Model relationship consistency.
In your code you have done half of the synchronization (binding the category to the report):
existingReport.setCategory(category);
What is missing is the binding of the report to the category:
category.addReport(existingReport);
where the Category.addReport() may be like that:
public void addReport(Report r){
if (this.report == null){
this.report = new ArrayList<>();
}
this.report.add(r);
}
Recommended Solution - Best practice for synchronizing both sides of the mapping
The suggested code above works, but it is error prone as the programmer may forget to call one of the lines when updating the relationship.
A better approach is to encapsulate that sychronization logic in a method in the owning side of the relationship. And that side is the Category as stated here: mappedBy = "category".
So what we do is to encapsulate in the Category.addReport(...) all the logic of cross-reference between Category and Report.
Considering the above version of addReport() method, what is missing is adding r.setCategory(this).
public class Category {
public void addReport(Report r){
if (this.reports == null){
this.reports = new ArrayList<>();
}
r.setCategory(this);
this.reports.add(r);
}
}
Now, in the updateReport() it is enough to call the addReport() and the commented line bellow can be deleted:
//existingReport.setCategory(category); //That line can be removed
category.addReport(existingReport);
It is a good practice including in Category a removeReport() method as well:
public void removeReport(Report r){
if (this.reports != null){
r.setCategory = null;
this.reports.remove(r);
}
}
That is the code of Category.java after the two methods were added:
public class Category {
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
private Collection<Report> reports;
//Code ommited for brevity
public void addReport(Report r){
if (this.reports == null){
this.reports = new ArrayList<>();
}
r.setCategory(this);
this.reports.add(r);
}
public void removeReport(Report r){
if (this.reports != null){
r.setCategory = null;
this.reports.remove(r);
}
}
}
And the code for updating a report category now is this:
public ReportUpdateDto updateReport(UUID id, ReportUpdateDto reportUpdateDto) {
if (reportRepository.findById(id).isPresent()) {
Report existingReport = reportRepository.findById(id).get();
existingReport.setReportTitle(reportUpdateDto.getTitle());
Category existingCategory = categoryRepository.findById(reportUpdateDto.getCategory().getId()).get();
existingCategory.addReport(existingReport);
reportRepository.save(existingReport);
return new ReportUpdateDto(existingReport.getId(),
existingReport.getReportTitle(), existingReport.getCategory());
} else {
return null;
}
}
A good resource to see a practical example of synchronization in bidirectional associations: https://vladmihalcea.com/jpa-hibernate-synchronize-bidirectional-entity-associations/
Lombok and Hibernate - not the best of the combinations
Though we can not blame Lombok for the error described in your question, many problems may arrise when using Lombok alongside with Hibernate:
Properties being loaded even if marked for lazy loading...
When generating hashcode(), equals() or toString() using Lombok, the getters of fields marked as lazy are very likelly to be called. So the programmer's initial intention of postponing some properties loading will no be respected as they will be retrieved from the database when one of hascode(), equals() or toString() is invoked.
In the best case scenario, if a session is open, this will cause additional queries and slow down your application.
In the worst case scenarios, when no session is available, a LazyInitializationException will be thrown.
Lombok's hashcode()/equals() affecting the bevahior of collections
Hibernate uses hascode() and equals() logic to check if a object is order to avoid inserting the same object twice. The same applies to removing from a list.
The way Lombok generates the methods hashcode() and equals() may affect hibernate and create inconsistent properties (especially Collections).
See this article for more info on this subject: https://thorben-janssen.com/lombok-hibernate-how-to-avoid-common-pitfalls/
Lombok/Hibernate integration in a nutshell
Don't use Lombok for entity classes. Lombok annotations you need to avoid are #Data, #ToString, and #EqualsAndHashCode.
Off-topic - Beware of delete-orphan
In Category, the #OneToMany mapping is defined with orphanRemoval=true as bellow:
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
private Collection<Report> reports;
The orphanRemoval=true means that when deleting a category, all the reports in that category will be deleted as well.
It is important to assess if that is the desired behavior in your application.
See an example of the SQLs hibernate will execute when calling categoryRepository.delete(category):
//Retrieving all the reports associated to the category
select
report0_.category_id as category3_1_0_,
report0_.id as id1_1_0_,
report0_.id as id1_1_1_,
report0_.category_id as category3_1_1_,
report0_.report_title as report_t2_1_1_
from
report report0_
where
report0_.category_id=?
//Deleting all the report associated to the category (retrieved in previous select)
delete from
report
where
id=?
//Deleting the category
delete from
category
where
id=?
Just an update based on the accepted answer to avoid a StackOverflow and circular loop that came up after the changes.
I had to create a new Category object to remove the reports inside it within my return dto, otherwise as the category contains that same report, that again contains that category and so on, the infinite loop could be seen on my response.
#Override
public ReportUpdateDto updateReport(UUID id, ReportUpdateDto reportUpdateDto) {
if (reportRepository.findById(id).isPresent()) {
Report existingReport = reportRepository.findById(id).get();
existingReport.setReportTitle(reportUpdateDto.getTitle());
Category existingCategory = categoryRepository.findById(reportUpdateDto.getCategory().getId()).get();
Category category = new Category(existingCategory.getId(), existingCategory.getTitle());
existingCategory.addReport(existingReport);
reportRepository.save(existingReport);
return new ReportUpdateDto(existingReport.getId(),
existingReport.getReportTitle(), existingReport.getRun_date(),
existingReport.getCreated_date(), category);
} else {
return null;
}
}
So added this part:
Category existingCategory = categoryRepository.findById(reportUpdateDto.getCategory().getId()).get();
Category category = new Category(existingCategory.getId(), existingCategory.getTitle());
existingCategory.addReport(existingReport);
As if I have something like
Category category = new Category(existingCategory.getId(), existingCategory.getTitle(), existingCategory.getReports);
I can see the issue once again, which is what the existingCategory object itself contains.
And here my final entities
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "report")
#Data
public class Report {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(name = "report_title", nullable = false)
private String reportTitle;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
#JoinColumn(name = "category_id", nullable = false)
private Category category;
#Entity
#Table(name = "category")
#AllArgsConstructor
#NoArgsConstructor
#Data
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(name = "category_title", nullable = false)
private String title;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
private Collection<Report> reports;
public Category(UUID id, String title) {
this.id = id;
this.title = title;
}
public void addReport(Report r) {
if (this.reports == null) {
this.reports = new ArrayList<>();
}
r.setCategory(this);
this.reports.add(r);
}
public void removeReport(Report r) {
if (this.reports != null) {
r.setCategory(null);
this.reports.remove(r);
}
}
}

Fetch List Using DTO projections using a Constructor Expression and JPQL

Perform a search on DisabScreenRequest and fetch its child details also. Using DTO projections using a Constructor Expression and JPQL.
The parent entity with a child table.
#Entity
#Table(name = "SCREEN_REQUEST")
public class DisabScreenRequest implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private long requestId;
#Column(name = "CIVILID")
private Long civilId;
#ManyToMany()
#JoinTable(name = "_DISAB_SCREEN_REQ_DETAILS", joinColumns = {
#JoinColumn(name = "REQUEST_ID") }, inverseJoinColumns = { #JoinColumn(name = "DISABILTY_TYPE_ID") })
private Set<DisabMaster> disabilities = new HashSet<DisabMaster>();
public DisabScreenRequest() {
}
}
This is the disability table.
#Entity
#Table(name="DISAB_MASTER")
#Immutable
public class DisabMaster implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="DIS_TYPE_ID")
private long disabilityTypeId;
#Column(name="DIS_TYPE_DESC")
private String disTypeDesc;
public DisabMaster() {
super();
}
}
Had to fetch all the requests along with the disability for each request.
Search DTO(using this I had other joins to add other than one mentioned here).
public class RequestSearchDto {
private long requestId;
private Long civilId;
private Set<DisabMaster> disabilities;
public RequestSearchDto() {
super();
}
public RequestSearchDto(long requestId, Long civilId) {
super();
this.requestId = requestId;
this.civilId = civilId;
}
public RequestSearchDto(long requestId, Long civilId, Set<DisabMaster> disabilities) {
super();
this.requestId = requestId;
this.civilId = civilId;
this.disabilities = disabilities;
}
}
This is my JPQL query
public interface ReposJPQL {
public String GET__REQUEST = "SELECT DISTINCT new org.test.RequestSearchDto "
+ "(dsr.requestId, dsr.civilId, dsr.disabilities)"
+ " FROM DisabScreenRequest dsr WHERE 1=1 ";
}
This will get an
org.hibernate.exception.SQLGrammarException: could not extract ResultSet.
What Iam I doing wrong here, how can I fetch the child table data ?
Let me know if you need any info
Stack trace :
Caused by: java.sql.SQLException: ORA-00936: missing expression
at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:113)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:331)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:288)
at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:754)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:219)
at oracle.jdbc.driver.T4CPreparedStatement.executeForDescribe(T4CPreparedStatement.java:813)
at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:1051)
at oracle.jdbc.driver.T4CPreparedStatement.executeMaybeDescribe(T4CPreparedStatement.java:854)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1156)
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3415)
at oracle.jdbc.driver.OraclePreparedStatement.executeQuery(OraclePreparedStatement.java:3460)
at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeQuery(NewProxyPreparedStatement.java:76)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:60)
If you need to fetch parent entity with a collection of its nested child entities you can use this simple approach using #EntityGraph annotation or JPQL with join fetch:
#Entity
public class Parent {
//...
#OneToMany
private List<Child> children;
}
#Entity
public class Child {
//...
}
interface ParentRepo extends JpaRepository<Parent, Integer> {
// with #EntityGraph
#EntityGraph(attributePaths = "children")
#Override
List<Parent> findAll();
// or manually
#Query("select distinct p from Parent p left join fetch p.children")
List<Parent> findWithQuery();
}
Note to use distinct in your query to avoid duplicate records.
Example: duplicate-parent-entities
More info: DATAJPA-1299
AFAIK, you can't use constructor expression which take a Collection.
See the JPA 2.2 Spec, section 4.14 BNF, read about the constructor expression:
constructor_expression ::=
NEW constructor_name ( constructor_item {, constructor_item}* )
constructor_item ::=
single_valued_path_expression |
scalar_expression |
aggregate_expression |
identification_variable
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 defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure the way you like and map attributes(getters) via JPQL expressions to the entity model. Since the attribute name is used as default mapping, you mostly don't need explicit mappings as 80% of the use cases is to have DTOs that are a subset of the entity model.
A mapping for your model could look as simple as the following
#EntityView(DisabScreenRequest.class)
interface RequestSearchDto extends Serializable {
#IdMapping
long getRequestId();
Long getCivilId();
Set<DisabMaster> getDisabilities();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
RequestSearchDtodto = entityViewManager.find(entityManager, RequestSearchDto.class, id);
But the Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/1.4/entity-view/manual/en_US/#spring-data-features

Lazy attribute is null inside transaction after creation

I have a small example with some get/post mappings and JpaRepository calls in Spring Boot.
Firstly I have two entity Classes:
#Entity
#Table(name = "stock")
public class Stock extends BaseEntity
{
#Column(name = "value")
public String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
#Entity
#Table(name = "stock_item")
public class StockItem extends BaseEntity
{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", insertable = false, updatable = false)
public Stock stock;
#Column(name = "stock_id")
public Long stockId;
#Column(name = "value")
public String value;
}
I have a many-to-one association from StockItem to Stock.
I insert a Stock and have a controller as below:
#Autowired
public Controller(StockItemRepository stockItemRepository) {
this.stockItemRepository = stockItemRepository;
}
#RequestMapping("/")
#Transactional(readOnly = true)
public String get() {
List<StockItem> stockItemList = stockItemRepository.getItemsById(1L);
System.out.println("TX MANAGER: " + TransactionSynchronizationManager.isActualTransactionActive());
for (StockItem stockItem : stockItemList) {
System.out.println(stockItem.getStock().getValue());
}
return "get";
}
#RequestMapping("/fromSave")
#Transactional
public String post() {
StockItem stockItem = new StockItem();
stockItem.setStockId(1L);
stockItemRepository.saveAndFlush(stockItem);
System.out.println("saveCalled");
return get();
}
and getItemsById in the repository is defined as follows:
#Query("FROM StockItem si " +
"JOIN FETCH si.stock stk " +
"WHERE si.stockId = :id")
List<StockItem> getItemsById(#Param("id") Long id);
From my understanding, when I call the post method:
it creates a new item
sets the id of the associated attribute
saves and ends the transaction
Heres where things get strange...
I call get after the post and make the above repository call, which has a join fetch and when I call stockitem.getStock().getValue() I get a null pointer when I expect a LazyInitializationException.
If I call the get() from the mapping, outside the class, it successfully loads the associated object.
I have even removed the #Transaction annotation from the get, as well as
the join-fetch from my query and again, if I call from outside of the class it works and from the post, it crashes with a NullPointerException.
I have put the get inside of a TransactionTemplate.execute() and I still get a NullPointerException when calling from inside the class.
So the main questions are:
Why am I getting a NullPointerException instead of LazyInitializationException?
What is the transaction magic behind having no transaction but successfully fetching a lazy attribute??
The problem here is that you are misusing JPA. As you are seemingly aware judging from the comments on the other answer you have mapped the stock_id column twice. Once as a many-to-one relationship
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", insertable = false, updatable = false)
public Stock stock;
and once as a simple column
#Column(name = "stock_id")
public Long stockId;
When you set the simple column and flush the changes as in your post() method the following happens:
the value gets set in the simple column. The reference is still null.
the value gets stored in the database. The reference is still null.
The repository call will find the id of the StockItemin the Persistence Context and return that instance, i.e. the exact same used in the post method, with the reference still null.
What is the transaction magic behind having no transaction but successfully fetching a lazy attribute??
No magic involved here. fetch specifications are only used for object traversal. JPQL queries don't honor these.
The unasked question remains: how to fix the situation?
The obvious fix is to lose the simple column and just use entity references as intended by JPA.
You don't want to do that in order to avoid DB access somewhere. But as long as you only access the id of the referenced Stock it shouldn't get initialized. So it seems that this should be possible with just Lazy Fetching.
Alternatively, I'd suggest removing the many-to-one relationship and creating a repository for Stock and manually loading it when required.
#Entity
#Table(name = "stock_item")
public class StockItem extends BaseEntity
{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", insertable = false, updatable = false) //here is your problem
public Stock stock;
#Column(name = "stock_id")
public Long stockId; // why explicitly define a separate column for foreign key after mapping it above
#Column(name = "value")
public String value;
}
with insertable = false and updatable = false it won't insert in your DB and neither it will allow updation, so you are getting NullPointerException. You should atleast allow insertion in order to run the query based on the foreign key stock_id
UPDATE
Change your Entity class with property-based access:
#Entity
#Table(name = "stock_item")
public class StockItem extends BaseEntity
{
private Stock stock; // variables should always be private since you have getters and setters
private String value;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", updatable = false)
public Stock getStock() {
return stock;
}
public void setStock(Stock stock) {
this.stock = stock;
}
#Basic
#Column(name = "value")
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

Spring/JPA: composite Key find returns empty elements [{}]

I have build my data model using JPA and am using Hibernate's EntityManager to access the data. I am using this configuration for other classes and have had no problems.
The issue is that I created an entity with a composite primary key (the two keys are foreign keys) , adding elements works perfectly I checked it in database but I am not able to retrieve the populated row from database.
For example if I query "FROM Referentiel" to return a list of all referentiels in the table, I get this [{},{}] my list.size() has the proper number of elements (2), but the elements are null.
The entity:
#Entity
#Table(name = "Et_referentiel")
public class Referentiel implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#ManyToOne
#JoinColumn(name = "id_projet")
private Projet projet;
#Id
#ManyToOne
#JoinColumn(name = "id_ressource")
private Ressource ressource;
#Column(name = "unite", nullable = false)
private String unite;
}
here is my controller getList method:
#PostMapping(value = "/list", consumes = { MediaType.APPLICATION_JSON_UTF8_VALUE })
public List<Referentiel> listReferentiel(#RequestBody Long idProjet) {
List<Referentiel> referentiel = referentielService.listReferentiel(idProjet);
return referentiel;
}
and here is my dao methods:
#Autowired
private EntityManager em;
#Override
public void ajouterReferentiel(Referentiel ref) {
em.persist(ref);
em.flush();
}
#SuppressWarnings("unchecked")
#Override
public List<Referentiel> listReferentiel(Long idProjet) {
Query query = em.createQuery("Select r from Referentiel r where r.projet.idProjet=:arg1");
query.setParameter("arg1", idProjet);
em.flush();
List<Referentiel> resultList = query.getResultList();
return resultList;
}
Any help is greatly appreciated.
Try creating a class representing your composite key:
public class ReferentielId implements Serializable {
private static final long serialVersionUID = 0L;
private Long projet; // Same type than idProjet, same name than inside Referentiel
private Long ressource; // Same type than idRessource (I guess), same name than inside Referentiel
// Constructors, getters, setters...
}
And assign it to your entity having that composite key.
#Entity
#IdClass(ReferentielId.class) // <- here
#Table(name = "Et_referentiel")
public class Referentiel implements Serializable {
// ...
}
Notice that it is required to have a class representing your composite keys, even if that does not help in your problem.

Resources