Java Annotation for composite Range Keys in DDB - java

I have a my_table with a composite sort key made of two combined attributes id and model_name (i.e., id_model_name, similarly from what is done here here and here).
So I created this Java model:
#Builder
#Data
#AllArgsConstructor
#NoArgsConstructor
#DynamoDBTable(tableName = "my_table")
public class TableModel {
private static final String COMPOSITE_KEY_SEPARATOR = "_";
#DynamoDBAttribute(attributeName = "id")
private String id;
#DynamoDBAttribute(attributeName = "model_name")
private String modelName;
#DynamoDBRangeKey(attributeName = "id_model_name")
public String getIdModelName() {
return String.format("%s%s%s", id, COMPOSITE_KEY_SEPARATOR, modelName);
}
// more stuff...
}
However I'm getting:
DynamoDBMappingException: DRTFacet[id_model_name]; could not unconvert attribute
Notice that there is no String idModelName field, because it could mess up with #AllArgsConstructor and #Builder (since it's a derived field). Is it because this field is missing (together with a setter method?). How can I overcome this?

I found out that providing a dummy setter solved the problem:
public void setIdModelName(final String idModelName) {}

Related

Getting DynamoDBMappingException: not supported; requires #DynamoDBTyped or #DynamoDBTypeConverted for multilevel/complex object

