I have an entity called ContentPath, with may have a parent of the same type, and sons of the same type, represented with something like:
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "NAME", length = 50)
#NotNull
private String name;
#Column(name = "DESCRIPTION")
private String description;
#ManyToOne
#JoinColumn(name="CONTENT_PATH_ID")
public ContentPath contentPath;
#OneToMany(mappedBy="contentPath")
public Set<ContentPath> contentPaths;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "ACTIVITY_CONTENT_PATH",
joinColumns = {#JoinColumn(name = "CONTENT_PATH_ID", referencedColumnName = "ID")},
inverseJoinColumns = {#JoinColumn(name = "ACTIVITY_ID", referencedColumnName = "ID")})
private Set<Activity> activities;
I have my ContentPathRepository, which exposes it as an API.
#RepositoryRestResource(collectionResourceRel = "contentPaths", path = "contentPaths", excerptProjection = ContentPathProjection.class)
public interface ContentPathRestRepository extends PagingAndSortingRepository<ContentPath, Long> {
}
and my projection, which should format correctly my object.
#Projection(name = "contentPathProjection", types = ContentPath.class)
public interface ContentPathProjection {
Long getId();
String getName();
Set<ContentPath> getContentPaths();
}
I was expecting to get a list of ContentPaths, which have their ContentPaths inside, and I got success, but it is not bringing IDs, it is bringing Name and Description only, and it is very weird because my projection doesn't have the Description.
Current response:
"name": "BOOK 1",
"id": 1,
"contentPaths": [
{
"name": "UNIT 1",
"description": "UNIT 1 description"
},
{
"name": "UNIT 2",
"description": "UNIT 2 description"
}
]
Why is it happening? How fix it?
This is a normal behavior of SDR. It don't show id by default. To turn this on just register a bean like this:
#Bean
public RepositoryRestConfigurerAdapter repositoryRestConfigurerAdapter() {
return new RepositoryRestConfigurerAdapter() {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(ContentPath.class);
super.configureRepositoryRestConfiguration(config);
}
};
}
About description - you have this field:
#Column(name = "DESCRIPTION")
private String description;
Related
I have two entity User and Group and the relationship between both entities are many-to-many. When I call view-group/groupName, I am getting the list of users of group as expected. But when I call view-user/userEmail, I am not getting the list of groups with user details of which user is part of.
Group.java
#Entity
#Table(name = "group_")
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
private String groupName;
#ManyToMany
#JoinTable(name = "group_user",
joinColumns = { #JoinColumn(name = "group_id") },
inverseJoinColumns = {#JoinColumn(name = "user_id") })
public Set<User> usersOfgroup = new HashSet<>();
public Group() {
}
}
User.java
#Entity
#Table(name="users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Column(name="name")
private String name;
#NotBlank
#Column(name="email")
private String email;
#JsonIgnore
#ManyToMany(mappedBy = "usersOfgroup")
public Set<Group> memberInGroups =new HashSet<>();
public User() {
}
localhost:8080/view-group/groupName
{
"id": 1,
"groupName": "Group1",
"usersOfgroup": [
{
"id": 2,
"name": "Abhishek",
"email": "Abhishek#abc.com",
}
]
}
localhost:8080/view-user/Abhishek#abc.com
{
"id": 2,
"name": "Abhishek",
"email": "Abhishek#abc.com",
}
Expected response :
{
"id": 2,
"name": "Abhishek",
"email": "Abhishek#abc.com",
"memberInGroups":[
{
"id": 1,
"groupName": "Group1",
}
]
}
You have added #JsonIgnore on public Set<Group> memberInGroups =new HashSet<>();, thats why the json response doesn't have the data for this. Remove the annotation and you will see the expected response
The #JsonIgnore annotation is used to ignore the logical property used in serialization and deserialization.
You can go with below. It will address your immediate need for this specific case. Usually for a many-to-many, bi-directional relationship with lists, usually the solution is to decide which side of your relation is dominant and to have a JsonManagedReference and JsonBackReference combo, or use #JsonIdentityInfo if you have a list. Below is very good read on bidirectional cases to avoid infinite loops..
https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion
Coming to the solution I am referring to, you will have to override the getter of the list attribute and also use a #JsonInclude
In your Group class - use #JsonIgnore as shown and also put the getter as below to manually kill the loop
#ManyToMany
#JoinTable(name = "group_user", joinColumns = { #JoinColumn(name = "group_id") }, inverseJoinColumns = {
#JoinColumn(name = "user_id") })
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public Set<User> usersOfgroup = new HashSet<>();
public Set<User> getUsersOfgroup() {
return this.usersOfgroup.stream().map(user -> {
user.memberInGroups = new HashSet<>();
return user;
}).collect(Collectors.toSet());
}
and in your User class, too do the same.
#ManyToMany(mappedBy = "usersOfgroup")
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public Set<Group> memberInGroups = new HashSet<>();
public Set<Group> getMemberInGroups() {
return this.memberInGroups.stream().map(group -> {
group.usersOfgroup = new HashSet<>();
return group;
}).collect(Collectors.toSet());
}
My task is to create endpoint, the logic is:
User provides input --> nip (one of the variables in Contractor.class)
on the basis of that nip, I must return JSON, which will contain information about the product that is assigned to the contractor with the provided nip.
Example JSON should look like: {"name": "product_name", "quantity": "0", "address": "storage_address"}
I spent lots of time on this problem, but still don't know what logic to implement.
It's over my newbie head;
Product.class:
public class Product extends AbstractEntity {
#Id
#GeneratedValue
private Long id;
private String name;
private long quantity;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "product", fetch = FetchType.EAGER)
private List<Assignment> assignments;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "product")
private List<Place> places;
}
Contractor.class:
public class Contractor extends AbstractEntity {
#Id
#GeneratedValue
private Long id;
private String contractorName;
private int nip;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "contractor")
private List<Assignment> assignments;
}
Assignment.class:
public class Assignment extends AbstractEntity {
#Id
#GeneratedValue
private Long id;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "id_product", referencedColumnName = "id", nullable = false)
private Product product;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "id_contractor", referencedColumnName = "id", nullable = false)
private Contractor contractor;
}
Storage.class:
public class Storage extends AbstractEntity {
#Id
#GeneratedValue
private Long id;
private String address;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "storage")
private List<Place> places;
}
Place.class:
public class Place extends AbstractEntity {
#Id
#GeneratedValue
private Long id;
private Long shelfNumber;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "id_product", referencedColumnName = "id")
private Product product;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "id_storage", referencedColumnName = "id", nullable = false)
private Storage storage;
}
image of ERD Diagram
I do not know what logic do you need precisely. Also I am not sure if that code will fit to rest of your app. But maybe it will help.
The interface which will be returned by repository and by the rest controller:
public interface GetProductResponse {
public String getName();
public int getQuantity();
public String getAddress();
}
The repository where you can write your query:
public interface ProductRepository extends CrudRepository<Product, Long> {
#Query(nativeQuery = true,
value = (
"SELECT product.name AS name, product.quantity AS quantity, storage.address " +
//be sure to name the result columns the variables in GetProductResponse (without the 'get')
"FROM contractor INNER JOIN assignment ON contractor.id = assignment.id_contractor " +
" INNER JOIN product ON product.id = assignment.id " +
" INNER JOIN place ON product.id = place.id_product " +
" INNER JOIN storage ON storage.id = place.id_storage " +
"WHERE contractor.nip = :nip_ "
)
public List<GetProductResponse> getProducts(#Param("nip_")String nip)
}
The rest controller:
#RestController
public class Controller {
#RequestMapping(value = "/getProductsByNip", method = { RequestMethod.POST})
public List<GetProductResponsee> getProductsByNip(#RequestBody String nip) {
return productRepository.getProducts(nip);
}
}
The output will look like:
[
{"name": "product_name1", "quantity": "0", "address": "storage_address1"},
{"name": "product_name2", "quantity": "2", "address": "storage_address2"}
]
I have two entities. Customer which is mapped in one to many relation with the CustomerDepartment. CustomerDepartment table has a column to store customer Id.
I want to map them in such a way that Customer Object store a list of Customer Department, and the Customer Department stores the id of the customer it belongs to.
The code that is working compels me to send the all the customer details while creating or updating a customer Department.
Is there a way I can only send the id of the customer and it maps itself?
I have tried changing from -
#JsonBackReference
#ManyToOne
#JoinColumn(name = "customer_no", nullable = false)
private Customer customer;
to this -
#JsonBackReference
#ManyToOne(targetEntity = Customer.class)
#JoinColumn(name = "customer_no", nullable = false)
private Integer customer;
which gives me the requestbody I want but it does not work giving the following error -
2019-08-03 04:59:08 ERROR CustomerController:72 - org.springframework.orm.jpa.JpaSystemException: Error accessing field [private java.lang.Integer com.enquero.pulse.entity.Customer.customerNo] by reflection for persistent property [com.enquero.pulse.entity.Customer#customerNo] : 1; nested exception is org.hibernate.property.access.spi.PropertyAccessException: Error accessing field [private java.lang.Integer com.enquero.pulse.entity.Customer.customerNo] by reflection for persistent property [com.enquero.pulse.entity.Customer#customerNo] : 1
Working Code:
Customer:-
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#DynamicUpdate
#Entity
#Table(name = "customer")
public class Customer extends Auditable<Integer>{
#Id
#Column(name = "customer_no")
private Integer customerNo;
#NotBlank
#Column(name = "customer_name")
private String customerName;
#Column(name = "industry")
private String industry;
#Column(name = "country")
private String country;
#Column(name = "state")
private String state;
#Column(name = "city")
private String city;
#Column(name = "postal_code")
private String postalCode;
#Column(name = "address_line1")
private String addressLine1;
#Column(name = "address_line2")
private String addressLine2;
#Column(name = "address_line3")
private String addressLine3;
#Column(name = "payment_term")
private String paymentTerm;
#Column(name = "customer_segment")
private String customerSegment;
#JsonFormat(pattern="dd-MMM-yyyy")
#Column(name = "engagement_start_on")
private Date engagementStartOn;
#JsonManagedReference
#OneToMany(fetch = FetchType.LAZY, mappedBy = "customer")
private List<CustomerDepartment> customerDepartments;
}
CustomerDepartment:-
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#DynamicUpdate
#Entity
#Table(name = "customer_department")
public class CustomerDepartment extends Auditable<Integer>{
#Id
#Column(name = "dept_id", updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer deptId;
#Column(name = "dept_name")
private String deptName;
#Column(name = "primary_contact")
private String primaryContact;
#JsonBackReference
#ManyToOne
#JoinColumn(name = "customer_no", nullable = false)
private Customer customer;
}
Current RequestBody:-
{
"createdBy": 0,
"creationDate": "2019-08-02T23:05:33.993Z",
"customer": {
"addressLine1": "string",
"addressLine2": "string",
"addressLine3": "string",
"city": "string",
"country": "string",
"createdBy": 0,
"creationDate": "2019-08-02T23:05:33.993Z",
"customerDepartments": [
null
],
"customerName": "string",
"customerNo": 0,
"customerSegment": "string",
"engagementStartOn": "string",
"industry": "string",
"lastUpdateDate": "2019-08-02T23:05:33.993Z",
"lastUpdatedBy": 0,
"paymentTerm": "string",
"postalCode": "string",
"state": "string"
},
"deptId": 0,
"deptName": "string",
"lastUpdateDate": "2019-08-02T23:05:33.994Z",
"lastUpdatedBy": 0,
"primaryContact": "string"
}
expected requestbody:-
{
"createdBy": 0,
"creationDate": "2019-08-02T23:05:33.993Z",
"customer": 1, //id instead of json
"deptId": 0,
"deptName": "string",
"lastUpdateDate": "2019-08-02T23:05:33.994Z",
"lastUpdatedBy": 0,
"primaryContact": "string"
}
Have you considered a unidirectional #OneToMany: https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#associations?
For example on CustomerDeparment change
#JsonBackReference
#ManyToOne
#JoinColumn(name = "customer_no", nullable = false)
private Customer customer;
}
to
#JsonBackReference
#ManyToOne
#Column(name = "customer_no")
private int customer;
...and on Customer change
#JsonManagedReference
#OneToMany(fetch = FetchType.LAZY, mappedBy = "customer")
private List<CustomerDepartment> customerDepartments;
}
to
#JsonManagedReference
#OneToMany(cascade = CascadeType.ALL)
private List<CustomerDepartment> customerDepartments;
}
As a bit of an aside, I honestly find Hibernate relationships to sometimes be more a hindrance than a help. As an alternative, you may wish to consider dropping the explicit relationship properties, using "regular" columns (#Column(name="customer_no") private int customer') and just writing queries in your repo classes (ex. findByCustomerNo(int customNumber)) to meet your requirements.
I have problem getting only ID from objects that are in nested collection in Hibernate.
Here is my code:
public class Company {
private int id;
private Set<Account> owners = new HashSet<Account>();
#Id
public int getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY, targetEntity = Account.class)
#Fetch(org.hibernate.annotations.FetchMode.JOIN)
#JoinTable(
name = "company_accountOwners",
joinColumns = #JoinColumn(name = "company_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "account_id", referencedColumnName = "id")
)
public Set<Account> getOwners() {
return owners;
}
public void setOwners(Set<Account> owners) {
this.owners = owners;
}
Account.class
public class Account {
private String id;
private String name;
private int age;
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Column(columnDefinition = "VARCHAR(36)")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
How only get JSON with all Company.class data and array of owners (Account.class) that has only ID, not all other attributes (name, age)?
I'm in complete dark.
Json i get now:
{
"id": 1,
"owners": [
{
"id": "testID",
"name": "name",
"age": 45
},
{
"id": "testID2",
"name": "name2",
"age": 90
}]
}
I want json like:
{
"id": 1,
"owners": [
{
"id": "testID"
},
{
"id": "testID2"
}]
}
I'm having a weird problem with Jackson serialization - I have a Role entity have a nested Permission entity which, in turn, contains a nested Metadata entity. When these entities are retrieved from a Spring MVC #RestController as a list, Jackson serializes the Permission collection into a JSON array. The problem is that sometimes the element placed in this array is just the id of the Permission rather than a serialized representation of the object.
Role.class:
#Entity
#Table(name = "t_db_roles")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Role.class)
public class Role implements GrantedAuthority {
private final static Logger log = LoggerFactory.getLogger(Permission.class);
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "auto_id")
private int id;
#Column(name = "role", length = 50)
private String name;
#OneToMany(fetch = FetchType.EAGER)
#JoinTable(name = "t_db_role_permissions",
joinColumns = {#JoinColumn(name = "roleid", referencedColumnName = "auto_id")},
inverseJoinColumns = {#JoinColumn(name = "permid", referencedColumnName = "auto_id")}
)
private Set<Permission> permissions;
// getters and setters omitted
}
Permission.class:
#Entity
#Table(name = "t_db_permissions")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Permission.class)
public class Permission implements GrantedAuthority {
private final static Logger log = LoggerFactory.getLogger(Permission.class);
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "auto_id")
private int id;
#Column(name = "name")
private String name;
#OneToOne(mappedBy = "permission")
private Metadata metadata;
}
Metadata.class
#Entity
#Table(name = "t_report_data")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Metadata.class)
public class Metadata {
#Id
#Column(name = "id", insertable = false, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "file_name")
private String fileName;
#Column(name = "human_name")
private String humanName;
#Column(name = "links_to")
#JsonIgnore
private Integer linksTo;
#Column(name = "is_subreport")
#JsonIgnore
private Boolean isSubreport;
#OneToOne(cascade = javax.persistence.CascadeType.ALL, fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "permid")
private Permission permission;
}
The controller:
#RestController
public class RoleRestController {
private final static Logger log = LoggerFactory.getLogger(PermissionRestController.class);
private RoleService roleService;
private MetadataService metadataService;
#Autowired
public void setRoleService(RoleService service) {
this.roleService = service;
}
#Autowired
public void setMetadataService(ReportMetadataService service) { this.metadataService = service; }
#RequestMapping(value = "/admin/roles/", method = RequestMethod.GET)
public List<Role> getRoles() {
return roleService.getRoles();
}
}
I'm fairly sure that the problem is in serialization - echoing the List<Role> to the console works as expected, but here is the JSON returned (note the first element of the permissions array is an integer rather than a JSON object):
{
"id": 10,
"name": "ROLE_TESTER",
"permissions": [
14,
{
"id": 7,
"name": "ViewDailySettlementSummaryGL",
"metadata": {
"id": 41,
"fileName": "acct_summary_gl.rptdesign",
"humanName": "Daily Settlement Summary GL",
"permission": 7
},
"authority": "ViewDailySettlementSummaryGL"
},
{
"id": 6,
"name": "ViewDailySettlementSummary",
"metadata": {
"id": 24,
"fileName": "acct_summary_os.rptdesign",
"humanName": "Daily Settlement Summary",
"permission": 6
},
"authority": "ViewDailySettlementSummary"
}
],
"authority": "ROLE_TESTER"
}
I can work around this by handling Role serialization manually, but since the SpringMVC/Jackson serialization works for other classes in the project it seems like there must be a problem in these classes that i'm overlooking. Any ideas?