BeanPropertySqlParameterSource with pojo annotations - java

I have the following
#Data //Lombok annotation to generate getters and setters
#Entity
public class TradeLog {
#Id
#Column(name="P_TRADE_ID")
private String tradeId;
}
tradeLog.setTradeId("1");
SqlParameterSource insertParam = new BeanPropertySqlParameterSource(tradeLog);
System.out.println(insertProc.execute(insertParam));
And I get this error Exception in thread "main" org.springframework.dao.InvalidDataAccessApiUsageException: Required input parameter 'P_TRADE_ID' is missing
I know that I could do a mapping directly on the jdbc template, but is there any way I could use the java persistence annotations or something like that to handle that for me?

JDBC template does not provide such thing, But you can easily use reflection to scan all the #Column fields and populate its value to MapSqlParameterSource which is another implementation of SqlParameterSource. Something like below , you can wrap it to a function for convenient :
tradeLog.setTradeId("1");
//Create MapSqlParameterSource based on tradeLog
MapSqlParameterSource param = new MapSqlParameterSource();
for(Field field : TradeLog.class.getDeclaredFields()) {
Column column = field.getAnnotation(Column.class);
if(column != null) {
field.setAccessible(true);
param.addValue(column.name(), field.get(tradeLog));
}
}
insertProc.execute(param)

Related

My annotation created with reflection API return null even when there is data

