Store enum name, not value in database using EBean - enums

I have this enum :
public enum DocumentTypes {
PDF("PDF Document"), JPG("Image files (JPG)"), DOC("Microsoft Word documents");
private final String displayName;
DocumentTypes(final String display) {
this.displayName = display;
}
#Override
public String toString() {
return this.displayName;
}
}
And a model like this :
#Entity
#Table(name = "documents")
public class Document extends Model {
#Id
public Long id;
#Constraints.Required
#Formats.NonEmpty
#Enumerated(EnumType.STRING)
#Column(length=20, nullable=false)
public DocumentTypes type;
#Constraints.Required
#Formats.NonEmpty
#Column(nullable=false)
public String document;
}
I match the enum using this in my controller :
DynamicForm form = form().bindFromRequest();
// ...
Document doc = new Document();
doc.type = DocumentTypes.valueOf(form.field("type").value());
doc.save();
The problem is that in database, it's stored as "Microsoft Word documents", but I would prefer to store it as DOC.
How can I do that?

You can define it very fine granular with the Anotation EnumMapping or EnumValue. This works with the old version org.avaje.ebean.
It seems that there was a complete rewrite of the code. In the actual version there is a different approach.

Related

What is the ideal way to serialize and deserialize polymorphic entity attribute in spring boot?

