How to control fetched sub entities spring boot - java

I have following relationship in my spring boot:
public class Clazz {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
#Column(name = "id")
private Integer id;
#Column(name = "lesson_id")
#NotNull(message = "{lesson.is_required}")
private Integer lessonId;
#Column(name = "level_id")
#NotNull(message = "{level.is_required}")
private Integer levelId;
#Column(name = "name")
#NotEmpty(message = "{name.is_required}")
private String name;
#OneToOne
#JoinColumn(name = "lesson_id", referencedColumnName = "id", insertable = false, updatable = false)
private Lesson lesson;
#OneToOne
#JoinColumn(name = "level_id", referencedColumnName = "id", insertable = false, updatable = false)
private Level level;
}
Now my Lesson entity has Level entity:
public class Lesson {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "name")
#NotEmpty(message = "{name.is_required}")
private String name;
#Column(name = "description")
private String description = "";
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "lesson_id")
Collection<Level> levels = new ArrayList<>();
}
And finally my Level class:
public class Level {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
#Column(name = "id", insertable = false, updatable = false)
private Integer id;
#Column(name = "lesson_id")
private Integer lessonId;
#Column(name = "name")
private String name;
#Column(name = "step")
private Integer step;
#Column(name = "description")
private String description="";
}
With this relationship defined, when I fetch data, getting some extra info in my results:
[
{
"id": 2,
"lessonId": 1,
"levelId": 1,
"name": "English Class March",
"branch": {
"id": 1,
"name": "3 mikr branch",
"email": "",
"phone": "",
"address": "3 mikr, foo, bar"
},
"lesson": {
"id": 1,
"name": "math",
"description": "math lesson",
"levels": [
{
"id": 1,
"lessonId": 1,
"name": "First level",
"step": 1,
"description": "1st level descr"
}
]
},
"level": {
"id": 1,
"lessonId": 1,
"name": "First level",
"step": 1,
"description": "1st level descr"
}
}
]
How can I tell JPA not to fetch sub-entities, ideally I do not need levels in lesson field of result.

Use #JsonIgnore. It can be used at setter,getter or field.
Please refer the code below.
public class Lesson {
#Id #Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "name")
#NotEmpty(message = "{name.is_required}")
private String name;
#Column(name = "description")
private String description = "";
#JsonIgnore // Add this to ignore the property in json output
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "lesson_id")
Collection<Level> levels = new ArrayList<>();
}

Related

JPA created too many fields in table

enter image description here
I am trying to map some entities to tables in MySQL database using Spring Boot JPA. I have a problem with one of the tables because in that one too many foreign keys are added. I highlighted the columns in the picture. I suppose that the problem might be linked with the fact that the Tutorial table has either One to Many or Many to Many relations with the other 3 tables, but I am not sure
#Entity(name = "authors")
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "author_id")
private Long authorId;
#Column(name = "first_name", nullable = false, length = 100)
private String firstName;
#Column(name = "last_name", nullable = false, length = 100)
private String lastName;
#Column(name = "email", length = 320, unique = true)
private String email;
#Column(name = "job_title", length = 255)
private String jobTitle;
#Lob
#Type(type = "org.hibernate.type.BinaryType")
#Column(name = "profile_picture")
private byte[] profilePicture;
#Column(name = "about", length = 2000)
private String about;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "author_id")
private List<Tutorial> tutorials;
}
#Entity(name = "categories")
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "category_id")
private Long categoryId;
#Column(name = "category_name", nullable = false, unique = true, length = 100)
private String categoryName;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "category_id")
private List<Tutorial> tutorials;
}
#Entity(name = "tutorials")
public class Tutorial {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "tutorial_id")
private Long tutorialId;
#Column(name = "tutorial_title", nullable = false, length = 150)
private String tutorialTitle;
#Column(name = "tutorial_description", nullable = false, length = 2000)
private String tutorialDescription;
#Column(name = "time_to_complete")
private Integer timeToComplete;
#Column(name = "date_published")
private Long datePublished;
#Column(name = "last_updated")
private Long lastUpdated;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "tutorials")
private List<User> users = new ArrayList<>();
#ManyToOne(fetch = FetchType.EAGER)
private Category category;
#ManyToOne(fetch = FetchType.EAGER)
private Author author;
}
Tutorials is the table where the problems appear as 4 foreign keys are generate instead of two
#Entity(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Long userId;
#Column(name = "first_name", nullable = false, length = 100)
private String firstName;
#Column(name = "last_name", nullable = false, length = 100)
private String lastName;
#Column(name = "user_name", nullable = false, unique = true, length = 100)
private String userName;
#Column(name = "age")
private Integer age;
#Column(name = "email", length = 320, unique = true)
private String email;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "users_tutorials",
joinColumns = { #JoinColumn(name = "user_id") },
inverseJoinColumns = { #JoinColumn(name = "tutorial_id") })
private List<Tutorial> tutorials = new ArrayList<>();
}
Try this changes:
remove #JoinColumn(name = "author_id")from Author and place in Tutorial:
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "author_id")
private Author author;
remove #JoinColumn(name = "category_id")from Category and place it in Tutorial as well:
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "category_id")
private Author author;
To get more information look here: Baeldung - Hibernate One to Many

