JPA - Attributeconverter not working with native query - sql

I have created AttributeConverter class which is converting Enum to DB value and overriden the necessary methods.
If I use JPA query like below then converter is getting called and getting correct result.
public List<Driver> findByStatus(DriverStatus status);
BUT If I use with Query annotation that AttributeConverter is not getting called. I have more complex query where I need to use native query with Attribute Converter but it is not working for me.
#Query(value = "select * from driver where status=:status", nativeQuery = true)
public List<Driver> findByStatus1(DriverStatus status);
is there any way to handle this requirement ?
Update 1 - below is the Converter code
#Converter(autoApply = true)
public class DriverStatusConverter implements AttributeConverter<DriverStatus, String> {
#Override
public String convertToDatabaseColumn(DriverStatus driverStatus) {
if (driverStatus == null) {
return null;
}
System.err.println("from converter" +driverStatus.getCode());
return driverStatus.getCode();
}
#Override
public DriverStatus convertToEntityAttribute(String code) {
if (code == null) {
return null;
}
return Stream.of(DriverStatus.values()).filter(c -> c.getCode().equals(code)).findFirst()
.orElseThrow(IllegalArgumentException::new);
}
}

Faced similar issue, wasn't able to find satisfying solution, so had to resort to the ugly hack... Repository method signature was changed like so:
public List<Driver> findByStatus1(Integer status);
...and was called like so:
repository.findByStatus1(DriverStatus.YOUR_DESIRED_STATUS.getCode());
Yes, it's ugly and kinda defeats the purpose of AttributeConverter, but at least it works. In my particular case I had to resort to such measures just with one native query. AttributeConverter still works for the all other JPA queries.

I just come across same problem and use following to go around it
DriverStatusConverter move logic code in convertToEntityAttribute to public static method:
#Override
public DriverStatus convertToEntityAttribute(String code) {
return convertStringToEnum(code);
}
public static DriverStatus convertStringToEnum(String code) {
if (code == null) {
return null;
}
return Stream.of(DriverStatus.values()).filter(c -> c.getCode().equals(code)).findFirst()
.orElseThrow(IllegalArgumentException::new);
}
create new DriverDTO for native query result
public interface DriverDTO {
public Integer getId();
public String getName();
public String getStatusCode();
default DriverStatus getStatus() {
return DriverStatusConverter.convertStringToEnum(getStatusCode());
}
}
DriverRepository findByStatusCode() select columns in query and return DriverDTO class
#Query(value = "select id, name, status as statusCode from driver WHERE status=:statusCode", nativeQuery = true)
public List<DriverDTO> findByStatusCode(String statusCode);

Remove the nativeQuery = true and try.
#Query(value = "select * from driver where status=:status")
public List<Driver> findByStatus1(DriverStatus status);
Alternatively you can try using entityManager like so -
This is just sample code.
TypedQuery<Trip> q = entityManager.createQuery("SELECT t FROM Trip t WHERE t.vehicle = :v", Trip.class);
q.setParameter("v", Vehicle.PLANE);
List<Trip> trips = q.getResultList();
Reference -
https://thorben-janssen.com/jpa-21-type-converter-better-way-to/

Related

spring-data-rest return multiple projections at once (compose projections)

