With JPA/JPQL is it possible to manually load and Entity with its OneToMany Association? - spring-boot

Suppose I have 2 entities, Parent and Child, where the parent contains 1..n children:
#Entity
#Data #NoArgsConstructor
public class Parent {
#Id #GeneratedValue
private long id;
private String basic;
private String detail;
#OneToMany(fetch = FetchType.EAGER)
private Set<Child> children = new HashSet<>();
public Parent(String basic, String detail, Set<Child> children) {...}
}
#Entity
#Data #NoArgsConstructor
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
private long id;
private String basic;
private String detail;
public Child(String basic, String detail) {...}
}
I can load Parent-entities by using a JpaRepository:
public interface ParentRepository extends JpaRepository<Parent, Long> { }
// in Controller or Service
List<Parent> parents = parentRepository.findAll();
I'm trying to use projections. For this reason I want to know if it is possible to manually load the parents with a Query, so that i could only load needed data. In a perfect world this could look something like this:
// Dtos, the String detail is not required for both Parent and Child
#Value
public class ParentDto {
long id;
String basic;
Collection<Child> children;
public ParentDto(long id, String basic, Collection<ChildDto> children) {...}
}
#Value
public class ChildDto {
long id;
String basic;
public ChildDto(long id, String basic) {...}
}
and
public interface ParentRepository extends JpaRepository<Parent, Long> {
// Projection - *NOT WORKING*, this is what i would like
#Query("select p.id, p.basic, p.children.id, p.children.basic from Parent p")
List<ParentDto> findAllProjected();
}
This obviously fails, because it will join the all parents with their child, resulting in amount_of_parents * amount_of_their_children rows. The ParentDto would need a contrustor public ParentDto(long id, String basic, ChildDto child) {...}, so i had n ParentDtos per parent where n is the number of children the parent has.
Do i have to manually group the rows by the parents Id's and collect the ChildDtos together? Can I solve this using a subselect? I know Jpa solves this by selecting the parents and doing 1 select for each parent, fetching their children (when i use the autogenerated repository method). I was really hoping that projections/views would be much easier as they are such a fundamental requirement for me and most likely many other applications. Having to always load all data, load only the wanted data - but without their associations or having to load the associations manually for each entity seems like a bumper.
Note: i did try InterfaceProjection but do not want to use it due to it loading all data and stripping away unnessecary parts only when serializing it to json.
Thanks in regards!

Related

Spring Data JPA save child object with the ID of parent object

I have two objects, one parent and one child as follows :
#Entity
#Table(name="category")
public class CategoryModel {
private #Id #GeneratedValue Long id;
private String name;
#OneToMany(mappedBy="category", cascade=CascadeType.PERSIST)
private List<AttributeModel> attributes;
}
#Entity
#Table(name="attribute")
public class AttributeModel {
private #Id #GeneratedValue Long id;
private String name;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="category_id")
private CategoryModel category;
}
I also have dtos which maps to these model objects but I ommited them.
When I try to save a category object with this payload Attribute values are also created in the attribute table but with null category ids.
{
"name":"Chemicals",
"attributes":[
{"name": "volume"}, {"name":"humidity"}
]
}
What can I do to have my attribute values persisted into the database with the category id which is created before them?
First of all, this problem is not a "Spring Data JPA" problem, it is a JPA (probably Hibernate) problem.
Analysis
Since you left out the code for the controller and the JSON mapping, I have to guess a bit:
fact 1: The relationship between category and attributes is controlled by the attribute AttributeModel.category but not by CategoryModel.attributes. (That is how JPA works).
observation 2: Your JSON object define CategoryModel.attributes (i.e. opposite to how JPA works).
Without knowing your JSON mapping configuration and controller code, I would guess that the problem is: that your JSON mapper does not set the AttributeModel.category field when it deserialises the JSON object.
Solution
So you need to instruct the JSON mapper to set the AttributeModel.category field during deserialisation. If you use Jackson, you could use:
#JsonManagedReference and
#JsonBackReference
#Entity
#Table(name="category")
public class CategoryModel {
...
#JsonManagedReference
#OneToMany(mappedBy="category", cascade=CascadeType.PERSIST)
private List<AttributeModel> attributes;
}
#Entity
#Table(name="attribute")
public class AttributeModel {
...
#JsonBackReference
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="category_id")
private CategoryModel category;
}
I solved this by manually setting child object's reference to the parent object as follows :
public Long createCategory(CategoryDto categoryDto) {
CategoryModel categoryModel = categoryDto.toModel(true,true);
categoryModel.getAttributes().forEach(a -> a.setCategory(categoryModel));
return categoryRepository.save(categoryModel).getId();
}

