Assign custom lombok builder method as singular handler - java

I have the following table class which makes usage of #Builder Lombok annotation with a custom builder class:
#Builder
public class MyTable {
...
public static class MyTableBuilder {
public void entry(final MyEntry myEntry) {
...
}
}
}
In another class that is composed by MyTable I would like to make usage of #Builder + #Singular annotations so that I can build TableOwner instances specifying entry by entry.
#Builder
public class TableOwner {
#Singular("entry")
private final MyTable entries;
}
TableOwner.builder()
.entry(...)
.entry(...)
.entry(...)
.build()
However the #Singular annotation on entries results the error "Lombok does not know how to create the singular-form builder methods for type 'MyTable'; they won't be generated.".
Is there a way I can point to MyTableBuilder#entry method as the singular handler for entries?

Related

Convert List of Objects to List of Long IDs using Mapstruct

I am using MapStruct to convert entities to DTOs.
I have an entity A with list of entity B:
public class A {
List<B> bs;
}
I want to have a list of B ids in ADto class:
public class ADto {
List<Long> bIds;
}
You’ll have to define a custom mapping method. (I do not know any other easier method to map a custom object to a Lang)
Anyways here would be the mapper
#Mapper
public interface AMapper {
#Mapping(source = "bs", target = "bIds", qualifiedByName = "bToId")
public ADto aToADto(A a);
#Named("bToId")
public static Long bToId(B b) {
return b.getId();
}
}
Basically the first method would map the list of Bs to Longs using the method defined by you beneath it (based on the #Named value)
This approach is similar to the approach taken by L_Cleo, but assumes that we have more (possibly many) use cases in which we want to map entities to their id. If you upvote my answer, you may want to give them an upvote aswell; my answer was inspired by theirs.
First, we create an interface HasId and let all entities having a long getId()-method implement this interface:
public interface HasId {
long getId();
}
#Builder
#Value
public class A implements HasId {
long id;
List<B> bs;
}
#Builder
#Value
public class B implements HasId {
long id;
}
Alternativley, if we already have an (abstract) superclass providing a long getId()-method, we can use this class instead. It works in the same manner.
Next, we define a mapper that maps a HasId-entity to their id. We do not have any support from MapStruct for this, so we have to create this mapper manually:
#Mapper
public class HasIdMapper {
final long toId(HasId entity) {
return entity.getId();
}
}
We define our Dto-class as usual:
#Builder
#Value
public class ADto {
List<Long> bIds;
}
And finally we define the mapper from A to ADto:
#Mapper(uses = HasIdMapper.class)
public interface AMapper {
#Mapping(source = "bs", target = "bIds")
ADto toDto(A entity);
}
The important bit here is the uses-attribute with which we trigger the usage of the HasIdMapper.
And for completeness sake, here is a small test case:
public static void main(String... args) {
final A a = A.builder()
.bs(List.of(
B.builder().id(1).build(),
B.builder().id(2).build(),
B.builder().id(3).build()))
.build();
final ADto dto = Mappers.getMapper(AMapper.class).toDto(a);
System.out.println(dto);
}
This will print out:
ADto(bIds=[1, 2, 3])

Mapstruct Invoking implicitly other mapper with multiple parameter

Given the following classes and a mapper that takes mulitple source arguments
(I use lombok to keep source as short as possible.)
#Getter
#Setter
public class MySourceOne {
private String src1;
}
#Getter
#Setter
public class MySourceTwo {
private String src2;
}
#Getter
#Setter
public class MyTargetObject {
private String prop1;
private String prop2;
}
#Mapper
public interface MyTargetObjectMapper {
#Mapping(target="prop1", source="a")
#Mapping(target="prop2", source="b")
public MyTargetObject mapMyObject(String a, String b);
}
#Getter
#Setter
public class MyComplexTargetObject {
private MyTargetObject myTargetObject;
}
I am trying to create a mapper for MyComplexTargetObject that will invoke implicitly the MyTargetObjectMapper .
But the "source" won't allow to map multiple parameter like this
#Mapper(uses= {MyTargetObjectMapper.class})
public interface MyComplexTargetObjectMapper {
#Mapping(target="myTargetObject", source="one.src1, two.src2")
public MyComplexTargetObject convert(MySourceOne one, MySourceTwo two);
}
So I am trying to use an expression="..." instead of source, but nothing works so far.
Any thoughts a clean way to do this without calling the MyTargetObjectMapper in a concrete method?
MapStruct does not support selection of methods with multiple sources.
However: you can do target nesting to do this.
#Mapper
public interface MyComplexTargetObjectMapper {
#Mapping(target="myTargetObject.prop1", source="one.src1" )
#Mapping(target="myTargetObject.prop2", source="two.src2")
public MyComplexTargetObject convert(MySourceOne one, MySourceTwo two);
}
And let MapStruct take care of generating the mapper. Note: you can still use a MyComplexTargetObjectMapper to do single source to target to achieve this.

Best way to initialise fields in a Class without boilerplate code

I have a PivotModel class which I will initialise using the new keyword.
PivotModel pivotModel = new PivotModel()
When pivotModel gets initialised, all the dependant fields(model1, model2,cell1,cell2) should get initialised with new object but not to null.
I wanted to initialise all the fields and the fields of dependant classes without using new constructor. I don't want to have boilerplate code.
If you have any standard practice of way doing it, post it here. I am also using lombok in my project.
public class PivotModel {
#Getter
#Setter
private Model1 model1;
#Getter
#Setter
private Model2 model2;
private Model3 model3 = new Model3() -----> Dont want to initialise this way for these fields
}
public class Model1 {
private Map<String,Cell> cell1;
private Map<String,Cell> cell2;
private Map<String,Cell> cell3;
------ will have some 10 fields here
}
It seems that you are using Lombok project in your java project you can add #Getter #Setter above your class Scope, Lombok also provides Constructor Annotation, so Just type above your class Scope #AllArgsConstructor
so you class Should be like this
#Getter
#Setter
#AllArgsConstructor
public class PivotModel {
private Model1 model1;
private Model2 model2;
}
#Getter
#Setter
#AllArgsConstructor
public class Model1 {
private Map<String,Cell> cell1;
private Map<String,Cell> cell2;
private Map<String,Cell> cell3;
}
For initialization, I would recommended Builder Pattern.
//keep your initialization logic in builder class and use build()/create() wherever required. Let's say:
Class Pivot{
// Note: this have only getters for members
//inner builder class
PivotModelBuilder{
//Note: all setter will be part of builder class
/**
* method which return instantiated required object.
*/
public PivotModel build(){
return new PivotModel(this);
}
}
}
//access initilization code as:
PivotModel pivot = new Pivot.PivotModelBuilder().build()
Adding referral link: https://www.javaworld.com/article/2074938/core-java/too-many-parameters-in-java-methods-part-3-builder-pattern.html
(You can search more about builder pattern and it's implementation online)
Limitations:
However, it's good way to initialize/create bean, but, you might find duplication of member fields in both Parent and builder class.

(CRUD) Repository for a large number of JPA classes

I can create a repository via defining an interface on the appropriate JPA class A like the following:
public interface ARepository extends CrudRepository<A, Long>
{
}
and I can use that in my Controller (for example) via
#Autowired
private ARepository aRepository;
and just can do things like this:
aRepository.save(..);
aRepository.findAll();
..
No problem so far.
But my problem is that I have ca. 500 JPA classes and need to access each table which means to define 500 Repositories in the style of above.
So does exist an thing to create that either dynamically via some Spring Data "magic" which from my point of view should exist otherwise the above would not be possible. It looks like this is similar to my problem.
Apart from that one more issue related to the above. I can define findBy... methods in the interface and in the background there will be generated a query method for this particular attribute. The question is also if this can be done in a dynamic way related to the previous question, cause I have groups of tables which need supplemental query methods..
There is spring-data-generator which can automatically generate the interfaces for you.
Regarding your 2nd question I don't think you that can be done in a dynamic way. Java is statically compiled and there's no way to add members dynamically. There could be a tool that generates code for those methods but if that tool generates methods for all combinations of columns you will end up with a huge amount of methods.
You can make a base abstract entity for your 500 classes an then create one repo for this class. (I think it's a common practice to have a BaseEntity class with id, version etc. for every entity in the project).
For simple repo methods (like save, findAll etc.) it will work right from the box (note - entities must have the equal id type). For example:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstarct class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
}
#Entity
public class Entity1 extends BaseEntity {
private String name;
}
#Entity
public class Entity2 extends BaseEntity {
private String name;
}
public interface BaseEntityRepo extends JpaRepository<BaseEntity, Long> {
}
Note that BaseEntity must have #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) to prevent of using singe table base_entity for every entity. And their ids must not intersect (see #GeneratedValue(strategy = GenerationType.SEQUENCE)).
Usage:
#RunWith(SpringRunner.class)
#SpringBootTest
public class BaseEntityRepoTest {
#Autowired private BaseEntityRepo repo;
#Before
public void setUp() throws Exception {
repo.save(asList(
new Entity1("entity1"),
new Entity2("entity2")
));
}
#Test
public void readingTest() throws Exception {
List<BaseEntity> entities = repo.findAll();
assertThat(entities).hasSize(2);
}
}
Related to your second question you can use this approach:
public interface BaseEntityRepo extends JpaRepository<BaseEntity, Long> {
<T> T findById(Long id, Class<T> type);
}
Usage:
#Test
public void findById() {
final Entity1 entity1 = repo.findById(1L, Entity1.class);
final Entity2 entity2 = repo.findById(2L, Entity2.class);
assertThat(entity1).isNotNull();
assertThat(entity2).isNotNull();
}
But you can build repo query methods only for 'common' properties of inherited entities which are present in the base class. To make this method work you must move the name parameter to the BaseEntity:
<T> List<T> findAllByNameLike(String name, Class<T> type);

Java Object mapping framework working with builder pattern

Is there any class mapping framework which works with builders? I would like to keep some of my classes immutable and avoid multiple constructors - the Builder Pattern comes to the rescue. However I can't any mapping framework which would use builder automatically instead of getters/setters.
I got the following working with Lombok and ModelMapper. See: http://modelmapper.org/getting-started/
public class MyService {
private ModelMapper modelMapper;
public MyService(){
this.modelMapper = new ModelMapper();
this.modelMapper.getConfiguration()
.setMatchingStrategy(MatchingStrategies.STRICT)
.setDestinationNamingConvention(LombokBuilderNamingConvention.INSTANCE)
.setDestinationNameTransformer(LombokBuilderNameTransformer.INSTANCE);
}
public OutputDTO aMethod(final InputDTO input){
return modelMapper.map(input, OutputDTO.OutputDTOBuilder.class).build();
}
}
Where LombokBuilderNamingConvention is:
import org.modelmapper.spi.NamingConvention;
import org.modelmapper.spi.PropertyType;
public class LombokBuilderNamingConvention implements NamingConvention {
public static LombokBuilderNamingConvention INSTANCE = new LombokBuilderNamingConvention();
#Override
public boolean applies(String propertyName, PropertyType propertyType) {
return PropertyType.METHOD.equals(propertyType);
}
#Override
public String toString() {
return "Lombok #Builder Naming Convention";
}
}
And LombokBuilderNameTransformer is:
import org.modelmapper.spi.NameTransformer;
import org.modelmapper.spi.NameableType;
public class LombokBuilderNameTransformer implements NameTransformer {
public static final NameTransformer INSTANCE = new LombokBuilderNameTransformer();
#Override
public String transform(final String name, final NameableType nameableType) {
return Strings.decapitalize(name);
}
#Override
public String toString() {
return "Lombok #Builder Mutator";
}
}
And OutputDTO can look like:
#Builder // Has .builder() static method
#Value // Thus immutable
public class OutputDTO {
private String foo;
private int bar;
}
This can be easily done with MapStruct and using a custom naming strategy for builders.
Have a look here in the documentation how to use Custom Accessor naming strategy.
Your mappings then need to look like:
#Mapper
public interface MyMapper {
default Immutable map(Source source) {
return mapToBuilder(source).build();
}
Immutable.Builder mapToBuilder(Source source);
}
Within MapStruct we are already working on a feature that would support out of the box support for builders. You can follow this issue for more details.
Update
MapStruct now (since 1.3.0.Beta1) has out of the box support for Immutables. This means that the mapper before can be written like:
#Mapper
public interface MyMapper {
Immutable map(Source source);
}
The assumption is that there is a public static method without parameters in Immutable that returns the builder
Uing Lombok and ModelMapper configure as:
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration()
.setFieldMatchingEnabled(true)
.setFieldAccessLevel(AccessLevel.PRIVATE);
By default ModelMapper uses only public setter method to map. When the class annotated with Lombok builder annotation it made the setter method as private. So to allow the ModelMapper to use the private setter method we need to add the above configureation.
OR
Configuration builderConfiguration = modelMapper.getConfiguration().copy()
.setDestinationNameTransformer(NameTransformers.builder())
.setDestinationNamingConvention(NamingConventions.builder());
modelMapper.createTypeMap(MyEntity.class, MyDto.MyDtoBuilder.class, builderConfiguration);
where MyEnity class is:
#Data
private static class MyEntity {
private Long id;
private String name;
private String value;
}
and builder class is:
#Data
#Builder
private static class MyDto {
private final Long id;
private final String name;
private final String value;
}
click here for detail

Categories

Resources