Is there possibility to compose multiple projections? Is it best approach for my use case?
I have these projections for CarInBazar class for example:
SimpleCarInList
WidgetForHotSale
NumberOfItemViews
FullCarData
CarMainImage
CarMainImageIconSize
CarAdditionalImages
And frontend is now making some UI page, which requires some of these projections.
Should I do:
frontend will do multiple requests for same resource with different projections
Implement Projection for each screen of frontend (duplicating things like NumberOfItemViews calculation,...)
Use inheritance and make specific projections utilizing extends keyword. i.e.:
#Projection(name = "screen-dashboardHome", types = {CarInBazar.class})
public interface DashboardHomeProjectionForCarInBazar extends SimpleCarInList,
WidgetForHotSale, CarMainImageIconSize {
}
Is there any possibility to request more projections at once? It could be than rendered in UI using provided profiles perhaps.
EDIT: as requested, providing projection definition example:
import org.springframework.data.rest.core.config.Projection;
import java.awt.*;
import java.util.Date;
#Projection(name = "CarMainImage", types = {CarInBazar.class})
public interface CarMainImage {
Date getLastUpdateDate();
Image getMainImage();
default String getMainImageAdditionalInformation() {
final var updated = this.getLastUpdateDate().getTime();
final var created = this.getCreatedAtDate().getTime();
if (created >= (updated - 10 minutes)) {
return "some business logic on not published fields";
} else {
return "could happen not only in spel";
}
}
}
Many projections does not contain any business logic and are only filtering fields.
Using multiple projections seems to not be supported (or widely supported or easy). Using suggestion from #Aivaras comment I have used this approach:
Repository code with custom JPQL query:
#Repository
#RepositoryRestResource
#Transactional(readOnly = true)
public interface SomeRepository extends PagingAndSortingRepository<Some, Long> {
Page<Some> findByNameContaining(String namePart, Pageable pageable);
#Query("select new sk.qpp.documents.projections.SomeCustomViewByQuery(s.name, s.startDate, s.endDate, s.goLiveDate, 42) from Some s where s.id = :id")
Optional<SomeCustomViewByQuery> getByIdProjectedForSpecialScreen(Long id);
}
And class SomeCustomViewByQuery is just simple DTO like thing. Using lombok it can look like:
#Value
public class SomeCustomViewByQuery {
private String name;
private Date startDate;
private Date endDate;
private Date goLiveDate;
// TODO make SomeHealth to be enum and specific logic behind it.
String getSomeHealth() {
final var start = this.getStartDate().getTime();
final var end = this.getEndDate().getTime();
final var goLive = this.getGoLiveDate().getTime();
final var now = System.currentTimeMillis();
if (now < start) {
return "not started yet";
} else {
if (now < end) {
return "work in progress";
} else {
if (now < goLive) {
return "passed end, but before goLive";
} else {
return "something after goLive time";
}
}
}
}
private int unicornsCount;
}
This way, I can make hand-crafted query (JQPL) and also create custom DTO instance. It is handy, where I need to do some joins to other tables with aggregation (count, avg, max, min, ...) and other things, which are better done on database side.

Search where A or B with querydsl and spring data rest