I have a complex object Test in the entity class Item.
#AllArgsConstructor
#Getter
public enum TestStatus {
TO_RUN("To Run"),
RUNNING("Running"),
PASSED("Passed"),
FAILED("Failed");
public static TestStatus fromValue(String value) {
//...implementation
}
private final String value;
}
#Data
#ToString
#Accessors(chain = true)
#DynamoDBFlattened(attributes = {
#DynamoDBAttribute(attributeName = "test.task.id", mappedBy = "id"),
#DynamoDBAttribute(attributeName = "test.task.status", mappedBy = "status")
})
public class TestTask {
private String id;
#DynamoDBTypeConvertedEnum
private TestStatus status;
}
#Data
#ToString
#Accessors(chain = true)
#DynamoDBFlattened(attributes = {
#DynamoDBAttribute(attributeName = "test.suite.name", mappedBy = "name"),
#DynamoDBAttribute(attributeName = "test.suite.version", mappedBy = "version")
})
public class TestSuite {
private String name;
private String version;
}
#Data
#ToString
#Accessors(chain = true)
public class Test {
private TestSuite suite;
private TestTask task;
}
#Data
#ToString
#Accessors(chain = true)
#DynamoDBTable(tableName = "com.example.item")
public class Item {
private String name;
private Test test; // This is a complex object as structure given above.
}
On the call of dynamoDBMapper.save(item); getting exception.
#Repository
#RequiredArgsConstructor
public class DynamoDBItemRepository implements ItemRepository {
//...
#Override
public Item save(Item item) {
dynamoDBMapper.save(item); // Getting DynamoDBMappingException: not supported; requires #DynamoDBTyped or #DynamoDBTypeConverted
return item;
}
//...
}
I am getting the exception
com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException: not supported; requires #DynamoDBTyped or #DynamoDBTypeConverted
at com.amazonaws.services.dynamodbv2.datamodeling.StandardModelFactories$Rules$NotSupported.set(StandardModelFactories.java:664) ~[aws-java-sdk-dynamodb-1.11.578.jar:?]
What am I missing? Please help!
There are two problems in the code.
I tried to reproduce the error, but found the first problem: no hash key specified.
so I used Item.name as the hash key in order to go further on the test.
The second problem matched your description
com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException: not supported; requires #DynamoDBTyped or #DynamoDBTypeConverted
Found out that you missed an annotation #DynamoDBDocument, which should be added to the class Test since it is a nested type:
...
#DynamoDBDocument
...
public class Test {
see document here
I suggest to migrate to AWS SDK for Java 2.0 where you can use complex objects: doc

Hibernate throws NotWritablePropertyException when saving entity

This happened to me other times, but this time the reason is different.
I reduced the scenario where it can be reproduced down to two entities, Child and Parent:
Child:
#Table(name = "Childs")
#Entity
#IdClass(KeyChild.class)
#Builder
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Child {
#Id
#ManyToOne
private Parent parent;
#Id
private int id0;
}
Parent:
#Table(name = "Parents")
#IdClass(KeyParent.class)
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Getter
#Setter
public class Parent {
#Id
private String id1;
#Id
private int id2;
}
The composite keys:
public class KeyChild implements Serializable {
private KeyParent parent;
private int id0;
}
and
public class KeyParent implements Serializable {
private String id1;
private int id2;
}
The problem happens when trying to save a Child (specifying the parent as you would spect).
An example controller:
#Controller
public class ParticiparController {
#Autowired
ParentRepository parentRepository;
#Autowired
ChildRepository childRepository;
#GetMapping("/test/")
public String evento() {
Parent p = Parent.builder().id("test").id2(1).build();
parentRepository.save(p);
childRepository.save(Child.builder()
.parent(p)
.id(0)
.build());
return "test";
}
}
The save method of the child repository throws:
org.springframework.beans.NotWritablePropertyException: Invalid property 'id1' of bean class [com.example.myProject.Entities.Keys.KeyParent]: Bean property 'id1' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
I also tried without Lombok (just generating the getters and setters), but I got same result. I'm pretty sure setters are not the problem.
Update: There were, but not the entity ones... It seems that are only required in some cases.
As it suggested in the error message you should have getters/setters in the KeyParent which correspond to the appropriate getters/setters of the Parent entity fields annotated by the #Id.

How to save enum variable to DB (instead enum value itself)

I wonder if it's posible to map Enum VARIABLE and DB. I want to save in database the yellow values (variables of enum)
The enum is used in this class:
#Getter
#Setter
#Table(name = "TIPOS_MOVIMIENTO")
#Entity
public class TipoMovimiento {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column
#Enumerated(EnumType.STRING)
private TipoMov tipo;
public String getTipo() {
return tipo.getTipoNombre();
}
#OneToMany(mappedBy = "tipoMov")
private List<Movimiento> movimientos;
My DTO class:
#Getter
public class TipoMovimientoDto implements DtoEntity {
private TipoMov tipo;
}
I've tried DTO with
#Convert(converter = TipoMovEnumConverter.class)
private TipoMov tipo;
But it doesn't works
Write an AttributeConverter for your enum which will convert your enum data into it's value when store in database.
#Converter(autoApply = true)
public class TipoMovConverter implements AttributeConverter<TipoMov, String> {
#Override
public Integer convertToDatabaseColumn(TipoMov attribute) {
return attribute.getTipoNombre();
}
#Override
public TipoMov convertToEntityAttribute(String value) {
return value == null ? null : TipoMov.findByValue(value);
}
}
Note: Here findByValue is a static method to get enum from value string
0
I got it!!! I never thought this would work.
#Getter
public class TipoMovimientoDto implements DtoEntity {
private TipoMov tipo;
}
I just changed in the code above (Dto):
private TipoMov tipo;
to
private String tipo;
I can't explain how Enum from Entity could have been converted to DTO, using String instead Enum... But that worked!
In case you have the same problem... you need to use Attribute Converter (you can see it in the answer of #User - Upvote don't say Thanks)
Is still necessary to use it in Entity class, above of the enum variable:
#Convert(converter = TipoMovEnumConverter.class)
But not necessary in DTO. Just use String instead Enum in DTO!

How to create an Instance of an Object via RestController while not passing all the fields of the constructor via RequestBody?

I have an object defined as:
#Entity
#NoArgsConstructor
public class Task {
#Id
#Getter #Setter private Integer id;
#Getter #Setter private String text;
#Getter #Setter private Boolean isDone = Boolean.FALSE;
public Task(int id, String text){
this.id = id;
this.text = text;
this.isDone = Boolean.FALSE;
}
}
If I pass in {
"id:":1,
"text":"buy juice"
} as request body, RestController can create an instance of task. However I don't want to pass id as a field in my request body. How do I handle that?
Assuming you are using MySQL, change your entity as below
#Entity
#NoArgsConstructor
#Getter
#Setter
public class Task {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String text;
private Boolean isDone = Boolean.FALSE;
public Task(int id, String text){
this.id = id;
this.text = text;
this.isDone = Boolean.FALSE;
}
}
and then your request would change to simply passing text: {"text":"buy juice" }
JPA's save would now work as following
If id is present in the request, it will assume entity exists and update it
If id is not present in the request, it will create a new row in DB and auto-assign id
P.S. - Depending on what DB you have, you can choose optimal id generation strategy for that DB. For example, identity-based generation works well with MySQL, sequence-based generation works well with Oracle db.

Lombok builder override default constructor

I was setting the value of recordId from the child classes using the default constructor and was not using lombok #Builder initially. Eventually i decided to use the Builder here, but the problem now is lombok Builder overrides my default constructor internally hence the value is never set.
How can I put any hook too make lombok #Builder use my default constructor?
Parent class:
#Getter
#Setter
public abstract class Record {
private String recordId;
}
Child class:
#Getter
#Setter
#Builder
#ToString
#AllArgsConstructor
public class SRecord extends Record {
private static final String RECORD_ID = "REC001";
private String street;
private String city;
public SRecord() {
setRecordId(RECORD_ID); //value of recordId being set
}
}
Lombok's #Builder simply does not use the default constructor. It passes its values to an all-args constructor so that this constructor can fill the new instance with these values. #Builder does not use setters or direct access to the fields to do so. So your default constructor is simply ignored by #Builder.
What you can do is write your own all-args constructor. In it, you set your value for recordId and assign the rest of the fields from the parameters.
I think you should create a constructor in your base class:
#Getter
#Setter
public abstract class Record {
private String recordId;
public Record(String recordId) {
this.recordId = recordId;
}
}
Then use it in the constructor of the inherited class:
#Getter
#Setter
#Builder
public class SRecord extends Record {
private static final String RECORD_ID = "REC001";
private String street;
private String city;
public SRecord(String street, String city) {
super(RECORD_ID);
this.street = street;
this.city = city;
}
}
P.S. If you want to use Lombok Builder with inheritance you can use this technique.

Categories