Design entities to join three tables using a mapping table Using JPA

Please help me write entities the proper way, so that it can be easily fetched using JPA. I have a DB design as below image:
Table Design Structure
I have created entities
#Entity
#Table(name = "ROLE")
public class Role {
#Id
#GeneratedValue()
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Column(name = "ROLE_ID")
private UUID roleId;
#Column(name = "ROLE_NAME")
private String roleName;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "ROLE_MODULE_PERMISSION_MAP",
joinColumns = #JoinColumn(name = "ROLE_ID"),
inverseJoinColumns = #JoinColumn(name = "MODULE_ID"))
private List<Module> modules;
}
#Entity
#Table(name = "MODULE")
public class Module {
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Column(name = "MODULE_ID", columnDefinition = "BINARY(16)")
private UUID uuid;
#Column(name = "MODULE_NAME")
private String moduleName;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "ROLE_MODULE_PERMISSION_MAP",
joinColumns = #JoinColumn(name = "MODULE_ID"),
inverseJoinColumns = #JoinColumn(name = "PERMISSION_ID"))
private List<Permission> permission;
}
#Entity
#Table(name = "PERMISSION")
public class Permission {
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Column(name = "PERMISSION_ID", columnDefinition = "BINARY(16)")
private UUID permissionId;
#Column(name = "PERMISSION_TYPE")
private String permissionType;
#ManyToMany
#JoinTable(name = "ROLE_MODULE_PERMISSION_MAP",
joinColumns = #JoinColumn(name = "PERMISSION_ID"),
inverseJoinColumns = #JoinColumn(name = "ROLE_ID"))
#MapKeyJoinColumn(name="MODULE_ID")
#ElementCollection
private Map<Role, Module> modulePermissions;
}
#Entity
#Table(name = "ROLE_MODULE_PERMISSION_MAP")
public class RoleModulePermissionMap implements Serializable {
#Id
#Column(name = "ROLE_ID", columnDefinition = "BINARY(16)")
private UUID roleId;
#Id
#Column(name = "MODULE_ID", columnDefinition = "BINARY(16)")
private UUID moduleId;
#Id
#Column(name = "PERMISSION_ID", columnDefinition = "BINARY(16)")
private UUID permissionId;
}
I am trying to fetch using:
Role role = roleRepository.findByroleName(roleName)
Where roleRepository is
#Repository
public interface RoleRepository extends JpaRepository<Role, UUID> {
Role findByroleName(String roleName);
}
I want to fetch the Module and Permissions for a specific Role. something like:
{
"roleName": "Development",
"roleAcronym": "DEV",
"permissionGroup": "AdminUser",
"modules": [
{
"moduleName": "Agreement",
"permission": [
{
"permissionName": "CREATE",
"permissionType": "C"
},
{
"permissionName": "UPDATE",
"permissionType": "U"
},
{
"permissionName": "READ",
"permissionType": "R"
}
]
},
{
"moduleName": "Reports",
"permission": [
{
"permissionName": "DELETE",
"permissionType": "C"
},
{
"permissionName": "UPDATE",
"permissionType": "U"
},
{
"permissionName": "READ",
"permissionType": "R"
}
]
}
]
}
I am using Spring Boot Starter JPA - 2.6.2 version.