I have an Entity class with a column attribute whose type is an abstract class. I want to serialize (object to JSON string) while saving it in the database column and deserialize it into an abstract class (which in turn converts the string to the appropriate concrete class) when it is retrieved from the database.
Here's how I accomplished it:
ProductEntity.java
#Entity
#Table(name="PRODUCT")
#Data
public class ProductEntity{
#Id
#Column(name = "ID", insertable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private BigInteger id;
#Column(name = "DESCRIPTION")
private String description;
#Column(name = "NAME")
private String name;
#Column(name = "PRODUCT_TYPE")
private String productType;
#Column(name = "PRODUCT_SPECS")
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property =
"productType") // -------------------> Map to concrete class based on productType value
#Convert(converter = ObjectConverter.class) // ------------> custom converter
private ProductSpecification productSpec;
}
NOTE : "PRODUCT_SPECS" database column is of JSON type.
ProductSpecification.java
#NoArgsConstructor
#JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS,
include = JsonTypeInfo.As.WRAPPER_OBJECT,
#JsonSubTypes({
#JsonSubTypes.Type(value = ComputerSpecification.class, name = "computer"),
#JsonSubTypes.Type(value = SpeakerSpecification.class, name = "speaker")
})
public abstract class ProductSpecification{ }
ComputerSpecification.java
#Getter
#Setter
#NoArgsConstructor
#JsonTypeName("computer")
public class ComputerSpecification extends ProductSpecification {
String memory;
String displaySize;
String processor;
#JsonCreator
public ComputerSpecification (#JsonProperty("memory") String memory,
#JsonProperty("displaysize") String displaySize,
#JsonProperty("processor") String processor){
super();
this.memory = memory;
this.displaySize = displaySize;
this.processor = processor;
}
}
SpeakerSpecification.java
#Getter
#Setter
#NoArgsConstructor
#JsonTypeName("computer")
public class SpeakerSpecification extends ProductSpecification {
String dimension;
String sensitivity;
String bassPrinciple;
String amplifierPower;
#JsonCreator
public SpeakerSpecification (#JsonProperty("sensitivity") String sensitivity,
#JsonProperty("dimension") String dimension,
#JsonProperty("bassPrinciple") String bassPrinciple,
#JsonProperty("amplifierPower") String amplifierPower){
super();
this.sensitivity = sensitivity;
this.dimension = dimension;
this.bassPrinciple = bassPrinciple;
this.amplifierPower = amplifierPower;
}
}
ObjectConverter.java
NOTE: I am using Jackson ObjectMapper for serialization and deserialization.
public class ObjectConverter implements AttributeConverter<Object, String>{
private final static Logger LOGGER = LoggerFactory.getLogger(ObjectConverter.class);
private static final ObjectMapper mapper;
static {
mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
}
#Override
public String convertToDatabaseColumn(Object attributeObject) {
if (attributeObject == null) {
return "";
}
try {
return mapper.writeValueAsString(attributeObject);
} catch (JsonProcessingException e) {
LOGGER.error("Could not convert to database column", e);
return null;
}
}
#Override
public Object convertToEntityAttribute(String dbColumnValue) {
try {
if (StringUtils.isBlank(dbColumnValue)) {
return null;
}
return mapper.readValue(dbColumnValue, ProductSpecification.class); // ----> mapped to
abstract class
} catch (Exception e) {
LOGGER.error("Could not convert to entity attribute", e);
return null;
}
}
}
Request body 1:
{
"name" : "Bose Bass Module 700 - Black- Wireless, Compact Subwoofer",
"description" : "This wireless, compact subwoofer is designed to be paired with the Bose sound
bar 700 to bring music, movies, and TV to life with Deep, dramatic bass. ",
"productSpec" : {
"sensitivity" : "90 dB",
"bassPrinciple" : "reflex",
"amplifierPower" : "700 watts",
"dimension" : "14-5/16inW x 42-13/16inH x 16-5/16inD"
}
}
This request gets saved in the database column "PRODUCT_SPECS" as :
{".SpeakerSpecification ":{"sensitivity" : "90 dB","bassPrinciple" : "reflex", "amplifierPower" :"700
watts", "dimension" : "14-5/16inW x 42-13/16inH x 16-5/16inD" }}
Now this solution works perfectly fine. The "SpeakerSpecification " key neither appears in the response of GET API call nor in the swagger doc. But having to store the type info in the database really bothers me.
Is there a better approach to this problem where I could avoid having the typeinfo (".SpeakerSpecification ") in the column value?

Relationship Exists in neo4j but not in Spring #NodeEntity

I have a class in my domain called Activity that looks like the following
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
#NodeEntity
public class Activity {
#GraphId
private Long id;
private String title;
private String description;
#Relationship(type = "RELATED_TO", direction = Relationship.UNDIRECTED)
private List<Activity> relatedActivities = new ArrayList<>();
public Activity() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public Collection<Activity> getRelatedActivities() {
System.out.println("getting relatedActivities");
System.out.println(relatedActivities);
return relatedActivities;
}
public void addRelatedActivity(Activity activity) {
this.relatedActivities.add(activity);
}
}
I create relationships using the following repository class:
#RepositoryRestResource(collectionResourceRel = "relationships", path = "relationships")
public interface RelationshipRepository extends GraphRepository<Relationship> {
#Query("MATCH (a1:Activity), (a2:Activity) " +
"WHERE a1.title = {0} AND a2.title = {1}" +
"CREATE (a1)-[:RELATED_TO]->(a2)")
void addRelationship(String a1Title, String a2Title);
}
I have verified that this code works using the neo4j browser, which lets me see existing nodes and relationships between them. However, when I access getRelatedActivities() on an Activity object, it's always an empty array, even if that Activity has other Activity nodes related to it, clearly visible in neo4j.
How can I get the relatedActivites on an Activity to automatically populate based on its relationships correctly?
The problem in your code is that you define the "target" as an Activity here
#Relationship(type = "RELATED_TO", direction = Relationship.UNDIRECTED)
private List<Activity> relatedActivities = new ArrayList<>();
but you also have a RelationshipEntity class in your code base: Relationship with the same type RELATED_TO.
When OGM gets the result it tries to match every field but since it converts the relationship type RELATED_TO to the RelationshipEntity and not an Activity object, it does not fill the list in the Activity class.

MongoRepository custom Id

I have the following MongoDB Repository
public interface TeamRepository extends MongoRepository<Team, TeamId> {
....
}
And the following classes:
public abstract class DbId implements Serializable {
#Id
private final String id;
public DbId(final String id) { this.id = id;}
public String getId() { return id;}
}
public class TeamId extends DbId {
public TeamId(final String id) {
super(id)
}
}
As you can see, I have like a custom id for the repository (I have MongoRepository instead of something like MongoRepository). But, when I am trying to save a Team object, I get an error saying that MongoDB does not know how to generate DBId. Any clue?
MongoDb (or any database) would not know how to generate a string ID without you informing it what the value of the string is.
The default #Id is a string representation of ObjectId, which can be auto-generated by MongoDB. If you are changing the type of string ObjectId to a class, then at least the class needs to define:
** Conversion to string (serialisable), for example:
#Override
public String toString() {
return String.format(
"TeamID[uniqueString=%s]",
myUniqueString);
}
** How to generate the Id.
You can define a method in your TeamRepository i.e. save() to specify how your string can be generated. Alternatively you can check out
https://www.mkyong.com/mongodb/spring-data-mongodb-auto-sequence-id-example/
Where the example specify getNextSequenceId() to generate NumberLong custom id. Hopefully that guides you to your answer.

No composite key property found for type error in Spring JPA2

I have an error in spring JPA
org.springframework.data.mapping.PropertyReferenceException: No property CompanyId found for type CompanyUserDetail!
#Embeddable
public class CompanyUserKey implements Serializable {
public CompanyUserKey() {
}
#Column(name = "company_id")
private UUID companyId;
#Column(name = "user_name")
private String userName;
public UUID getCompanyId() {
return companyId;
}
public void setCompanyId(UUID companyId) {
this.companyId = companyId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
#Entity
#Table(name = "company_user_detail")
public class CompanyUserDetail {
#EmbeddedId
CompanyUserKey companyUserkey;
public CompanyUserKey getCompanyUserkey() {
return companyUserkey;
}
public void setCompanyUserkey(CompanyUserKey companyUserkey) {
this.companyUserkey = companyUserkey;
}
}
I am trying to access below method Service layer
#Component
public interface CompanyUserRepository extends JpaRepository<CompanyUserDetail, CompanyUserKey> {
public List<CompanyUserDetail> findByCompanyId(UUID companyId);
}
How can I achieve this ?
Thanks
Since in java model your CompanyUserKey is a property in the CompanyUserDetail class, I believe you should use full path (companyUserkey.companyId) to reach companyId:
public List<CompanyUserDetail> findByCompanyUserkeyCompanyId(UUID companyId);
Also note that you have a naming inconsistency: field in CompanyUserDetail is named companyUserkey instead of companyUserKey.
Assuming you are not using spring-data-jpa's auto generated implementations, your method contents might look something like the following:
FROM CompanyUserDetail c WHERE c.companyUserKey.companyId = :companyId
Now simply provide that query to the EntityManager
entityManager.createQuery( queryString, CompanyUserDetail.class )
.setParameter( "companyId", companyId )
.getResultList();
The key points are:
Query uses a named bind parameter called :companyId (not the leading :).
Parameter values are bound in a secondary step using setParameter method variants.
createQuery uses a second argument to influence type safety so that the return value from getResultList is a List<CompanyUserDetail> just like you requested.
Looking at spring-data-jpa's implementation however, I suspect it could look like this:
public interface CustomerUserRepository
extends JpaRepository<CompanyUserDetail, CompanyUserKey> {
#Query("select c FROM CompanyUserDetail c WHERE c.companyUserKey.companyId = :companyId")
List<CompanyUserDetail> findByCompanyId(#Param("companyId") UUID companyId);
}

Upsert Mongo Document using spring data mongo

I have a Class
#Document
public class MyDocument {
#Id
private String id;
private String title;
private String description;
private String tagLine;
#CreatedDate
private Date createdDate;
#LastModifiedDate
private Date updatedDate;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getTagLine() {
return tagLine;
}
public void setTagLine(String tagLine) {
this.tagLine = tagLine;
}
}
i have added annotated application with #EnableMongoAuditing
i have created interface which implements mongorepository
public interface MyDocumentRepository extends MongoRepository<MyDocument, String> {
}
when i have created RestController with GET,POST,PATCH methods
in POST I'm sending
{'title':'first'}
Controller Class POST method is
#RequestMapping(value = "/", method = RequestMethod.POST)
public ResponseEntity<?> saveMyDocument(#RequestBody MyDocument myDocument) {
MyDocument doc = myDocumentRepo.save(myDocument);
return new ResponseEntity<MyDocument>(doc, HttpStatus.CREATED);
}
Its saving the data in mongo.
{
"_id" : ObjectId("56b3451f0364b03f3098f101"),
"_class" : "com.wiziq.service.course.model.MyDocument",
"title" : "test"
}
and PATCH request is like
#RequestMapping(value = "/{id}", method = RequestMethod.PATCH)
public ResponseEntity<MyDocument> updateCourse(#PathVariable(value = "id") String id,
#RequestBody MyDocument myDocument) {
myDocument.setId(id);
MyDocument doc = courseService.save(myDocument);
return ResponseEntity.ok(course);
}
when in make PATCH request with data {"description":"This is test"}
it update the docuent BUT it removes title field and createdDate form the document, its doing update which is ok. But i wanted to do an upsert, i can do its using mongoTemplate,
but there i have to set each property which i want to set.
Is there any generic way to that if i get a PATCH request i can update only not null properties.. properties which are coming in request
spring-data-rest seems to do it using #RepositoryRestResource. How can i achieve the same.
I don't want to code like this
Update update = new Update().set("title", myDocument.getTitle()).set("description", myDocument.getdescription());
Unfortunately its the behavior in MongoDB, you can verify the same using shell.
So to update create an Update Object and using
Query query = new Query(Criteria.where("id").is(ID));
Here ID is the document which you want to update.Based on your requirement set upsert after that using findAndModify update document.
mongoTemplate.findAndModify(query, update,
new FindAndModifyOptions().returnNew(true).upsert(false),
someclass.class);
If you have a model like MyModel.class and you need a smooth way to create an Update object from it there is no real clear way how to do this but you can use MongoConverter bean that is created in Spring Data Mongo auto configuration and then just use replaceOne method of MongoCollection.
#Autowired
private MongoTemplate template;
#Autowired
private MongoConverter mongoConverter;
...
#Override
public void upsertMyModel(MyModel model) {
Document documentToUpsert = new Document();
mongoConverter.write(model, documentToUpsert);
template.getCollection(collectionName).replaceOne(
Filters.eq("_id", model.getId()),
documentToUpsert,
new ReplaceOptions().upsert(true));
}
Upsert can be done in Spring data mongodb using BulkOperations.
Suppose there are two entities Entity1 and Entity2. Entity1 has foreginId which is primary id of Entity2. Both have a field title. Now, to upsert from entity2 to entity1, we can do it as follows:
Query query = new Query(Criteria.where("foreignId").is(entity2.getId()));
Update update = new Update();
update.set("title",entity2.getTitle());
List<Pair<Query, Update>> updates = new ArrayList<Pair<Query, Update>>();
updates.add(Pair.of(query, update););
BulkOperations bulkOps = this.mongoTemplate.bulkOps(BulkMode.UNORDERED, Entity1.class);
bulkOps.upsert(updates);
bulkOps.execute();

Resources