Using ENUMs with FlatFileReader in Spring Batch job - java

I have a Spring Batch job set up to read in a CSV.
In the reader it creates ProductCSV objects which represent each row using FlatFileReader.
In the writer it then converts each row into an actual Object object which is mapped using hibernate into a database using an extended ItemWriter.
Works great the only problem I have is ENUM typed fields. The error I get is:
Field error in object 'target' on field 'category': rejected value [Some Category]; codes [typeMismatch.target.category,typeMismatch.category,typeMismatch.com.project.enums.ProductCategory,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [target.category,category]; arguments []; default message [category]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'com.project.enums.ProductCategory' for property 'category'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [com.project.ProductCategory] for property 'category': no matching editors or conversion strategy found]
Here is what the ENUM looks like:
package com.project.enums;
public enum ProductCategory
{
SomeCategory( "Some Category" ),
AnotherCategory( "Another Category" );
final String display;
private ProductCategory( String display )
{
this.display = display;
}
#Override
public String toString()
{
return display;
}
}
Here is what the ProductCSV object looks like:
package com.project.LoadSavingInfo;
import com.project.enums.ProductCategory;
public class ProductCSV
{
private ProductCategory category;
public ProductCategory getCategory()
{
return this.category;
}
public void setCategory( ProductCategory category )
{
this.category = category;
}
}
Here is what the actual object looks like:
package com.project;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Table;
import com.project.enums.ProductCategory;
#Entity
#Table( name = "product" )
public class Product
{
#Column( nullable = false )
#Enumerated(EnumType.STRING)
private ProductCategory category;
public ProductCategory getCategory()
{
return category;
}
public void setCategory( ProductCategory category )
{
this.category = category;
}
}
So when it reads in something like "Some Category" from the CSV, how do I convert this into the ENUM type? Any help or advice is greatly appreciated and if you need any more info please just ask.

