#Accessors(fluent = true) from lombok and mapstruct together - java-8

I have a downstream service whose beans I want to map to my beans. But there seems to be a problem with using mapstruct.
Pojo from the downstream service (whose definition i can't change)
#ToString
#Getter
#Setter
#Accessors(fluent = true)
public class PojoA {
private String stringA;
private int integer;
}
and I want to convert it to PojoB
#Setter
#Getter
#ToString
public class PojoB {
private String stringB;
private int integer;
}
Mapper Interface
#Mapper
public interface PojoMapper {
PojoMapper INSTANCE = Mappers.getMapper(PojoMapper.class);
#Mapping(source = "stringA", target = "stringB")
PojoB pojoAToPojoB(PojoA pojoA);
}
This fails as it fails to find getters as fluent remove the prefixes from the getters and,
I know mapstruct generates the implementation of the mapper interface and uses bean specifications (i.e get and set prefixes)
Is there a way to get past that?

As specified in the comments this is possible by writing a custom AccessorNamingStrategy which would return the name of the method as a fluent accessor.
I have written a custom builder accessor (that can be applied in your case as well), you can find it here.
On top of that there is an open PR #1373 for MapStruct which adds support for builders and in the same go it adds support for fluent chained accessors.
Your custom strategy (as proposed in the PR) can look like:
public class FluentAccessorNamingStrategy extends DefaultAccessorNamingStrategy {
#Override
public boolean isSetterMethod(ExecutableElement method) {
String methodName = method.getSimpleName().toString();
return methodName.startsWith( "set" ) && methodName.length() > 3 || isBuilderSetter( method );
}
protected boolean isBuilderSetter(ExecutableElement method) {
return method.getParameters().size() == 1 &&
!JAVA_JAVAX_PACKAGE.matcher( method.getEnclosingElement().asType().toString() ).matches() &&
method.getReturnType().toString().equals( method.getEnclosingElement().asType().toString() );
}
#Override
public String getPropertyName(ExecutableElement getterOrSetterMethod) {
String methodName = getterOrSetterMethod.getSimpleName().toString();
if ( methodName.startsWith( "is" ) || methodName.startsWith( "get" ) || methodName.startsWith( "set" ) ) {
return Introspector.decapitalize( methodName.substring( methodName.startsWith( "is" ) ? 2 : 3 ) );
}
else if ( isBuilderSetter( getterOrSetterMethod ) ) {
return methodName;
}
return Introspector.decapitalize( methodName.substring( methodName.startsWith( "is" ) ? 2 : 3 ) );
}
}
NB: This would work for fluent setters, you would need to do something extra to make it work for the fluent getters. Based on the meethod.getEnclosingElement(), which returns you the type, you would need to get the Accessors annotation check for the property and return the name of the method, otherwise you need to use the defaults.
Disclaimer: I am one of the members of the MapStruct team

Related

mapstruct map iterable to non iterable

I want to map a list of Objects to an Object that contains a list:
public class Group {
private List<Person> people;
}
public class Person {
private String name;
}
I tried creating a mapper like this:
Group toGroup(List<Person> people);
and I'm getting this error:
error: Can't generate mapping method from iterable type to non-iterable type.
What is the most elegant solution for this kind of mapping?
Mapstruct actually can do it. I have the exact same situation, a CollectionResponse that just contains items. I worked around it by adding a dummy parameter like this:
#Mapper(componentModel = "spring", uses = ItemMapper.class)
public interface CollectionResponseMapper {
// Dummy property to prevent Mapstruct complaining "Can't generate mapping method from iterable type to non-iterable type."
#Mapping( target = "items", source = "items")
CollectionResponse map( Integer dummy, List<Item> items);
}
Mapstruct generates the desired code. Something like this:
public class CollectionResponseMapperImpl implements CollectionResponseMapper {
#Autowired
private ItemMapper itemMapper;
#Override
public CollectionResponse map(Integer dummy, List<Item> items) {
if ( dummy == null && items == null ) {
return null;
}
CollectionResponse collectionResponse = new CollectionResponse();
if ( items != null ) {
collectionResponse.setItems( itemListToItemDtoList( items ) );
}
return collectionResponse;
}
protected List<ItemDto> itemListToItemDtoList(List<Item> list) {
if ( list == null ) {
return null;
}
List<ItemDto> list1 = new ArrayList<ItemDto>( list.size() );
for ( Item item : list ) {
list1.add( itemMapper.mapItemToDto( item ) );
}
return list1;
}
}
General answer - Such mapping is prohibited.
To map a list of objects to an object that would wrap this list could be done by:
/// your class with business code
List<Person> people = ....
new Group(people);
/// group class
public class Group {
private List<Person> people = new ArrayList<>();
public Group(List<Person> people) {
this.people = people
}
}
When the Group would just have simply constructor with a list as param. You don't need to use Mapstruct for this.
In the mapstruct sources have this check Mapstruct github sources for MethodRetrievalProcessor.java:
Type parameterType = sourceParameters.get( 0 ).getType();
if ( parameterType.isIterableOrStreamType() && !resultType.isIterableOrStreamType() ) {
messager.printMessage( method, Message.RETRIEVAL_ITERABLE_TO_NON_ITERABLE );
return false;
}
So basically even Mapstruct team wants you to use mapping only when you need it. And doesn't want to allow transforming List<Object> to another Object as it doesn't make sense.
This would make some sense if you're adding some additional information(non-iterable :) ) for your Group object, for example:
//group class
public class Group {
private Long someCounter;
private List<Person> people;
}
//mapper
#Mapping(target= "people", source ="people")
#Mapping(target= "someCounter", source ="counterValue")
Group toGroup(Long counterValue, List<Person> people);
But better use DTOs, Views, Entites and any other kind of objects that would hide all the nested stuff. In this case Mapstruct would be your greatest friend.

Multiple aliases with QuerydslBinderCustomizer

I'm using QuerydslPredicate in my RestController on an entity which has a date object, I want to be able to query for a date before/after/between given dates, hoping to have something like
GET /problems?createdOnAfter=XXX
GET /problems?createdOnBefore=YYY
GET /problems?createdOnAfter=XXX&createdOnBefore=YYY
My entity has the date field createdOn and I was hoping I could customise bindings for an entity path using multiple aliases i.e. adding aliases createdOnAfter & createdOnBefore - it doesn't look like I can create multiple aliases though, e.g.
#Repository
public interface ProblemRepository extends JpaRepository<Problem, String>, QueryDslPredicateExecutor<Problem>,
QuerydslBinderCustomizer<QProblem> {
....
#Override
default void customize(QuerydslBindings bindings, QProblem root) {
bindings.bind(root.createdOn).as("createdOnAfter").first(TemporalExpression::after);
bindings.bind(root.createdOn).as("createdOnBefore").first(TemporalExpression::before);
}
}
The before alias is obviously overwriting the after one.
What's the correct approach to avoid having to manually create the predicates?
Why not using QueryDSL Predicate ? You could do :
#GetMapping("/problems")
#Timed
public ResponseEntity<List<ProblemDTO>> getAllProblems(
#RequestParam(required = false) LocalDateTime createdOnAfter,
#RequestParam(required = false) LocalDateTime createdOnBefore,
#ApiParam Pageable pageable) {
BooleanBuilder where = new BooleanBuilder();
if (startDate != null) {
where = where.and(problem.createdOn.after(createdOnAfter));
}
if (endDate != null) {
where = where.and(problem.createdOn.before(createdOnBefore));
}
Page<Donnee> page = problemRepository.findAll(where, pageable);
return new ResponseEntity<>(problemMapper.toDTO(page.getContent())), null, HttpStatus.OK);
}
Hope it helps,
Regards
How about adding new Entity properties and marking those as transient like this:
#Transient
private Instant createdOnBefore;
#Transient
private Instant createdOnAfter;
and then customise your repository class like this:
#Override
default void customize(final QuerydslBindings bindings, final QProblem root) {
bindings.bind(root.createdOnBefore).first((path, value) -> root.createdOn.goe(value));
bindings.bind(root.createdOnAfter).first((path, value) -> root.createdOn.loe(value));
}

