Spring-Data Repository #Query by class with generic class - java

assuming I have the following entities:
#Entity
public class Word { ... }
#Entity
public class Noun extends Word { ... }
#Entity
public class Verb extends Word { ... }
(Plus the usual Disriminator- and Join-Strategy stuff, simply assume that the entities work fine, which they do.)
I tried ...
public interface WordRepository extends CrudRepository<Word, Long>{
#Query("SELECT x FROM Word x WHERE type(x) = ?1")
<T extends Word> List<T> findByClass(Class<T> clz);
}
...but this gives me an exception, caused by:
org.hibernate.QueryException: Not all named parameters have been set: [1] [SELECT x FROM Word x WHERE type(x) = ?1]
One solution is to replace Class<T> with Class<?>, then the code works, but obviously that's not type-safe anymore since then I can write...
List<Verb> verbs = repository.findByClass(Noun.class);
...which runs, but obviously throws a ClassCastException whenever I try to access the verbs (since all the objects in the list are actually Nouns, not Verbs)
Is there any way to write this type-safe with spring-data, preferably without hardcoding all types into their own methods (findNouns, findVerbs, etc.) or defining Repositories for all types?
Edit: The problem seems to be the Parameter.isDynamicProjectionParameter(MethodParameter) method, that seems to define a special behavior for Class<T> parameters, so they are only used for dynamic parameters but cannot be given into the query itself. Hm. Wish anyone had a way around that.

I just faced the same issue. For your/every other userĀ“s information i filed a spring data jpa issue, which you folks can follow here:
https://jira.spring.io/browse/DATAJPA-1257

Related

Get Count for Generic Repository