http://localhost:8080/users?firstName=a&lastName=b ---> where firstName=a and lastName=b
How to make it to or ---> where firstName=a or lastName=b
But when I set QuerydslBinderCustomizer customize
#Override
default public void customize(QuerydslBindings bindings, QUser user) {
bindings.bind(String.class).all((StringPath path, Collection<? extends String> values) -> {
BooleanBuilder predicate = new BooleanBuilder();
values.forEach( value -> predicate.or(path.containsIgnoreCase(value) );
});
}
http://localhost:8080/users?firstName=a&firstName=b&lastName=b ---> where (firstName=a or firstName = b) and lastName=b
It seem different parameters with AND. Same parameters with what I set(predicate.or/predicate.and)
How to make it different parameters with AND like this ---> where firstName=a or firstName=b or lastName=b ??
thx.
Your current request param are grouped as List firstName and String lastName. I see that you want to keep your request parameters without a binding, but in this case it would make your life easier.
My suggestion is to make a new class with request param:
public class UserRequest {
private String lastName;
private List<String> firstName;
// getters and setters
}
For QueryDSL, you can create a builder object:
public class UserPredicateBuilder{
private List<BooleanExpression> expressions = new ArrayList<>();
public UserPredicateBuilder withFirstName(List<String> firstNameList){
QUser user = QUser.user;
expressions.add(user.firstName.in(firstNameList));
return this;
}
//.. same for other fields
public BooleanExpression build(){
if(expressions.isEmpty()){
return Expressions.asBoolean(true).isTrue();
}
BooleanExpression result = expressions.get(0);
for (int i = 1; i < expressions.size(); i++) {
result = result.and(expressions.get(i));
}
return result;
}
}
And after you can just use the builder as :
public List<User> getUsers(UserRequest userRequest){
BooleanExpression expression = new UserPredicateBuilder()
.withFirstName(userRequest.getFirstName())
// other fields
.build();
return userRepository.findAll(expression).getContent();
}
This is the recommended solution.
If you really want to keep the current params without a binding (they still need some kind of validation, otherwise it can throw an Exception in query dsl binding)
you can group them by path :
Map<StringPath,List<String>> values // example firstName => a,b
and after that to create your boolean expression based on the map:
//initial value
BooleanExpression result = Expressions.asBoolean(true).isTrue();
for (Map.Entry entry: values.entrySet()) {
result = result.and(entry.getKey().in(entry.getValues());
}
return userRepository.findAll(result);

Spring JDBC and Java 8 - JDBCTemplate: retrieving SQL statement and parameters for debugging

I am using Spring JDBC and some nice Java 8 lambda-syntax to execute queries with the JDBCTemplate.
The reason for choosing Springs JDBCTemplate, is the implicit resource-handling that Spring-jdbc offers (I do NOT want a ORM framework for my simple usecase's).
My problem is that I want to debug the whole SQL statements with their parameters. Spring prints the SQL by default but not the parameters. Therefor I have subclassed the JDBCTemplate and overridden a query-method.
An example usage of the JDBCTemplate:
public List<Product> getProductsByModel(String modelName) {
List<Product> productList = jdbcTemplate.query(
"select * from product p, productmodel m " +
"where p.modelId = m.id " +
"and m.name = ?",
(rs, rowNum) -> new Product(
rs.getInt("id"),
rs.getString("stc_number"),
rs.getString("version"),
getModelById(rs.getInt("modelId")), // method not shown
rs.getString("displayName"),
rs.getString("imageUrl")
),
modelName);
return productList;
}
To get hold of the parameters I have, as mentioned, overridden the JDBCTemplate class. By doing a cast and using reflection I get the Object[] field with the parameters from an instance of ArgumentPreparedStatementSetter.
I suspect this implementation could potentially be dangerous, as the actual implementation of the PreparedStatementSetter may not always be ArgumentPreparedStatementSetter (Yes I should do an instanceOf check). Also, the reflection code may not be as elegant, but that is besides the point now though :).
Here's my custom implementation:
public class CustomJdbcTemplate extends JdbcTemplate {
private static final Logger log = LoggerFactory.getLogger(CustomJdbcTemplate.class);
public CustomJdbcTemplate(DataSource dataSource) {
super(dataSource);
}
public <T> T query(PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor<T> rse)
throws DataAccessException {
if(log.isDebugEnabled()) {
ArgumentPreparedStatementSetter aps = (ArgumentPreparedStatementSetter) pss;
try {
Field args = aps.getClass().getDeclaredField("args");
args.setAccessible(true);
Object[] parameters = (Object[]) args.get(aps);
log.debug("Parameters for SQL query: " + Arrays.toString(parameters));
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new GenericException(e.toString(), e);
}
}
return super.query(psc, pss, rse);
}
}
So, when I execute the log.debug(...) statement I would also like to have the original SQL query logged (same line). Has anyone done something similar or are there any better suggestions as to how this can be achieved?
I do quite a few queries using this CustomJDBCTemplate and all my tests run, so I think it may be an acceptable solution of for most debug purposes.
Kind regards,
Thomas
I found a way to get the SQL-statement, so I will answer my own question :)
The PreparedStatementCreator has the following implementation:
private static class SimplePreparedStatementCreator implements PreparedStatementCreator, SqlProvider
So the SqlProvider has a getSql() method which does exactly what I need.
Posting the "improved" CustomJdbcTemplate class if anyone ever should need to do the same :)
public class CustomJdbcTemplate extends JdbcTemplate {
private static final Logger log = LoggerFactory.getLogger(CustomJdbcTemplate.class);
public CustomJdbcTemplate(DataSource dataSource) {
super(dataSource);
}
public <T> T query(PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor<T> rse)
throws DataAccessException {
if(log.isDebugEnabled()) {
if(pss instanceof ArgumentPreparedStatementSetter) {
ArgumentPreparedStatementSetter aps = (ArgumentPreparedStatementSetter) pss;
try {
Field args = aps.getClass().getDeclaredField("args");
args.setAccessible(true);
Object[] parameters = (Object[]) args.get(aps);
log.debug("SQL query: [{}]\tParams: {} ", getSql(psc), Arrays.toString(parameters));
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new GenericException(e.toString(), e);
}
}
}
return super.query(psc, pss, rse);
}
private static String getSql(Object sqlProvider) { // this code is also found in the JDBCTemplate class
if (sqlProvider instanceof SqlProvider) {
return ((SqlProvider) sqlProvider).getSql();
}
else {
return null;
}
}
}