Dynamic Queries in Spring Data JPA

I am looking for a solution to dynamically build queries using Spring Data JPA. I have a GameController which has a RESTful service endpoint /games which takes 4 optional parameters: genre, platform, year, title. The API may be passed none of those, all 4, and every combination in between. If any parameter is not passed it defaults to null. I need a method in the Repository that will build the appropriate query and ideally also still allow Spring Data JPA Paging, although I'm not sure if that is possible.
I found this article but this doesn't seem to be what I need unless I am misunderstanding. http://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
I know JPA has a Query Criteria API but really have no idea how to implement this.
I realize I could create a method for each possible scenario but that seems like really bad practice and a lot of unnecessary code.
GameRepository:
package net.jkratz.igdb.repository;
import net.jkratz.igdb.model.Game;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface GameRepository extends JpaRepository<Game, Long> {
#Query("select g from Game g, GamePlatformMap gpm, Platform p where g = gpm.game and gpm.platform = p and p.id = :platform")
Page<Game> getGamesByPlatform(#Param("platform") Long platformId, Pageable pageable);
#Query("select g from Game g where g.title like :title")
Page<Game> getGamesByTitle(#Param("title") String title, Pageable pageable);
#Query("select g from Game g, GameGenreMap ggm, Genre ge where g = ggm.game and ggm.genre = ge and ge.id = :genreId")
Page<Game> getGamesByGenre(#Param("genre") Long genreId, Pageable pageable);
}
I would say that using QueryDSL is one way of doing what you want.
For example I have a repository defined as below:
public interface UserRepository extends PagingAndSortingRepository<User, Long>, QueryDslPredicateExecutor<User> {
public Page<User> findAll(Predicate predicate, Pageable p);
}
I can call this method with any combination of parameters, like below:
public class UserRepositoryTest{
#Autowired
private UserRepository userRepository;
#Test
public void testFindByGender() {
List<User> users = userRepository.findAll(QUser.user.gender.eq(Gender.M));
Assert.assertEquals(4, users.size());
users = userRepository.findAll(QUser.user.gender.eq(Gender.F));
Assert.assertEquals(2, users.size());
}
#Test
public void testFindByCity() {
List<User> users = userRepository.findAll(QUser.user.address.town.eq("Edinburgh"));
Assert.assertEquals(2, users.size());
users = userRepository.findAll(QUser.user.address.town.eq("Stirling"));
Assert.assertEquals(1, users.size());
}
#Test
public void testFindByGenderAndCity() {
List<User> users = userRepository.findAll(QUser.user.address.town.eq("Glasgow").and(QUser.user.gender.eq(Gender.M)));
Assert.assertEquals(2, users.size());
users = userRepository.findAll(QUser.user.address.town.eq("Glasgow").and(QUser.user.gender.eq(Gender.F)));
Assert.assertEquals(1, users.size());
}
}
For those using Kotlin (and Spring Data JPA), we've just open-sourced a Kotlin JPA Specification DSL library which lets you create type-safe dynamic queries for a JPA Repository.
It uses Spring Data's JpaSpecificationExecutor (i.e. JPA criteria queries), but without the need for any boilerplate or generated metamodel.
The readme has more details on how it works internally, but here's the relevant code examples for a quick intro.
import au.com.console.jpaspecificationsdsl.* // 1. Import Kotlin magic
////
// 2. Declare JPA Entities
#Entity
data class TvShow(
#Id
#GeneratedValue
val id: Int = 0,
val name: String = "",
val synopsis: String = "",
val availableOnNetflix: Boolean = false,
val releaseDate: String? = null,
#OneToMany(cascade = arrayOf(javax.persistence.CascadeType.ALL))
val starRatings: Set<StarRating> = emptySet())
#Entity
data class StarRating(
#Id
#GeneratedValue
val id: Int = 0,
val stars: Int = 0)
////
// 3. Declare JPA Repository with JpaSpecificationExecutor
#Repository
interface TvShowRepository : CrudRepository<TvShow, Int>, JpaSpecificationExecutor<TvShow>
////
// 4. Kotlin Properties are now usable to create fluent specifications
#Service
class MyService #Inject constructor(val tvShowRepo: TvShowRepository) {
fun findShowsReleasedIn2010NotOnNetflix(): List<TvShow> {
return tvShowRepo.findAll(TvShow::availableOnNetflix.isFalse() and TvShow::releaseDate.equal("2010"))
}
/* Fall back to spring API with some extra helpers for more complex join queries */
fun findShowsWithComplexQuery(): List<TvShow> {
return tvShowRepo.findAll(where { equal(it.join(TvShow::starRatings).get(StarRating::stars), 2) })
}
}
For more complex and dynamic queries it's good practice to create functions that use the DSL to make queries more readable (as you would for QueryDSL), and to allow for their composition in complex dynamic queries.
fun hasName(name: String?): Specifications<TvShow>? = name?.let {
TvShow::name.equal(it)
}
fun availableOnNetflix(available: Boolean?): Specifications<TvShow>? = available?.let {
TvShow::availableOnNetflix.equal(it)
}
fun hasKeywordIn(keywords: List<String>?): Specifications<TvShow>? = keywords?.let {
or(keywords.map { hasKeyword(it) })
}
fun hasKeyword(keyword: String?): Specifications<TvShow>? = keyword?.let {
TvShow::synopsis.like("%$keyword%")
}
These functions can be combined with and() and or() for complex nested queries:
val shows = tvShowRepo.findAll(
or(
and(
availableOnNetflix(false),
hasKeywordIn(listOf("Jimmy"))
),
and(
availableOnNetflix(true),
or(
hasKeyword("killer"),
hasKeyword("monster")
)
)
)
)
Or they can be combined with a service-layer query DTO and mapping extension function
/**
* A TV show query DTO - typically used at the service layer.
*/
data class TvShowQuery(
val name: String? = null,
val availableOnNetflix: Boolean? = null,
val keywords: List<String> = listOf()
)
/**
* A single TvShowQuery is equivalent to an AND of all supplied criteria.
* Note: any criteria that is null will be ignored (not included in the query).
*/
fun TvShowQuery.toSpecification(): Specifications<TvShow> = and(
hasName(name),
availableOnNetflix(availableOnNetflix),
hasKeywordIn(keywords)
)
for powerful dynamic queries:
val query = TvShowQuery(availableOnNetflix = false, keywords = listOf("Rick", "Jimmy"))
val shows = tvShowRepo.findAll(query.toSpecification())
JpaSpecificationExecutor supports paging, so you can achieve pageable, type-safe, dynamic queries!
I have got a solution for this. I wrote some code to extend the spring-data-jpa .
I call it spring-data-jpa-extra
spring-data-jpa-extra comes to solve three problem:
dynamic native query support like mybatis
return type can be anything
no code, just sql
You can try it : )

Java 8 Date Time api in JPA

What is the best way how to integrate Java 8 Date Time api in jpa?
I have added converters:
#Converter(autoApply = true)
public class LocalDatePersistenceConverter implements AttributeConverter<LocalDate, Date> {
#Override
public Date convertToDatabaseColumn(LocalDate localDate) {
return Date.valueOf(localDate);
}
#Override
public LocalDate convertToEntityAttribute(Date date) {
return date.toLocalDate();
}
}
and
#Converter(autoApply = true)
public class LocalDateTimePersistenceConverter implements AttributeConverter<LocalDateTime, Timestamp> {
#Override
public Timestamp convertToDatabaseColumn(LocalDateTime entityValue) {
return Timestamp.valueOf(entityValue);
}
#Override
public LocalDateTime convertToEntityAttribute(Timestamp databaseValue) {
return databaseValue.toLocalDateTime();
}
}
Everything seems fine, but how should I use JPQL for querying? I am using Spring JPARepository, and goal is to select all entities where date is the same as date given, only difference is that it is saved in entity as LocalDateTime.
So:
public class Entity {
private LocalDateTime dateTime;
...
}
And:
#Query("select case when (count(e) > 0) then true else false end from Entity e where e.dateTime = :date")
public boolean check(#Param("date") LocalDate date);
When executing it just gives me exception, which is correct.
Caused by: java.lang.IllegalArgumentException: Parameter value [2014-01-01] did not match expected type [java.time.LocalDateTime (n/a)]
I have tried many ways, but it seems that none is working, is that even possible?
Hibernate has an extension library, hibernate-java8 I believe, which natively supports many of the time types.
You should use it before writing converters.
in hibernate 5.2 you won't need this additional library, it is part of core.
To query temporal fields you should use the #Temporal Anotation in the temporal fields, add the converters to persistence.xml and also be sure you are using the java.sql.Date,java.sql.Time or java.sql.Timestamp in the converters. (Sometimes i imported from the wrong package)
for example thats works for me:
#Temporal(TemporalType.TIMESTAMP)
#Convert(converter = InstantPersistenceConverter.class)
private Instant StartInstant;
#Temporal(TemporalType.TIME)
#Convert(converter = LocalTimePersistenceConverter.class)
private LocalTime StartTime;
and my Instant converter:
#Converter(autoApply = true)
public class InstantPersistenceConverter implements AttributeConverter <Instant,java.sql.Timestamp>{
#Override
public java.sql.Timestamp convertToDatabaseColumn(Instant entityValue) {
return java.sql.Timestamp.from(entityValue);
}
#Override
public Instant convertToEntityAttribute(java.sql.Timestamp databaseValue) {
return databaseValue.toInstant();
}
}
Did you add LocalDatePersistenceConverter and LocalDateTimePersistenceConverter in persistence.xml placed in 'class' element ?

OpenEntityManagerInViewFilter Not Working In Unit Test

I'm writing a unit test for a controller, but when my test reaches the line where it called this method
private Predicate predicate( final Integer pid )
{
return
new Predicate()
{
public boolean evaluate( Object o )
{
ProviderAuxiliaryAccount proAux = ( ProviderAuxiliaryAccount) o;
return proAux.getProviderAccount().getId() == pid;
}
};
}
It throws a NullPointerException on this line:
return proAux.getProviderAccount().getId() == pid;
When I checked the ProviderAuxiliaryAccount object, It can not seem to retrieve its ProviderAccount property. Therefore, calling getProviderAccount().getId() will surely fire a null pointer exception. But this only happens during the unit test. I suspect that OpenEntityManagerInViewFilter is not working on the unit test.
Here are some excerpts from my java classes:
Controller Test class
#Transactional
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration( loader = CustomContextLoader.class, locations = {"/applicationContext-web-test.xml","/applicationContext-web-mail-test.xml"})
public class ControllerTest extends AbstractTransactionalJUnit4SpringContextTests
ProviderAuxiliaryAccount model
public class ProviderAuxiliaryAccount
implements Serializable
{
private static final long serialVersionUID = 1L;
private Integer id;
private Integer providerId;
#XmlTransient
private ProviderAccount providerAccount;
3.Here's the part in my actual controller which called the method 'predicate'.
List<AuxiliaryAccount> accounts = auxiliaryService.getAuxiliaryAccounts( account.getPerson() );
ProviderAccount provider = auxiliaryService.getProviderAccount( UserAccountUtil.getUserName() );
AuxiliaryAccount aux =
( AuxiliaryAccount ) CollectionUtils.find( accounts, PredicateUtils.notNullPredicate() );
Integer pid = provider.getId();
if ( CollectionUtils.find( aux.getProviderAuxiliaryAccounts(), predicate( pid ) ) != null )
Notice that the ProviderAuxiliaryAccount has a transient ProviderAccount property. Also,
I added a #Transactional anotation on my controller test class as stated here.
OpenEntityManagerInView equivalent for Junit and Quartz Jobs .
But this doesn't seem to work for me.
Does anyone know how to fix this?

Resources