In Blaze Persistence, is there a way to use an optional parameter in a subquery?
In my current use case, let's say I have topics, users, and a third table called topic_last_seen to record the date at which each user has last read each topic. I would like to create an entity view for the Topic entity which also maps that date for each topic with the current user given as an "optional parameter". This is roughly what I've been trying:
#EntityView(Topic.class)
public interface TopicView
{
#IdMapping
Long getId();
String getSummary();
#MappingParameter("currentUserId")
Long getCurrentUserId();
#MappingSubquery(LastSeenDateSubqueryProvider.class)
Date getLastSeenDate();
class LastSeenDateSubqueryProvider implements SubqueryProvider
{
#Override
public <T> T createSubquery(SubqueryInitiator<T> subqueryBuilder)
{
return subqueryBuilder.from(TopicLastSeen.class, "lastSeen")
.select("dateSeen")
.where("lastSeen.user.id").eqExpression("EMBEDDING_VIEW(currentUserId)")
.where("lastSeen.topic.id").eqExpression("EMBEDDING_VIEW(id)")
.end();
}
}
}
Unfortunately, this results in an exception:
java.lang.IllegalArgumentException: Attribute 'currentUserId' not found on type 'Topic'
Is there any way to use an optional parameter in a subquery like this at all? Or is there a different way to map this information? Changing the DB schema is not an option at this point.
Looks like I figured this out a possible suolution by trial and error. It seems that the "optional parameters" from EntityViewSetting can be used like regular parameters in JPQL:
return subqueryBuilder.from(TopicLastSeen.class, "lastSeen")
.select("dateSeen")
.where("lastSeen.user.id").eqExpression(":currentUserId")
.where("lastSeen.topic.id").eqExpression("EMBEDDING_VIEW(id)")
.end();
Unfortunately though, with this subquery mapping the currentUserId parameter is no longer "optional". When it is not set I get another exception:
org.hibernate.QueryException: Named parameter not bound : currentUserId
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 write two classes in one mongo collection, say One and Two. In these classes I have field someId. Then I want to get all One objects with specified someId. I created a method:
Page<One> findBySomeId(String id, Pageable pageable);
But get error:
org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate ...
As I see, mongo take both One and Two objects, and then trying to create One objects from them.
I tried write next:
Page<One> findOneBySomeId(String id, Pageable pageable);
and got the same error. How to write a proper method to retrieve only specified objects from mongo DB with spring data?
Your query should be like this
Page<IOne> findByOneSomeId(String id, Pageable pageable);
public interface IOne {
public One getOne();
}
You should limit the query by specifying something that distinguishes One and Two. For example, you can filter by:
the (internal) _type field that spring automatically persist, which is equal to the fully qualified class name (unless #TypeAlias is provided, when it could be whatever you set)
some other property that you know is present only in the One documents (Page<One> findBySomeIdAndPropertyExistsTrue(String id, Pageable pageable))
With Spring Data you can use following syntax :
One findFirstBySomeId(String someId);
Here is documentation about spring data(4.4.5. Limiting Query Results) where you find the explanation and examples: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#reference
List findAll(#Nullable Specification spec) return all value that matches the Specification .
Example: If in specification i have a parameter = 3.0 , findAll return me all the value matches.
So it return for example 543.0 , but i want just 3.0
Do you know a function that return me just the values that are exactly that?
If I understand correctly what you want to achieve, you don't need Specification.
Assuming that you want to fetch a list of YourObject based on the value of a field named price, you just have to add a method in the repository interface :
public interface YourObjectRepository extends JpaRepository<YourObject, Short>, JpaSpecificationExecutor<YourObject> {
public List<YourObject> findByPrice(Double price);
}
Spring will automaticaly implements the prototype as you want it : fetching all YourObjects that have field price equaks to the value given in parameter.
You need to call:
List findBySpecification(#Nullable Specification spec)
How your database tables look like?
After some reserch I don t discover any predefined method.
So I rezolved this with a #Query
#Query(Select t FROM Table t Where (t.price= :priceValue or null= :priceValue) and ( t.location= :locationValue or null= :locationValue)
List<Entity> getAllWithExactValue(#Param("priceValue") String priceValue, #Param("locationValue") String locationValue);
This let you have endpoints like this:
localhost:8080/param?locationValue=ClujNapoca&priceValue=1234
or
localhost:8080/param?priceValue=1234
or
localhost:8080/param?locationValue=ClujNapoca
And in return we will receive just variables with the exact value we asked
I'm trying to use spring PagingAndSortingRepository with a find MyEntity where field in fieldValues query as follows:
#Repository
public interface MyEntity extends PagingAndSortingRepository<MyEntity, String> {
List<MyEntity> findByMyField(Set<String> myField);
}
But of no success.
I expected the above function to return all entities whose field matches one of the field values but it only returns empty results.
Even though it seems like a pretty straight forward ability i could not find any reference to it in the docs.
Is / How that could be achieved?
Thanks.
This should indeed be possible if you are searching on a specific field within your entity and you want to return a list of all that field matches at least one entry in some collection. The documentation here says this can be achieved using the keyword In example: findByAgeIn(Collection<Age> ages) and is equivalent to … where x.age in ?1
From your post i'm not 100% sure if this is the use case you are after but give this a try. You will need to search on a specific field, so replace 'field' with whatever field you are searching on. If you are searching on multiple fields it may be possible to concatenate the results with the Or keyword and specify multiple fields that way.
#Repository
public interface MyEntity extends PagingAndSortingRepository<MyEntity, String> {
List<MyEntity> findByFieldIn(Set<String> myField);
}
http://docs.spring.io/spring-data/jpa/docs/current/reference/html/
Your method name has to be findByMyFieldIn so you got to add an In at the end to get a where ... in (..) query.
I know this has been asked before, but I wasn't able to implement a solution based on the information I found so far. so perhaps someone can explain it to me.
I have a table "status". It has two columns:id and name. id is a PK.
Instead of using a POJO Status, I would like to use an enum. I created such an enum as follows:
public enum Status {
NEW(1), READY(2), CLOSED(3);
private int id;
public void setId(int id) {
this.id = id;
}
public int getId() {
return this.id;
}
Status(int id) {
this.id = id;
}
}
here is my mapper
<select id="getStatusByName" resultType="Status" parameterType="String">
SELECT ls.id, ls.name
FROM status AS ls
WHERE ls.name = #{name}
</select>
but for some reason, when I try to retrieve an enum, something breaks, but no exception is thrown.
I have worked on this question from a couple of angles and here are my findings. Caveat: I did all these investigations using MyBatis-3.1.1, so things might have behaved differently in earlier versions.
First, MyBatis has a built-in EnumTypeHandler. By default, any time you specify a Java enum as a resultType or parameterType, this is what will handle that type. For queries, when trying to convert a database record into a Java enum, the EnumTypeHandler only takes one argument and tries to look up the Java enum value that corresponds to that value.
An example will better illustrate. Suppose your query above returns 2 and "Ready" when I pass in "Ready" as the argument. In that case, I get the error message No enum constant com.foo.Status.2. If I reverse the order of your SELECT statement to be
SELECT ls.name, ls.id
then the error message is No enum constant com.foo.Status.Ready. I assume you can infer what MyBatis is doing. Note that the EnumTypeHandler is ignoring the second value returned from the query.
Changing your query to
SELECT UPPER(ls.name)
causes it to work: the Status.READY enum is returned.
So next I tried to define my own TypeHandler for the Status enum. Unfortunately, as with the default EnumTypeHandler, I could only get one of the values (id or name) in order to reference the right Enum, not both. So if the database id does not match the value you hardcoded above, then you will have a mismatch. If you ensure that the database id always matches the id you specify in the enum, then all you need from the database is the name (converted to upper case).
Then I thought I'd get clever and implement a MyBatis ObjectFactory, grab both the int id and String name and ensure those are matched up in the Java enum I pass back, but that did not work as MyBatis does not call the ObjectFactory for a Java enum type (at least I couldn't get it to work).
So my conclusion is that Java enums in MyBatis are easy as long as you just need to match up the name from the database to the enum constant name - either use the built-in EnumTypeHandler or define your own if doing UPPER(name) in the SQL isn't enough to match the Java enum names. In many cases, this is sufficient, as the enumerated value may just be a check constraint on a column and it has only the single value, not an id as well. If you need to also match up an int id as well as a name, then make the IDs match manually when setting up the Java enum and/or database entries.
Finally, if you'd like to see a working example of this, see koan 23 of my MyBatis koans here: https://github.com/midpeter444/mybatis-koans. If you just want to see my solution, look in the completed-koans/koan23 directory. I also have an example there of inserting a record into the database via a Java enum.
You can use Custom TypeHandler for converting you result directly into ENUM so that you don't need to put all values in your database as UPPER CASE ENUM Names.
This is how your Status Enum Custom Handler will look like
public class StatusTypeHandler implements TypeHandler<Status> {
public Status getResult(ResultSet rs, String param) throws SQLException {
return Status.getEnum(rs.getInt(param));
}
public Status getResult(CallableStatement cs, int col) throws SQLException {
return Status.getEnum(cs.getInt(col));
}
public void setParameter(PreparedStatement ps, int paramInt, Status paramType, JdbcType jdbctype)
throws SQLException {
ps.setInt(paramInt, paramType.getId());
}
}
Define your TypeHandler to handle Status by default in your mybatis-config.xml by adding this code.
<typeHandlers>
<typeHandler javaType='Status' handler='StatusTypeHandler' />
</typeHandlers>
Now let us consider an example where you have following two functions in your Dao,
Status getStatusById(int code);
Status getStatusByName(String name);
Your mapper will look like
<select id="getStatusById" resultType="Status" parameterType="int">
SELECT ls.id
FROM status AS ls
WHERE ls.id = #{id}
</select>
<select id="getStatusByName" resultType="Status" parameterType="String">
SELECT ls.id
FROM status AS ls
WHERE ls.name = #{name}
</select>
Now as the resultType for both the mapper is Status, myBatis will use the CustomTypeHandler for this type i.e. StatusTypeHandler instead of EnumTypeHandler that it uses by default for Handling Enums, so there would be no need to maintain proper Enum names in your database.