Trying to use PlaceRequest the right way

i have two Presenters: A DevicePresenter and a ContainerPresenter. I place a PlaceRequest in the DevicePresenter to call the ContainerPresenter with some parameters like this:
PlaceRequest request = new PlaceRequest.Builder()
.nameToken("containersPage")
.with("action","editContainer")
.with("containerEditId", selectedContainerDto.getUuid().toString())
.build();
placeManager.revealPlace(request);
In my ContainersPresenter i have this overridden method:
#Override
public void prepareFromRequest(PlaceRequest placeRequest) {
Log.debug("prepareFromRequest in ContainersPresenter");
super.prepareFromRequest(placeRequest);
String actionString = placeRequest.getParameter("action", "");
String id;
//TODO: Should we change that to really retrieve the object from the server? Or should we introduce a model that keeps all values and inject that into all presenters?
if (actionString.equals("editContainer")) {
try {
id = placeRequest.getParameter("id", null);
for(ContainerDto cont : containerList) {
Log.debug("Compare " + id + " with " + cont.getUuid());
if(id.equals(cont.getUuid())) {
containerDialog.setCurrentContainerDTO(new ContainerDto());
addToPopupSlot(containerDialog);
break;
}
}
} catch (NumberFormatException e) {
Log.debug("id cannot be retrieved from URL");
}
}
}
But when revealPlace is called, the URL in the browser stays the same and the default presenter (Home) is shown instead.
When i print the request, it seems to be fine:
PlaceRequest(nameToken=containersPage, params={action=editContainer, containerEditId=8fa5f730-fe0f-11e3-a3ac-0800200c9a66})
And my NameTokens are like this:
public class NameTokens {
public static final String homePage = "!homePage";
public static final String containersPage = "!containersPage";
public static final String devicesPage = "!devicesPage";
public static String getHomePage() {
return homePage;
}
public static String getDevicesPage() {
return devicesPage;
}
public static String getContainersPage() {
return containersPage;
}
}
What did i miss? Thanks!
In your original code, when constructing your PlaceRequest, you forgot the '!' at the beginning of your nametoken.
.nameToken("containersPage")
while your NameTokens entry is
public static final String containersPage = "!containersPage";
As you noted, referencing the constant in NameTokens is less prone to such easy mistakes to make!
Sometimes the problem exists "between the ears". If i avoid strings but use the proper symbol from NameTokens like
PlaceRequest request = new PlaceRequest.Builder()
.nameToken(NameTokens.containersPage)
.with("action","editContainer")
.with("containerEditId", selectedContainerDto.getUuid().toString())
.build();
it works just fine. Sorry!

Hibernate Search with AnalyzerDiscriminator - Analyzer called only when creating Entity?