so I'm building ORM Manager from scratch with reflection API, everything works fine but there is one annotation #Table that i created and it doesn't want to work as it should. So annotation looks like this
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Table {
String value() default "";
}
and I'm using it on my model class as so
#Entity
#Table(value = "Books")
public class Book {
and now i'm trying to get that value passed as a parameter like this
#Override
void register(Class... entityClasses) throws SQLException {
for (Class entityClass : entityClasses) {
if (entityClass.isAnnotationPresent(Entity.class)) {
String tableName;
if (entityClass.isAnnotationPresent(Table.class)) {
tableName = entityClass.getClass().getDeclaredAnnotation(Table.class).value();
} else {
tableName = entityClass.getSimpleName();
}
Dont mind #Entity annotation it works fine. Only problem is that #Table annotation always returns null so it throws NullPointerException and crashing.
Where is a problem, how can i solve this. I already implemented #Column annotation and when i use .value() on #Column annotation it works fine so no clue why it doesn't work for #Table
#EDIT
So i just discovered that's not Value returning null but .getAnnotation but still no clue why is it returning null
Cannot invoke "teamblue.annotations.Table.value()"
because the return value of "java.lang.Class.getAnnotation(java.lang.Class)" is null
The problem was a foreach loop, i had to pass generic types in it like that
for (Class<?> entityClass : entityClasses)
Not sure how exactly but it did work so happy about it

How to get Java's object field's name from JSON fields name

I want to filter out some fields in the response. Filtering should be done before the Java object is serialised into the JSON.
Consider:
public class Entity {
#JsonProperty("some_property")
String someProperty;
#JsonProperty("nested_entity")
#OneToMany(mappedBy = "entity", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
NestedEntity nestedEntity;
// other fields for eg fieldA, fieldB
}
API endpoint
get api/entity/{id}?fields=some_property,field_a
Now the ask is, in the o/p we should filter out only someProperty and fieldA. Like
{
"some_property": "foo",
"field_a": "bar"
}
But since these are JSON fields not Java object fields I can't filter or get these fields them by Reflection. Is there a way we can achieve this, i.e. filtering of Java object based on json fields ?
FYI: The advantage of filtering before serialization is that the lazy-fields' DB calls are saved unless these fields are required
Thanks in advance!
On the suggestion of #robocode using #JsonFilter and also to support empty fields or no fields filtering added JacksonConfiguration
#JsonFilter("entityFilter")
public class Entity {
#JsonProperty("some_property")
String someProperty;
// other fields for eg fieldA, fieldB
}
#Configuration
public class JacksonConfiguration {
public JacksonConfiguration(ObjectMapper objectMapper) {
objectMapper.setFilterProvider(new SimpleFilterProvider().setFailOnUnknownId(false));
}
}
public class FieldMapper {
#SneakyThrows
public static Dto getFilteredFields(Dto make, String fields[]) {
ObjectMapper objectMapper = new ObjectMapper();
if(ArrayUtils.isNotEmpty(fields)) {
FilterProvider filters = new SimpleFilterProvider().addFilter(
"entityFilter", SimpleBeanPropertyFilter.filterOutAllExcept(fields)
);
objectMapper.setFilterProvider(filters);
} else {
objectMapper.setFilterProvider(new SimpleFilterProvider().setFailOnUnknownId(false));
}
JsonNode j = objectMapper.readTree(objectMapper.writeValueAsString(make));
return objectMapper.convertValue(j, Dto.class);
}
}
// controller code to get the dto for api/entity/{id}?fields=some_property,field_a
Dto filteredFields = getFilteredFields(dto, fields);
return filteredFields

Spring Pageable does not translate #Column name

I have Entity object :
#Entity(name = "table")
public class SomeEntity {
#Id
#Column(name = "id_column_name")
public final BigDecimal entityId;
#Column(name = "table_column_name")
public final String entityFieldName;
}
And I have database view defined like this:
CREATE OR REPLACE FORCE EDITIONABLE VIEW "V_TABLE" ("ID_COLUMN_NAME", "TABLE_COLUMN_NAME", "SOME_OTHER_COLUMN") AS ... (some SQL magic)
And I have repository with custom query:
#RepositoryRestResource
interface SomeEntityRepository extends PagingAndSortingRepository<SomeEntity, BigDecimal> {
#Query(value = "select id_column_name, table_column_name FROM V_TABLE where some_other_column = ?#{#parameter} order by ?#{#pageable}",
countQuery = "SELECT count(*) from V_TABLE v where some_other_column = ?#{#parameter}",
nativeQuery = true)
Page<SomeEntity> findBySomeParameter(#Param("parameter") long parameter, Pageable pageable);
}
Everything works fine when I request standard data with url:
http://localhost:8080/someEntity/search/findBySomeParameter?parameter=25&page=0&size=20
But when I add sorting information it doesn't work:
http://localhost:8080/someEntity/search/findBySomeParameter?parameter=25&page=0&size=20&sort=entityFieldName,asc
will throw following exception (I'm using Oracle database):
Caused by: java.sql.SQLSyntaxErrorException: ORA-00904: "ENTITYFIELDNAME": invalid identifier
It seems like sorting field are not translated with #Column(name), but are inlined into SQL query.
Is there any way to make pageable sort translated, so that it will use not field name but column name?
This article sheds light on the issue. Read from section 3.1 on.
Apparently dynamic sorting is not supported for native queries. Actually, if you change your findBySomeParameter method to take a Sort instead of a Pageable you will get org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException: Cannot use native queries with dynamic sorting.
Using pageable you don't get the exception, and pagination actually seems to work fine, but dynamic sorting does not substitute the column name as you found. Looks to me like the only solution is to use JPQL instead of native query, which is not a problem as long as the query you need to make is the one you provide. You would need to map the view though to a SomeEntityView class in order to use JPQL.
EDIT
I thought the issue was not documented but it actually is here in the official doc
Spring Data JPA does not currently support dynamic sorting for native queries, because it would have to manipulate the actual query declared, which it cannot do reliably for native SQL. You can, however, use native queries for pagination by specifying the count query yourself, as shown in the following example:
This workaround works for me in SpringBoot 2.4.3:
#PersistenceContext
private EntityManager entityManager;
// an object ptoperty name to a column name adapter
private Pageable adaptSortColumnNames(Pageable pageable) {
if (pageable.getSort().isSorted()) {
SessionFactory sessionFactory;
if (entityManager == null || (sessionFactory = entityManager.getEntityManagerFactory().unwrap(SessionFactory.class)) == null)
return pageable;
AbstractEntityPersister persister = (AbstractEntityPersister) ((MetamodelImplementor) sessionFactory.getMetamodel()).entityPersister(CommentEntity.class);
Sort adaptedSort = pageable.getSort().get().limit(1).map(order -> {
String propertyName = order.getProperty();
String columnName = persister.getPropertyColumnNames(propertyName)[0];
return Sort.by(order.getDirection(), columnName);
}).findFirst().get();
return PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), adaptedSort);
}
return pageable;
}
#GetMapping()
public ResponseEntity<PagedResponse<CommentResponse>> findByTextContainingFts(#RequestParam(value = "text", required = false) String text, Pageable pageable) {
// apply this adapter in controller
pageable = adaptSortColumnNames(pageable);
Page<CommentEntity> page = commentRepository.find(text, pageable);
return ResponseEntity.ok().body(domainMapper.fromPageToPagedResponse(page));
}

Spring data mongo aggregation with #Field annotation

I have requirement to perform group by on nested fields in mongo.
The second level nested field is annotated with #Field. I am using projection with groupBy.
Example
ProjectionOperation projectionOperation = Aggregation.project("id")
.and("author.eid").as("user");
GroupOperation groupOperation = Aggregation.group(aggregationBy, "user").count().as("total");
Aggregation aggregation =
Aggregation.newAggregation(projectionOperation groupOperation);
AggregationResults<Document> aggregationResults = myRepository.getMongoTemplate().aggregate(aggregation, MyClass.class, Document.class);
On execution I am getting error "org.springframework.data.mapping.PropertyReferenceException: No property eid found for type User!"
public class MyClass {
User author;
}
public class User {
#Field("eid")
#JsonProperty("eid") // fasterxml
public String externalId;
}
What I can think of is when casting the aggregation result to MyClass, it is unable to find "eid" because it is annotated.
How to handle this usecase ?
The #Field annotation parsed to replace the pojo property with the field name.
So you should be using
ProjectionOperation projectionOperation = Aggregation.project("id")
.and("author.externalId").as("user");
the query generated will be
{ "$project" : { "user" : "$author.eid" }

Validation of one attribute with Hibernate validator

Good evening, I'm trying to use Hibernate Validator, in the following scenario:
public class Car {
#NotNull
private String manufacturer;
#NotNull
#Size(min = 2, max = 14)
private String licensePlate;
#Min(2)
private int seatCount;
//setters and getters....
}
and I am trying to validate its attributes as follows:
public class CarMain {
public static Validator validator;
public static void main(String[] args) {
ValidatorFactory factory = Validation. buildDefaultValidatorFactory() ;
validator = factory. getValidator();
Car car = new Car(null,null,0);
Set<ConstraintViolation<Car>> st= validator.validate(car);
while(st.iterator.hasNext()){
ConstraintViolation<Car> cv = st.iterator.next();
System.out.println("Value: ("+cv.getInvalidValue()+") -->"+cv.getMessage());
System.out.println("Attribute: "+cv.getPropertyPath());
}
}
Here the whole entity is validated and the invalid values with the validation message and property path are displayed.
My question is:"Is it possible to validate only one attribute at a time with Hibernate Validator? Like I don't have to work with the whole object to validate it.
The Validator interface defines also a [Validator.validateProperty][1] method where you explicitly specify the property to validate. Mind you, you still need the object instance and you need to know the property you want to validate. This method is for example used by the integration of Bean Validation into JSF. Whether it makes sense to use it inm your case, will depend on your use case? Why don't you want to validate the whole object?
BTW, there is also Validator.validateValue which does not require an actual bean instance.

Categories