GraphQL SPQR: id shall not be shown at creation

I use GraphQL SPQR with the entity
#Entity
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#GraphQLNonNull
#GraphQLQuery(name = "a", description = "Any field")
private String a;
// Getters and Setters
}
and the service
#Service
#Transactional
public class MyService {
#Autowired
private MyRepository myRepository;
#GraphQLMutation(name = "createEntity")
public MyEntity createEntity(#GraphQLArgument(name = "entity") MyEntity entity) {
myRepository.save(entity);
return entity;
}
}
In GraphiQL I am allowed to set the id:
mutation {
createEntity(entity: {
id: "11111111-2222-3333-4444-555555555555"
a: "any value"
}) {
id
}
}
But the id shall not be made editable to the user because it will be overwritten by the DB. It shall only be shown at the queries. I tried and added #GraphQLIgnore, but the id is shown all the same.
How can I hide the id at creation?
In GraphQL-SPQR version 0.9.9 and earlier, the private members are not scanned at all, so annotations on the private fields don't normally do anything. Incidentally, Jackson (or Gson, if so configured) is used to discover the deserializable fields on input types, and those libraries do look at private fields, so some annotations will appear to be working for input types. This is what is happening in your case. But, #GraphQLIgnore is not among the annotations that will work on a private field.
What you need to do is move the annotations to getters and setters.
#Entity
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#GraphQLIgnore //This will prevent ID from being mapped on the input type
//#JsonIgnore would likely work too
public void setId(UUID id) {...}
}
There's other ways to achieve this, but this is the most straight-forward.
Note: In the future versions of SPQR (post 0.9.9), it will be possible to place the annotations on private fields as well, but mixing (placing some annotations on a field and some on the related getter/setter) will not work.

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

Spring JPA saving distinct entities with composite primary key not working as expected, updates same entity

