I have a JSON like this:
{
"pcSignatureHash": "String",
"pcSignature": "String",
"infectedBy": "String",
"agent": {
"userCode": 0
},
"pc": {
"name": "String",
"userName": "String",
"osType": "String",
"domainName": "String",
"cpuId": "String",
"osVersion": "String",
"macId": "String",
"vm": true,
"friendlyName": "String",
"statuses": [
{
"firewall": true,
"firewallTestDate": "String"
}
]
}
}
It's not storing the statuses in the database.
The infections has 1 to 1 relationship with pcs and pcs has 1 to Many relationship with the statuses.
The models are properly set like 1 have Infections in PCs and have Set<Statuses> in the PCs and Statuses has PCs in its model.
Is there any other way to store the data?
If I store the statuses separately that doesn't make a sense it stores the PCs twice because we need to pass the PCs object as a foreign key.
You missunderstood the Relational Mapping. In a OneToMany Relationship you have one side with #OneToMany and one with #ManyToOne.
Your entities should be like below .
please modify accordingly.
Cart entity
#Entity
#Table(name="CART")
public class Cart {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="cart_id")
private long id;
#Column(name="total")
private double total;
#Column(name="name")
private String name;
#OneToMany(mappedBy="cart")
private Set<Items> items;
// Getter Setter methods for properties
}
Item Entity
#Entity
#Table(name="ITEMS")
public class Items {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private long id;
#Column(name="item_id")
private String itemId;
#Column(name="item_total")
private double itemTotal;
#Column(name="quantity")
private int quantity;
#ManyToOne
#JoinColumn(name="cart_id", nullable=false)
private Cart cart;
// Getter Setter methods for properties
}
Related
I am very new to Hibernate and I am trying to solve an issue similar to this question, specifically the answer.
I have something very similar set up (in my case it's Author, Book, and the mapping table AuthorBook). I am using this in Spring Data JPA, so I have the following components:
Repositories: AuthorRepository, BookRepository, AuthorBookRepository
Services: AuthorService, BookService, AuthorBookRepository
Controllers: AuthorController, BookController, AuthorBookController
My entities are:
#Entity
public class Author {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy = "author")
private Set<AuthorBook> authorBooks;
// getters and setters
}
#Entity
public class Book {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy = "book")
private Set<AuthorBook> authorBooks;
// getters and setters
}
#Entity
public class AuthorBook {
#Id
#GeneratedValue
private Integer id;
#ManyToOne
#JoinColumn(name = "user_id")
private Author author;
#ManyToOne
#JoinColumn(name = "book_id")
private Book book;
#Column(name = "isMainAuthor")
private boolean isMainAuthor;
// getters and setter
}
My understanding is that I should make the following POST requests:
Create an author:
{
"name": "Test Author"
}
Create a book:
{
"name": "Test Book"
}
Create the mapping:
{
"author": {
"id": 1
},
"book": {
"id": 2
},
"isMainAuthor": true
}
First of all: is this the correct way to use this? If no, how should I use it instead? If yes, how is serialisation supposed to work? Because if I do it like this, and then fetch the list of books, the response will be infinitely long like so:
[
{
"id": 2,
"name": "Test Book",
"authorBooks": [
{
"id": 3,
"author": {
"id": 1,
"name": "Test Author",
"authorBooks": [
{
"id": 3,
"author": {
"id": 1,
"name": "Test Author",
"authorBooks": [
{
"id": 3,
"author": {
"id": 1,
"name": "Test Author",
"authorBooks": [
...
I know that I could use #JsonIgnore on the Author and Book getters in AuthorBook, and #JsonProperty on the setters so that deserialisation still works. However, as someone who is unexperienced with Hibernate, this seems like a hack to me. What is the cleanest and best way to solve this?
Also, is there a way to update the mapping via the author and book endpoints directly? I might already have an author in the database and want to just add a new book he wrote by adding a new Book entity and providing the relation as part of it, but I seem not to be able to do that.
This is absolutely not a hacker trick. With the #JsonIgnore annotation, this problem is solved. This problem is called infinite recursion with the release of Jackson JSON and nothing can be done with it. For this, they came up with an annotation.
You probably need to set an embedded ID within the mapping table. Keeping the Author and Book table as such, create a new class
#Embeddable
public class AuthorBookId{
#Column(name = "user_id")
private String authorId;
#Column(name = "book_id")
private String bookId;
// Constructors
// Getters and Setters
}
And change the AuthorBook table as
#Entity
public class AuthorBook {
#EmbeddedId private AuthorBookId id = new AuthorBookId();
#ManyToOne
#MapsId("user_id")
#JsonIgnore
private Author author;
#ManyToOne
#MapsId("book_id")
#JsonIgnore
private Book book;
#Column(name = "isMainAuthor")
private boolean isMainAuthor;
// getters and setter
}
I have following entities:
#Entity
#Table(name = "profile")
public class Profile {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.ALL)
private ProfileContacts profileContacts;
...
}
and
#Entity
#Table(name = "profile_contacts")
public class ProfileContacts {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "description")
private String description;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
}
I am trying to update it by sending this JSON with update to REST controller:
{
"id": 1,
"description": "an update",
"profileContacts": {
"firstName": "John",
"lastName": "Doe"
}
}
so in the end it calls
profileRepository.save(profile);
where profileRepository is instance of ProfileRepository class:
public interface ProfileRepository extends JpaRepository<Profile, Long> {
}
which is spring-data-jpa interface.
But each time after such update it updates profile table but adds new row to profile_contacts table (table which corresponds to ProfileContactsentity) instead of updating existing ones.
How can I achieve updating?
As per your JSON structure. Yes it will create new profileContacts entry for every time.
The problem every time while saving profile entity you are passing "id": 1 that means Hibernate can identify the entity by this id value (primary key) but for profileContacts mapping you are not sending the id that's why Hibernate considering it has a new entity every time.
To update your profileContacts entity make sure to pass the id of it.
Example:
{
"id": 1,
"description": "an update",
"profileContacts": {
"id" : yourEntityId
"firstName": "John",
"lastName": "Doe"
}
}
Well, that's the expected behavior.
You're not telling hibernate to update the profileContacts.
For the framework to be able to update it, you need to send the profileContact's primary key - which in your case is the ProfileContacts#id.
Something like this:
{
"id": 1,
"description": "an update",
"profileContacts": {
"id": 1
"firstName": "John",
"lastName": "Doe"
}
}
Need to specify the join column in the parent Entity.
#Entity
#Table(name = "profile")
public class Profile {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.ALL)
**#JoinColumn(name = "id")** //
private ProfileContacts profileContacts;
...
}
Now when you try to save Profile entity it will save the child entity also.
And also needs to include Id in jason request for child entity also
{
"id": 1,
"description": "an update",
"profileContacts": {
"id": 1,
"firstName": "John",
"lastName": "Doe"
}
}
Ok, I see the problem. As #Matheus Cirillo pointed out, you need to tell the hibernate to update the row.
Now, how do you tell the hibernate to update a row - By providing the primary key of the existing row.
But, creating an object with the primary key set is not enough. You need that entity class to be attached to the entity manager and the persistence context.
You can have something like,
//This attaches the entity to the entity manager
ProfileContacts existingProfileContacts = profileContactRepository.getOne(2);
Profile profile = new Profile();
....
....
profile.setProfileContacts(existingProfileContacts);
profileRepository.save(profile);
I hope this helps.
I am trying to map an array of Objects to a field. All the fields in that object are being mapped to columns with different name but similar structure. The response structure should be:
"customers": [
{
"firstName": "string",
"lastName": "string",
"products": [
{
"description":"string",
"amount": "string"
},
{
"description":"string",
"amount": "string"
}
]
}
]
Inside the products field, I have a list of product(description and amount). In DB, columns are stored like
product_des1,product_amt1,product_des2,product_amt2.....product_des30,product_amt30
. I need to map these two fields to the product(object). How should I approach to solve the problem using JPA annotations if possible?
For the reference:
Customers.class
#Entity
public class Customers implements Serializable {
#Column(name = "firstName")
private String firstName;
#Column(name = "lastName")
private String lastName;
#ElementCollection
List<Products> products;
}
Product.class
#Embeddable
public class Product implements Serializable {
#Column(?)
private String description;
#Column(?)
private String amount;
}
Inside the products field, I have a list of product(description and amount). In DB, columns are stored like
product_des1,product_amt1,product_des2,product_amt2.....product_des30,product_amt30
So your Products JPA entity should simply look like this:
#Embeddable
public class Products implements Serializable {
#Column(name = "product_des1")
private String description1;
#Column(name = "product_amt1")
private String amount1;
#Column(name = "product_des2")
private String description2;
#Column(name = "product_amt2")
private String amount2;
// ... repeat
}
if you don't want to do additional mapping between the DB and JPA entities (which I don't recommend - I try to keep JPA entities as exact representation of a DB row and map, if necessary, in Java and not between different technologies).
Does not save an enum value in the database, I think that's it, or I do not understand, I try to save since the console shows me a data, but when I ask from the database I get another value
#Entity
#Table(name = "User")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "name")
private String name;
#Column(name = "password")
private String password;
#Column(unique = true, name = "email")
private String email;
#Column(name="rol")
Rol rol;
#Column(name = "life")
Life life;
public User() {
}
}
i have this in the .info, show this message "OWNER"
Set<User> users =new HashSet<>();
log.info("CONSOLE"+ user.getRol());
users.add(user);
meet.setUsers(users);
return meetRepository.save(meet);
but in swagger i get other value
ROL: PARTICIPANT
[
{
"id": 1,
"name": "string2",
"state": "pause",
"bet": {
"profit": 0,
"inversion": 0
},
"users": [
{
"id": 1,
"name": "string",
"password": "string",
"email": "ema",
"rol": "PARTICIPANT",
"life": "suspend"
}
]
}
]
If your fields rol and life are Enums you have to declare them as Enums with #Enumerated. There are two options. Default will store the ordinal number. Or you can choose to use the string name to store in the DB. That's the better option in terms of maintainability:
#Enumerated(EnumType.STRING)
#Column(name="rol")
Rol rol;
#Enumerated(EnumType.STRING)
#Column(name = "life")
Life life;
Two remarks:
When the database field has the same name as the attribute you can omit #Column. And if the table has the same name as the entity this is also true for the #Table annotatoin.
Read more about Enums and JPA and if you really should use it in one of my articles: https://72.services/de/should-you-use-enums-with-jpa/
When you save a Enum to the database as stated above you have the option of saving it as String or as Numerical Ordinal. The numerical option is very painfull because every time you need to update your enum values or change the order of the values you will mess with your database options for the field. Also another thing that is very painfull even when you store the strings on the database is that when you are using some ORM such as Hibernate and you have NULL values. So keep your enums as strings and avoid NULL value.
I have an object that contains a list of other objects that I cannot seem to get to update correctly. I can create object(Product) with the list of objects(ProductItemQuantity) no problem. I can also do a PUT with the list of objects, but it creates a new list of objects everything I do a PUT. I want the list of objects I provide to be updated instead of having it create a new list every time I do a put of the parent object.
If I add an ID to the ProductItemQuantity, I get an exception:
detached entity passed to persist
Here are my classes:
Product.java
#Entity
public class Product {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#ManyToOne
private Organization org;
private String barCode;
private String name;
#ManyToOne(cascade=CascadeType.MERGE)
private Status status;
#ManyToMany(cascade=CascadeType.MERGE)
private List<Fee> fees;
#ManyToMany(cascade=CascadeType.ALL)
private List<Note> notes;
#ManyToMany(cascade=CascadeType.ALL)
private List<ProductItemQuantity> productItems;
private Integer stock;
private BigDecimal msrp;
#CreationTimestamp
private LocalDateTime createdOn;
#UpdateTimestamp
private LocalDateTime updatedOn;
// Getter & Setters
ProductItemQuantity.java
#Entity
public class ProductItemQuantity {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private Integer count;
#ManyToOne
private ProductItem productItem;
#CreationTimestamp
private LocalDateTime createdOn;
#UpdateTimestamp
private LocalDateTime updatedOn;
// Getters / setters
ProductItem.java
#Entity
public class ProductItem {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#ManyToOne
private Organization org;
#ManyToOne
private Supplier supplier;
private String barCode;
private String description;
private String name;
private Integer stock;
private Integer caseQty;
private BigDecimal caseCost;
#ManyToMany(cascade=CascadeType.ALL)
private List<Note> notes;
#CreationTimestamp
private LocalDateTime createdOn;
#UpdateTimestamp
private LocalDateTime updatedOn;
ProductController.java
#PutMapping("/{id}")
public Product update(#RequestBody Product product, #PathVariable long id) {
Product savedProduct = productService.save(product);
return savedProduct;
}
Working CRUD PUT request: http://localhost:8080/product/1
{
"barcode":"12347163",
"name":"Product 1",
"stock": 12,
"msrp": 29.99,
"org": {
"id":1
},
"status":{
"id":1
},
"productItems":[{
"count":30
},{
"count":30
}
],
"fees":[{
"id":1
},{
"id":2
}],
"notes":[{
"title":"Product Created",
"description":"Note created by user X on 12/16/2019 11:00PM"
},{
"title":"Product Updated",
"description":"Product updated stock by user X on 12/16/2019 11:00PM"
}]
}
Broken CRUD PUT request: http://localhost:8080/product/1
{
"barcode":"12347163",
"name":"Product 1",
"stock": 12,
"msrp": 29.99,
"org": {
"id":1
},
"status":{
"id":1
},
"productItems":[{
"id":1,
"count":30
},{
"id":2,
"count":30
}
],
"fees":[{
"id":1
},{
"id":2
}],
"notes":[{
"title":"Product Created",
"description":"Note created by user X on 12/16/2019 11:00PM"
},{
"title":"Product Updated",
"description":"Product updated stock by user X on 12/16/2019 11:00PM"
}]
}
Your objects are detached because the relations (OneToMany, ManyToMany) is set only from one direction. In order to persist them, you must set the relationship bidirectional. Your relation is unidirectional, because the parser (jackson) will make the following objects:
Product product = new Product();
Fee fee = new Fee();
fee.setId(1);
product.setFees(Arrays.asList(fee));
In a bidirectional relationship, both sides must be set :
product.getFees().forEach(fee-> fee.getProducts().add(product));
It is a good practice to keep separated the persistence object from the controller object, because the persistence object deals with relationships as well.
From my experience, if you want to use GeneratedValue, you have first to select the entity from the database and after to modify it. Does not make much sense to create a new object and set the id on it, if you expect hibernate to generate it.
So, you may need to do a select first :
List fees = // select all fees with id in the product fee list
and after:
product.setFees(fees);
fees.forEach(fee -> fee.getProducts().add(product));
Your method is a PUT, so you should not save directly the product object (it will create a new entry in the database).
#PutMapping("/{id}")
public Product update(#RequestBody Product product, #PathVariable long id) {
Optional<Product> originalProductOptional = productRepository.findById(id);
// you should add a check here : if originalProduct is not found, return 404
Product originalProduct = originalProductOptional.get();
originalProduct.setName(product.getName());
// here update all fields and relations
productRepository.save(originalProduct);
return originalProduct;
}
The answer was to update the property to this:
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private List<ProductItemQuantity> productItemQuantities;
And then set it manually in the put
List<ProductItemQuantity> piqs = product.getProductItemQuantities();
if (piqs != null) {
List<ProductItemQuantity> piiList = new ArrayList<ProductItemQuantity>();
for (ProductItemQuantity pii : piqs) {
// Check for ID in here?
ProductItemQuantity curPII = piqService.getOne(pii.getId());
curPII.setCount(pii.getCount());
piiList.add(curPII);
}
originalProduct.setProductItemQuantities(piiList);
}