I am trying to get the total number (count) for a given field from an index using a generic repository. The index mapping is huge and I do not wish to have an equivalent mapping on the Springboot side. The query is extremely simple;
http://localhost:9203/type_index/_count?q=person.name:john;
Should I have a generic class for the repository?
Can I use the #Query annotation?
How do I define which type and index that the repository should go to? Normally, this is done by the #Document(indexName="type_index) annotation on the Entity class but in this case, I will not have one.
#Repository
public interface GenericElasticsearchRepository<T, ID> extends ElasticsearchRepository<T, ID> {
#Query("{\"bool\": {\"must\": [{\"match\": {\"person.name\": \"?0\"}}]}}")
long countByName(String name);
}
When I use the above code, I am getting the following error;
ElasticSearchConfiguration: Invocation of init method failed;
nested exception is org.springframework.data.mapping.MappingException: Couldn't find PersistentEntity for type class java.lang.Object!
Thanks!
As for building a query method without having the entity you could go for a custom implementation (https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#repositories.single-repository-behavior).
Define an interface (not extending ElasticsearchRepository) like
interface CountByNameRepository {
long countByName(String name);
}
and an implementation (just typing this here, not using an IDE, ao there might be errors):
public class CountByNameRepositoryImpl {
private final ElasticsearchOperations operations;
public CountByNameRepositoryImpl(ElasticsearchOperations operations) {
this.operations = operations;
}
public long countByName(String name) {
Query query = new CriteriaQuery(Criteria.where("person.name").is(name));
return operations.count(query, IndexCoordinates.of("type-index"));
}
}
Your respository then will need to extend bot ElasticsearchRepository and CountByNameRepository, Spring Data Elasticsearch will use the provided implementation.
I have the index name hard coded here, as I don't know where you have access to this. You might be able to pass it as a second parameter to the method, but this depends on your concrete use case.

How to make sure that class sent as parameter has a certain annotation

I created a DAL few weeks ago which connects to Mongo Database.
When I want to query the database with a certain class, I need to know collection it belongs.
I thought about creating an annotation, that I'll put above each class which will contain the name of the related collection, and when I'll need to query the database I'll get the annotation value by reflection.
My question is how can I declare that the class that is sent to me has the annotation.
Pretty much like:
public List<T> query(Class<T extends Interface>)
only:
public List<T> query(Class<T has Annotation>)
Thanks.
You should either use interface or enumeration to do this. It is much simpler and more explicit.
But, if you want to experiment it is fine. Following should work
public List query(Class klass) {
for(Annotations a : klass.getAnnotations()) {
//Iterate and do stuff
}
//do other stuff
}

How can I add my own EnumValueMapperSupport

I'm trying to persist some enums in Hibernate and it looks like my two options for built in support are to use the name of the enum, which I would rather not do because it's string based instead of int based, or the ordinal of the enum, which I would rather not do because if I add one of the enum values at the top of the class later on, I break everything down the line.
Instead, I have an interface called Identifiable that has public int getId() as part of its contract. This way, the enums I want to persist can implement Identifable and I can know that they'll define their own id.
But when I try to extend EnumValueMapperSupport so I can utilize this functionality, I'm greeted with errors from the compiler because the EnumValueMapper interface and the EnumValueMapperSupport class are not static, and thus are expected to be locked into a given EnumType object.
How can I extend this functionality in Hibernate, short of rewriting a bunch of Hibernate code and submitting a patch. If I can't, is there another way to somehow store an enum based on something other than the ordinal or name, but instead on your own code?
In a related thought, has anyone personally been down this road and decided "let's see how bad the name mapping is" and just went with name mapping because it wasn't that much worse performance? Like, is it possible I'm prematurely optimizing here?
I'm working against Hibernate version 5.0.2-final.
At least for Hibernate 4.3.5 the EnumValueMapper is static - although private.
But you can extend EnumValueMapperSupport in an extension of EnumType:
public class ExampleEnumType extends EnumType {
public class ExampleMapper extends EnumValueMapperSupport {
...
}
}
To create an instance of this mapper you need an instance of your EnumType:
ExampleEnumType type = new ExampleEnumType();
ExampleMapper mapper = type.new ExampleMapper();
Or you create it inside your type:
public class ExampleEnumType extends EnumType {
public class ExampleMapper extends EnumValueMapperSupport {
...
}
public ExampleMapper createMapper() {
return new ExampleMapper();
}
}

Programmatically assess relationship between type variables in class hierarchy

Suppose I have a Java class hierarchy defined as follow:
interface Bar<T> {}
class Foo<A,B> implements Bar<B> {}
How can I programmatically assess (using reflection) that the type parameter of Bar in Foo is the second of foo's parameters and not the first (B instead of A)?
I've tried using TypeVariable#getName() in order to compare the names, but when I apply getGenericInterfaces() to Foo<A,B> I get Bar<T> and not Bar<B>
Solution (thanks to #LouisWasserman): use Foo.class.getGeenricInterfaces()[0].getActualTypeParameters() returns the correct TypeVariable (B instead of T, in the previous example)
well using TypeVariable#getName() return the type as it appears in the source code in your case it's normal to get Bar<T>. TypeVariable Doc
Using reflection in generic Classes can't help, because of Type Erasure. Erasure of Generic Types
I've the same issue in some personal projects, I tried to change the design of my class, have a look at the example below:
Instead of this:
public class Mapper<T> {
public Mapper(){
}
}
I used this:
public class Mapper {
private Class<?> entityClazz;
public Mapper(Class<?> entity){
this.entityClazz = entity
//Here I've donne all reflection issues i want !
}
}
You can use Class#isAssignableFrom() Doc to test assignability between Class Objects.
I hope this helps, good luck !

JPA Entity inheritance using DiscriminatorColumn with a 'wild' DiscriminatorValue

I'm trying to implement a inheritence relationship between JPA entities.
Borrowing the example from:
http://openjpa.apache.org/builds/1.0.2/apache-openjpa-1.0.2/docs/manual/jpa_overview_mapping_discrim.html
#Entity
#Table(name="SUB", schema="CNTRCT")
#DiscriminatorColumn(name="KIND", discriminatorType=DiscriminatorType.INTEGER)
public abstract class Subscription {
...
}
#Entity(name="Lifetime")
#DiscriminatorValue("2")
public class LifetimeSubscription
extends Subscription {
...
}
}
#Entity(name="Trial")
#DiscriminatorValue("3")
public class TrialSubscription
extends Subscription {
...
}
What I need to be able to do is have an additional entity that catches the rest, something like:
#Entity(name="WildCard")
#DiscriminatorValue(^[23])
public class WildSubscription
extends Subscription {
...
}
Where if it does not match LifetimeSubscription or TrialSubscription it will match WildSubscription.
It actually makes a bit more sense if you think of it where the wild is the superclass, and if there is not a more concrete implementation that fits, use the superclass.
Anyone know of a method of doing this?
Thanks!
The JPA API allows only plain values here, and for a reason: discriminator values are mapped to SQL WHERE:
SELECT ... WHERE kind = 1
If you could specify regular expressions here, it wouldn't be transferable to SQL, as it does not support such constructs.

Categories