can you help me?
I am implementing Hibernate Search, to retrieve results for a global search on a localized website (portuguese and english content)
To do this, I have followed the steps indicated on the Hibernate Search docs:
http://docs.jboss.org/hibernate/search/4.5/reference/en-US/html_single/#d0e4141
Along with the specific configuration in the entity itself, I have implemented a "LanguageDiscriminator" class, following the instructions in this doc.
Because I am not getting exactly the results I was expecting (e.g. my entity has the text "Capuchinho" stored, but when I search for "capucho" I get no hits), I have decided to try and debug the execution, and try to understand if the Analyzers which I have configured are being used at all.
When creating a new record for the entity in the database, I can see that the "getAnalyzerDefinitionName()" method from the "LanguageDiscriminator" gets called. Great. But the same does not happen when I execute a search. Can anyone explain me why?
I am posting the key parts of my code below. Thanks a lot for any feedback!
This is one entity I want to index
#Entity
#Table(name="NEWS_HEADER")
#Indexed
#AnalyzerDefs({
#AnalyzerDef(name = "en",
tokenizer = #TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
#TokenFilterDef(factory = LowerCaseFilterFactory.class),
#TokenFilterDef(factory = SnowballPorterFilterFactory.class,
params = {#Parameter(name="language", value="English")}
)
}
),
#AnalyzerDef(name = "pt",
tokenizer = #TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
#TokenFilterDef(factory = LowerCaseFilterFactory.class),
#TokenFilterDef(factory = SnowballPorterFilterFactory.class,
params = {#Parameter(name="language", value="Portuguese")}
)
}
)
})
public class NewsHeader implements Serializable {
static final long serialVersionUID = 20140301L;
private int id;
private String articleHeader;
private String language;
private Set<NewsParagraph> paragraphs = new HashSet<NewsParagraph>();
/**
* #return the id
*/
#Id
#Column(name="ID")
#GeneratedValue(strategy=GenerationType.AUTO)
#DocumentId
public int getId() {
return id;
}
/**
* #param id the id to set
*/
public void setId(int id) {
this.id = id;
}
/**
* #return the articleHeader
*/
#Column(name="ARTICLE_HEADER")
#Field(index=Index.YES, store=Store.NO)
public String getArticleHeader() {
return articleHeader;
}
/**
* #param articleHeader the articleHeader to set
*/
public void setArticleHeader(String articleHeader) {
this.articleHeader = articleHeader;
}
/**
* #return the language
*/
#Column(name="LANGUAGE")
#Field
#AnalyzerDiscriminator(impl=LanguageDiscriminator.class)
public String getLanguage() {
return language;
}
...
}
This is my LanguageDiscriminator class
public class LanguageDiscriminator implements Discriminator {
#Override
public String getAnalyzerDefinitionName(Object value, Object entity, String field) {
String result = null;
if (value != null) {
result = (String) value;
}
return result;
}
}
This is my search method present in my SearchDAO
public List<NewsHeader> searchParagraph(String patternStr) {
Session session = null;
Transaction tx;
List<NewsHeader> result = null;
try {
session = sessionFactory.getCurrentSession();
FullTextSession fullTextSession = Search.getFullTextSession(session);
tx = fullTextSession.beginTransaction();
// Create native Lucene query using the query DSL
QueryBuilder queryBuilder = fullTextSession.getSearchFactory()
.buildQueryBuilder().forEntity(NewsHeader.class).get();
org.apache.lucene.search.Query luceneSearchQuery = queryBuilder
.keyword()
.onFields("articleHeader", "paragraphs.content")
.matching(patternStr)
.createQuery();
// Wrap Lucene query in a org.hibernate.Query
org.hibernate.Query hibernateQuery =
fullTextSession.createFullTextQuery(luceneSearchQuery, NewsHeader.class, NewsParagraph.class);
// Execute search
result = hibernateQuery.list();
} catch (Exception xcp) {
logger.error(xcp);
} finally {
if ((session != null) && (session.isOpen())) {
session.close();
}
}
return result;
}
When creating a new record for the entity in the database, I can see that the "getAnalyzerDefinitionName()" method from the "LanguageDiscriminator" gets called. Great. But the same does not happen when I execute a search. Can anyone explain me why?
The selection of the analyzer is dependent on the state of a given entity, in your case NewsHeader. You are dealing with entity instances during indexing. While querying you don't have entities to start with, you are searching for them. Which analyzer would you Hibernate Search to select for your query?
That said, I think there is a shortcoming in the DSL. It does not allow you to explicitly specify the analyzer for a class. There is ignoreAnalyzer, but that's not what you want. I guess you could create a feature request in the Search issue tracker - https://hibernate.atlassian.net/browse/HSEARCH.
In the mean time you can build the query using the native Lucene query API. However, you will need to know which language you are targeting with your query (for example via the preferred language of the logged in user or whatever). This will depend on your use case. It might be you are looking at the wrong feature to start with.