I have en entity class which has an natural ID field mapped as #Id and I don't have any surrogate ID(invented field only for table ID) field. And, in the Jackson marshalled JSON I see an extra id exposed.
So instead of:
{
"bin":"123456", ...
}
I see:
{
"id":"123456", "bin":"123456", ...
}
which I don't want because they are repeated information. How can I prevent this?
I haven't touched REST/MVC configuration adapter; they are for exposing the ID classes, but I don't want that.
Bean:
#Entity
#Data
#Table(name="bin_info")
public class BinInfo implements Serializable, Persistable<String> {
#Id
#NotBlank //this is for absent parameter. Not equal to Pattern regex check
#Pattern(regexp = "^\\d{6,8}$") //6-8 digits
#Column(name="bin")
#JsonProperty("bin")
private String bin;
...
I am with these dependencies:
dependencies {
compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.boot:spring-boot-starter-aop')
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-data-rest')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-undertow')
runtime('com.h2database:h2')
runtime('org.postgresql:postgresql')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('io.cucumber:cucumber-java:3.0.2')
testCompile('io.cucumber:cucumber-junit:3.0.2')
testCompile('io.cucumber:cucumber-spring:3.0.2')
}
Spring Boot 2.0.3.
Try annotating with #NaturalId instead of #Id.
Thanks all, I think it has to do with some configuration of Spring or Jackson that will automatically expose the field mapped with #Id. I can just guess because no time for a confirmation.
And some colleague suggests me to define a DTO instead of putting the #Jsonxxx annotations in the class, saying the model represents data model and are related to the table, while DTO is related with view layer. So I did it and now all is fine.
Now the model is free of id field and #JsonProperty/#JsonIgnore:
#Entity
#Data
#Table(name="bin_info")
public class BinInfo implements Serializable, Persistable<String> {
#Id
#NaturalId
#NotBlank //this is for absent parameter. Not equal to Pattern regex check
#Pattern(regexp = "^\\d{6,8}$") //6-8 digits
#Column(name="bin")
//#JsonProperty("bin")
private String bin;
...
And the DTO is totally without #Id:
#Data
public class BinInfoDTO {
#JsonProperty("bin")
private String bin;
#JsonProperty("json_full")
private String json_full;
...
When I retrieve an entity, with a mapping method I set all values I need in a DTO to the DTO and return it to the endpoint. Then the JSON is normal and fine.
You have to implement the get and set from id field.
Try annotating that field with #JsonIgnore
Related
I have an entity 'Product' and I want the primary key in ES to be used as a combination of 'id' and 'name' attributes. How can we do that using spring data elastic search.
public class Product {
#Id
private String id;
#Id
private String name;
#Field(type = FieldType.Keyword)
private Category category;
#Field(type = FieldType.Long)
private double price;
#Field(type = FieldType.Object)
private List<ValidAge> age;
public enum Category {
CLOTHES,
ELECTRONICS,
GAMES;
}
}
One way to achieve this would be the following:
first rename your id property, I changed it to documentId here. This is necessary, because in Spring Data
Elasticsearch an id-property can be either annotated with #Id or it can be namend id. As there can only be one
id-property we need to get this out of the way. It can have the name id in Elasticsearch, set by the #Field
annotation, but the Java property must be changed.
second, add a method annotated with #Id and #AccessType(AccessType.Type.PROPERTY) which returns the value you
want to use in Elasticsearch.
third, you need to provide noop-setter for this property. This is necessary because Spring Data Elasticsearchsoe
not check the id property to be read only when populating an entity after save or when reading from the index.
This is a bug in Spring Data Elasticsearch, I'll create an issue for that
So that comes up with an entity like this:
#Document(indexName = "composite-entity")
public class CompositeEntity {
#Field(name="id", type = FieldType.Keyword)
private String documentId;
#Field(type = FieldType.Keyword)
private String name;
#Field(type = FieldType.Text)
private String text;
#Id
#AccessType(AccessType.Type.PROPERTY)
public String getElasticsearchId() {
return documentId + '-' + name;
}
public void setElasticsearchId(String ignored) {
}
// other getter and setter
}
The repository definition would be straight forward:
public interface CompositeRepository extends ElasticsearchRepository<CompositeEntity,
String> {
}
Remember that for every method that needs an Elasticsearch Id, you'll need to create like it's done in the entity
class.
I am not sure about spring data elasticsearch but spring jpa provides the facility of defining composite primary key by using #IdClass where we can define a separate class(let us say class A) in which we can define all the fields which we want to be a part of composite key Then we can use #IdClass(A.class) in entity class and use #Id annotation on all the fields which should be the part of the composite key
you can refer to this article, although I am not sure whether the same concept will be applicable for spring data es - https://www.baeldung.com/jpa-composite-primary-keys
Please note: while I would accept an XML-based solution if that's truly the only way to accomplish what I'm looking for, I would greatly prefer a solution using Dozer's Java API.
I am new to Dozer and am trying to figure out how to use its API. It seems to default to field-level mappings (if the field names match) and to allow for custom mappers and converters in the event that field-level mapping (based on field name) is either not possible or not logical for your application needs.
I have a situation where my app will take a DTO, say, ReportedIssue (an issue reported by a user and sent to my application over HTTP), and an Issue entity (a data entity that will be persisted to a MySQL DB).
Here are my two objects:
#Data
public class ReportedIssue {
private String typeRefId;
private String reporterRefId;
private String info;
}
#Entity
#Table(name = "issues")
#Data
public class Issue {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "issue_ref_id")
private String refId;
#Column(name = "issue_tracking_number")
private String trackingNumber;
#OneToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "issue_type_id", referencedColumnName = "issue_type_id")
private IssueType type;
#Column(name = "issue_reported_on")
private Date reportedOn;
#OneToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "issue_reporter_id", referencedColumnName = "account_id")
private Account reporter;
#Column(name = "issue_info")
private String info;
}
So in the application frontend, a user can report an issue. The frontend sends a JSON version of a ReportedIssue to the backend, where that JSON is deserialized into a ReportedIssue DTO bean. Then I need Dozer to convert my ReportedIssue into an Issue entity that I can then easily save to my MySQL DB.
Here is my best attempt:
public class ReportedIssueConverter extends DozerConverter<ReportedIssue, Issue> {
private AuthService authService;
public ReportedIssueConverter(AuthService authService, Class<ReportedIssue> prototypeA, Class<Issue> prototypeB) {
super(prototypeA, prototypeB);
this.authService = authService;
}
public ReportedIssueConverter(Class<ReportedIssue> prototypeA, Class<Issue> prototypeB) {
super(prototypeA, prototypeB);
}
#Override
public Issue convertTo(ReportedIssue source, Issue destination) {
Issue issue = new Issue();
issue.setRefId(UUID.randomUUID().toString());
issue.setType(IssueUtils.determineType(source));
issue.setReportedOn(DateTimeUtils.nowInUTC());
issue.setReporter(authService.currentUser());
issue.setInfo(destination.getInfo());
return issue;
}
#Override
public ReportedIssue convertFrom(Issue source, ReportedIssue destination) {
throw new UnsupportedOperationException("we currently don't map from issues to reported issues");
}
}
Several concerns here. For one, is such a custom converter even necessary? Or is there a "better" (more standards compliant or using generally-accepted Dozer practices) way to use the Dozer API to perform this conversion? But mainly, this DozerConverter seems to be intended for bi-directional mapping use cases. Whereas, in my application, I will never have an Issue instance and need to map it back to a ReportedIssue DTO instance. So I only need one-way mapping from ReportedIssue --> Issue. Am I using Dozer correctly by throwing an UnsupportedOperationException or is there another interface or API trick I can use to only leverage the one-way mapping I need?
It could actually be done without a custom converter using custom getter methods in your dto class corresponding to fields in Issue. Dozer works by mapping each field in destination class by trying to invoke the getter method of the corresponding name in the source class.
public class ReportedIssue {
// fields.......
public String getRefId() {
UUID.randomUUID().toString()
}
public IssueType getType() {
IssueUtils.determineType(this);
}
// similarly create getters for other required fields.
}
But for reporter field in Issue, you need an AuthService object. I would suggest writing a static method as below:
public static Issue getIssue(AuthService auth, ReportedIssue dto) {
Issue issue = //map using dozer
issue.setReporter(authService.currentUser());
return issue;
}
Gauntham answer will work. Another option:
Implement a com.github.dozermapper.core.BeanFactory
Your custom BeanFactory can handle
Issue issue = new Issue();
issue.setRefId(UUID.randomUUID().toString());
issue.setReportedOn(DateTimeUtils.nowInUTC());
issue.setReporter(authService.currentUser());
Then depending on your preferences, this could also go into the bean factory
issue.setType(IssueUtils.determineType(source));
Or you could handle that separately in the mapping. Something would need to know how to call IssueUtils, so that is either 1) a customer converter or 2) a change to the DTO or entity to have the functionality through a getter or setter.
Finally, this line would be handled in the Dozer Java API mapping
issue.setInfo(destination.getInfo());
Personally, I like Dozer's com.github.dozermapper.core.loader.api.BeanMappingBuilder where you can explicitly tell it how to map 2 beans, specify the bean factory to use and the custom converter for a specific field.
mapping(ReportedIssue.class, Issue.class, oneWay(), wildcard(true), beanFactory(IssueBeanFactory.class.getName()).fields("this", "type", customConverter(IssueTypeConverter.class)
oneWay(), wildcard(boolean), and beanFactory(String) are found in Dozer's TypeMappingOptions and customConverter(Class.class) is found in Dozer's FieldMappingOptions.
oneWay() makes the mapping work only in the direction specified in the BeanMappingBuilder.
wildcard(true) tells Dozer to automatically map matching fields (this is default behavior).
I use crnk (JSON-API) in java project and I have 3 questions regarding its usage with spring boot and jpa - haven't found exact implementation details in documentation.
For example, I have 2 entities and respective tables:
#Entity
#JsonApiResource(type = "employee")
public class Employee {
#Id
#JsonApiId
private int id;
private String name;
#ManyToOne
#JoinColumn(name = "typeId")
private EmployeeType employeeType; //stored in table as typeId
}
#Entity
#JsonApiResource(type = "type")
public class EmployeeType {
#Id
#JsonApiId
private int id;
private String typeName;
private int salary;
}
How should JsonApiRelation be introduced in order to be able to call "/employee/1" and "/employee/1/type" urls?
For example there is one more entity.
#Entity
#JsonApiResource(type = "project")
public class Project {
#Id
#JsonApiId
private int id;
private String supervisorName;
private String projectName;
}
First, I'd like to have List of Projects for each Employee, where he is a supervisor, joint by name and have it listed as attribute in Json.
Tried implementing it with #OneToMany and #JoinColumn annotations but got StackOverflowException. How could this be implemented. And second, how could this be implemented with Relation? Like "/employee/1/projects" url.
How should I implement custom filtering of results for findAll method? For example, I have a List of all Employees, but I'd like to exclude some of them from the response. Which class/method should be introduced for this behaviour?
#JsonApiRelation annotation should not be necessary. Crnk will detect the #ManyToOne annotation and map it accordingly.
in case of crnk-jpa it is sufficient to specify all relationships in JPA. Matching JSON API relationships. So your approach seems good. What was the StackoverflowException stacktrace? (next to the examples, there are also many example entities in crnk-jpa)
I would make use of a decorator. See http://www.crnk.io/documentation/#_request_filtering. RepositoryDecoratorFactory allows to place a custom repository between the caller and crnk-jpa (or any other kind of repository). There you can do any kind of modification perform (maybe) calling the "real" repository. => Will add an example for this
feel free also make open up tickets in crnk for any documentation/example clarifications.
I am working on a Java web application that I think use Hibernate and I am not so into Hibernate so I have the following doubt:
I have a model class named ReaDichiarazioneIntento that map a database table named REA_DICHIARAZIONE_INTENTO, something like this:
#javax.persistence.IdClass(it.enel.wearea.entity.ReaDichiarazioneIntentoPK.class)
#javax.persistence.Table(name = "REA_DICHIARAZIONE_INTENTO", schema = "EDIWEA")
#Entity
public class ReaDichiarazioneIntento implements Cloneable {
private Integer idDichiarazione;
#javax.persistence.Column(name = "ID_DICHIARAZIONE")
#Id
public Integer getIdDichiarazione() {
return idDichiarazione;
}
public void setIdDichiarazione(Integer idDichiarazione) {
this.idDichiarazione = idDichiarazione;
}
private Integer idCliente;
#javax.persistence.Column(name = "ID_CLIENTE")
#Basic
public Integer getIdCliente() {
return idCliente;
}
public void setIdCliente(Integer idCliente) {
this.idCliente = idCliente;
}
...................................................................
...................................................................
...................................................................
SOME OTHER FIELDS AND RELATED GETTER AND SETTER METHODS
...................................................................
...................................................................
...................................................................
}
Ok I have some doubts about this class. My doubt are:
1) Is it using Hibernate for mapping the class to the database table? Or what? I know that to map a database table to a class I have to do something like:
#Entity
#Table(name = "REA_DICHIARAZIONE_INTENTO")
Why in this project do:
#javax.persistence.IdClass(it.enel.wearea.entity.ReaDichiarazioneIntentoPK.class)
#javax.persistence.Table(name = "REA_DICHIARAZIONE_INTENTO", schema = "EDIWEA")
#Entity
What is the difference between the #Table(name = "REA_DICHIARAZIONE_INTENTO") annotation and the #javax.persistence.Table(name = "REA_DICHIARAZIONE_INTENTO", schema = "EDIWEA") annotation (used in my project)?
2) The second doubt is related to this annotation:
#javax.persistence.IdClass(it.enel.wearea.entity.ReaDichiarazioneIntentoPK.class)
What exactly means?
3) The last doubt is related to the mapping between a class field to a table column on the DB. Why is it done only on the getter method and not directly on the field name?
Tnx
It is using JPA annotations, and Hibernate is a JPA implementation. JPA by itself is just a set of interfaces/annotations, while JPA implementation (like Hibernate) provides meat around those interfaces/annotations. There is no difference between the two annotations, other than specified schema. Hibernate also has its own #Table annotation but it is used for additional information supplied by JPA'a #Table annotation
#IdClass means that the complex primary key is used for this entity
Specifies a composite primary key class that is mapped to multiple fields or properties of the entity.
You can annotate fields or properties (getters), it's up to you. But, #Id mapping dictates what is valid, meaning if you put #Id on field then you must put all other mappings on fields also, and vice versa.
This is using JPA, looks like, not hibernate. Here is the difference according to SO and here is another link
I have the following object structure:
#Document(collection = "user")
#TypeAlias("user")
public class User {
#Id
private ObjectId id;
private Contact info = new Contact();
}
and here is the Contact pojo:
public class Contact {
#Indexed(unique = true)
private String mail;
}
But for some reasons not known to me, I don't see Spring-data creating a unique index for the property info.mail
To summarize, I have this json structure of user object:
{_id:xxxxx,info:{mail:"abc#xyz.shoes"}}
And I want to create a unique index on info.mail using Spring data with the above pojo structure. Please help.
As far as I remember, annotating embedded fields with #Indexed will not work. #CompoundIndex is the way to go:
#Document(collection = "user")
#TypeAlias("user")
#CompoundIndexes({
#CompoundIndex(name = "contact_email", def = "{ 'contact.mail': 1 }", unique = true)
})
public class User {
#Id
private ObjectId id;
private Contact info = new Contact();
}
In my case I had a fresh spring boot application 2.3.0 with just #Document, #Id and #Indexed annotations. I was able to retrieve and insert documents but it refused to create the index other than the PK. Finally I figured that there is a property that you need to enable.
spring.data.mongodb.auto-index-creation = true
As a matter of fact it even works on nested objects without #Document annotation.
Hope this helps :)
Obsolete answer, this was with and older version of mongodb 1.x.
Had the same issue, it seems that your Contact class is missing the #Document annotation i.e.
#Document
public class Contact {
#Indexed(unique = true)
private String mail;
}
Should work, quote from the spring mongodb reference
Automatic index creation is only done for types annotated with #Document.
Extending #Xenobius's answer:
If any configuration extending AbstractMongoClientConfiguration is set, MongoMappingContext will back off. The result is:
spring.data.mongodb.auto-index-creation = true will not be effective
You will need add this into your own configuration:
#Override
protected boolean autoIndexCreation() {
return true;
}
ref: https://github.com/spring-projects/spring-boot/issues/28478#issuecomment-954627106