I have a logic that saves some data and I use spring boot + spring data jpa.
Now, I have to save one object, and after moment, I have to save another objeect.
those of object consists of three primary key properties.
- partCode, setCode, itemCode.
let's say first object has a toString() returning below:
SetItem(partCode=10-001, setCode=04, itemCode=01-0021, qty=1.0, sortNo=2, item=null)
and the second object has a toString returning below:
SetItem(partCode=10-001, setCode=04, itemCode=01-0031, qty=1.0, sortNo=2, item=null)
there is a difference on itemCode value, and itemCode property is belonged to primary key, so the two objects are different each other.
but in my case, when I run the program, the webapp saves first object, and updates first object with second object value, not saving objects seperately.
(above image contains different values from this post question)
Here is my entity information:
/**
* The persistent class for the set_item database table.
*
*/
#Data
#DynamicInsert
#DynamicUpdate
#Entity
#ToString(includeFieldNames=true)
#Table(name="set_item")
#IdClass(SetGroupId.class)
public class SetItem extends BasicJpaModel<SetItemId> {
private static final long serialVersionUID = 1L;
#Id
#Column(name="PART_CODE")
private String partCode;
#Id
#Column(name="SET_CODE")
private String setCode;
#Id
#Column(name="ITEM_CODE")
private String itemCode;
private Double qty;
#Column(name="SORT_NO")
private int sortNo;
#Override
public SetItemId getId() {
if(BooleanUtils.ifNull(partCode, setCode, itemCode)){
return null;
}
return SetItemId.of(partCode, setCode, itemCode);
}
#ManyToMany(fetch=FetchType.LAZY)
#JoinColumns(value = {
#JoinColumn(name="PART_CODE", referencedColumnName="PART_CODE", insertable=false, updatable=false)
, #JoinColumn(name="ITEM_CODE", referencedColumnName="ITEM_CODE", insertable=false, updatable=false)
})
private List<Item> item;
}
So the question is,
how do I save objects separately which the objects' composite primary keys are partially same amongst them.
EDIT:
The entity extends below class:
#Setter
#Getter
#MappedSuperclass
#DynamicInsert
#DynamicUpdate
public abstract class BasicJpaModel<PK extends Serializable> implements Persistable<PK>, Serializable {
#Override
#JsonIgnore
public boolean isNew() {
return null == getId();
}
}
EDIT again: embeddable class.
after soneone points out embeddable class, I noticed there are only just two properties, it should be three of it. thank you.
#Data
#NoArgsConstructor
#RequiredArgsConstructor(staticName="of")
#Embeddable
public class SetGroupId implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#NonNull
private String partCode;
#NonNull
private String setCode;
}
Check howto use #EmbeddedId & #Embeddable (update you might need to use AttributeOverrides in id field, not sure if Columns in #Embeddable works).
You could create class annotated #Embeddable and add all those three ID fields there.
#Embeddable
public class MyId {
private String partCode;
private String setCode;
private String itemCode;
}
Add needed getters & setters.
Then set in class SetItem this class to be the id like `#EmbeddedId´.
public class SetItem {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name="partCode",
column=#Column(name="PART_CODE")),
#AttributeOverride(name="setCode",
column=#Column(name="SET_CODE"))
#AttributeOverride(name="itemCode",
column=#Column(name="ITEM_CODE"))
})
MyId id;
Check also Which annotation should I use: #IdClass or #EmbeddedId
Be sure to implement equals and hashCode in SetGroupId.
Can you provide that class?

Spring Data REST convert/show EmbeddedId including ManyToOne

I have a question for my project in Spring Data REST.
My model includes two tables with EmbeddedIds.
The first table (name=B) consist of two integers.
The second table (name=A) consist of a simple FK and the model of B (includes the EmbeddedId).
Now, if I make a request for table B, I'll get the two IDs.
However, if I make a request for table A, I wont get the IDs..
So I overrid the toString()-method in my EmbeddedId-class, to return at least the IDs right in the URI-link.
I read about BackendIdConverter or Spring core.converter and tried to convert the IDs right, but I wasn't able to reach my goal (got errors). So now, I need your help!
To fully understand my problem, here's my structure (as demo):
#Embeddable
public class IDsFromA implements Serializable {
#ManyToOne
#JoinColumn(name="cID")
private C c;
#ManyToOne
#JoinColumns({
#JoinColumn(name="b_1", referencedColumnName="b_1"),
#JoinColumn(name="b_2", referencedColumnName="b_2")
})
private B b;
}
#Embeddable
public class IDsFromB implements Serializable {
private int b_1;
private int b_2;
}
#Entity
public class A {
#EmbeddedId
private IDsFromA idsFromA;
// ...
}
#Entity
public class B {
#EmbeddedId
private IDsFromA idsFromA;
// ...
}
#Entity
public class c {
#Id
private int cID;
// ...
}
The Jackson JSON serializer will by default also serialize any public get..() methods. So you can simply add a couple of methods to return the relevant data and the values should be in the response:
e.g.
#Entity
public class A {
#EmbeddedId
private IDsFromA idsFromA;
public int getValueOne(){
return idsFromA.getB().getIdsFromB().getB_1();
}
public int getValueTwo(){
return idsFromA.getB().getIdsFromB().getB_2();
}
}

Resources