Is there a way to map properties of Sort objects? Just a little example:
a Dto
public class OrderDto {
private long totalAmount;
private long openAmount;
}
and an entity
#Entity
public class Order {
private long amount;
}
and a controller
#RestController
public class OrderController {
#GetMapping("/orders")
public Page<OrderDto> findOrders(Pageable pageable) {
// Do something
}
}
I would now call /orders?sort=totalAmount,desc
Is it possible to map the sort property, in this case the totalAmount from the Dto to only amount to use this for a generated JPA query in a charmant way?
I don't know exactly if I get your question right, but...
Do I understand you correct, that you want to map the request-parameter sort with the value totalAmount,desc to be available through the Sort object of the given Pageable object as amount (with order desc)?
Because of the names you mention in your post, I expect that you have Spring Data on the classpath.
IDKFS but maybe class SortHandlerMethodArgumentResolverCustomizer could help you to customize the SortHandlerMethodArgumentResolver the way, that you can map the request-parameter like you want to have.
If not, it should be possible to provide a #Bean of type SortHandlerMethodArgumentResolver that implements the behavior you want to have.
I'm not completely sure if the framework allows to override the specific part you need to be customized but it is IMO worth to have a look. :)
Related
I'm trying to customize the springdoc-openapi, make it can work with my framework, but I meet two problems.
1. How to treat methods that do not start with is/get as properties of Model?
If users use my ORM framework by Java language, the property getters in the entity interface can either start with is/get like a traditional Java Bean, or don't start with is/get like a Java record, for example
#Entity
public interface Book {
#Id
long id();
String name();
int edition();
BigDecimal price();
#ManyToOne
BookStore store();
#ManyToMany
List<Author> authors();
}
Here, the wording that does not start with is/get is used, which looks like a java record, not a traditional java bean.
However, doing this will cause swagger-ui to think that the model doesn't have any attributes. So I have to change the behavior of swagger.
After some research, I found that this behavior can be changed using io.swagger.v3.core.converter.ModelConverter, which is the most likely solution.
However, springdoc-openapi does not explain in detail how to use ModelConverter in the documentation. Ultimately, this goal was not achieved.
2. How to control the shape of dynamic objects in HTTP response?
My ORM is GraphQL-style, its entity objects are dynamic so that data structures of arbitrary shapes can be queried, just like GraphQL does. For example
#RestController
public class BookController {
#AutoWired
private JSqlClient sqlClient;
// Query simple book objects
#GetMapping("/books")
public List<Book> books() {
return sqlClient.getEntities().findAll(Book.class);
}
// Query complex book objects
#GetMapping("/books/details")
public List<Book> bookDetails() {
return sqlClient.getEntities().findAll(
// Like the request body of GraphQL
BookFetcher$
.allScalarFields()
.store(
BookStoreFetcher.$.allScalarFields()
)
.authors(
AuthorFetcher.$.allScalars()
)
);
}
}
The first query returns a list of simple book objects in the format {id, name, edition, price}
The second query returns a list of complex book objects in the format {id, name, edition, price, store: {id, name, website}, authors: {id, firstName, lastName, gender}}
Dynamic objects can vary in shape, and these are just two special cases.
I expect swgger to tell the client the shape of the object returned by each business scenario. So, I defined an annotation called #FetchBy. It should be used like this
#RestController
public class BookController {
private static final Fetcher<Book> BOOK_DETAIL_FETCHER =
BookFetcher$
.allScalarFields()
.store(
BookStoreFetcher.$.allScalarFields()
)
.authors(
AuthorFetcher.$.allScalars()
);
#AutoWired
private JSqlClient sqlClient;
#GetMapping("/books")
public List<Book> books() {
return sqlClient.getEntities().findAll(Book.class);
}
#GetMapping("/books/details")
public List<#FetchBy("BOOK_DETAIL_FETCHER") Book> bookDetails() {
return sqlClient.getEntities().findAll(BOOK_DETAIL_FETCHER);
}
}
Declare the shape of the complex object as a static constant.
The #FetchBy annotation uses the constant name to tell swgger the shape of the returned dynamic object.
After some research, I found that this behavior can be changed using org.springdoc.core.customizers.OperationCustomizer, which is the most likely solution.
However, I found that the schema tree of swagger is not consistent with the generic type definition tree in the java language. For example, Spring's ResponseEntity<> wrapper will be ignored by swagger and will be not parsed as a node of schema tree. Theoretically speaking, this ability of swagger can be customized infinitely, so the two trees may not always be consistent and difficult to analyze.
For example in rust, we can use type-safe json! macro like this:-
let value = json!({
"user": json!({
"data" : json!({"
filled": false
})
})
});
What is the similar way in java without creating POJO or string literal?
In spring, you normally don't need lines like your RUST-example. Spring is doing a lot behind the scenes for you.
Two common use-cases
1. Provide a REST-API
Normally, when you use spring, then you want to build a REST-API. A simple API looks like this:
// User.java
#Data // <- Lombok annotation, which creates getter/setter/equals/hashcode for us
public class User {
private String name;
}
// UserController.java
#Controller
#RequiredArgsConstructor
#RequestMapping("/users")
public class UserController {
private final UserRepository userRepository;
#GetMapping("/{id}")
public User getUser(#PathVariable("id") String id) {
// spring will serialize the user automatically for you
// there is no need to build a json yourself
return userRepository.getById(id);
}
2. Consume a REST-API
On the other hand, we want to consume a REST-API. But also here: Spring and his ecosystem is doing the JSON-part for you.
// User.java
#Data
public class User {
private String name;
}
// some random method in a class with a webClient
private User consumeRest() {
// even here: no manual json parsing at all
// Spring-magic is working for you
return this.webClient.get()
.uri(someUriHere)
.retrive()
.bodyToMono(User.class)
.block();
}
tl;dr
Spring (and java) requires a strong typing. You need almost always a type-class (aka POJO). The classes can be really complex. Hundred of fields and nested fields? No problem.
There are some "hacks" to bypass the type. Like the link in the comments or a Map<String, Object> but you will loose the type-safety.
If that is too much "boilerplate", than Java is not perhaps the right choice for you.
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...
}
It's about passing interface of DTO to DAO.
For example I have following code
public interface User {
String getName();
}
public class SimpleUser implements User {
protected String name;
public SimpleUser(String name) {
this.name = name;
}
#Override
public String getName() {
return name;
}
}
// Mapped by Hibernate
public class PersistentUser extends SimpleUser {
private Long id;
// Constructor
// Getters for id and name
// Setters for id and name
}
I'm using generic DAO. Is it ok if I create DAO with using interface User instead PersistentUser?
User user = new PersistentUser(name);
UserDao.create(user);
I read a lot of topics on stack but not figured out is this approach ok or no. Please help me. Maybe this is stupid and I can achive only problems.
About separating beans.
I did this because some classes I want to share via API module, that can be used outside to create entities and pass them to my application. Because they uses interface I developed so I can pass them to my DAO for persisting.
Generally, I would say it is ok, but there are a few hidden problems. A developer could cast the object down or access some state via a toString method that shouldn't be accessible. If you don't be careful, it could happen that state is serialized as JSON/XML in webservices that shouldn't be serialized. The list goes on.
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 your example could look like this
#EntityView(PersistentUser.class)
interface User {
String getName();
}
Querying could look like this
List<User> dtos = entityViewManager.applySetting(
EntityViewSetting.create(User.class),
criteriaBuilderFactory.create(em, PersistentUser.class)
).getResultList();
Right now I have a class BaseSchedule It is used by 4 classes (composition). I would like to validate in two use classes and not in the others. I am a little stumped on how to do so.
My BaseSchedule looks like the following:
#Embeddable
#DatesStartBeforeEnd(start = "startDateTime", end = "endDateTime")
public class BaseSchedule implements Serializable {
private Date startDateTime;
private Date endDateTime;
}
I would like to check to make sure that the startDateTime and endDateTime are not null when I go to persist the data to my database. Normally I would provide a #NotNull to each of the fields.
public class TimeSlot implements Scheduleable {
#Embedded
private BaseSchedule schedule;
}
But... in the case of my TimeSlotTemplate I do not want validation as I know it will be null.
public class TimeSlotTemplate extends SchedulableClassTemplateEvent {
#Embedded
private BaseSchedule schedule;
}
If you're using Hibernate Validator as your BV provider, one solution might be to use a custom default group sequence provider.
For this to work, your BaseSchedule object would have to know about the "role" it currently has, e.g. by passing an enum with values such as SlotSchedule, TemplateSchedule etc. to its constructor. Depending on the role a group sequence provider could then determine the sequence to validate and return a sequence which does not contain the #NotNull constraints if the role is TemplateSchedule.
Not that this approach requires that you use the default sequence during JPA lifecycle validation.
I think this could be done using the #PrePersist Annotation.
#PrePersist
public void prePersist(){
if(isPerformNullableCheck()){
// check for null values and raise an error if invalid
}
}
in your TimeSlotTemplate, you can set the performNullableCheck property to false...
Another way might be to add a class into the hierarchy.
BaseSchedule contains the properties needed and e.g. ValidatedSchedule (extends BaseSchedule) overrides those and performs the notnull checks. Don't know whether this works or not. Also this would probably not be the best solution for your problem..?