I have a few repositories that extend org.springframework.data.mongodb.repository.MongoRepository. I have added some methods for searching entities by different parameters, however, in any search, I only want to search for entities that have the active field set to true (have opted for marking as active=false in place of deleting). For example, two sample repositories would look like this:
interface XxRepository extends MongoRepository<Xx, String> {
Optional<Xx> findOneByNameIgnoreCaseAndActiveTrue(String name)
Page<Xx> findByActiveTrue(Pageable pageable)
Xx findOneByIdAndActiveTrue(String id)
}
interface YyRepository extends MongoRepository<Yy, String> {
Optional<Yy> findOneByEmailAndActiveTrue(String email)
}
Is there any way that would allow me not to add byActiveTrue\ andActiveTrue to each and every method and set it up somewhere in one place for all the queries?
Please try this. No need to provide implementation. Change 'active' and 'email' to your db column name.
interface YyRepository extends MongoRepository<Yy, String> {
#Query(value = "{ 'active' : { '$eq': true }, 'email' : ?0 }")
Optional<Yy> findOneByEmailAndActiveTrue(#Param("email") String email)
}
You can write an template query into the MongoRepository using Criteria.
Example
class abstract MongoRepository<W, X> {
protected Class<W> typeOfEntity;
private Class<X> typeOfId;
public MongoRepository() {
typeOfEntity = (Class<W>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
typeOfId = (Class<X>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
public W get(X id) throws Exception {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<W> q = cb.createQuery(typeOfEntity);
Root<W> r = q.from(typeOfEntity);
String idName = CommonEntity.findIdFieldName(typeOfEntity);
Predicate andClose = cb.and(cb.equal(r.get("active"), true));
q.where(cb.equal(r.get(idName), id), andClose);
return em.createQuery(q).getSingleResult();
}
}
After that, you stay confident into the object running way ans stereotyping to run the good type of request.
The findIdFieldNameis an method using the #Id to get the id field name.
public abstract class CommonEntity implements Serializable {
public static String findIdFieldName(Class cls) {
for (Field field : cls.getDeclaredFields()) {
String name = field.getName();
Annotation[] annotations = field.getDeclaredAnnotations();
for (int i = 0; i < annotations.length; i++) {
if (annotations[i].annotationType().equals(Id.class)) {
return name;
}
}
}
return null;
}
}
Related
Graphql Query
type Query {
filteredPersons(filter: Person) : PersonApi
}
PersonApi
type PersonApi{
items: [Person]
total_count: Int
}
Person
type Person{
id: ID!
firstName : String
lastName : String
}
Resolver
public PersonApi filteredPersons(Person filter) {
ExampleMatcher matcher = ExampleMatcher.matchingAny()
.withMatcher("firstName", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
.withMatcher("lastName", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase()));
Example<Person> filteredPerson = Example.of(filter, matcher);
Page<Person> personPage = repository.findAll(example);
if (personPage != null) {
return new PersonApi (
new ArrayList<>(personPage.getContent()),
NumberUtils.toInt(String.valueOf(personPage.getTotalElements())));
}
return new PersonApi(new ArrayList<>(), NumberUtils.INTEGER_ZERO);
}
}
Repository
#Repository
public interface PersonRepository extends JpaRepository<Person, Integer> {}
As show the code above, I'm trying to make a query with a filter parameter that is a type. Obviously, graphql expect this parameter to be an input.
This mean I have to use a dto (PersonFilter) and translate it into an entity in order to do the query.
private CsvRawEntity getPersonFrom(PersonFilter filter) {
Person entity = new Person();
entity.setFirstName(filter.getFirstName());
entity.setLastName(filter.getLastName());
return entity;
}
public PersonApi filteredPersons(Person filter) {
...
Person entity = getEntityFrom(filter);
Example<Person> example = Example.of(entity, customExampleMatcher);
personPage = repository.findAll(example, pageable);
PersonApi response = new PersonApi(
new ArrayList<>(personPage.getContent()),
NumberUtils.toInt(String.valueOf(personPage.getTotalElements())));
return response;
}
(which then work like a charm).
Same issue with outputif my type contains another type.
Question
Is there is a way or trick to make GraphQL to accept a type when it expect an input or an output ?
I am using Hibernate envers to audit entities in my application. I have separate _audit tables for each entity and then in those tables I have _mod boolean column to indicate if the field has changed or not.
But, I m not getting how to use that column in queries or even how do I get this data in the code?
e.g. following code gives list of audited persons. How do I check which data has changed?
List person = getAuditReader().createQuery()
.forEntitiesAtRevision(Person.class, 12)
.getResultList();
You can loop through all the fields of the given entity and find out if that field is changed or not.
Sample code snippet to loop through all the fields of Person entity for revision number 12 and find value of fields which are updated.
public void processFields() throws Exception {
for (Field field : Person.class.getDeclaredFields()) {
final Person changedEntity = fetchEntityForRevisionWhenPropertyChanged(Person.class, 12, field.getName());
if (changedEntity != null) {
// Updated field value for the field. This will list all fields which are changed in given revision.
final Object fieldValue = getField(changedEntity, field.getName());
}
}
}
private <T> Object getField(final T object, final String fieldName) throws Exception {
return new PropertyDescriptor(fieldName, object.getClass()).getReadMethod().invoke(object);
}
private <T> T fetchEntityForRevisionWhenPropertyChanged(final Class<T> entityClass, final int revisionNumber, final String propertyName) {
final List<T> resultList = getAuditReader()
.createQuery()
.forEntitiesModifiedAtRevision(entityClass, revisionNumber)
.add(AuditEntity.property(propertyName).hasChanged())
.addOrder(AuditEntity.id().asc())
.getResultList();
if (CollectionUtils.isNotEmpty(resultList)) {
return resultList.get(0);
} else {
return null;
}
}
In any case if you want to find previous revison of your entity for comparision, you can use following method:
public T getPreviousVersion(final Class<T> entityClass, final Long entityId, final Long currentRevisionNumber) {
final AuditReader reader = AuditReaderFactory.get(entityManager);
final Number previousRevision = (Number) reader.createQuery()
.forRevisionsOfEntity(entityClass, false, true)
.addProjection(AuditEntity.revisionNumber().max())
.add(AuditEntity.id().eq(entityId))
.add(AuditEntity.revisionNumber().lt(currentRevisionNumber))
.getSingleResult();
return Optional.ofNullable(previousRevision)
.map(previousRev -> reader.find(entityClass, entityId, previousRev))
.orElse(null);
}
You can try to use Hibernate interceptors. Here is good article about interceptors.
With interceptor you can create a callback which would be executed on entity updates/creation etc. It would be smth like this:
public class EntityInterceptor extends EmptyInterceptor {
#Override
public boolean onFlushDirty(Object entity,
Serializable id,
Object[] currentState,
Object[] previousState,
String[] propertyNames,
Type[] types) {
if ( entity instanceof YourEntity ) {
//do update stuff
return true;
}
return false;
}
}
You can compare currentState with previousState to collect information about entity changes and persist it to other tables.
I would like to pass parameter name as a parameter to other method, f.e:
I have class:
public class Foo() {
public Bar bar;
public Bar anotherBar;
public Bar yetAnotherBar;
public void doSomethingWithBar() {
common.doingSomething(
getMostImportantBarParameterName()
);
}
}
And in this class I would to have method:
public String getMostImportantBarParameterName() {
return Foo.bar;
}
but instead of returning value of bar, I would like to get a name of parameter bar, so it should just return "bar".
For now I have to do this that way:
public String getMostImportantBarParameterName() {
return "bar";
}
Why I wanna achieve something like that?
I am trying as much I can to avoid using strings in my code, cause in refactorization process I will bypass (skip) it accidentally.
But if I will have "hard coded" parameters that way, when I will later rename this parameter it will be automatically replaced in all instances by Eclipse IDE (Using LALT+LSHIFT+R)
Also my method: common.doingSomething() use parameter in runtime, So I won't get compilation error, which it makes hard to maintain this method.
I don't write unit test, cause I can't yet.
Please give me some help on this. Thanks
----------------- EDIT ------------------------
Real life usage.
I would like to have method to access database records in generic way.
Common database operation in my application is:
Getting records from TableName where Parameter = SomeValue
So I would like to have generic method for that in generic entity listed below:
#MappedSuperclass
public abstract class GenericModel<T extends GenericModel> {
#Transient protected Class<T> entityClass;
private List<T> getByParameterAndValue(String parameter, String value) {
List<T> entities = new ArrayList<T>();
String sqlString = "SELECT e FROM " + entityClass.getSimpleName() + " e WHERE e."+ parameter + " = :value";
TypedQuery<T> query = JPA.em().createQuery(sqlString, entityClass).setParameter("value", value);
try {
entities = query.getResultList();
} catch (NoResultException e1) {
entities = null;
} catch (Exception e) {
Index.toLog("error","Unsupported error in Generic model class in " + entityClass);
}
return entities;
}
which is extended by real entities f.e.:
public class User extends GenericModel<User> {
public String name;
public String email;
public String date;
public String department;
public List<User> getUsersByDepartments(String dep) {
return getByParameterAndValue("department", dep);
}
}
The problem is that in JPA TypedQuery:
TypedQuery<User> query = em.createQuery("SELECT u FROM User u WHERE u.department = :department", User.class);
return query.setParameter("department", department).getSingleResult();
First of all, I think you should reconsider your approach. Using field names like this (either by reflection or hard coded Strings) is not very robust. In general, reflection should be avoided if possible.
What are you trying to achieve? What will common.doingSomething be doing with the field name?
It might be better to model the importance explicitly with an accessor:
class Foo {
private Bar bar;
private Bar anotherBar;
private Bar yetAnotherBar;
public Bar getMostImportantBar() {
return bar;
}
}
To answer your question about generics. You can either select the field by its index or by its name. Both are not robust, for when you change the field name, the String used to get it via reflection will not change with it, and if you change the order of the fields, the index will be wrong.
Here's how to do it:
Class foo = Foo.class;
Field[] fields = foo.getFields();
// get by index
Field firstField = fields[0];
String firstFieldName = firstField.getName();
// get by name
Field barField = foo.getField("bar");
String barFieldName = barField.getName();
EDIT (after reading updated question):
In any Object Relational Mapping solution there is a boundary where the object-oriented realm ends and the relational realm begins. With your solution you are pulling that boundary a bit further into your code, in order to gain ease of use for your specific model classes and queries. The consequence of that is that you get more 'boiler plate' style code as part of your application (the GenericModel class) and that the boundary becomes more visible (the reference to a field by index or name using reflection). This type of code is generally harder to understand, test and maintain. On the other hand, once you get it right it doesn't change that often (if your assumption about the query type you usually need turns out to be valid).
So I think this is not a ridiculous use case for reflection, even though I myself would probably still stick to JPA and accept the similarity of the queries. With a good JPA framework, expressing these queries does not incur a lot of code.
About the hard-coded field names vs indexes, I advise you to go with the field names because they are easier to understand and debug for your successors. I would make sure the field name is expressed in the model class where the field resides, to make it as clear as possible that the two belong together, similar to the example you gave:
public class User extends GenericModel<User> {
public static final String FIELD_NAME = "name";
public static final String FIELD_EMAIL = "email";
public static final String FIELD_DATE = "date";
public static final String FIELD_DEPARTMENT = "department";
private String name;
private String email;
private String date;
private String department;
// the byXXX naming scheme is a quite common shorthand for lookups
public List<User> byDepartment(String department) {
return getByParameterAndValue(FIELD_DEPARTMENT, department);
}
BTW I think getByParameterAndValue cannot be private (must be at least default). Also I don't think you should initialize List<T> entities = new ArrayList<T>() at the start. You can do that in the catch(Exception e) to avoid unnecessary initialization if the query succeeds or returns no results. An your fields should be private (shown above).
Of course, this approach still results in one lookup method for each field. A different solution is to create a service for this and leave the model objects aenemic (without behavior):
public class DaoService {
public <T extends GenericModel> List<T> get(Class<T> entityClass, String fieldName, String value) {
List<entityClass> entities;
String sqlString = "SELECT e FROM " + entityClass.getSimpleName() + " e WHERE e."+ fieldName+ " = :value";
TypedQuery<T> query = JPA.em().createQuery(sqlString, entityClass).setParameter("value", value);
try {
entities = query.getResultList();
} catch (NoResultException e) {
entities = null;
} catch (Exception e) {
entities = new ArrayList<T>()
}
return entities;
}
}
Usage:
List<User> = daoService.get(User.class, User.FIELD_DEPARTMENT, value);
Here's another (slightly wild) idea I just had. Each model class is also a query template:
public abstract class ModelQuery<T extends ModelQuery> {
// TODO set from constructor
private Class<T> entityClass;
private Field[] allFields = entityClass.getFields();
private List<T> getByTemplate() {
List<Field> queryFields = new ArrayList<Field>();
String sql = selectFieldsAndCreateSql(queryFields);
TypedQuery<T> query = setQueryParameters(queryFields, sql);
return executeQuery(query);
}
private String selectFieldsAndCreateSql(List<Field> queryFields) throws IllegalAccessException {
StringBuilder sql = new StringBuilder();
sql.append("SELECT e FROM ")
.append(entityClass.getSimpleName())
.append("e WHERE ");
for (Field field : allFields) {
if (field.get(this) != null) {
sql.append("e.")
.append(field.getName())
.append(" = :")
.append(field.getName());
// keep track of the fields used in the query
queryFields.add(field);
}
}
return sql.toString();
}
private TypedQuery<T> setQueryParameters(List<Field> queryFields, String sql) throws IllegalAccessException {
TypedQuery<T> query = JPA.em().createQuery(sql, entityClass);
for (Field field : queryFields) {
query.setParameter(field.getName(), field.get(this));
}
return query;
}
private List<T> executeQuery(TypedQuery<T> query) {
List<T> entities;
try {
entities = query.getResultList();
} catch (NoResultException e1) {
entities = null;
} catch (Exception e) {
entities = new ArrayList<T>();
}
return entities;
}
}
Usage:
User userQuery = new User();
userQuery.setDepartment("finance");
List<User> results = userQuery.getByTemplate();
I guess there are more ways to skin this cat. Good luck with finding your optimal solution!
To get private field names
use foo.getDeclaredFields(); instead of foo.getFields();
Here are also you have some minor issue
fields[0] means, the first declared field, in which 0 is again hard coded
If you change the order of declaration then again it could be a trouble for you, which will never get refracted
I would recommend using
1.) The Class.forName() SPI logic where you can inject the expected business logic on the fly.
2.) The Spring DI with interfaces and implementations using auto wiring
What I am trying to achieve is to set a result transformer on a query defined in the following way:
String hqlQueryString = "select o.id as id, o.name as objectName from MyObject";
Class resultClass = MyObject.class;
Query query = session.createQuery(hqlQueryString).setResultTransformer(
new new AliasToBeanResultTransformer(resultClass));
List result = query.list();
MyObject looks like this:
public class MyObject {
private int id;
private String objectName;
public int getId() {
return id;
}
public void setId(int value) {
this.id = value;
}
public String getObjectName() {
return objectName;
}
public void setobjectName(String value) {
this.objectName = value;
}
}
The problem is, that although I have specified id and objectName to be my aliases, the actual query being executed uses different aliases. This causes my AliasToBeanResultTransformer to fail to construct MyObject because the aliases do not match property names.
Is it possible to obtain the aliases of the query generated by hibernate programmatically (I can set them to the alias to bean result tranformer)? I tried using query.getReturnAliases() but it returns the aliases that I have defined in my HQL, not the ones that Hibernate actually uses.
Can I explicitly specify the aliases in a createQuery statement? Currently I am trying not to use criterion for this to work, so I'd appreciate an approach that uses query objects, if such exists.
Update
Although the issue described above is invalid for standard HQL queries (see comments), it is valid when executing a native query. To be specific - native queries seemed to treat all aliases as lowecase strings (despite specific capitalization that might have been introduced in the query). This causes the AliasToBeanResultTransformer to fail when setting the properties, in cases where capitalization matters.
Actually don't need to implement another AliasToBeanResultTransformer , you can use addScalar(String columnAlias, Type type) to explicitly alias the columns of the native SQL:
String nativeSQL = "select o.id as id, o.name as objectName from MyObject";
List<MyObject> resultList = session.createSQLQuery(nativeSQL)
.addScalar("id" ,StandardBasicTypes.INTEGER)
.addScalar("objectName",StandardBasicTypes.STRING)
.setResultTransformer(new AliasToBeanResultTransformer(MyObject.class))
.list();
The transformer will then look for a MyObject class and expect it having the setters setId() and setObjectName() in order to populate the returned values to the MyObject instance
As for native queries, there was no simple solution involved. I had to look into the implementation of the AliasToBeanResultTransformer class and put a fix in there. I resolved the problem by creating a copy of the AliasToBeanResultTransformer class and modified the private initialize method of that class in the following way:
public class CaseInsensitiveAliasToBeanResultTransformer {
private void initialize(String[] aliases) {
this.aliases = new String[ aliases.length ];
setters = new Setter[aliases.length];
for ( int i = 0; i < aliases.length; i++ ) {
String alias = aliases[i];
if (alias != null) {
this.aliases[i] = alias;
setters[i] = CaseInsensitiveSetter.getSetter(resultClass, alias);
}
}
isInitialized = true;
}
}
This code differs mainly in the line CaseInsensitiveSetter.getSetter(resultClass, alias), where I have introduced a CaseInsensitiveSetter class I will describe below. This class implements the Setter interface and allows retrieving the setter method of a class using case-insensitive matching - so this will allow me to bind the lower-cased query aliases to the proper members of my result class. Here is the code of the custom setter (only the important lines are shown for brevity):
public class CaseInsensitiveSetter {
public static Setter getSetter(Class<?> theClass, String propertyName) {
Setter setter;
if (theClass == Object.class || theClass == null) {
setter = null;
} else {
setter = doGetSetter(theClass, propertyName);
if (setter != null) {
if (!ReflectHelper.isPublic(theClass, setter.getMethod())) {
setter.getMethod().setAccessible(true);
}
} else {
setter = doGetSetter(theClass.getSuperclass(), propertyName);
if (setter == null) {
Class<?>[] interfaces = theClass.getInterfaces();
for (int i = 0; setter == null && i < interfaces.length; i++) {
setter = doGetSetter( interfaces[i], propertyName);
}
}
}
if (setter == null) {
throw new PropertyNotFoundException(
"Could not find a setter for property " +
propertyName + " in class " + theClass.getName());
}
}
return setter;
}
// The actual work is done here
private static Setter doGetSetter(Class<?> resultClass, String propertyName) {
Method[] methods = resultClass.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
// only carry on if the method has 1 parameter
if ( methods[i].getParameterTypes().length == 1 ) {
String methodName = methods[i].getName();
if (methodName.startsWith("set")) {
String testStdMethod = methodName.substring(3);
if (testStdMethod.equalsIgnoreCase(propertyName)) {
Setter result = new CustomSetter(
resultClass, methods[i], propertyName);
return result;
}
}
}
}
return null;
}
}
The source of this is based on the BaseSetter class that comes with Hibernate, but is changed to support case-insensitive matching. Still, this one, and the original class that Hibernate uses, lacks performance because of the heavy usage of reflection.
Also, keep in mind that if the result class contains different properties with names that would be equal in case-insensitive comparison, then only one of them will be picked by the current code and it might not work as expected.
I use Couch DB with Ektorp at Spring 3. I read the document and have tried to implement examples. I am so new to these technologies. This is the point where I didn't understand:
#View( name = "all", map = "function(doc) { if (doc.type == 'Sofa' ) emit( null, doc._id )}")
public class SofaRepository extends CouchDbRepositorySupport<Sofa> {
#View( name = "avg_sofa_size", map = "function(doc) {...}", reduce = "function(doc) {...}")
public int getAverageSofaSize() {
ViewResult r = db.queryView(createQuery("avg_sofa_size"));
return r.getRows().get(0).getValueAsInt();
}
}
How does that wievs work and how to define them, what happens at that lines?
CouchDbRepositorySupport out of the box provides the following methods to the SofaRepository:
public void add(Sofa entity);
public void update(Sofa entity);
public void remove(Sofa entity);
public Sofa get(String id);
public Sofa get(String id, String rev);
public List<T> getAll();
public boolean contains(String docId);
By having this inline view annotation for CouchDbRepositorySupport:
#View( name = "all", map = "function(doc) { if (doc.type == 'Sofa' ) emit( null, doc._id )}")
You redefine the return from a getAll() method.
You also adding another method getAverageSofaSize() to your repository, with inline View:
#View( name = "avg_sofa_size", map = "function(doc) {...}", reduce = "function(doc) {...}")
which explicitly provides a query that db.queryView(createQuery("avg_sofa_size")); undersntad. db here is a CouchDbConnector that is able to create, delete, purge, find, etc..
Take a look at more documentation about defining in line Views