I need to know if it's possible for me to convert my JQPL query result into a DTO.
The query result is a Array of Arrays like this Json:
[
[
ModuleID: number,
ModuleName: string,
ToolId: number,
ToolName: string,
Enabled: boolean
],
]
And I want to convert into this DTO:
public class ModuleDTO {
private Long ModuleID;
private String ModuleName;
private List<ToolsDTO> Tools;
}
public class ToolsDTO {
private Long ToolId;
private String ToolName;
private Boolean Enabled;
}
You can see that the last three are children of the module, that means that in the search there may be repeated modules, but all children must be within the same list.
This is a perfect use case for Blaze-Persistence Entity Views.
Blaze-Persitence is a query builder on top of JPA which supports many of the advanced DBMS features on top of the JPA model. I created Entity Views on top of it 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.
You didn't specify an entity model so I'm going to assume some things here. A mapping could look as simple as the following
#EntityView(Module.class)
interface ModuleDTO {
#IdMapping
Long getModuleId();
String getModuleName();
List<ToolsDTO> getTools();
}
#EntityView(Tools.class)
interface ToolsDTO {
#IdMapping
Long getToolId();
String getToolName();
Boolean getEnabled();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
ModuleDTO dto = entityViewManager.find(entityManager, ModuleDTO.class, id);
But the Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
It will only fetch the mappings that you tell it to fetch
Related
Let's say I have a simple REST app with Controller, Service and Data layers. In my Controller layer I do something like this:
#PostMapping("/items")
void save(ItemDTO dto){
Item item = map(dto, Item.class);
service.validate(item);
service.save(item);
}
But then I get errors because my Service layer looks like this:
public void validate(Item item) {
if(item.getCategory().getCode().equals(5)){
throw new IllegalArgumentException("Items with category 5 are not currently permitted");
}
}
I get a NullPointerException at .equals(5), because the Item entity was deserialized from a DTO that only contains category_id, and nothing else (all is null except for the id).
The solutions we have found and have experimented with, are:
Make a special deserializer that takes the ids and automatically fetches the required entities. This, of course, resulted in massive performance problems, similar to those you would get if you marked all your relationships with FetchType.EAGER.
Make the Controller layer fetch all the entities the Service layer will need. The problem is, the Controller needs to know how the underlying service works exactly, and what it will need.
Have the Service layer verify if the object needs fetching before running any validations. The problem is, we couldn't find a reliable way of determining whether an object needs fetching or not. We end up with ugly code like this everywhere:
(sample)
if(item.getCategory().getCode() == null)
item.setCategory(categoryRepo.findById(item.getCategory().getId()));
What other ways would you do it to keep Services easy to work with? It's really counterintuitive for us having to check every time we want to use a related entity.
Please note this question is not about finding any way to solve this problem. It's more about finding better ways to solve it.
From my understanding, it would be very difficult for modelMapper to map an id that is in the DTO to the actual entity.
The problem is that modelMapper or some service would have to do a lookup and inject the entity.
If the category is a finite set, could use an ENUM and use static ENUM mapping?
Could switch the logic to read
if(listOfCategoriesToAvoid.contains(item.getCategory())){ throw new IllegalArgumentException("Items with category 5 are not currently permitted"); }
and you could populate the listOfCategoriesToAvoid small query, maybe even store it in a properties file/table where it could be a CSV?
When you call the service.save(item), wouldn't it still fail to populate the category because that wouldn't be populated? Maybe you can send the category as a CategoryDTO inside the itemDTO that populated the Category entity on the model.map() call.
Not sure if any of these would work for you.
From what I can gather the map(dto, Item.class) method does something like this:
Long categoryId = itemDto.getCategoryId();
Category cat = new Category();
cat.setId(categoryId);
outItem.setCategory(cat);
The simplest solution would be to have it do this inside:
Long categoryId = itemDto.getCategoryId();
Category cat = categoryRepo.getById(categoryId);
outItem.setCategory(cat);
Another option is since you are hardcoding the category code 5 until its finished, you could hard-code the category IDs that have it instead, if those are not something that you expect to be changed by users.
Why aren't you just using the code as primary key for Category? This way you don't have to fetch anything for this kind of check. The underlying problem though is that the object mapper is just not able to cope with the managed nature of JPA objects i.e. it doesn't know that it should actually retrieve objects by PK through e.g. EntityManager#getReference. If it were doing that, then you wouldn't have a problem as the proxy returned by that method would be lazily initialized on the first call to getCode.
I suggest you look at something like Blaze-Persistence Entity Views which has first class support for something like that.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Item.class)
// You can omit the strategy to default to QUERY when using the code as PK of Category
#UpdatableEntityView(strategy = FlushStrategy.ENTITY)
public interface ItemDTO {
#IdMapping
Long getId();
String getName();
void setName(String name);
CategoryDTO getCategory();
void setCategory(CategoryDTO category);
#EntityView(Category.class)
interface CategoryDTO {
#IdMapping
Long getId();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
ItemDTO a = entityViewManager.find(entityManager, ItemDTO.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<ItemDTO> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!
And in your case of saving data, you can use the Spring WebMvc integration
that would look something like the following:
#PostMapping("/items")
void save(ItemDTO dto){
service.save(dto);
}
class ItemService {
#Autowired
ItemRepository repository;
#Transactional
public void save(ItemDTO dto) {
repository.save(dto);
Item item = repository.getOne(dto);
validate(item);
}
// other code...
}
I have a large entity-class with many, many fields and a projection-class which should be a part of the large one.
Everything works fine, except the #OneToMany field. The #OneToMany field should be a list of addresses, but when converting it to the projection-class I always get the error "Unable to locate appropriate constructor [...] Expected arguments are: long, [...], ***.entity.Address".
The converter is searching for a single address Object instead of a List of Address Objects, and I don't understand why. I use lombok #Data and #AllArgsConstructor, so Getter and Setter should be there.
#Entity
#Data
public class House implements Serializable {
#Id
#Column(name = "ID", precision = 5)
private Long id;
#OneToMany
#JoinColumn(name = "HouseID")
private List<Address> identAdressen;
}
/// ----------------
#Data
#AllArgsConstructor
public class HouseView {
private Long objnr;
private List<Address> identAdressen;
}
When I remove the "List" in the HouseView-class it works as long as there are only single addresses, but when there are multiple it crashes too.
I don't get it why he tries to find a HouseView-Constructor with an single address-object, when in both classes there are Lists of Addresses.
Let trying not using lombok and see if it works
Spring Data Projections do not support collections but I think 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 or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(House.class)
public interface HouseView {
#IdMapping
Long getObjnr();
Set<AddressView> getIdentAddressen();
#EntityView(Address.class)
interface AddressView {
#IdMapping
Long getId();
String getName();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
HouseView a = entityViewManager.find(entityManager, HouseView.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<HouseView> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!
What you can do is to write custom query on HouseRepository as
#Query("SELECT new com.packagename.HouseView(hos) from House hos")
then house view class can be as follows
public class HouseView {
private Long objnr;
private List<Address> identAdressen;
public HouseView(House house) {
identAdressen = house.identAdressen;
}
}
Hope it will work :) You can add more fields into constructor from join tables if required. It might help others coming late on post.
I need in metainfo for entity (hierarchy level from recursive sql query) so i created next projection
#Value
public class ProjectionObject{
MyEntity entity;
int metainfo;
}
#Query(value = "select my_entity.*, 1 as metainfo from my_entities", nativeQuery = true)
List<ProjectionObject> findSome();
But it returns List<List> but i expect List.
As result i what to manipulate with ProjectionObject#entity as with managed (by Entity Manager) ProjectionObject#entity, in other word i want to get managed entity with metainfo once without getting f.e. hierarchy Ids and after get entities
I'm not sure Spring Data Projections supports that.
However, this is a perfect use case for Blaze-Persistence Entity Views.
Blaze-Persistence is a query builder on top of JPA which supports many of the advanced DBMS features on top of the JPA model. I created Entity Views on top of it 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 projection with Entity Views could look as simple as the following
#EntityView(MyEntity.class)
interface ProjectionObject {
#Mapping("this")
MyEntity getEntity();
#Mapping("1")
int getMetaInfo();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
ProjectionObject dto = entityViewManager.find(entityManager, ProjectionObject.class, id);
But the Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
List<ProjectionObject> findAll();
You can also make use of updatable entity views which allows you to eliminate the entity type completely, which reduces the amount of data fetched and flush back only the parts that you actually want to change:
#UpdatableEntityView
#EntityView(MyEntity.class)
interface ProjectionObject {
#IdMapping
Integer getId();
String getName();
void setName(String name);
#Mapping("1")
int getMetaInfo();
}
Now you can fetch that object and then after changing the state flush it back to the database:
ProjectionObject o = repository.findOne(123);
o.setName(o.getName().toUpperCase());
repository.save(o);
And it will only flush back the name as you will see in the SQL.
So I've been using Spring Data Repositories most of the time. But I've reached a use-case where I cannot use my Spring Repository to retrieve the entity that I need to return to the client.
So I have my class ResourceEntity which is a Spring Data Entity. And I'd like to return this entity as a ResourceProjectioninterface.
#Getter
#Setter
#NoArgsConstructor
#Entity
public class ResourceEntity{
private Long id;
private String name;
private String anotherFieldThatIsNotInTheProjection;
}
public interface ResourceProjection {
Long getId();
String getName();
}
Usually with a Spring Repository, I'd define something like that :
public interface ResourceRepository extends PagingAndSortingRepository<ResourceEntity, Long> {
Optional<ResourceProjection> getById(Long id);
}
In this case I can't use the "automatic proxy" generated by Spring Data to automatically implement my projection with the entity's data.
So my question is : Is there a way to "manually" convert the entity to the projection ?
Another solution I thought of is returning the entity and using Jackson annotations like #JsonIgnore to prevent some of my data to be returned, but that is not optimal with the way my code was written.
Otherwise I can always create a DTO class that will fill up with the data from the Entity. But as I have already created my projection for other purposes, I would like avoid creating a second "DTO".
You can do the projection programmatically in this way:
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
//...
resourceEntity = //find resource
ProjectionFactory pf = new SpelAwareProxyProjectionFactory();
ResourceProjection rp = pf.createProjection(ResourceProjection.class, resourceEntity)
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(ResourceEntity.class)
interface ResourceProjection {
#IdMapping
Long getId();
String getName();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
ResourceProjection dto = entityViewManager.find(entityManager, ResourceProjection.class, id);
But the Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Implementing interface projection using native sql query
1. OurResourceEntity.java class
#Getter
#Setter
#NoArgsConstructor
#Entity
public class ResourceEntity{
private Long id;
private String name;
}
2. Creating projection Interface name ProjectedResource.java, which maps data collected by the SQL query from repository layer method
public interface ProjectedResource {
Long getId();
String getName();
String getAnotherProperty();
}
3. Creating Repository layer method: getProjectedResources()
We are considering the database table name is resource.
We are only fetching id and name here.But using interface projection we can change the properties name according to our desire.
#Query(name="select id, name, anotherProperty from resource", nativeQuery=true)
List<ProjectedResource> getProjectedResources();
Hope the issue will be resolved!
I have an issue with mapping HQL query to complex DTO. By complex DTO I mean DTO that composites another DTOs / collection DTOs. I tried to find solution but didn't find anything that can suit my requirements. For instance there is a DTO (I omit properties for simplicity):
public class Consignment {
private List<OrderData> orderData;
private List<AttributesData> attributesData;
private CostData costData;
public Consignment(List<OrderData> orderData, List<AttributesData> attributesData, CostData costData) {
//setting fields
}
}
The HQL lets to create DTO object through constructor by passing columns from result set as parameters. Is it possible to create subqueries or smth. Else to fetch data in collection and then pass it as arguments in main DTO? It looks that it is impossible but maybe I missed something.
Otherwise there is only the way to do that is to fetch data in separate HQL queries and then create main DTO as plain Java object. If anyone has alternative ideas how to do that - please share your ideas.
You can fetch other data in the same query like this:
FROM Consignment cons JOIN FETCH cons.orderData ord
I created Blaze-Persistence Entity Views for exactly that use case. You essentially define DTOs for JPA entities as interfaces and apply them on a query. It supports mapping nested DTOs, collection etc., essentially everything you'd expect and on top of that, it will improve your query performance as it will generate queries fetching just the data that you actually require for the DTOs.
The entity views for you example could look like this
#EntityView(ConsignmentEntity.class)
interface Consignment {
List<OrderData> getOrderData();
List<AttributesData> getAttributesData();
CostData getCostData();
}
#EntityView(OrderDataEntity.class)
interface OrderData {
// attributes of OrderDataEntity that you need
}
#EntityView(AttributesDataEntity.class)
interface AttributesData {
// attributes of AttributesDataEntity that you need
}
#EntityView(CostDataEntity.class)
interface CostData {
// attributes of CostDataEntity that you need
}
Querying could look like this
List<Consignment> dtos = entityViewManager.applySetting(
EntityViewSetting.create(Consignment.class),
criteriaBuilderFactory.create(em, ConsignmentEntity.class)
).getResultList();