The problem is that standard Spring text->enum conversion is done using enum's name (SomeCategory,AnotherCategory) and not his displayName.
My advice is to convert enum's display name to ProductCategory object in your ItemProcessor as:
class MyItemProcessor<ProductCSV,Product> {
public Product process(ProductCSV item) {
Product p = new Product();
p.setCategory(ProductCategory.fromDisplayName(item.getCategory());
}
}
As side effect you have to declare
public class ProductCSV {
private String category;
public String getCategory() {
return this.category;
}
public void setCategory( String category ) {
this.category = category;
}
}
You have the full process in your hand (and this is my preferred way, is cleaner).
Another solution is to use your current classes and write a custom enum property editor/converted as described in Spring custom converter for all Enums.

Related

How to use #ModelAttribute to map nested pojo class from javascript form-data

This is RestController,
In this, i was trying to map data coming from javascript to ContactDetaislDto class
#RestController
public class ContactDetailsRestController {
#PostMapping("/save/contact/details")
public ContactDetailsDto saveContactDetails(#ModelAttribute ContactDetailsDto contactDetailsDto,
Principal principal) {
System.out.println(contactDetailsDto);
return contactDetailsDto;
}
}
ContactDetailsDto.java class
In this which have list of phone number class
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ContactDetailsDto {
//Remaining other fields
#NotBlank
private List<PhoneNumber> phoneNos = new ArrayList<>();
}
PhoneNumber class
In this , just one more parametrized constructor which take string phoneNo
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class PhoneNumber {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(nullable = false, length = 10)
private String phoneNo;
// Mapping
#ManyToOne
#JoinColumn(name = "contact_detail_id", nullable = false)
private ContactDetails contactDetails;
#Override
public String toString() {
return "PhoneNumber [id=" + id + ", phoneNo=" + phoneNo + "]";
}
public PhoneNumber(String phoneNo) {
super();
this.phoneNo = phoneNo;
}
}
From javascript sending data in formdata
let fdata = new FormData();
//some
fdata.append("phoneNos", "1234");
fdata.append("phoneNos", "4567");
fdata.append("phoneNos", "8971");
fetch("/save/contact/details", {
body: fdata,
method: "post"
}).then((response) => {
if (response.ok) {
//Some code
} else {
//Some code
}
});
And error is
[2m2023-01-13 19:51:20.859[0;39m [33m WARN[0;39m [35m7176[0;39m
[2m---[0;39m [2m[nio-8080-exec-3][0;39m
[36m.w.s.m.s.DefaultHandlerExceptionResolver[0;39m [2m:[0;39m Resolved
[org.springframework.validation.BindException:
org.springframework.validation.BeanPropertyBindingResult: 1
errorsField error in object 'contactDetailsDto' on field
'phoneNos': rejected value [asd,awsd,wew]; codes
[typeMismatch.contactDetailsDto.phoneNos,typeMismatch.phoneNos,typeMismatch.java.util.List,typeMismatch];
arguments
[org.springframework.context.support.DefaultMessageSourceResolvable:
codes [contactDetailsDto.phoneNos,phoneNos]; arguments []; default
message [phoneNos]]; default message [Failed to convert property value
of type 'java.lang.String[]' to required type 'java.util.List' for
property 'phoneNos'; nested exception is
org.springframework.core.convert.ConversionFailedException: Failed to
convert from type [java.lang.String] to type [java.lang.Integer] for
value 'asd'; nested exception is java.lang.NumberFormatException: For
input string: "asd"]]
Not able to understand why it converting string to int
and how can I map using #ModelAttribute with nested pojo class
I have creating simple project with similar class and that works perfectly.

How to define a sortKey for DynamoDB?

When I try to execute a query on a GSI on DynamoDB, the following error is reported:
java.lang.IllegalArgumentException: A query conditional requires a sort key to be present on the table or index being queried, yet none have been defined in the model
What I should do in order to define this required sort key?
Here are some pieces of my code:
package com.test;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.*;
import java.time.Instant;
#Data
#Builder
#DynamoDbBean
#NoArgsConstructor
#AllArgsConstructor
#ToString
public class Accumulator {
private Instant createdAt;
private String userId;
private String transactionIds;
private String paymentAt;
private String type;
private Instant startAt;
private Instant endAt;
private Integer ordersTotal;
private Integer retries;
private String sortKey;
#DynamoDbPartitionKey
public String getUserId() {
return userId;
}
#DynamoDbSecondaryPartitionKey(indexNames= {"idx_by_payment_at"})
#DynamoDbSortKey
public String getPaymentAt() {
return paymentAt.toString();
}
}
Creating my table at DynamoDBConfig:
public DynamoDbAsyncTable<Accumulator> tableLocal(final DynamoDbEnhancedAsyncClient dynamoDbEnhancedAsyncClient) {
var tableAccumulator = dynamoDbEnhancedAsyncClient.table(tablename, BeanTableSchema.create(Accumulator.class));
try {
tableAccumulator.createTable(CreateTableEnhancedRequest.builder()
.provisionedThroughput(
ProvisionedThroughput.builder()
.writeCapacityUnits(5L).readCapacityUnits(5L)
.build())
.globalSecondaryIndices(
EnhancedGlobalSecondaryIndex.builder()
.indexName("idx_by_payment_at")
.projection(p -> p.projectionType(ProjectionType.KEYS_ONLY))
.provisionedThroughput(ProvisionedThroughput.builder()
.writeCapacityUnits(5L).readCapacityUnits(5L)
.build())
.build()
)
.build()).get();
Thread.sleep(3000);
} catch (InterruptedException | ExecutionException e) {
log.info("Skipping");
}
return tableAccumulator;
}
And my query:
DynamoDbTable<Accumulator> table = dynamoDbEnhancedClient.table(tableName, TableSchema.fromBean(Accumulator.class));
DynamoDbIndex<Accumulator> index = table.index("idx_by_payment_at");
QueryConditional queryConditional = QueryConditional.sortBetween(
Key.builder()
.partitionValue("2022-01-23T06:10:12.948334Z")
.build(),
Key.builder()
.partitionValue("2022-01-23T06:10:22.515769Z")
.build());
SdkIterable<Page<Accumulator>> query = index.query(queryConditional);
List<Page<Accumulator>> pages = query.stream().toList();
pages.forEach(page -> {
List<Accumulator> accumulators = page.items();
accumulators.stream().forEach(accumulator -> {
System.out.println(accumulator);
});
});
Thanks for any help.
From OP comment:
My intent is to query between 2 payment_at of one user and one type.
If that is the case, you'll need a GSI with
a partition (hash) that is a combination of user and type;
and a range (sort) that is the payment_at.
This will allow you to perform the access you require. The projection will depend on your use.
As it stands your primary table keys will allow you to select a range of payment_at for a given user. It might suffice to query that. Or it may be that you could use a sort key that is a compound of type and payment_at. The best choice really depends on your access patterns.

How to perform a custom JSON deserialization with Jackson that respects a custom annotation?

I am trying to deserialize JSON into a custom POJO that I am not able to modify. That POJO has annotations from a different custom internal serialization framework that I'm not able to use. How can I create a custom deserializer that will respect these annotations?
Here is an example POJO:
public class ExampleClass {
#Property(name = "id")
public String id;
#Property(name = "time_windows")
#NotNull
public List<TimeWindow> timeWindows = new ArrayList<>();
public static class TimeWindow {
#Property(name = "start")
public Long start;
#Property(name = "end")
public Long end;
}
}
So in this case, the deserializer would look for fields in the JSON that correspond to the Property annotations, and use the value in that annotation to decide what field to grab. If a property doesn't have the Property annotation, it should be ignored.
I have been going through the Jackson docs but haven't been able to find exactly what I need. Is this a place where an AnnotationIntrospector would be useful? Or possibly a ContextualDeserializer?
Any pointers in the right direction would be greatly appreciated!
Update: I tried implementing the advice in the comments, but without success.
Here is my initial implementation of the introspector:
class CustomAnnotationInspector : JacksonAnnotationIntrospector () {
override fun hasIgnoreMarker(m: AnnotatedMember?): Boolean {
val property = m?.getAnnotation(Property::class.java)
return property == null
}
override fun findNameForDeserialization(a: Annotated?): PropertyName {
val property = a?.getAnnotation(Property::class.java)
return if (property == null) {
super.findNameForDeserialization(a)
} else {
PropertyName(property.name)
}
}
}
And here is where I actually use it:
// Create an empty instance of the request object.
val paramInstance = nonPathParams?.type?.getDeclaredConstructor()?.newInstance()
// Create new object mapper that will write values from
// JSON into the empty object.
val mapper = ObjectMapper()
// Tells the mapper to respect custom annotations.
mapper.setAnnotationIntrospector(CustomAnnotationInspector())
// Write the contents of the request body into the StringWriter
// (this is required for the mapper.writeValue method
val sw = StringWriter()
sw.write(context.bodyAsString)
// Deserialize the contents of the StringWriter
// into the empty POJO.
mapper.writeValue(sw, paramInstance)
Unfortunately it seems that findNameForDeserialization is never called, and none of the JSON values are written into paramInstance. Can anybody spot where I'm going wrong?
Thank you!
Update 2: I changed the code slightly, I'm now able to identify the property names but Jackson is failing to create an instance of the object.
Here's my new code:
val mapper = ObjectMapper()
// Tells the mapper to respect CoreNg annotations.
val introspector = CustomAnnotationInspector()
mapper.setAnnotationIntrospector(introspector)
val paramInstance = mapper.readValue(context.bodyAsString,nonPathParams?.type)
My breakpoints in the custom annotation introspector are getting hit. But I'm getting the following exception:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `app.employee.api.employee.BOUpsertEmployeeRequest` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
Here is the POJO I'm trying to deserialize:
public class BOUpsertEmployeeRequest {
public BOUpsertEmployeeRequest () { }
#NotNull
#Property(name = "xref_code")
public String xrefCode;
#Property(name = "first_name")
public String firstName;
#Property(name = "last_name")
public String lastName;
#Property(name = "email_address")
public String emailAddress;
#Property(name = "phone")
public String phone;
#Property(name = "address")
public List<String> address;
#Property(name = "employment_status")
public String employmentStatus;
#Property(name = "pay_type")
public String payType;
#Property(name = "position")
public String position;
#Property(name = "skills")
public List<String> skills;
#Property(name = "gender")
public String gender;
}
As far as I can tell it has a default constructor. Anybody have any idea what the problem is?
Thank you!
Method hasIgnoreMarker is called not only for fields, but also for the constructor, including the virtual one:
Method called to check whether given property is marked to be ignored. This is used to determine whether to ignore properties, on per-property basis, usually combining annotations from multiple accessors (getters, setters, fields, constructor parameters).
In this case you should ignore only fields, that are not marked properly:
static class CustomAnnotationIntrospector extends JacksonAnnotationIntrospector {
#Override
public PropertyName findNameForDeserialization(Annotated a) {
Property property = a.getAnnotation(Property.class);
if (property == null) {
return PropertyName.USE_DEFAULT;
} else {
return PropertyName.construct(property.name());
}
}
#Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
return m instanceof AnnotatedField
&& m.getAnnotation(Property.class) == null;
}
}
Example:
class Pojo {
// #Property(name = "id")
Integer id;
// #Property(name = "number")
Integer number;
#Property(name = "assure")
Boolean assure;
#Property(name = "person")
Map<String, String> person;
}
String json =
"{\"id\" : 1, \"number\" : 12345, \"assure\" : true," +
" \"person\" : {\"name\" : \"John\", \"age\" : 23}}";
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new CustomAnnotationIntrospector());
Pojo pojo = mapper.readValue(json, Pojo.class);
System.out.println(pojo);
Pojo{id=null, number=null, assure=true, person={name=John, age=23}}
Note: Custom Property annotation should have RetentionPolicy.RUNTIME (same as JsonProperty annotation):
#Retention(RetentionPolicy.RUNTIME)
public #interface Property {
String name();
}
I will suggest a different approach:
In the runtime, with the bytecode instrumentation library Byte Buddy and its Java agent, re-annotate the fields with the proper Jackson Annotations. Simply implement the logic via reflection. See the following example:
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.dynamic.DynamicType.Builder;
import net.bytebuddy.dynamic.DynamicType.Builder.FieldDefinition.Valuable;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.matcher.ElementMatchers;
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#interface MyJsonIgnore {
}
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#interface MyJsonProperty {
String name();
}
public class Sample {
public static void main(String[] args) throws JsonProcessingException {
ByteBuddyAgent.install();
ClassReloadingStrategy classReloadingStrategy = ClassReloadingStrategy.fromInstalledAgent();
ByteBuddy byteBuddy = new ByteBuddy();
AnnotationDescription jsonIgnoreDesc =
AnnotationDescription.Builder.ofType(JsonIgnore.class).build();
Builder<Person> personBuilder = byteBuddy.redefine(Person.class);
for (Field declaredField : Person.class.getDeclaredFields()) {
Valuable<Person> field = personBuilder.field(ElementMatchers.named(declaredField.getName()));
MyJsonProperty myJsonProperty = declaredField.getAnnotation(MyJsonProperty.class);
if (myJsonProperty != null) {
AnnotationDescription jsonPropertyDesc =
AnnotationDescription.Builder.ofType(JsonProperty.class)
.define("value", myJsonProperty.name())
.build();
personBuilder = field.annotateField(jsonPropertyDesc);
}
MyJsonIgnore myJsonIgnore = declaredField.getAnnotation(MyJsonIgnore.class);
if (myJsonIgnore != null) {
personBuilder = field.annotateField(jsonIgnoreDesc);
}
}
personBuilder.make().load(Sample.class.getClassLoader(), classReloadingStrategy);
Person person = new Person("Utku", "Ozdemir", "Berlin");
ObjectMapper objectMapper = new ObjectMapper();
String jsonString = objectMapper.writeValueAsString(person);
System.out.println(jsonString);
}
}
class Person {
#MyJsonProperty(name = "FIRST")
private String firstName;
#MyJsonProperty(name = "LAST")
private String lastName;
#MyJsonIgnore private String city;
public Person(String firstName, String lastName, String city) {
this.firstName = firstName;
this.lastName = lastName;
this.city = city;
}
}
In the example above, I
create MyJsonProperty and MyJsonIgnore annotations and a Person class for the demonstration purpose
instrument the current Java process with the Byte buddy agent
create a bytebuddy builder to redefine the Person class
loop over the fields of the Person class and check for these annotations
add an additional annotation to each of those fields (on the builder), Jackson's JsonProperty (with the correct field name mapping) and JsonIgnore.
after being done with the fields, make the new class bytecode and load it to the current classloader using the byte buddy agent's class reloading mechanism
write a person object to the stdout.
It prints, as expected:
{"FIRST":"Utku","LAST":"Ozdemir"}
(the field city is ignored)
This solution might feel like an overkill, but on the other side, it is pretty generic solution - with a few changes in the logic, you could handle all the 3rd party classes (which you are not able to modify) instead of handling them case by case.

Join two tables and send as one record

Suppose, that we have such tables:
Table Users
iduser | password
Table Marks
id | iduser | mark | idtest
Table Tests
idtest | title
Query looks like this:
#GET
#Path("/{id}/marks")
#Produces(MediaType.APPLICATION_JSON)
public Object funkcja(#PathParam("id") Integer iduser) {
Query query = em.createQuery("select m,t from Marks m, Tests t where m.idusers=:iduser and m.idtest = t.idtests");
query.setParameter("iduser", id);
List<Object> results = (List<Object>)query.getResultList();
return results;
}
I have entity classes:
Marks , Users, Tests
What I should to do in order to join tables and send JSON type on web service and how to convert JSON to entity class because I would like to show in TableView.
Perhaps there are other simple ways?
Maybe map or JsonObject?
You seem to have multiple questions here; I think you need to break these down into separate questions.
For the "main" question, which is about JPA and how to join the entities, I would do that at the entity level, not at the query level. I.e. I think I would have entity classes like:
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name="Tests")
public class Test {
#Id
#Column(name="idtest")
private int id ;
private String title ;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
#Override
public boolean equals(Object other) {
if (other instanceof Test) {
return Objects.equals(title, ((Test)other).title);
} else return false ;
}
#Override
public int hashCode() {
return Objects.hash(title);
}
}
and then the Mark entity can use a #ManyToOne annotation to reference the actual Test object (not its id):
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
#Entity
#Table(name="Marks")
public class Mark {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id ;
#ManyToOne
#JoinColumn(name="idtest")
private Test test ;
// You probably don't want a reference to User here, as the User class
// encapsulates the password, which you don't want to throw back and
// forward across the network unnecessarily. But if the User class
// instead had a user name etc you wanted, you could use the same
// #ManyToOne technique to reference a User object here if you needed.
#Column(name="iduser")
private int userId ;
private int mark ;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public Test getTest() {
return test;
}
public void setTest(Test test) {
this.test = test;
}
public int getMark() {
return mark;
}
public void setMark(int mark) {
this.mark = mark;
}
#Override
public boolean equals(Object obj) {
if (obj instanceof Mark) {
Mark other = (Mark)obj ;
return Objects.equals(userId, other.userId)
&& Objects.equals(test, other.test)
&& mark == other.mark ;
} else return false ;
}
#Override
public int hashCode() {
return Objects.hash(userId, test, mark);
}
}
Now your query looks like
TypedQuery<Mark> query = em.createQuery("select m from Mark m where m.userId=:userid");
query.setParameter("userid", iduser);
List<Mark> results = query.getResultList();
and the Mark instances in the list already have all the data you need:
for (Mark mark : results) {
System.out.println(mark.getTest().getTitle() + ": " + mark.getMark());
}
For the remaining questions:
Assuming you have a server set up with a JAX-RS implementation (e.g. Jersey) the code snippet you showed (modified with the new query) should generate JSON output. (You can use a developer tool such as Firefox REST client to view the JSON output by specifying the appropriate URL and request headers, and viewing the response.)
On the client (JavaFX) side you can use Jersey's client library (or maybe Apache HttpComponent library) to create the web requests easily, and a library such as GSON or Jackson for mapping the JSON content you get back to a Java object for display in the TableView.
I recommend trying this and asking specific questions about the remaining pieces if you get stuck.

How to persist classes like java.util.Currency?

I am using hibernate to persist my data. It's a financial application and I am having a hard time persisting the most fundamental entity of the application which is 'Money'. I was using JodaMoney but it's immutable so I am not able to find a good way to persist it. And without persisting my money to database, there is no point of making the application. What would I do with the immutability when I can't even store the state of my object?
Then I started creating my own 'Money'(fields as BigDecimal amount and java.util.Currency for currency), I wanted to use 'Currency' of java.util . But, that doesn't have a public constructor so hibernate can not persist that.
Please guide me on how to deal with this?
EDIT1: The code for most basic class:
#Entity
public class BasicMoney {
#Embedded
#Id
private BigDecimal amount;
#Embedded
private Currency currency;
//getters and setters, other methods
}
Now, when I make an object of this class and try to store it into database, it doesn't work. Hibernate throws:
org.hibernate.InstantiationException: No default constructor for entity: : java.util.Currency
So, this is the problem which I am facing.
You could use a workaround
#Embedded
String currency; //store data as a string
public void setCurrency(Currency currency) {
this.currency = currency.getCurrencyCode(); //conversion to an actual currency
}
public Currency getCurrency() {
return Currency.getInstance(currency); //conversion to an actual currency
}
Instead of dealing with a currency string and an amount, teach Hibernate about your type instead. That way you would have:
private Money amount;
instead of
private BigDecimal amount;
private String currency;
so you don't need to convert all over the place.
Here's how I do it:
1) Instead of JodaMoney, use JavaMoney, the JSR-354 project that is expected to be included in Java 9. If you want to stick to JodaMoney, you don't need step #3 below.
2) Add this UserType library to your classpath
3) Create this simple class:
public class CustomPersistentMoneyAmountAndCurrency extends AbstractMultiColumnUserType<MonetaryAmount> {
private static final ColumnMapper<?, ?>[] COLUMN_MAPPERS = new ColumnMapper<?, ?>[] { new CustomStringColumnCurrencyUnitMapper(), new BigDecimalBigDecimalColumnMapper() };
private static final String[] PROPERTY_NAMES = new String[]{ "currency", "number" };
#Override
protected ColumnMapper<?, ?>[] getColumnMappers() {
return COLUMN_MAPPERS;
}
#Override
protected Money fromConvertedColumns(Object[] convertedColumns) {
CurrencyUnit currencyUnitPart = (CurrencyUnit) convertedColumns[0];
BigDecimal amountPart = (BigDecimal) convertedColumns[1];
return Money.of(amountPart, currencyUnitPart);
}
#Override
protected Object[] toConvertedColumns(MonetaryAmount value) {
return new Object[] { value.getCurrency(), value.getNumber().numberValue(BigDecimal.class) };
}
#Override
public String[] getPropertyNames() {
return PROPERTY_NAMES;
}
}
4) Now wherever you want to use this in your Entities you would do:
#TypeDefs(value = {
#TypeDef(name = "moneyAmountWithCurrencyType", typeClass = CustomPersistentMoneyAmountAndCurrency.class)
})
#Entity
#Table(name = "account_entry")
public class AccountEntry {
private Money referenceMoney;
...
#Basic( optional = false )
#Columns(columns = {
#Column( name = "reference_money_currency", nullable = false, length = 3 ),
#Column( name = "reference_money", nullable = false )
})
#Type(type = "moneyAmountWithCurrencyType")
public Money getReferenceMoney() {
return this.referenceMoney;
}
}
And that's it. You will get strong typing all throughout.
Hibernate does support java.util.Currency natively.
https://docs.jboss.org/hibernate/orm/3.6/reference/en-US/html/types.html#types-basic-value-currency
Sorry for the late answer, but I didnĀ“t see this option as an answer and I think is pretty elegant. You can use a JPA converter with autoapply:
#Converter(autoApply = true)
public class CurrencyConverter implements AttributeConverter<Currency, String> {
#Override
public String convertToDatabaseColumn(Currency currency) {
return currency.getCurrencyCode();
}
#Override
public Currency convertToEntityAttribute(String currencyCode) {
return Currency.getInstance(currencyCode);
}
}

Categories