How to persist #OneToMany and #ManyToOne from Json

I have next JSON:
{
"name": "String",
"time": int,
"serve": int,
"type": "String",
"about": "String",
"userId": int,
"food": [{
"main_id": long
"name_Id": int,
"size": int,
"measure": "String",
"foodImgId": int
},
{
"main_id": long
"name_Id": int,
"size": int,
"measure": "String",
"foodImgId": int
}, ... ],
"steps": [{
"main_id": long
"step_id": int,
"step": "String",
"stepImgId": int
},
{
"main_id": long
"step_id": int,
"step": "String",
"img": int
}, ... ],
"img": [{
"main_id": long
"foodImgId": int,
"stepImgId": int,
"imgLink": "String"
},
{
"main_id": long
"foodImgId": int,
"stepImgId": int,
"imgLink": "String"
}, ... ],
}
And next models for this JSON:
#Entity
#Table(name = "MAIN")
public class Main implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "NAME", nullable = false)
private String name;
#Column(name = "TIME", nullable = false)
private int time;
#Column(name = "SERVE", nullable = false)
private int serve;
#Column(name = "TYPE", nullable = false)
private String type;
#Column(name = "ABOUT", nullable = false)
private String about;
#Column(name = "USER_ID", nullable = false)
private int userId;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "main_id", orphanRemoval = true)
private Set<Food> food;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "main_id", orphanRemoval = true)
private Set<Steps> steps;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "main_id", orphanRemoval = true)
private Set<Image> img;
// setter & getter
}
#Entity
#Table(name = "STEPS")
public class Steps implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "MAIN_ID", updatable = false)
public Main main_id;
#Column(name = "STEP_ID", nullable = false)
public int step_id;
#Column(name = "STEP", nullable = false)
public String step;
#OneToOne(optional = false)
#JoinColumn(name = "stepImgId", nullable = true)
public Image stepImgId;
// setter & getter
}
#Entity
#Table(name = "IMAGE")
public class Image implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.PERSIST})
#JoinColumn(name = "MAIN_ID", nullable = false)
private Main main_id;
#OneToOne(optional = false, mappedBy="foodImgId")
#Column(name = "foodImgId", nullable = true)
private Food food;
#OneToOne(optional = false, mappedBy="cookStepId")
#Column(name = "stepImgId", nullable = true)
private CookStep cookStepId;
#Column(name = "ImgLink", nullable = false)
private String imgLink;
// setter & getter
}
#Entity
#Table(name = "FOOD")
public class Food implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "MAIN_ID", nullable = false)
private Main main_idain_;
#Column(name = "NAME_ID", nullable = false)
private int nameId;
#Column(name = "SIZE", nullable = false)
private int size;
#Column(name = "MEASURE", nullable = false)
private String measure;
#Column(name = "foodImgId", nullable = true)
private int foodImgId;
// setter & getter
}
My question. How I can save that JSON to DB? Main_id, in each model, have to have id from Main class.
I have an empty repositories because I tried save data by default method repo.save(My_JSON), but i can not receive id from main class. I need any ideas, because I don't have enough experience with spring-boot.
Try this:
#JsonBackReference // add this to prevent Jackson (json) infinite recursion problem
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="MAIN_ID", referencedColumnName = "main_id" ,updatable = false,insertable = false)
public Main main_id;
Possible you forgot to add referencedColumnName which is the name of the corresponding column in the database.
I run your example in SprinBoot using Hibernate so I suggest in Food
class to change line
private Main main_idain_
to
private Main main_id
That was the error I run into.
OK guys, I found solution. My problem was what I couldn't get response which I need. I made one huge mistake. I did bidirectional connection and received loop and empty field. Finally my entities look next. I'll only show what changed.
In the main:
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "RECIPE_ID", referencedColumnName = "ID")
private Set<FoodEntity> foodEntity;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "RECIPE_ID", referencedColumnName = "ID")
private Set<stepEntity> stepEntity;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "RECIPE_ID", referencedColumnName = "ID")
private Set<ImageEntity> imageEntity;
In another entities, I removed annotation #ManyToOne and left only row where I define variable(but it need not everywhere) where it need for query.

