I have an enum:
public enum NotificationType {
OPEN("open"),
CLOSED("closed");
public String value;
NotificationType(String value) {
this.value = value;
}
}
I want to pass the custom string open or closed rather than OPEN or CLOSED to entity. Currently, I've mapped it in the entity as follows:
#Enumerated(EnumType.STRING)
private NotificationType notificationType;
Which is the best way to store/ fetch enum value?
You can create a custom converter like this:
#Converter(autoApply = true)
public class NotificationTypeConverter implements AttributeConverter<NotificationType, String> {
#Override
public String convertToDatabaseColumn(NotificationType notificationType) {
return notificationType == null
? null
: notificationType.value;
}
#Override
public NotificationType convertToEntityAttribute(String code) {
if (code == null || code.isEmpty()) {
return null;
}
return Arrays.stream(NotificationType.values())
.filter(c -> c.value.equals(code))
.findAny()
.orElseThrow(IllegalArgumentException::new);
}
}
And perhaps you'll need to remove annotation from your notificationType field so that this converter takes effect.
Yes, basically you have to develop a custom converter for that but I suggest you use Optional to avoid dealing with null and exceptions.
Add in NotificationType:
public static Optional<NotificationType> getFromValue(String value) {
return Optional.ofNullable(value)
.flatMap(dv -> Arrays.stream(NotificationType.values())
.filter(ev -> dv.equals(ev.value))
.findFirst());
}
Create the required converter:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
#Converter(autoApply = true)
public class NotificationTypeConverter implements AttributeConverter<NotificationType, String> {
#Override
public String convertToDatabaseColumn(NotificationType notificationType) {
return null == notificationType ? null : notificationType.value;
}
#Override
public NotificationType convertToEntityAttribute(String databaseValue) {
return NotificationType.getFromValue(databaseValue)
.orElse(null);
}
}
And now, you only have to modify your model:
#Entity
#Table
public class MyEntity {
#Convert(converter=NotificationTypeConverter.class)
private NotificationType notificationType;
}
Related
I have a DTO interface which fetches data from different tables using joins. I have made a DTO interface with the abstract getter methods something like this.
public interface HRJobsDTO {
String getEditorName();
String getEditorId();
String getBillingMonth();
Integer getEditorWordCount();
Integer getJobCount();
Integer getEmployeeGrade();
Float getGrossPayableAmount();
Float getJobBillingRate();
Float getTaxDeduction();
Float getTaxDeductionAmount();
Float getNetPayableAmount();
String getInvoiceStatus();
String getFreelanceInvoiceId();
}
In this interface my getFreelanceInvoiceId(); method returns a JSON Array using json_arrayagg function of mysql. I changed the datatype to String, String[] and Arraylist but it returns something like this in my response
"freelanceInvoiceId": "[\"4af9e342-065b-4594-9f4f-a408d5db9819/2022121-95540\", \"4af9e342-065b-4594-9f4f-a408d5db9819/2022121-95540\", \"4af9e342-065b-4594-9f4f-a408d5db9819/20221215-53817\", \"4af9e342-065b-4594-9f4f-a408d5db9819/20221215-53817\", \"4af9e342-065b-4594-9f4f-a408d5db9819/20221215-53817\"]"
Is there any way to return only array with exclusion of backslashes?
You can use #Converter from JPA (implemented by hibernate also)
#Converter
public class List2StringConveter implements AttributeConverter<List<String>, String> {
#Override
public String convertToDatabaseColumn(List<String> attribute) {
if (attribute == null || attribute.isEmpty()) {
return "";
}
return StringUtils.join(attribute, ",");
}
#Override
public List<String> convertToEntityAttribute(String dbData) {
if (dbData == null || dbData.trim().length() == 0) {
return new ArrayList<String>();
}
String[] data = dbData.split(",");
return Arrays.asList(data);
}
}
And references it in the pojo class as below
#Column(name="freeLanceInvoiceId")
#Convert(converter = List2StringConveter.class)
private List<String> tags=new ArrayList<>();
Basically I have list of names separated by camma (;),
CREATE TABLE familynames (
id int4 NOT NULL DEFAULT nextval('family_id_seq'::regclass),
names varchar NULL
);
INSERT INTO familynames (id, names) VALUES(1, 'Animalia;Arthropoda;Pancrustacea;Hexapoda');
The converter :
#Converter
public class NameConverter implements AttributeConverter<List<String>, String> {
#Override
public List<String> convertToEntityAttribute(String attribute) {
if (attribute == null) {
return null;
}
List<String> namesList = new ArrayList<String>(Arrays.asList(attribute.split(";")));
return namesList;
}
#Override
public String convertToDatabaseColumn(List<String> namesList) {
if (namesList == null || namesList.size() == 0) {
return null;
}
String result = namesList.stream().collect( Collectors.joining( ";" ) );
return result;
}
}
An entity that map the sql table
#Entity
#Table(name = "familynames")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class FamilyNames implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Convert(converter = NameConverter.class, attributeName = "names")
private List<String> names;
}
An interface for projection
public interface FamilyNamesDto {
Integer getId();
List<String> getNames();
}
The repository that ensure the selection and the projection
#Repository
public interface FamilyNamesRepository extends JpaRepository<FamilyNames, Long> {
/** #return the whole elements of mapped elements as dto */
List<FamilyNamesDto> findAllProjectedBy();
}
How to autowire it
#Autowired
private FamilyNamesRepository familyNamesRepository;
how to call repository within any service
List<FamilyNamesDto> fs = familyNamesRepository.findAllProjectedBy();
results :
I have implemented my basic requirements, which work well in one simple scenario as mentioned below code snippet. But for new requirements what is the best way out there I need help.
New requirement: Statuses in numeric format are used on other services but in request-response status representation are these user-friendly string ["Not Started", "In Progress", "Completed"]
#AllArgsConstructor
#Getter
public enum StatusEnum {
NOT_STARTED(1,"Not Started"),
IN_PROGRESS(2, "In Progress"),
COMPLETED(3, "Completed");
private final int key;
private final String value;
}
Below is my MapStruct logic to convert enum to string and visa-versa conversion logic. This works fine for basic requirements. But what is the logic of the new requirement?
ActionItem.java:
private Constants.StatusEnum status;
Basic Requirements works with below implementation:
#AllArgsConstructor
#Getter
public enum StatusEnum {
NOT_STARTED("Not Started"),
IN_PROGRESS("In Progress"),
COMPLETED("Completed");
private final String value;
}
#Mapper
public interface ActionItemMapper extents BaseMapper {
#Mapping(source = "status", target = "status", qualifiedByName = "statusEnumToString")
ActionItemResponse toActionItemResponse(ActionItem actionItem);
}
#Mapper
public interface BaseMapper {
#Named("statusEnumToString")
default String statusEnumToString(Constants.StatusEnum statusEnum) {
return statusEnum.getValue();
}
#Named("statusStringToEnum")
default Constants.StatusEnum statusStringToEnum(String status) {
return List.of(Constants.StatusEnum.values()).stream().filter(s -> s.getValue().equals(status)).findAny()
.orElse(null);
}
}
I got the solution.
#AllArgsConstructor
#Getter
public enum StatusEnum {
NOT_STARTED(1, "Not Started"),
IN_PROGRESS(2, "In Progress"),
COMPLETED(3, "Completed");
private final String key;
private final String value;
}
#Mapper
public interface ActionItemMapper extents BaseMapper {
#Mapping(source = "status", target = "status", qualifiedByName = "statusEnumToString")
ActionItemResponse toActionItemResponse(ActionItem actionItem);
}
#Mapper
public interface BaseMapper {
#Named("statusEnumKeyToValue")
default String statusEnumKeyToValue(Integer status) {
String value = null;
for (Constants.StatusEnum statusEnum: Constants.StatusEnum.values()) {
if (statusEnum.getKey().equals(status)) {
value = statusEnum.getValue();
break;
}
}
if (value == null) {
throw new IllegalArgumentException("No status value found for status key " +status);
}
return value;
}
#Named("statusEnumValueToKey")
default Integer statusEnumValueToKey(String status) {
return statusStringToEnum(status).getKey();
}
default Constants.StatusEnum statusStringToEnum(String status) {
return List.of(Constants.StatusEnum.values()).stream().filter(s -> s.getValue().equals(status)).findAny()
.orElseThrow()
}
}
I'm trying to set a defaultValue for a boolean field using MapStruct, but the generated code simply ignores it.
My code:
public class CreateEventRequest {
#NotNull
#JsonProperty
private Boolean isPrivate;
#JsonProperty
private Boolean friendCanInviteFriends;
#JsonProperty
private boolean showGuestList;
public boolean isPrivate() {
return isPrivate;
}
public String getDescription() {
return description;
}
public boolean isFriendCanInviteFriends() {
return friendCanInviteFriends;
}
public boolean isShowGuestList() {
return showGuestList;
}
}
My mapper:
#Mapper(componentModel = "spring")
public interface CreateEventRequestMapper {
#Mapping(target = "showGuestList", source = "showGuestList", defaultExpression = "java(true)")
#Mapping(target = "friendCanInviteFriends", source = "friendCanInviteFriends", defaultValue = "true")
Event map(CreateEventRequest request);
}
The generated code:
public class CreateEventRequestMapperImpl implements CreateEventRequestMapper {
#Override
public Event map(CreateEventRequest request) {
if ( request == null ) {
return null;
}
Event event = new Event();
event.setShowGuestList( request.isShowGuestList() );
event.setFriendCanInviteFriends( request.isFriendCanInviteFriends() );
event.setPrivate( request.isPrivate() );
return event;
}
}
As you can see, I've tried using primitive/non-primitive type but it simply ignores the defaultValue.
Am I missing something here?
Thanks!
The problem is that the return type of your getter methods in the source object is always primitive which can't be null, you need to return Boolean.
MapStruct doesn't support direct private field access which requires reflection.
My question is actually a spin-off of this question as seen here... so it might help to check that thread before proceeding.
In my Spring Boot project, I have two entities Sender and Recipient which represent a Customer and pretty much have the same fields, so I make them extend the base class Customer;
Customer base class;
#MappedSuperclass
public class Customer extends AuditableEntity {
#Column(name = "firstname")
private String firstname;
#Transient
private CustomerRole role;
public Customer(CustomerRole role) {
this.role = role;
}
//other fields & corresponding getters and setters
}
Sender domain object;
#Entity
#Table(name = "senders")
public class Sender extends Customer {
public Sender(){
super.setRole(CustomerRole.SENDER);
}
}
Recipient domain object;
#Entity
#Table(name = "recipients")
public class Recipient extends Customer {
public Recipient(){
super.setRole(CustomerRole.RECIPIENT);
}
}
NOTE - Sender and Recipient are exactly alike except for their roles. These can be easily stored in a single customers Table by making the Customer base class an entity itself, but I intentionally separate the entities this way because I have an obligation to persist each customer type in separate database tables.
Now I have one form in a view that collects details of both Sender & Recipient, so for example to collect the firstname, I had to name the form fields differently as follows;
Sender section of the form;
<input type="text" id="senderFirstname" name="senderFirstname" value="$!sender.firstname">
Recipient section of the form;
<input type="text" id="recipientFirstname" name="recipientFirstname" value="$!recipient.firstname">
But the fields available for a customer are so many that I'm looking for a way to map them to a pojo by means of an annotation as asked in this question here. However, the solutions provided there would mean that I have to create separate proxies for both domain objects and annotate the fields accordingly e.g
public class SenderProxy {
#ParamName("senderFirstname")
private String firstname;
#ParamName("senderLastname")
private String lastname;
//...
}
public class RecipientProxy {
#ParamName("recipientFirstname")
private String firstname;
#ParamName("recipientLastname")
private String lastname;
//...
}
So I got very curious and was wondering, is there a way to map this Proxies to more than one #ParamName such that the base class for example can just be annotated as follows?;
#MappedSuperclass
public class Customer extends AuditableEntity {
#Column(name = "firstname")
#ParamNames({"senderFirstname", "recipientFirstname"})
private String firstname;
#Column(name = "lastname")
#ParamNames({"senderLastname", "recipientLastname"})
private String lastname;
#Transient
private CustomerRole role;
public Customer(CustomerRole role) {
this.role = role;
}
//other fields & corresponding getters and setters
}
And then perhaps find a way to select value of fields based on annotation??
A suggestion from Zhang Jie like ExtendedBeanInfo
so i do it this way
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Alias {
String[] value();
}
public class AliasedBeanInfoFactory implements BeanInfoFactory, Ordered {
#Override
public BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
return supports(beanClass) ? new AliasedBeanInfo(Introspector.getBeanInfo(beanClass)) : null;
}
private boolean supports(Class<?> beanClass) {
Class<?> targetClass = beanClass;
do {
Field[] fields = targetClass.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Alias.class)) {
return true;
}
}
targetClass = targetClass.getSuperclass();
} while (targetClass != null && targetClass != Object.class);
return false;
}
#Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 100;
}
}
public class AliasedBeanInfo implements BeanInfo {
private static final Logger LOGGER = LoggerFactory.getLogger(AliasedBeanInfo.class);
private final BeanInfo delegate;
private final Set<PropertyDescriptor> propertyDescriptors = new TreeSet<>(new PropertyDescriptorComparator());
AliasedBeanInfo(BeanInfo delegate) {
this.delegate = delegate;
this.propertyDescriptors.addAll(Arrays.asList(delegate.getPropertyDescriptors()));
Class<?> beanClass = delegate.getBeanDescriptor().getBeanClass();
for (Field field : findAliasedFields(beanClass)) {
Optional<PropertyDescriptor> optional = findExistingPropertyDescriptor(field.getName(), field.getType());
if (!optional.isPresent()) {
LOGGER.warn("there is no PropertyDescriptor for field[{}]", field);
continue;
}
Alias alias = field.getAnnotation(Alias.class);
addAliasPropertyDescriptor(alias.value(), optional.get());
}
}
private List<Field> findAliasedFields(Class<?> beanClass) {
List<Field> fields = new ArrayList<>();
ReflectionUtils.doWithFields(beanClass,
fields::add,
field -> field.isAnnotationPresent(Alias.class));
return fields;
}
private Optional<PropertyDescriptor> findExistingPropertyDescriptor(String propertyName, Class<?> propertyType) {
return propertyDescriptors
.stream()
.filter(pd -> pd.getName().equals(propertyName) && pd.getPropertyType().equals(propertyType))
.findAny();
}
private void addAliasPropertyDescriptor(String[] values, PropertyDescriptor propertyDescriptor) {
for (String value : values) {
if (!value.isEmpty()) {
try {
this.propertyDescriptors.add(new PropertyDescriptor(
value, propertyDescriptor.getReadMethod(), propertyDescriptor.getWriteMethod()));
} catch (IntrospectionException e) {
LOGGER.error("add field[{}] alias[{}] property descriptor error", propertyDescriptor.getName(),
value, e);
}
}
}
}
#Override
public BeanDescriptor getBeanDescriptor() {
return this.delegate.getBeanDescriptor();
}
#Override
public EventSetDescriptor[] getEventSetDescriptors() {
return this.delegate.getEventSetDescriptors();
}
#Override
public int getDefaultEventIndex() {
return this.delegate.getDefaultEventIndex();
}
#Override
public PropertyDescriptor[] getPropertyDescriptors() {
return this.propertyDescriptors.toArray(new PropertyDescriptor[0]);
}
#Override
public int getDefaultPropertyIndex() {
return this.delegate.getDefaultPropertyIndex();
}
#Override
public MethodDescriptor[] getMethodDescriptors() {
return this.delegate.getMethodDescriptors();
}
#Override
public BeanInfo[] getAdditionalBeanInfo() {
return this.delegate.getAdditionalBeanInfo();
}
#Override
public Image getIcon(int iconKind) {
return this.delegate.getIcon(iconKind);
}
static class PropertyDescriptorComparator implements Comparator<PropertyDescriptor> {
#Override
public int compare(PropertyDescriptor desc1, PropertyDescriptor desc2) {
String left = desc1.getName();
String right = desc2.getName();
for (int i = 0; i < left.length(); i++) {
if (right.length() == i) {
return 1;
}
int result = left.getBytes()[i] - right.getBytes()[i];
if (result != 0) {
return result;
}
}
return left.length() - right.length();
}
}
}
I'm trying to use a base class for my mongo collections and then have the collection name come from the derived classes; however, the collection name is always just entity ( instead of, in this case, Derived).
I have the following abstract class:
public abstract class Entity {
#Id
private String id;
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
}
And derived classes like:
// I've also tried #TypeAlias("Derived")
#Document(collection = "Derived")
public class Derived extends Entity {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
With a repository like:
#Component
public interface Repository<T extends Entity> extends MongoRepository<T, String> {
T findById(String id);
}
Follow this working example.
Let's declare a base document model from which every document model must inherit
import java.math.BigInteger;
import org.springframework.data.annotation.Id;
public class BaseDocument {
#Id
private BigInteger id;
public BigInteger getId() {
return id;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (this.id == null || obj == null || !(this.getClass().equals(obj.getClass()))) {
return false;
}
BaseDocument that = (BaseDocument) obj;
return this.id.equals(that.getId());
}
#Override
public int hashCode() {
return id == null ? 0 : id.hashCode();
}
}
Just for example, I declare my Code model. Note that collection name, if differs from class name, can be set with #Document annotation
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
#Document(collection = "codes")
public class Code extends BaseDocument {
#Field("code")
private String code;
#Field("holiday")
private String holiday;
public Code(String code, String holiday) {
super();
this.code = code;
this.holiday = holiday;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getHoliday() {
return holiday;
}
public void setHoliday(String holiday) {
this.holiday = holiday;
}
#Override
public String toString() {
return "Code [code=" + code + ", holiday=" + holiday + "]";
}
}
At this point you can easily use these classes using MongoOperations and other classes in order to operate on database and collections. MongoOperations has all the methods you need to operate on collections such as: findOne, findAll and so on.
Just to be clear, you can use MongoOperations in the following way:
MongoOperations mongoOps = new MongoTemplate(new SimpleMongoDbFactory(new MongoClient(), "yourDBname"));
I strongly suggest to implement a service class which operate on collection, like this one, instead of operating directly on it:
import java.util.List;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
public class CodesService extends BaseService implements BaseOperations{
#Override
public BaseDocument findOne(Long id) {
Query query = new Query(Criteria.where("code").is("8"));
return this.mongoOps.findOne(query, Code.class);
}
#Override
public List<? extends BaseDocument> findAll() {
List<Code> subs = this.mongoOps.findAll(Code.class);
List<? extends BaseDocument> codes = subs;
return codes;
}
}
This class implements the following interface:
import java.util.List;
public interface BaseOperations {
public BaseDocument findOne(Long id);
public List<? extends BaseDocument> findAll();
}
Hope this may help.
Try to implement the Repository<T extends Entity> interface like given below
public interface SomeInterfaceName extends Repository<Derived>{
}
Now try to #Autowire the Repository<Derived> repo
It will give you the desired output