Spring REST deserialize JSON array as a Set<?>

I'm using Spring Boot 2.2, Spring Data REST, Spring HATEOAS.
I'm facing a strange problem. I've a RestController accepting this object:
#Data
public class DocumentJSON {
#Valid
private Document document;
private List<DocumentRow> rows = new ArrayList<>();
private List<DocumentVat> vats = new ArrayList<>();
private Set<DocumentPayment> payments = new HashSet<>();
private boolean updateContactDetail = false;
}
and DocumentPayment is:
#Data
#EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
#NoArgsConstructor
#AllArgsConstructor
#Builder
#ToString(callSuper = true)
public class DocumentPayment extends AbstractEntity {
#ToString.Exclude
#JsonDeserialize(using = DocumentUriDeserializer.class)
#NotNull
#OnDelete(action = OnDeleteAction.CASCADE)
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Document document;
#NotNull
#Column(nullable = false, columnDefinition = "DATE")
private Instant date;
//Optional contact (the receipt has not a contact)
#ToString.Exclude
#ManyToOne(fetch = FetchType.LAZY)
private Contact contact;
#NotBlank
#Column(nullable = false)
private String description;
#ToString.Exclude
#JsonDeserialize(using = FinancialAccountUriDeserializer.class)
#NotNull(message = "{documentpayment.financialaccount.missing}")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private FinancialAccount financialAccount;
#Enumerated(EnumType.STRING)
#NotNull
#Column(nullable = false, length = 30)
private PaymentType paymentType;
//The amount, negative for payment to suppliers
#NotNull
#ColumnDefault("0.00")
#Column(nullable = false, scale = 2, columnDefinition = "DECIMAL(12,2)")
private BigDecimal amount = BigDecimal.ZERO;
//The amount paid
#NotNull
#ColumnDefault("0.00")
#Column(nullable = false, columnDefinition = "DECIMAL(12,2)")
private BigDecimal paid = BigDecimal.ZERO;
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
#Generated(value = GenerationTime.ALWAYS)
#Column(columnDefinition = "DECIMAL(12,2) AS (amount-paid) VIRTUAL NOT NULL")
private BigDecimal due;
#ToString.Exclude
#JsonDeserialize(using = StoreUriDeserializer.class)
//#NotNull(message = "{documentpayment.store.missing}")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "store_id", updatable = false)
private Store store;
}
The client is sending a Json like this:
{
"document": {
"date": "2019-10-18T00:00:00.000Z",
"type": "SALES_RECEIPT",
"store": "http://95.255.117.252:8082/api/v1/stores/1",
"rounding": 0,
"amount": 19.23,
"taxAmount": 0.77,
"totalAmount": 20
},
"rows": [
{
"index": 1,
"productType": "FRAME",
"qty": 1,
"rowGroup": null,
"unitPrice": 9.615,
"percentageDiscount": 0,
"purchaseUnitPrice": null,
"amount": 9.615,
"description": "Prodotto1",
"taxRate": "http://95.255.117.252:8082/api/v1/taxRates/2",
"note": false
},
{
"index": 1,
"productType": "OPHTHALMIC_LENS",
"qty": 1,
"rowGroup": null,
"unitPrice": 9.615,
"percentageDiscount": 0,
"purchaseUnitPrice": null,
"amount": 9.615,
"description": "Lente",
"taxRate": "http://95.255.117.252:8082/api/v1/taxRates/2",
"note": false
}
],
"payments": [
{
"date": "2019-10-18T00:00:00.000Z",
"financialAccount": "http://95.255.117.252:8082/api/v1/financialAccounts/1",
"paymentType": "CASH",
"amount": "10"
},
{
"date": "2019-10-18T00:00:00.000Z",
"financialAccount": "http://95.255.117.252:8082/api/v1/financialAccounts/3",
"paymentType": "CREDIT_CARD",
"amount": "10"
}
],
"updateContactDetail": false
}
but when I debug in the first line of the REST controller I see only 1 payment.
Changing the property payments in DocumentJSON from Set<DocumentPayment> to List<DocumentPayment>, I get 2 payments as expected with the same JSON.
Am I missing something or Spring/Jackson are not able to deserialize in the right way a Set<>?

Hibernate - OneToMany: Unable to find with id

I have webservice which saves an order in a mysql datbase with Hibernate:
this is my json Input:
{
"consignments": [
{
"entries": [
{
"entryNumber": 0,
"productId": "0563 5231",
"quantity": 1,
"totalPrice": 125.00,
"unit": "pieces"
},
{
"entryNumber": 1,
"productId": "0563 4800",
"quantity": 1,
"totalPrice": 125.00,
"unit": "pieces"
}
],
"price": 125.00
}
],
"currency": "EUR",
"erpCustomerId": "0001709147",
"erpUnitId": "string",
"hybrisOrderId": "1512986259483",
"orderDate": "2017-12-08T15:52:25.560Z",
"paymentType": "string",
"price": {
"deliveryCosts": 1,
"totalCosts": 125.00
},
"shopId": "DE-Site",
"shopID": "DE-Site"
}
This is the order object in java:
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "hybris_order_id")
private String hybrisOrderId;
#Column(name = "erp_unit_id")
private String erpUnitId;
#Column(name = "erp_customer_id")
private String erpCustomerId;
#Column(name = "shop_id")
private String shopId;
#Column(name = "payment_type")
private String paymentType;
#Column(name = "currency")
private String currency;
#Column(name = "order_date")
private ZonedDateTime orderDate;
#OneToOne(cascade = {CascadeType.ALL})
#JoinColumn(unique = true)
private Price price;
#OneToMany(mappedBy = "placedOrder", cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Consignment> consignments = new HashSet<>();
#Column(name = "erp_order_id")
private String erpOrderId;
#Column(name = "erp_accepted_date")
private ZonedDateTime erpAcceptedDate;
This is the consignment java object:
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "hybris_consignment_id")
private String hybrisConsignmentId;
#Column(name = "price")
private Double price;
#JsonIgnore
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="id",referencedColumnName="id", insertable=false, updatable=false)
private PlacedOrder placedOrder;
#OneToMany(mappedBy = "consignment", cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Entry> entries = new HashSet<>();
This is the entry java object:
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "entry_number")
private Integer entryNumber;
#Column(name = "quantity")
private Integer quantity;
#Column(name = "product_id")
private String productId;
#Column(name = "unit")
private String unit;
#Column(name = "total_price")
private Double totalPrice;
#JsonIgnore
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name="id",referencedColumnName="id", insertable=false, updatable=false)
private Consignment consignment;
My Problem is that when I try to get this order I added from the API, it works fine. But it misses one of the entries in the consignment list. When I try to access the entry table, i get this Exception:
Resolved exception caused by Handler execution: org.springframework.orm.jpa.JpaObjectRetrievalFailureException:
Unable to find com.testo.es.cloud.os.domain.Consignment with id 2;
Does anyone have an idea, why the second entry is referenced to a consignment, which doesn't exist?
Best regards

Categories

Resources